自從java 7以來,引入了FIles類和Path接口。他們兩封裝了用戶對文件的所有可能的操作,相比於java 1的File類來說,使用起來方便很多。但是其實一些本質的操作還是很類似的。主要需要知道的是,Path表示路徑可以使文件的路徑也可以是目錄的路徑,Files中所有成員都是靜態方法,通過路徑實現了對文件的基本操作。下面我們首先看看Path接口。
一、Path接口
和Path接口相關的還有一個類Paths,這個類非常簡單,只有兩個方法加一個私有構造方法。
public final class Paths {
private Paths() { }
public static Path get(String first, String... more) {
return FileSystems.getDefault().getPath(first, more);
}
public static Path get(URI uri) {/*這個方法我們暫時不研究*/}
}
大家可以看到,通過Paths的get靜態方法,我們可以獲得一個Path對象,而實際上我們通常都是通過Paths的這個get方法來獲取Path對象。(至於怎么調用這個方法返回的對象,主要是通過調用文件系統的抽象方法來實現的),獲得了Path對象,我們就可以調用其內部封裝的所有的方法,由於方法比較多,我們一點一點看。
boolean isAbsolute();
Path getFileName();
Path getParent();
int getNameCount();
Path getName(int index);
Path subpath(int beginIndex, int endIndex);
Path resolve(Path other);
Path resolveSibling(Path other);
Path relativize(Path other);
Path toAbsolutePath();
File toFile();
主要的方法就這么多,其中有些方法的功能乃至具體的實現代碼和上篇介紹的File類類似,這里就不在說明了。getFileName這個方法類似於File類的getName方法,返回路徑的文件名稱(目錄名或者文件名),兩者實現原理基本一樣。
Path p = Paths.get("a","b","c","d","e");
System.out.println(p.getNameCount());
/*執行代碼可以看到輸出結果:5*/
Path p = Paths.get("a/t","b","c","d","e");
System.out.println(p.getNameCount());
/*執行代碼輸出結果:6*/
可以看到,getNameCount方法並不是直接數構建Path對象時傳入了幾個字符串。當我們調用Path.get方法傳入可變字符串作為路徑時,它將每個獨立的字符串當成一個目錄名,使用默認路徑分隔符連接這些路徑名形成Path路徑,而調用getNameCount方法是根據默認路徑分隔符的個數來統計返回的。
getName方法要求傳入一個int型索引,在構建路徑時,從根路徑開始每一層都被編號了,根目錄為0,子目錄依次加一所以getName方法可以獲取任意層次目錄的名稱。
Path p = Paths.get("a","b","c","d","e");
System.out.println(p.getName(1));
/*輸出:b*/
subpath方法和我們String中的substring類似,給定開始和結束位置的索引值,獲取他們之間的路徑字符串。
Path p = Paths.get("a","b","c","d","e");
System.out.println(p.subpath(1,3));
/*輸出結果:b/c*/
需要注意一個細節,截取范圍[startIndex,endIndex),也就是endIndex位置的值是不會被截取的。
resolve方法是一個很有意思的方法,按照我的理解,這個方法實現的是對路徑的組合的操作。p.resolve(q),如果q是絕對路徑,返回結果為q,如果q是相對路徑返回結果是p+q,實現了拼接組合。
Path p = Paths.get("a","b","c","d","e");
Path q = Paths.get("c:/users");
System.out.println(p.resolve(q));
/*輸出:c:/users*/
Path p = Paths.get("a","b","c","d","e");
Path q = Paths.get("users");
System.out.println(p.resolve(q));
/*輸出結果:a\b\c\d\e\users*/
resolveSibling方法是通過解析當前路徑的父目錄,產生兄弟路徑。
Path p = Paths.get("a","b","c","d","e");
Path q = Paths.get("users");
System.out.println(p.resolveSibling(q));
/*輸出結果:a\b\c\d\users*/
替換了e為users,因為e作為當前目錄,而此方法就是在當前目錄下生成一個和他同級的兄弟目錄。通常可以用來修改當前目錄的目錄名。(在生成磁盤文件之前)
接下來說說這個relativize方法,它是一個用來生成一個相對路徑的方法。需要額外傳入一個Path對象。
Path p = Paths.get("a","b","c","d","e");
Path q = Paths.get("users");
System.out.println(p.relativize(q));
/*輸出結果:..\..\..\..\..\users*/
Path p = Paths.get("a","b","c","d","e");
Path q = Paths.get("a","b","c","d","e","f");
System.out.println(p.relativize(q));
/*輸出結果:f*/
通過比較可以發現,所謂生成相對路徑,實際上就是,q相對於p。從第二中情況我們可以看出來,整個p可以作為q的父目錄,於是相對路徑就是當前目錄f。對於第一種情況,因為沒有找到公共目錄,所以整個p作為q的父目錄,當然相對於q來說,相對路徑就是這樣了。
最后還想啰嗦一句,在Path中有一個方法toFile,這個方法對應於FIle中的toPath,為什么要實現這么兩個方法,其實還是為了兼容舊的File類,方便一些舊的系統成功的跨度到新的java標准中來。
二、Files類
上面說過,整個FIles類中,都是靜態方法,沒有一個實例域。(足以見得,這個類就是為了實現對文件的各種操作)首先看看對文件的讀寫操作。
public static InputStream newInputStream(Path path, OpenOption... options)
public static OutputStream newOutputStream(Path path, OpenOption... options)
public static BufferedReader newBufferedReader(Path path, Charset cs)
public static BufferedReader newBufferedReader(Path path)
public static BufferedWriter newBufferedWriter
public static byte[] readAllBytes(Path path)
public static List<String> readAllLines(Path path)
public static Path write(Path path, byte[] bytes, OpenOption... options)
public static long copy(InputStream in, Path target, CopyOption... options)
public static long copy(Path source, OutputStream out)
方法很多,我們慢慢看,首先有兩個方法可以根據Path路徑返回InputStream/OutputStream字節流對象,這兩個方法為我們下面的一些方法提供了一定的便利,也有兩個方法通過Path對象返回BufferedReader/BufferedWriter對象。這些都是對於我們其他的方法是有幫助的。
readAllBytes內部通過創建InputStream對象來讀取所有的字節到給定的字節數組中並返回。readAllLines內部通過創建List數組,使用BufferedReader創建字符緩沖流,一行一行的讀取。最后返回List集合。寫的操作基本都是讀的逆操作,這里不再贅述。
copy這個方法有多個重載,分別是:
private static long copy(InputStream source, OutputStream sink)
public static long copy(InputStream in, Path target, CopyOption... options)
public static long copy(Path source, OutputStream out)
public static Path copy(Path source, Path target, CopyOption... options)
第一個重載是一個私有的方法,是被被人調用的工具方法。主要的功能是:從一個source流中讀取所有的字節並寫入sink流中,返回實際讀入或寫入的字節數。第二個重載是選擇將Path對象通過方法newOutputStream,構建了一個OutputStream對象,然后調用第一個重載方法實現copy。完成的功能是:從一個InputStream流中讀取所有的字節並寫入一個指定的文件中。第三個重載方法主要是:從一個Path文件中讀取所有的字節並寫入一個OutputStream對象流中。操作流程類似,不在贅述。最后一個重載方法實現的是從一個Path對象復制到另一個Path對象。
//根目錄下只有hello.txt文件,沒有world文件
Path p = Paths.get("hello.txt");
Path q = Paths.get("world.txt");
Files.copy(p,q);
/*world文件被創建並且hello中的內容被復制到此*/
對於這個操作,需要注意的幾點:如果q在磁盤為位置的文件已經存在將不能完成復制操作,如果p在磁盤位置上沒有對應文件此操作依然失敗,如果p是一個目錄文件,結果會復制一個名為world的目錄文件,如果q是一個目錄文件則會創建一個無類型的文件(hello中的內容已經被復制進去)。
說完了有關文件的讀寫操作,下面說說文件或目錄的創建和獲取文件的基本信息。
public static Path createFile(Path path, FileAttribute<?>... attrs)
public static Path createDirectory(Path dir, FileAttribute<?>... attrs)
public static Path createDirectories(Path dir, FileAttribute<?>... attrs)
public static Path createTempFile
因為Path路徑中存放的可以是文件類型,也可以是目錄類型。那么在創建的時候就需要進行區分了。createFile根據指定路徑創建一個指定類型的文件,createDirectory和createDirectories的區別在,如果Path路徑上存在着沒有被創建的目錄,后者會將他們全部都創建。對於創建臨時文件,由於用的不多,就不說了。
對於文件信息的獲取主要有以下些方法:
public static boolean isSameFile(Path path, Path path2)
public static boolean isHidden(Path path)
public static String probeContentType(Path path)
public static boolean isDirectory(Path path, LinkOption... options)
public static boolean isRegularFile(Path path, LinkOption... options)
public static long size(Path path)
public static boolean exists(Path path, LinkOption... options)
public static boolean isReadable(Path path)
public static boolean isWritable(Path path)
/*這些方法名字就是注釋,相信大家一眼就能識別他們各自的功能*/
最后談談迭代和過濾器,在上篇文章的最后,我們說了FIle的過濾和迭代,因為在File類中,通常都是一次性返回一個File數組或者String數組,這往往是低效的。在Files類中,設計了一個方法newDirectoryStream,返回了一個目錄流,可以顯著提高效率。
public static DirectoryStream<Path> newDirectoryStream(Path dir)
public static DirectoryStream<Path> newDirectoryStream(Path dir, String glob)
public static DirectoryStream<Path> newDirectoryStream(Path dir,
DirectoryStream.Filter<? super Path> filter)
這是三個目錄流的重載方法,第一個只需要提供一個Path路徑即可,第二個方法提供了一個Path對象和一個glob字符串。glob模式:

第三個方法還外部指定了一個過濾器。具體怎么使用,下面看代碼。
DirectoryStream<Path> d = Files.newDirectoryStream(Paths.get("f:/360"));
for (Path p : d){
System.out.println(p.getFileName());
}
//輸出結果:
360defender
360sdSetup.exe
360zip
//這是我f盤360文件下的所有文件
DirectoryStream<Path> d = Files.newDirectoryStream(Paths.get("f:/360"),"*.exe");
for (Path p : d){
System.out.println(p.getFileName());
}
//輸出結果:
360sdSetup.exe
DirectoryStream<Path> d = Files.newDirectoryStream(Paths.get("f:/360"),new DirectoryStream.Filter<Path>(){
@Override
public boolean accept(Path entry) {
return Files.isDirectory(entry)?true:false;
}
});
for (Path p : d){
System.out.println(p.getFileName());
}
//輸出結果:
360defender
360zip
第三種方法通過顯式傳入一個過濾器來實現獲取子目錄中所有的目錄。
如果本文有錯誤,歡迎大家指出!
