近2萬(wàn)字詳解Java NIO2文件操作,過(guò)癮!
從classpath中讀取過(guò)文件的人,都知道需要寫(xiě)一些讀取流的方法,很是繁瑣。最近使用IDEA在打出.這個(gè)符號(hào)的時(shí)候,一行代碼讓人激動(dòng)不已:竟然提供直接讀出bytes字節(jié)的方法。
- byte[] bytes = Test
- .class
- .getResourceAsStream("/test.txt")
- .readAllBytes();
這真是太讓人振奮了,再也不用寫(xiě)一些丑陋的,還容易忘記關(guān)閉流的代碼了。
可惜的是,代碼提示給了當(dāng)頭一棒。這需要Java的9版本以上才能支持。這就像一個(gè)還有1年壽命的患者看到救命的藥,還需要兩年才能問(wèn)世的感覺(jué),是一樣的。
廢話(huà)少說(shuō),我們用不了這些函數(shù),難道Java7+的也用不了么(有些可憐的寶貝們還真用不了)?話(huà)說(shuō)回來(lái),JAVA7+對(duì)NIO進(jìn)行了增強(qiáng),主要在對(duì)文件操作部分做了大量的改進(jìn)。它體現(xiàn)在,將File操作進(jìn)行分離、封裝、改進(jìn),最終形成Path(Paths)、Files、FileSystem(FileSystems)三個(gè)主要類(lèi)。
我們親切的叫它NIO2。
其中,Paths、Files中提供了大量便捷的靜態(tài)操作方法;NIO2還提供了有關(guān)文件權(quán)限(屬性)操作、軟連接、文件查找等高級(jí)API,使得NIO2具有更全面的文件系統(tǒng)操作接口。
新入行的小鮮肉可能一開(kāi)始就接觸這個(gè)了,但對(duì)我們一些老程序員來(lái)說(shuō),突然看到這些東西,就像打開(kāi)了一個(gè)新大陸。所以本文面向的是還不知道這些個(gè)操作的菜鳥(niǎo),以及懶癌晚期的老Java程序員。
1、Path
文件系統(tǒng)都是Tree或者層級(jí)結(jié)構(gòu)來(lái)組織文件的,任何一個(gè)節(jié)點(diǎn)可以是一個(gè)目錄或者一個(gè)文件,在NIO2中稱(chēng)為Path,這和原來(lái)的File有很多相似之處,只是Path具有更多的表述語(yǔ)義。
1.1、基本屬性
以下代碼展示了它的基本屬性。
- Path path = Paths.get("/data/logs/web.log");
- //屬性
- //獲取路徑中的文件名或者最后一個(gè)節(jié)點(diǎn)元素
- System.out.printf("FileName:%s%n", path.getFileName());
- //路徑節(jié)點(diǎn)元素的格式
- System.out.printf("NameCount:%s%n", path.getNameCount());
- //遍歷路徑節(jié)點(diǎn)方法1
- Iterator<Path> names = path.iterator();
- int i = 0;
- while (names.hasNext()) {
- Path name = names.next();
- System.out.printf("Name %s:%s%n",i,name.toString());
- i++;
- }
- //方法2
- for(int j = 0; j < path.getNameCount(); j++) {
- System.out.printf("Name %s:%s%n",j,path.getName(j));
- }
- //父路徑
- System.out.printf("Parent:%s%n",path.getParent());
- //跟路徑,比如"/"、"C:";如果是相對(duì)路徑,則返回null。System.out.printf("Root:%s%n",path.getRoot());
- //子路徑,結(jié)果中不包含root,前開(kāi)后閉
- System.out.printf("Subpath[0,2]:%s%n",path.subpath(0,2));
結(jié)果:
- FileName:web.log
- NameCount:3
- Name 0:data
- Name 1:logs
- Name 2:web.log
- Name 0:data
- Name 1:logs
- Name 2:web.log
- Parent:/data/logs
- Root:/
- Subpath[0,2]:data/logs
1.2、路徑轉(zhuǎn)換
1)比如“/data/logs/./web.log”、“/data/logs/../db”這種包含“冗余”路徑的,有時(shí)候我們需要轉(zhuǎn)換為正常路徑語(yǔ)句,則使用“Path.normalize()”方法,無(wú)比的清爽清潔。
- Path path = Paths.get("/data/logs/../web.log");
- //輸出結(jié)果:/data/web.log
- System.out.printf("%s%n",path.normalize());
2)如果文件需要被外部資源訪(fǎng)問(wèn)(resource),你可以通過(guò)Path.toUri()來(lái)轉(zhuǎn)換,path對(duì)應(yīng)的文件或者目錄可以不存在,此方法不會(huì)check異常:
- Path path = Paths.get("/data/logs/web.log");
- //表示本地文件的uri,輸出結(jié)果:file:///data/logs/web.log
- System.out.printf("%s%n",path.toUri());
3)toAbsolutePath():如果路徑為相對(duì)路徑,則轉(zhuǎn)換為絕對(duì)路徑,對(duì)于JAVA程序而言,起始路徑為classpath。此方法不會(huì)檢測(cè)文件是否真的存在或者有權(quán)限。
4)其中toRealPath()是比較重要的方法,不過(guò)它會(huì)對(duì)文件是否存在、訪(fǎng)問(wèn)權(quán)限進(jìn)行檢測(cè),需要捕獲異常。首先檢測(cè)文件是否存在、是否有權(quán)限;如果path為相對(duì)路徑,則將會(huì)轉(zhuǎn)換為絕對(duì)路徑,同“3)”;如果是“符號(hào)連接”文件(軟連接),則獲取其實(shí)際target路徑(除非指定了NO_FOLLOW_LINKS);如果路徑中包含“冗余”,則移除,同1)。這個(gè)方法,通常用于對(duì)“用戶(hù)輸入的path”進(jìn)行校驗(yàn)和轉(zhuǎn)換,使用比較多。
5)resolve():路徑合并,當(dāng)前path與參數(shù)進(jìn)行路徑合并,append。
6)relativize():獲取相對(duì)路徑,“/data”與“/data/logs/p1”的相對(duì)路徑為“logs/p1”,反之為“../../”。
2、Files
Files類(lèi)中提供了大量靜態(tài)方法,用于實(shí)現(xiàn)文件(目錄)的創(chuàng)建、復(fù)制、遷移、刪除以及訪(fǎng)問(wèn)文件數(shù)據(jù)等操作。
2.1、檢測(cè)文件或目錄
Files.exists(Path)和notExists(Path)兩個(gè)方法,這兩個(gè)方法都會(huì)實(shí)際檢測(cè)文件或者目錄是否存在、以及是否有訪(fǎng)問(wèn)權(quán)限。注意:!exist() 與notExists()并不完全相等,exist可能有三種狀態(tài):如果不存在或者安全校驗(yàn)不通過(guò)則返回false,如果返回true則表示文件確實(shí)存在且有權(quán)限。notExists()檢測(cè)類(lèi)似,對(duì)于沒(méi)有通過(guò)安全校驗(yàn)的也會(huì)返回false;當(dāng)exists與notExists同時(shí)返回false時(shí),說(shuō)明文件不可以驗(yàn)證(即無(wú)權(quán)限),所以通常這兩個(gè)方法需要同時(shí)使用。
判斷文件(目錄)具有讀、寫(xiě)、執(zhí)行的權(quán)限,可以通過(guò)如下方法:
- Path path = Paths.get("data/logs/web.log");
- boolean isRegularExecutableFile = Files.isRegularFile(path) &
- Files.isReadable(path) & Files.isExecutable(path);
有時(shí)候,兩個(gè)不同的path,會(huì)指向同一個(gè)文件,比如當(dāng)一個(gè)path是軟連接時(shí),此時(shí)可以使用Files.isSameFile(p1,p2)來(lái)檢測(cè),當(dāng)然你可以通過(guò)Path + LinkOption相關(guān)組合獲取target實(shí)際path來(lái)比較。
2.2、刪除
delete和deleteIfExists兩個(gè)方法均可刪除文件,前者嘗試刪除的文件如果不存在則會(huì)拋出異常。如果文件是軟連接,則只刪除連接文件而不會(huì)刪除target文件,如果path為目錄,則目錄需要為空,否則刪除失敗(IOException)。在刪除操作之前,最后做一些常規(guī)的檢測(cè),比如文件是否存在(有權(quán)限)、目錄是否為空等。稍后我們?cè)俳榻B“遞歸刪除目錄樹(shù)和文件”。
2.3、文件(目錄)復(fù)制
copy(Path,Path,CopyOption…)方法可以復(fù)制文件,不過(guò),需要注意CopyOption的相關(guān)選項(xiàng)。
當(dāng)copy一個(gè)軟連接文件時(shí),默認(rèn)將會(huì)復(fù)制target文件,如果只想復(fù)制軟連接文件而不是target內(nèi)容,可以指定NOFOLLOW_LINKS選項(xiàng)。CopyOption的實(shí)現(xiàn)類(lèi)為StandardCopyOption,此外CopyOption也擴(kuò)展了LinkOption,即包含NOFOLLOW_LINKS選項(xiàng)。如下為CopyOption選項(xiàng)列表:
1)REPLACE_EXISTING:如果目標(biāo)文件已經(jīng)存在,則直接覆蓋;如果目標(biāo)文件是個(gè)軟連接,則軟連接文件本身被覆蓋(而非連接文件的target文件);如果復(fù)制的是目錄,且目標(biāo)目錄不為空時(shí),則會(huì)拋出異常(DirectoryNotEmptyException),稍后介紹“遞歸復(fù)制目錄樹(shù)和文件”。此參數(shù)通常必選。復(fù)制目錄時(shí),目標(biāo)目錄會(huì)自動(dòng)創(chuàng)建,源目錄中如果有文件,則不會(huì)復(fù)制文件,只會(huì)創(chuàng)建空的目標(biāo)目錄。source和target,要么同時(shí)是目錄、要么同時(shí)是文件。
2)COPY_ATTRIBUTES:復(fù)制文件時(shí),也同時(shí)復(fù)制目標(biāo)文件的屬性(metadata),對(duì)于文件屬性(FileAttribute)的支持依賴(lài)于文件系統(tǒng)(和平臺(tái)),不過(guò)lastModifiedTime通常會(huì)被復(fù)制。
3)NOFOLLOW_LINKS:繼承自L(fǎng)inkOption,表示如果文件是軟連接,則不followed,即只復(fù)制連接文件,不復(fù)制其target實(shí)際文件內(nèi)容。
4)ATOMIC_MOVE:只支持move操作,copy不支持。
- Path source = Paths.get("/data/logs/web.log");
- Path target = Paths.get("/data/logs/web.log.copy");
- Files.copy(source,target,REPLACE_EXISTING,COPY_ATTRIBUTES,NOFOLLOW_LINKS);
2.4、移動(dòng)
move(Path,Path,CopyIOption),基本原則同copy。需要注意,如果是目錄,目錄中包含文件時(shí)也可以移動(dòng)的(這可能依賴(lài)于平臺(tái)),子目錄也一起移動(dòng),但是目標(biāo)目錄必須為空(DirectoryNotEmptyException)。基本語(yǔ)義同“mv -rf”,目標(biāo)目錄不需要提前創(chuàng)建,move結(jié)束后,源目錄將不存在。支持兩種選項(xiàng):
1)REPLACE_EXISTING:如果目標(biāo)文件已存在,則覆蓋;如果目標(biāo)文件是軟連接,則連接文件被覆蓋但是其指向不會(huì)受影響。
2)ATOMIC_MOVE:原子復(fù)制,需要平臺(tái)的文件系統(tǒng)支持(不支持則拋出異常),指定此參數(shù)時(shí)其他選項(xiàng)將被忽略;如果文件不能被原子復(fù)制(或者替換),則會(huì)拋出AtomicMoveNotSupportedException。
2.5、打開(kāi)文件
Files類(lèi)中提供了多個(gè)靜態(tài)的方法,用于直接讀寫(xiě)文件。如下為文件打開(kāi)的幾個(gè)選項(xiàng)參數(shù)(StandardOpenOptions):
1)WRITE: 打開(kāi)文件用于write訪(fǎng)問(wèn)。
2)APPEND:在文件尾部追加數(shù)據(jù),伴隨用于WRITE或CREATE選項(xiàng)。
3)TRUNCATE_EXISTING:將文件truncate為空,伴隨用于WRITE選項(xiàng)。比如,文件存在時(shí),將文件數(shù)據(jù)清空并重新寫(xiě)入。
4)CREATE_NEW:創(chuàng)建新文件,如果文件已存在則拋出異常。
5)CREATE:如果文件已存在則直接打開(kāi),否則創(chuàng)建文件。
6)DELETE_ON_CLOSE:當(dāng)文件操作關(guān)閉時(shí)則刪除文件(close方法或者JVM關(guān)閉時(shí)),此選項(xiàng)適用于臨時(shí)文件(臨時(shí)文件不應(yīng)該被其他進(jìn)程并發(fā)訪(fǎng)問(wèn))。
7)SPARSE:創(chuàng)建一個(gè)“稀疏”文件,伴隨使用CREATE_NEW,適用于某些特殊的文件系統(tǒng)比如NTFS,這些大文件允許出現(xiàn)“gaps”(空洞)在某些情況下可以提高性能且這些gaps不消耗磁盤(pán)空間。
8)SYNC:對(duì)文件內(nèi)容(data)或者metadata的修改,都會(huì)同步到底層存儲(chǔ)。
9)DSYNC:對(duì)文件內(nèi)容的修改,會(huì)同步到底層存儲(chǔ)。
對(duì)于一些小文件(M級(jí)別),通常我們希望一次全部讀取所有內(nèi)容,而不再使用傳統(tǒng)的方式迭代讀取。
- //全部讀取小文件中的數(shù)據(jù)
- //Files.readAllBytes(Paths.get("/data/web.log"));
- List<String> lines = Files.readAllLines(Paths.get("/data/web.log"),Charset.forName("utf-8"));
- //將準(zhǔn)備好的數(shù)據(jù),直接全部寫(xiě)入文件。(打開(kāi)、寫(xiě)入)
- Files.write(Paths.get("/data/web-1.log"),lines,Charset.forName("utf-8"),
- StandardOpenOption.APPEND,
- StandardOpenOption.CREATE));
- //傳統(tǒng)操作
- try (BufferedReader reader = Files.newBufferedReader(Paths.get("/data/web.log"))) {
- while (true) {
- String line = reader.readLine();
- if (line == null) {
- break;
- }
- System.out.println(line);
- }
- } catch (IOException e) {
- //
- }
- //傳統(tǒng)操作
- try (BufferedWriter writer = Files.newBufferedWriter(Paths.get("/data/web.log"),Charset.forName("utf-8"),StandardOpenOption.APPEND,
- StandardOpenOption.CREATE)) {
- for(String line : lines) {
- writer.write(line);
- }
- } catch (IOException e) {
- //
- }
此外Files中還提供了基于buffer的channel操作,返回類(lèi)型為SeekableByteChannel,這種操作通常適用于讀或者寫(xiě),以及數(shù)據(jù)可以基于buffer進(jìn)行拆封包,其他特性類(lèi)似“隨機(jī)訪(fǎng)問(wèn)文件”。(Files.newByteChannel(),功能同F(xiàn)ile.open())
- Path path = Paths.get("/data/web.log");
- //創(chuàng)建一個(gè)普通文件,如果已存在則拋出異常,文件屬性為默認(rèn)。Files.createFile(path);
- //創(chuàng)建臨時(shí)文件,臨時(shí)文件的目錄和前綴可以為null,后綴如果為null時(shí)默認(rèn)使用".tmp";
- Files.createTempFile(null,null,".tmp");
- //創(chuàng)建一個(gè)軟連接文件
- Files.createSymbolicLink(Paths.get("/data/link.log"),Paths.get("/data/web.log"));
2.6、Metadata管理
2.6.1、BasicFileAttributes
BasicFileAttributes基本接口,提供了一些基本的文件metadata,比如lastAccessTime、lastModifiedTime等,它的實(shí)現(xiàn)類(lèi)因平臺(tái)而已有:DosFileAttributes、PosixFileAttribute、UnixFileAttribute等;不同平臺(tái)所能支持的屬性有所不同。(在跨平臺(tái)場(chǎng)景下,你可能需要使用FileStore來(lái)判斷當(dāng)前文件系統(tǒng)是否支持相應(yīng)的FileAttributeView)
- Path path = Paths.get("/data/logs/web.log");
- BasicFileAttributes attributes = Files.readAttributes(path,BasicFileAttributes.class);
- System.out.println("regular file:" + attributes.isRegularFile());
- System.out.println("directory:" + attributes.isDirectory());
- System.out.println("symbolic link:" + attributes.isSymbolicLink());
- System.out.println("modified time:" + attributes.lastModifiedTime().toMillis());
- //修改系統(tǒng)更新屬性
- Files.setLastModifiedTime(path,FileTime.fromMillis(System.currentTimeMillis()));
- //修改其他屬性
- Files.setAttribute(path,"dos:hidden",true);
屬性名格式為“view-name:attribute-name”,比如“dos:hidden”;其中合法的view-name目前有“basic”、“posix”、“unix”、“owner”(所有者信息,權(quán)限),屬性的列表需要根據(jù)自己的平臺(tái)對(duì)應(yīng)相應(yīng)的Attributes類(lèi),否則會(huì)導(dǎo)致設(shè)置異常。
2.6.2、PosixFileAttributes
- Path path = Paths.get("/data/logs/web.log");
- PosixFileAttributes attributes = Files.readAttributes(path,PosixFileAttributes.class);
- //用戶(hù)組和權(quán)限
- UserPrincipal userPrincipal = attributes.owner();
- System.out.println(userPrincipal.toString());
- GroupPrincipal groupPrincipal = attributes.group();
- System.out.println(groupPrincipal.toString());
- Set<PosixFilePermission> permissions = attributes.permissions();
- //將權(quán)限轉(zhuǎn)換為文件屬性,用于創(chuàng)建新的文件,目前文件權(quán)限也是一種屬性
- FileAttribute<Set<PosixFilePermission>> fileAttribute = PosixFilePermissions.asFileAttribute(permissions);
- Files.createFile(Paths.get("/data/test.log"),fileAttribute);
- //修改文件權(quán)限,可以在permissions中增減權(quán)限列表,枚舉
- Files.setPosixFilePermissions(path,permissions);
從權(quán)限字符串中,構(gòu)建權(quán)限列表
- Set<PosixFilePermission> permissions = PosixFilePermissions.fromString("-rw-r--r--");
- Files.setPosixFilePermissions(path, permissions);
修改文件(目錄)的所有者或者所在組:
- Path path = Paths.get("/data/logs/web.log");
- //首先找到系統(tǒng)中的其他用戶(hù),根據(jù)用戶(hù)名
- UserPrincipal userPrincipal = path.getFileSystem().getUserPrincipalLookupService().lookupPrincipalByName("userName");
- Files.setOwner(path,userPrincipal);
- //或者
- Files.getFileAttributeView(path,FileOwnerAttributeView.class).setOwner(userPrincipal);
- //修改group
- GroupPrincipal groupPrincipal = path.getFileSystem().getUserPrincipalLookupService().lookupPrincipalByGroupName("groupName");
- Files.getFileAttributeView(path,PosixFileAttributeView.class).setGroup(groupPrincipal);
2.6.3、UserDefinedFileAttributeView
用戶(hù)自定義文件屬性(即擴(kuò)展文件屬性),以及屬性值的長(zhǎng)度,這取決于底層文件系統(tǒng)或者平臺(tái)是否支持。目前,主流的平臺(tái)和文件系統(tǒng)都支持?jǐn)U展文件屬性,比如FreeBSD(ZFS)、Linux(ext3、ext4、ZFS)、MacOS等。默認(rèn)情況下,此操作是開(kāi)啟的,如果已關(guān)閉,可以通過(guò)“sudo mount -o remount,user_xattr {你的文件系統(tǒng)掛載路徑}”,否則也會(huì)拋出UnsupportedOperationException。
- Path path = Paths.get("/data/logs/web.log");
- UserDefinedFileAttributeView view = Files.getFileAttributeView(path,UserDefinedFileAttributeView.class);
- String name = "user.mimetype";
- int size = view.size(name);//我個(gè)人認(rèn)為JDK應(yīng)該直接支持獲取屬性值,而不是再周折一番
- ByteBuffer buffer = ByteBuffer.allocate(size);
- view.read(name,buffer);
- buffer.flip();
- String value = Charset.defaultCharset().decode(buffer).toString();
- System.out.println(value);
- //其他操作,比如list獲取所有屬性的列表等。//寫(xiě)入或者跟新自定義屬性
- view.write(name,Charset.defaultCharset().encode("text/html"));
3、FileSystem
FileStore是新增的API,用于描述底層存儲(chǔ)系統(tǒng),一個(gè)平臺(tái)有多個(gè)FileStore,我們可以通過(guò)FileSystem獲取FileStore的列表,以及每個(gè)store的存儲(chǔ)狀態(tài)、文件列表等。
- Path path = Paths.get("/data/logs/web.log");
- path.getFileSystem().getFileStores();//獲取文件所屬的文件系統(tǒng)的所有的存儲(chǔ)器。//當(dāng)前文件系統(tǒng)所能支持的FileAttributeView,此后可以對(duì)文件使用相應(yīng)的view獲取或者修改屬性
- Set<String> viewNames = FileSystems.getDefault().supportedFileAttributeViews();
- System.out.println(viewNames);//basic,unix,posix,owner,dos等
- //或者,全局
- //遍歷所有的磁盤(pán)存儲(chǔ)
- Iterable<FileStore> fileStores = FileSystems.getDefault().getFileStores();//獲取默認(rèn)文件系統(tǒng)的所有存儲(chǔ)器
- for(FileStore store : fileStores) {
- System.out.println("\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-");
- System.out.println("className:" + store.getClass().getName());
- System.out.println("name:" + store.name());//磁盤(pán)名稱(chēng)
- System.out.println("type:" + store.type());//類(lèi)型
- System.out.println("readOnly:" + store.isReadOnly());//是否為只讀
- System.out.println("usableSpace:" + store.getUsableSpace() + "/" + store.getTotalSpace());
- boolean supported = store.supportsFileAttributeView(BasicFileAttributeView.class);
- //或者
- //boolean supported = store.supportsFileAttributeView("basic");
- //fileStore的屬性,不同于FileAttributes,這些屬性應(yīng)該與FileStore的各個(gè)實(shí)現(xiàn)類(lèi)對(duì)應(yīng)。Long totalSpace = (Long)store.getAttribute("totalSpace");
- System.out.println(totalSpace);
可以獲取每個(gè)FileStore支持的FileAttributeView,此后即可通過(guò)Files.getFileAttributeView()來(lái)獲取相應(yīng)的視圖類(lèi)。每個(gè)view都有簡(jiǎn)寫(xiě)名稱(chēng),比如BasicFileAttributeView.name()返回其簡(jiǎn)寫(xiě)名稱(chēng)為“basic”。目前JDK支持很多FileAttributeView,比如BasicFileAttributeView、UnixFileAttributeView等等,以及允許自定義的UserDefinedFileAttributeView等。JDK將獲取文件屬性、修改文件屬性的操作全部基于對(duì)應(yīng)的FileAttributeView類(lèi)來(lái)實(shí)現(xiàn)。
- PosixFileAttributeView view = Files.getFileAttributeView(path,PosixFileAttributeView.class);
- PosixFileAttributes fileAttributes = view.readAttributes();
此外,根據(jù)FileStore的底層存儲(chǔ)不同,有多種實(shí)現(xiàn),可以參看FileStore的實(shí)現(xiàn)類(lèi),比如UnixFileStore、BsfFileStore(Mac)等,每個(gè)FileStore所能支持的attribute也不太相同,需要根據(jù)其對(duì)應(yīng)的實(shí)現(xiàn)類(lèi)獲取,也可以使用FileStore.getAttribute()來(lái)獲取,但是屬性名需要與類(lèi)中支持的屬性名對(duì)應(yīng)。
4、目錄操作
JAVA中目錄也用Path表示,其基本屬性與File一樣。
4.1、列舉根目錄
- //遍歷文件系統(tǒng)的所有根目錄
- Iterable<Path> roots = FileSystems.getDefault().getRootDirectories();
- for(Path root : roots) {
- System.out.print(root);
- }
4.2、創(chuàng)建、刪除
- Path dir = Paths.get("/data/xyz");
- Files.createDirectories(dir);
- Files.createDirectory(dir);
其中createDirectory()方法是一個(gè)“嚴(yán)格校驗(yàn)”的方法,如果父路徑不存在則會(huì)拋出異常,如果路徑已經(jīng)存在或者同名文件存在則會(huì)拋出異常,簡(jiǎn)單來(lái)說(shuō)此方法只能創(chuàng)建最后一級(jí)目錄(且此前不存在)。對(duì)于createDirectories()方法,比較兼容,會(huì)逐層校驗(yàn)并創(chuàng)建父路徑,如果不存在則創(chuàng)建。
創(chuàng)建目錄時(shí),也可以像文件那樣,指定文件屬性(包括權(quán)限)
- Path dir = Paths.get("/data/xyz/12x/123x");
- Set<PosixFilePermission> permissions = PosixFilePermissions.fromString("\-rw\-rw\-\-\-\-");
- FileAttribute<Set<PosixFilePermission>> fileAttribute = PosixFilePermissions.asFileAttribute(permissions);
- Files.createDirectories(dir,fileAttribute);
4.3、遍歷目錄
- Path dir = Paths.get("/data");
- DirectoryStream<Path> stream = Files.newDirectoryStream(dir);
- for (Path path : stream) {
- System.out.println(path);
- }
- stream.close();
stream方式會(huì)遍歷指定目錄下的所有文件,包括文件。目錄。連接、隱藏文件或者目錄等。
此外,JDK還支持基于filter模式,在遍歷時(shí)過(guò)濾“非必要”的文件或者目錄。
- Path dir = Paths.get("/data");
- DirectoryStream.Filter<Path> filter = new DirectoryStream.Filter<Path>() {
- @Override
- public boolean accept(Path entry) throws IOException {
- return Files.isRegularFile(entry);
- }
- };
- DirectoryStream<Path> stream = Files.newDirectoryStream(dir,filter);
- for (Path path : stream) {
- System.out.println(path);
- }
- stream.close();
4.4、Glob文件過(guò)濾器
NIO2中新增支持了基于Glob的文件過(guò)濾器,一種類(lèi)似于正則表達(dá)式的匹配語(yǔ)法;glob是來(lái)自u(píng)nlix(shell指令)用于文件匹配的表達(dá)式,很多主流語(yǔ)言和平臺(tái)(dos、window)都支持,不過(guò)不同語(yǔ)言對(duì)glob的支持程度不同(需要注意)。
1、*:匹配任意多個(gè)任意字符,包括空字符(none)。比如“/data/*.log”匹配“data”目錄下所有以“.log”結(jié)尾的文件。
2、**:類(lèi)似于*,匹配任意多個(gè)字符,不過(guò)它可以越過(guò)目錄分割符,此語(yǔ)法通常用于匹配全路徑。比如:“/data/**”
3、?:只匹配一個(gè)字符。
4、\:轉(zhuǎn)義符,用于轉(zhuǎn)義特殊字符,比如“\”、“-”、“{”、“}”、“[”等等。比如需要匹配“{”那么其字面表達(dá)式為“\{”,“\\”用于匹配單斜杠。
5、!:非,不包含,通常配合[]使用。(貌似不支持^)
6、{}:指定一組子規(guī)則,比如:
1){sum,moon,stars}:匹配“sun”或者“moon”或者“starts”(其一即可),子規(guī)則使用“,”分割。
2){temp*,tmp*}:匹配以temp或者tmp開(kāi)頭的所有字符串。
7、[]:匹配一組字符串中的單個(gè)字符,如果字符串集中包含“-”則匹配區(qū)間中單個(gè)字符。比如[abc]匹配“a”或者“b”或者“c”,[a-z]匹配a到z小寫(xiě)字符中的一個(gè),[0-9]匹配0~9任意一個(gè)數(shù)字。可以混合使用,比如[abce-g]匹配“a”、“b”、“c”、“e”、“f”、“g”,[!a-c]匹配除a、b、c之外的任意一個(gè)字符。復(fù)合子規(guī)則使用“,”分割,比如[a-z,0-9]匹配a~z或者0~9任意一個(gè)字符。
在[]中,“*”、“?”、“\”只匹配其自己(字面),如果“-”在[]內(nèi)且是第一個(gè)字符或者在!之后,也匹配自己。
8、文件中的前導(dǎo)字符、“.”將作為普通字符對(duì)待。比如“*”可以匹配“.tmp”這個(gè)文件,F(xiàn)Iles.isHidden()可以用來(lái)檢測(cè)此文件為隱藏文件。
9、其他所有字符匹配其自己(取決于因平臺(tái)而已的實(shí)現(xiàn)方式,包括路徑分隔符等)。
示例:
1、*.html匹配任意“.html”結(jié)尾的文件。
2、???:匹配任意三個(gè)字符(包括數(shù)字字符)
3、*[0-9]*:匹配包含一個(gè)數(shù)字的任意多個(gè)字符。
4、*.{htm,html},匹配任意以“html”或者“htm”結(jié)尾的字符串。
GLobbing表達(dá)式,一種比較便捷的過(guò)濾策略,對(duì)于一些簡(jiǎn)單的操作(主要是只根據(jù)文件或者路徑名特性匹配時(shí)),可以不使用Filter的情況下完成,當(dāng)然glob的內(nèi)部實(shí)現(xiàn)仍然是基于封裝的Filter來(lái)實(shí)現(xiàn)(PathMatcher);但是glob語(yǔ)法相對(duì)簡(jiǎn)單,JDK NIO2有關(guān)文件過(guò)濾表達(dá)式,可以同時(shí)支持glob和正則表達(dá)式。稍后介紹如何使用PathMatcher來(lái)遍歷文件或者目錄樹(shù)。
- Path dir = Paths.get("/data");
- //內(nèi)部,默認(rèn)會(huì)對(duì)glob表達(dá)式增加前綴,glob,為了兼容PathMatcher
- DirectoryStream<Path> stream = Files.newDirectoryStream(dir,"\*.txt");
- for (Path path : stream) {
- System.out.println(path);
- }
- stream.close();
4.5、操作鏈接文件
硬連接(或者連接):
1)文件有相同的 inode 及 data block;
2)只能對(duì)已存在的文件進(jìn)行創(chuàng)建;
3)不能交叉文件系統(tǒng)進(jìn)行硬鏈接的創(chuàng)建;
4)不能對(duì)目錄進(jìn)行創(chuàng)建,只可對(duì)文件創(chuàng)建;
5)刪除一個(gè)硬鏈接文件并不影響其他有相同 inode 號(hào)的文件。
軟連接(符號(hào)連接):軟鏈接與硬鏈接不同,若文件用戶(hù)數(shù)據(jù)塊中存放的內(nèi)容是另一文件的路徑名的指向,則該文件就是軟連接。軟鏈接就是一個(gè)普通文件,只是數(shù)據(jù)塊內(nèi)容有點(diǎn)特殊。軟鏈接有著自己的 inode 號(hào)以及用戶(hù)數(shù)據(jù)塊。因此軟鏈接的創(chuàng)建與使用沒(méi)有類(lèi)似硬鏈接的諸多限制:
1)軟鏈接有自己的文件屬性及權(quán)限等;
2)可對(duì)不存在的文件或目錄創(chuàng)建軟鏈接;
3)軟鏈接可交叉文件系統(tǒng);
4)軟鏈接可對(duì)文件或目錄創(chuàng)建;
5)創(chuàng)建軟鏈接時(shí),鏈接計(jì)數(shù) i_nlink 不會(huì)增加;
6)刪除軟鏈接并不影響被指向的文件,但若被指向的原文件被刪除,則相關(guān)軟連接被稱(chēng)為死鏈接(即 dangling link,若被指向路徑文件被重新創(chuàng)建,死鏈接可恢復(fù)為正常的軟鏈接)。
- ##創(chuàng)建連接
- ln 目標(biāo) 連接名
- ## 創(chuàng)建軟連接
- ln \-s 目標(biāo) 連接名
- ##查看軟連接目標(biāo)指向,對(duì)于硬連接是不顯示。ls \-l 軟連接文件
- ##通過(guò)stat指令可以查看軟硬連接的inode和block信息
- ##發(fā)現(xiàn)硬連接與目標(biāo)文件的信息完全一致。##軟連接文件有單獨(dú)的inode和block。
創(chuàng)建連接
- Path target = Paths.get("/data/test.log");
- //target必須存在
- Files.createLink(Paths.get("/data/hard\-link.log"),target);
- Files.createSymbolicLink(Paths.get("/data/soft\-link.log"),target);
- //檢測(cè)文件是否為軟連接問(wèn)題
- boolean isSymbolicLink = Files.isSymbolicLink(Paths.get("/data/soft\-link.log"));
- Path target2 = Files.readSymbolicLink(Paths.get("/data/soft\-link.log"));
- //檢測(cè)是否為同一個(gè)文件
- System.out.println(Files.isSameFile(target,target2));//true
- System.out.println(Files.isSameFile(target,Paths.get("/data/hard\-link.log")));//true
- System.out.println(Files.isSameFile(target,Paths.get("/data/soft\-link.log")));//true
- 通過(guò)Files.isSameFile()比較,我們會(huì)發(fā)現(xiàn),無(wú)論是軟連接、硬鏈接,都與目標(biāo)文件是同一個(gè)文件。
4.6、查找文件
前文中介紹了有關(guān)PathMatcher,在JAVA NIO2中用于匹配文件的表達(dá)式,可以支持glob和正則表達(dá)式(regex)兩種方式。其中g(shù)lob的語(yǔ)法更接近linux shell,regex是更廣泛、更豐富的一種方式。比如文件:/data/test.log
- Path path = Paths.get("/data/test.log");
- PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:\*\*.log");
- System.out.println(pathMatcher.matches(path));
- //基于正則
- pathMatcher = FileSystems.getDefault().getPathMatcher("regex:.\*\\\\.log");
- System.out.println(pathMatcher.matches(path));
表達(dá)式規(guī)則:syntax:pattern,其中合法的syntax為“glob”和“regex”;需要注意這兩種表達(dá)式的區(qū)別。內(nèi)部實(shí)現(xiàn)也比較簡(jiǎn)單,對(duì)于glob字符串將會(huì)轉(zhuǎn)化為正則表達(dá)式字符串,然后統(tǒng)一使用正則匹配。
4.7、遞歸遍歷目錄樹(shù)
曾經(jīng),使用JAVA遍歷文件數(shù)是一件比較繁瑣的事情,在NIO2中增加了原生提供了此操作。主要API為FileVisitor,其簡(jiǎn)單實(shí)現(xiàn)類(lèi)為SimpleFileVisitor:
1、preVisitDirectory:在瀏覽目錄之前。前置操作。比如遍歷并復(fù)制文件時(shí),可以在進(jìn)入目錄之前,創(chuàng)建遷移的目標(biāo)新目錄。
2、postVisitDirectory:在瀏覽目錄中所有文件之后(瀏覽其他目錄之前)。后置操作。
3、visitFile:瀏覽文件,Path和BaseFileAttributes會(huì)傳遞入方法。
4、visitFileFailed:瀏覽文件失敗時(shí)調(diào)用,比如文件屬性無(wú)法獲取、目錄無(wú)法打開(kāi)等異常時(shí),調(diào)用此方法,同時(shí)傳入Path和Exception。
簡(jiǎn)單的遍歷(查找、篩選匹配)
- Path dir = Paths.get("/data/redis");
- Stream<Path> stream = Files.walk(dir);
- stream.forEach(path \-> {
- System.out.println(path);
- });
- stream.close();
復(fù)雜遍歷(遍歷查找、文件遷移校驗(yàn))
- public static void main(String\[\] args) throws Exception{
- Path dir = Paths.get("/data/redis");
- Files.walkFileTree(dir,new Finder());
- }
- public static class Finder implements FileVisitor<Path> {
- @Override
- public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
- System.out.println("preVisitDirectory:" + dir);
- return FileVisitResult.CONTINUE;
- }
- @Override
- public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
- System.out.println("visitFile:" + file);
- return FileVisitResult.CONTINUE;
- }
- @Override
- public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
- System.out.println("visitFileFailed:" + file + ",exception:" + (exc != null ? exc.getMessage() : "\-"));
- return FileVisitResult.CONTINUE;
- }
- @Override
- public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
- System.out.println("postVisitDirectory:" + dir);
- return FileVisitResult.CONTINUE;
- }
- }
FileVisitResult用于表示執(zhí)行狀態(tài):
1)CONTINUE:表示繼續(xù)執(zhí)行(繼續(xù)下一步操作)
2)TERMINATE:終止遞歸遍歷,其他的后續(xù)方法不會(huì)被執(zhí)行,尚未瀏覽的文件也將不會(huì)被訪(fǎng)問(wèn)。
3)SKIP_SUBTREE:跳過(guò)子樹(shù),即當(dāng)前目錄以及其子目錄都將被跳過(guò)。適用在preVisitDirectory(),其他方法返回此值則等效于CONTINUE。
4)SKIP_SIBLINGS:跳過(guò)此文件(或者目錄)的同級(jí)文件或者文件,適用在postVisitDirectory(),如果preVisitDirectory返回此值,則當(dāng)前目錄也會(huì)跳過(guò),且postVisitDirectory()不會(huì)被執(zhí)行。