前言 將File操作進(jìn)行分離、封裝,、改進(jìn),,最終形成Path(Paths),、Files,、FileSystem(FileSystems)三個主要類,。 我們親切的叫它NIO2,。 其中,,Paths,、Files中提供了大量便捷的靜態(tài)操作方法;NIO2還提供了有關(guān)文件權(quán)限(屬性)操作,、軟連接,、文件查找等高級API,使得NIO2具有更全面的文件系統(tǒng)操作接口,。 1,、Path文件系統(tǒng)都是Tree或者層級結(jié)構(gòu)來組織文件的,任何一個節(jié)點(diǎn)可以是一個目錄或者一個文件,,在NIO2中稱為Path,,這和原來的File有很多相似之處,只是Path具有更多的表述語義,。 1.1,、基本屬性以下代碼展示了它的基本屬性。 Path path = Paths.get("/data/logs/web.log"); //屬性 //獲取路徑中的文件名或者最后一個節(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:";如果是相對路徑,,則返回null。System.out.printf("Root:%s%n",path.getRoot()); //子路徑,,結(jié)果中不包含root,,前開后閉 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”這種包含“冗余”路徑的,,有時候我們需要轉(zhuǎn)換為正常路徑語句,則使用“Path.normalize()”方法,,無比的清爽清潔。 Path path = Paths.get("/data/logs/../web.log"); //輸出結(jié)果:/data/web.log System.out.printf("%s%n",path.normalize());
2)如果文件需要被外部資源訪問(resource),,你可以通過Path.toUri()來轉(zhuǎn)換,,path對應(yīng)的文件或者目錄可以不存在,此方法不會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():如果路徑為相對路徑,,則轉(zhuǎn)換為絕對路徑,,對于JAVA程序而言,,起始路徑為classpath。此方法不會檢測文件是否真的存在或者有權(quán)限,。 4)其中toRealPath()是比較重要的方法,,不過它會對文件是否存在、訪問權(quán)限進(jìn)行檢測,,需要捕獲異常,。首先檢測文件是否存在、是否有權(quán)限,;如果path為相對路徑,,則將會轉(zhuǎn)換為絕對路徑,同“3)”,;如果是“符號連接”文件(軟連接),,則獲取其實(shí)際target路徑(除非指定了NO_FOLLOW_LINKS);如果路徑中包含“冗余”,,則移除,,同1)。這個方法,,通常用于對“用戶輸入的path”進(jìn)行校驗(yàn)和轉(zhuǎn)換,,使用比較多。 5)resolve():路徑合并,,當(dāng)前path與參數(shù)進(jìn)行路徑合并,,append。 6)relativize():獲取相對路徑,,“/data”與“/data/logs/p1”的相對路徑為“l(fā)ogs/p1”,,反之為“../../”。 2,、FilesFiles類中提供了大量靜態(tài)方法,,用于實(shí)現(xiàn)文件(目錄)的創(chuàng)建、復(fù)制,、遷移,、刪除以及訪問文件數(shù)據(jù)等操作。 2.1,、檢測文件或目錄Files.exists(Path)和notExists(Path)兩個方法,,這兩個方法都會實(shí)際檢測文件或者目錄是否存在、以及是否有訪問權(quán)限,。注意:!exist() 與notExists()并不完全相等,,exist可能有三種狀態(tài):如果不存在或者安全校驗(yàn)不通過則返回false,如果返回true則表示文件確實(shí)存在且有權(quán)限,。notExists()檢測類似,,對于沒有通過安全校驗(yàn)的也會返回false,;當(dāng)exists與notExists同時返回false時,說明文件不可以驗(yàn)證(即無權(quán)限),,所以通常這兩個方法需要同時使用,。 判斷文件(目錄)具有讀、寫,、執(zhí)行的權(quán)限,,可以通過如下方法: Path path = Paths.get("data/logs/web.log"); boolean isRegularExecutableFile = Files.isRegularFile(path) & Files.isReadable(path) & Files.isExecutable(path);
有時候,兩個不同的path,,會指向同一個文件,,比如當(dāng)一個path是軟連接時,此時可以使用Files.isSameFile(p1,p2)來檢測,,當(dāng)然你可以通過Path LinkOption相關(guān)組合獲取target實(shí)際path來比較,。 2.2、刪除delete和deleteIfExists兩個方法均可刪除文件,,前者嘗試刪除的文件如果不存在則會拋出異常,。如果文件是軟連接,則只刪除連接文件而不會刪除target文件,,如果path為目錄,,則目錄需要為空,否則刪除失?。↖OException),。在刪除操作之前,最后做一些常規(guī)的檢測,,比如文件是否存在(有權(quán)限),、目錄是否為空等。稍后我們再介紹“遞歸刪除目錄樹和文件”,。 2.3,、文件(目錄)復(fù)制copy(Path,Path,CopyOption…)方法可以復(fù)制文件,不過,,需要注意CopyOption的相關(guān)選項,。 當(dāng)copy一個軟連接文件時,默認(rèn)將會復(fù)制target文件,,如果只想復(fù)制軟連接文件而不是target內(nèi)容,,可以指定NOFOLLOW_LINKS選項。CopyOption的實(shí)現(xiàn)類為StandardCopyOption,,此外CopyOption也擴(kuò)展了LinkOption,,即包含NOFOLLOW_LINKS選項。如下為CopyOption選項列表: 1)REPLACE_EXISTING:如果目標(biāo)文件已經(jīng)存在,則直接覆蓋,;如果目標(biāo)文件是個軟連接,則軟連接文件本身被覆蓋(而非連接文件的target文件),;如果復(fù)制的是目錄,,且目標(biāo)目錄不為空時,則會拋出異常(DirectoryNotEmptyException),,稍后介紹“遞歸復(fù)制目錄樹和文件”,。此參數(shù)通常必選。復(fù)制目錄時,,目標(biāo)目錄會自動創(chuàng)建,,源目錄中如果有文件,則不會復(fù)制文件,,只會創(chuàng)建空的目標(biāo)目錄,。source和target,要么同時是目錄,、要么同時是文件,。 2)COPY_ATTRIBUTES:復(fù)制文件時,也同時復(fù)制目標(biāo)文件的屬性(metadata),,對于文件屬性(FileAttribute)的支持依賴于文件系統(tǒng)(和平臺),,不過lastModifiedTime通常會被復(fù)制。 3)NOFOLLOW_LINKS:繼承自LinkOption,,表示如果文件是軟連接,,則不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,、移動move(Path,Path,CopyIOption),基本原則同copy,。需要注意,,如果是目錄,目錄中包含文件時也可以移動的(這可能依賴于平臺),,子目錄也一起移動,,但是目標(biāo)目錄必須為空(DirectoryNotEmptyException)?;菊Z義同“mv -rf”,,目標(biāo)目錄不需要提前創(chuàng)建,move結(jié)束后,源目錄將不存在,。支持兩種選項: 1)REPLACE_EXISTING:如果目標(biāo)文件已存在,,則覆蓋;如果目標(biāo)文件是軟連接,,則連接文件被覆蓋但是其指向不會受影響,。 2)ATOMIC_MOVE:原子復(fù)制,需要平臺的文件系統(tǒng)支持(不支持則拋出異常),,指定此參數(shù)時其他選項將被忽略,;如果文件不能被原子復(fù)制(或者替換),則會拋出AtomicMoveNotSupportedException,。 2.5,、打開文件Files類中提供了多個靜態(tài)的方法,用于直接讀寫文件,。如下為文件打開的幾個選項參數(shù)(StandardOpenOptions): 1)WRITE: 打開文件用于write訪問,。 2)APPEND:在文件尾部追加數(shù)據(jù),伴隨用于WRITE或CREATE選項,。 3)TRUNCATE_EXISTING:將文件truncate為空,,伴隨用于WRITE選項。比如,,文件存在時,,將文件數(shù)據(jù)清空并重新寫入。 4)CREATE_NEW:創(chuàng)建新文件,,如果文件已存在則拋出異常,。 5)CREATE:如果文件已存在則直接打開,否則創(chuàng)建文件,。 6)DELETE_ON_CLOSE:當(dāng)文件操作關(guān)閉時則刪除文件(close方法或者JVM關(guān)閉時),,此選項適用于臨時文件(臨時文件不應(yīng)該被其他進(jìn)程并發(fā)訪問)。 7)SPARSE:創(chuàng)建一個“稀疏”文件,,伴隨使用CREATE_NEW,,適用于某些特殊的文件系統(tǒng)比如NTFS,這些大文件允許出現(xiàn)“gaps”(空洞)在某些情況下可以提高性能且這些gaps不消耗磁盤空間,。 8)SYNC:對文件內(nèi)容(data)或者metadata的修改,,都會同步到底層存儲。 9)DSYNC:對文件內(nèi)容的修改,,會同步到底層存儲,。 對于一些小文件(M級別),通常我們希望一次全部讀取所有內(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ù),直接全部寫入文件。(打開,、寫入) 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操作,,返回類型為SeekableByteChannel,這種操作通常適用于讀或者寫,,以及數(shù)據(jù)可以基于buffer進(jìn)行拆封包,,其他特性類似“隨機(jī)訪問文件”。(Files.newByteChannel(),,功能同F(xiàn)ile.open()) Path path = Paths.get("/data/web.log"); //創(chuàng)建一個普通文件,如果已存在則拋出異常,,文件屬性為默認(rèn),。Files.createFile(path);
//創(chuàng)建臨時文件,臨時文件的目錄和前綴可以為null,,后綴如果為null時默認(rèn)使用".tmp"; Files.createTempFile(null,null,".tmp"); //創(chuàng)建一個軟連接文件 Files.createSymbolicLink(Paths.get("/data/link.log"),Paths.get("/data/web.log"));
2.6,、Metadata管理2.6.1、BasicFileAttributesBasicFileAttributes基本接口,,提供了一些基本的文件metadata,,比如lastAccessTime、lastModifiedTime等,,它的實(shí)現(xiàn)類因平臺而已有:DosFileAttributes,、PosixFileAttribute、UnixFileAttribute等,;不同平臺所能支持的屬性有所不同,。(在跨平臺場景下,你可能需要使用FileStore來判斷當(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ù)自己的平臺對應(yīng)相應(yīng)的Attributes類,否則會導(dǎo)致設(shè)置異常,。 2.6.2,、PosixFileAttributesPath path = Paths.get("/data/logs/web.log"); PosixFileAttributes attributes = Files.readAttributes(path,PosixFileAttributes.class);
//用戶組和權(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)中的其他用戶,根據(jù)用戶名 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用戶自定義文件屬性(即擴(kuò)展文件屬性),,以及屬性值的長度,,這取決于底層文件系統(tǒng)或者平臺是否支持,。目前,,主流的平臺和文件系統(tǒng)都支持?jǐn)U展文件屬性,,比如FreeBSD(ZFS),、Linux(ext3、ext4,、ZFS),、MacOS等,。默認(rèn)情況下,,此操作是開啟的,如果已關(guān)閉,,可以通過“sudo mount -o remount,user_xattr {你的文件系統(tǒng)掛載路徑}”,,否則也會拋出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);//我個人認(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獲取所有屬性的列表等,。//寫入或者跟新自定義屬性 view.write(name,Charset.defaultCharset().encode("text/html"));
3,、FileSystemFileStore是新增的API,用于描述底層存儲系統(tǒng),,一個平臺有多個FileStore,我們可以通過FileSystem獲取FileStore的列表,,以及每個store的存儲狀態(tài)、文件列表等,。 Path path = Paths.get("/data/logs/web.log"); path.getFileSystem().getFileStores();//獲取文件所屬的文件系統(tǒng)的所有的存儲器,。//當(dāng)前文件系統(tǒng)所能支持的FileAttributeView,此后可以對文件使用相應(yīng)的view獲取或者修改屬性 Set<String> viewNames = FileSystems.getDefault().supportedFileAttributeViews(); System.out.println(viewNames);//basic,,unix,posix,,owner,,dos等
//或者,,全局 //遍歷所有的磁盤存儲 Iterable<FileStore> fileStores = FileSystems.getDefault().getFileStores();//獲取默認(rèn)文件系統(tǒng)的所有存儲器 for(FileStore store : fileStores) { System.out.println("\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-"); System.out.println("className:" store.getClass().getName()); System.out.println("name:" store.name());//磁盤名稱 System.out.println("type:" store.type());//類型 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的各個實(shí)現(xiàn)類對應(yīng),。Long totalSpace = (Long)store.getAttribute("totalSpace"); System.out.println(totalSpace);
可以獲取每個FileStore支持的FileAttributeView,,此后即可通過Files.getFileAttributeView()來獲取相應(yīng)的視圖類,。每個view都有簡寫名稱,,比如BasicFileAttributeView.name()返回其簡寫名稱為“basic”,。目前JDK支持很多FileAttributeView,,比如BasicFileAttributeView,、UnixFileAttributeView等等,以及允許自定義的UserDefinedFileAttributeView等,。JDK將獲取文件屬性,、修改文件屬性的操作全部基于對應(yīng)的FileAttributeView類來實(shí)現(xiàn)。 PosixFileAttributeView view = Files.getFileAttributeView(path,PosixFileAttributeView.class); PosixFileAttributes fileAttributes = view.readAttributes();
此外,,根據(jù)FileStore的底層存儲不同,,有多種實(shí)現(xiàn),,可以參看FileStore的實(shí)現(xiàn)類,比如UnixFileStore,、BsfFileStore(Mac)等,,每個FileStore所能支持的attribute也不太相同,需要根據(jù)其對應(yīng)的實(shí)現(xiàn)類獲取,,也可以使用FileStore.getAttribute()來獲取,,但是屬性名需要與類中支持的屬性名對應(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()方法是一個“嚴(yán)格校驗(yàn)”的方法,,如果父路徑不存在則會拋出異常,,如果路徑已經(jīng)存在或者同名文件存在則會拋出異常,,簡單來說此方法只能創(chuàng)建最后一級目錄(且此前不存在),。對于createDirectories()方法,,比較兼容,,會逐層校驗(yàn)并創(chuàng)建父路徑,,如果不存在則創(chuàng)建,。 創(chuàng)建目錄時,,也可以像文件那樣,,指定文件屬性(包括權(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方式會遍歷指定目錄下的所有文件,包括文件,。目錄,。連接、隱藏文件或者目錄等,。 此外,,JDK還支持基于filter模式,,在遍歷時過濾“非必要”的文件或者目錄。 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文件過濾器NIO2中新增支持了基于Glob的文件過濾器,,一種類似于正則表達(dá)式的匹配語法;glob是來自unlix(shell指令)用于文件匹配的表達(dá)式,,很多主流語言和平臺(dos,、window)都支持,不過不同語言對glob的支持程度不同(需要注意),。 1,、*:匹配任意多個任意字符,包括空字符(none),。比如“/data/*.log”匹配“data”目錄下所有以“.log”結(jié)尾的文件,。 2、**:類似于*,,匹配任意多個字符,,不過它可以越過目錄分割符,此語法通常用于匹配全路徑,。比如:“/data/**” 3,、?:只匹配一個字符。 4,、\:轉(zhuǎn)義符,,用于轉(zhuǎn)義特殊字符,比如“\”,、“-”,、“{”、“}”,、“[”等等。比如需要匹配“{”那么其字面表達(dá)式為“\{”,,“\\”用于匹配單斜杠,。 5、!:非,不包含,,通常配合[]使用,。(貌似不支持^) 6、{}:指定一組子規(guī)則,,比如: 1){sum,moon,stars}:匹配“sun”或者“moon”或者“starts”(其一即可),,子規(guī)則使用“,”分割。 2){temp*,tmp*}:匹配以temp或者tmp開頭的所有字符串,。 7,、[]:匹配一組字符串中的單個字符,如果字符串集中包含“-”則匹配區(qū)間中單個字符,。比如[abc]匹配“a”或者“b”或者“c”,[a-z]匹配a到z小寫字符中的一個,,[0-9]匹配0~9任意一個數(shù)字??梢曰旌鲜褂?,比如[abce-g]匹配“a”、“b”,、“c”,、“e”、“f”,、“g”,,[!a-c]匹配除a、b,、c之外的任意一個字符,。復(fù)合子規(guī)則使用“,”分割,比如[a-z,0-9]匹配a~z或者0~9任意一個字符,。 在[]中,,“*”、“?”,、“\”只匹配其自己(字面),,如果“-”在[]內(nèi)且是第一個字符或者在!之后,也匹配自己,。 8,、文件中的前導(dǎo)字符,、“.”將作為普通字符對待,。比如“*”可以匹配“.tmp”這個文件,F(xiàn)Iles.isHidden()可以用來檢測此文件為隱藏文件,。 9,、其他所有字符匹配其自己(取決于因平臺而已的實(shí)現(xiàn)方式,包括路徑分隔符等),。 示例: 1,、*.html匹配任意“.html”結(jié)尾的文件。 2,、???:匹配任意三個字符(包括數(shù)字字符) 3,、*[0-9]*:匹配包含一個數(shù)字的任意多個字符。 4,、*.{htm,html},,匹配任意以“html”或者“htm”結(jié)尾的字符串。 GLobbing表達(dá)式,,一種比較便捷的過濾策略,,對于一些簡單的操作(主要是只根據(jù)文件或者路徑名特性匹配時),可以不使用Filter的情況下完成,,當(dāng)然glob的內(nèi)部實(shí)現(xiàn)仍然是基于封裝的Filter來實(shí)現(xiàn)(PathMatcher),;但是glob語法相對簡單,JDK NIO2有關(guān)文件過濾表達(dá)式,,可以同時支持glob和正則表達(dá)式,。稍后介紹如何使用PathMatcher來遍歷文件或者目錄樹。 Path dir = Paths.get("/data"); //內(nèi)部,,默認(rèn)會對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)只能對已存在的文件進(jìn)行創(chuàng)建; 3)不能交叉文件系統(tǒng)進(jìn)行硬鏈接的創(chuàng)建,; 4)不能對目錄進(jìn)行創(chuàng)建,只可對文件創(chuàng)建; 5)刪除一個硬鏈接文件并不影響其他有相同 inode 號的文件,。 軟連接(符號連接):軟鏈接與硬鏈接不同,,若文件用戶數(shù)據(jù)塊中存放的內(nèi)容是另一文件的路徑名的指向,則該文件就是軟連接,。軟鏈接就是一個普通文件,,只是數(shù)據(jù)塊內(nèi)容有點(diǎn)特殊。軟鏈接有著自己的 inode 號以及用戶數(shù)據(jù)塊,。因此軟鏈接的創(chuàng)建與使用沒有類似硬鏈接的諸多限制: 1)軟鏈接有自己的文件屬性及權(quán)限等,; 2)可對不存在的文件或目錄創(chuàng)建軟鏈接; 3)軟鏈接可交叉文件系統(tǒng),; 4)軟鏈接可對文件或目錄創(chuàng)建,; 5)創(chuàng)建軟鏈接時,鏈接計數(shù) i_nlink 不會增加,; 6)刪除軟鏈接并不影響被指向的文件,,但若被指向的原文件被刪除,則相關(guān)軟連接被稱為死鏈接(即 dangling link,,若被指向路徑文件被重新創(chuàng)建,,死鏈接可恢復(fù)為正常的軟鏈接)。 ##創(chuàng)建連接 ln 目標(biāo) 連接名 ## 創(chuàng)建軟連接 ln \-s 目標(biāo) 連接名
##查看軟連接目標(biāo)指向,,對于硬連接是不顯示,。ls \-l 軟連接文件
##通過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);
//檢測文件是否為軟連接問題 boolean isSymbolicLink = Files.isSymbolicLink(Paths.get("/data/soft\-link.log"));
Path target2 = Files.readSymbolicLink(Paths.get("/data/soft\-link.log")); //檢測是否為同一個文件 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
通過Files.isSameFile()比較,,我們會發(fā)現(xiàn),無論是軟連接,、硬鏈接,,都與目標(biāo)文件是同一個文件。
4.6,、查找文件前文中介紹了有關(guān)PathMatcher,,在JAVA NIO2中用于匹配文件的表達(dá)式,可以支持glob和正則表達(dá)式(regex)兩種方式,。其中g(shù)lob的語法更接近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)也比較簡單,,對于glob字符串將會轉(zhuǎn)化為正則表達(dá)式字符串,,然后統(tǒng)一使用正則匹配。 4.7,、遞歸遍歷目錄樹曾經(jīng),,使用JAVA遍歷文件數(shù)是一件比較繁瑣的事情,在NIO2中增加了原生提供了此操作,。主要API為FileVisitor,,其簡單實(shí)現(xiàn)類為SimpleFileVisitor: 1、preVisitDirectory:在瀏覽目錄之前,。前置操作,。比如遍歷并復(fù)制文件時,可以在進(jìn)入目錄之前,,創(chuàng)建遷移的目標(biāo)新目錄,。 2、postVisitDirectory:在瀏覽目錄中所有文件之后(瀏覽其他目錄之前),。后置操作,。 3、visitFile:瀏覽文件,,Path和BaseFileAttributes會傳遞入方法,。 4、visitFileFailed:瀏覽文件失敗時調(diào)用,,比如文件屬性無法獲取,、目錄無法打開等異常時,調(diào)用此方法,,同時傳入Path和Exception,。 簡單的遍歷(查找、篩選匹配) 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ù)方法不會被執(zhí)行,尚未瀏覽的文件也將不會被訪問,。 3)SKIP_SUBTREE:跳過子樹,,即當(dāng)前目錄以及其子目錄都將被跳過。適用在preVisitDirectory(),,其他方法返回此值則等效于CONTINUE,。 4)SKIP_SIBLINGS:跳過此文件(或者目錄)的同級文件或者文件,適用在postVisitDirectory(),,如果preVisitDirectory返回此值,,則當(dāng)前目錄也會跳過,,且postVisitDirectory()不會被執(zhí)行。 End好了,,關(guān)于NIO2新增的API,,就已經(jīng)介紹完畢了。
|