列出目錄中的文件
用File類別的list()方法可以很容易的列出目錄中的所有檔案的檔案名稱。如果想要取得檔案而不只檔案名稱的話,可以使用它的listFiles()方法。這很簡單,難的是怎麼去處理這個回傳的清單。我們不再使用傳統的冗長的外部迭代器,而是使用優雅的函數式來實遍這個清單。這裡我們還得用到JDK的新的CloseableStream介面以及一些相關的高階函數。
下面這段程式碼可以列出目前目錄下所有檔案的名字。
複製代碼代碼如下:
Files.list(Paths.get("."))
.forEach(System.out::println);
如果想列出別的目錄的話,可以把”.”替換成想要訪問的目錄的完整路徑。
這裡先是使用了Paths的get()方法,透過一個字串建立了一個Path實例。然後透過Files工具類別的list()方法取得了一個ClosableStream對象,我們可以用它來遍歷目錄下的所有檔案。然後我們使用內部迭代器forEach()來列印出檔案名稱。我們先來看看這段程式碼的部分輸出結果:列出目前目錄下的檔案及子目錄。
複製代碼代碼如下:
./aSampleFiles.txt
./bin
./fpij
…
如果我們只想取得目前目錄的子目錄,而不要檔案的話,可以使用filter()方法:
複製代碼代碼如下:
Files.list(Paths.get("."))
.filter(Files::isDirectory)
.forEach(System.out::println);
ilter()方法將目錄從檔案流篩選出來。我們把Files類別的isDirectory方法的參考傳了進去,而不是傳遞一個lambda表達式。回想下filter()方法它需要的是一個傳回boolean值的Predicate型別,這個方法剛好適合。最後我們用一個內部迭代器來印出目錄的名字。程式將會列印出目前目錄的子目錄。
複製代碼代碼如下:
./bin
./fpij
./output
…
這樣寫簡單多了,跟Java老的寫法相比省了不少程式碼。下面我們來看看如何列出符合某個模式的檔案。
列出目錄下指定的文件
Java很早前就提供了一個list()方法的變種,用來篩選檔名。這個版本的list()方法接受一個FilenameFilter類型的參數。這個介面只有一個accept()方法,它接受兩個參數:File dir(代表目錄),以及String name(代表檔案名稱)。 accept()方法回傳true的話這個檔名就會出現在回傳的清單中,回傳false則不在。我們來實作一下這個方法。
習慣性的做法是將一個實作了FilenameFilter介面的匿名內部類別的實例傳給list()方法。比如說,我們來看下如何用這種方式來回傳fpij目錄下的.java檔。
複製代碼代碼如下:
final String[] files =
new File("fpij").list(new java.io.FilenameFilter() {
public boolean accept(final File dir, final String name) {
return name.endsWith(".java");
}
});
System.out.println(files);
這著實得費些工夫寫幾行程式碼。這樣的程式碼太聒噪了:創建對象,呼叫函數,定義匿名內部類,在類別裡面嵌入方法等等。我們不用再忍受這樣的痛苦了,只需傳一個接受兩個參數並返回bollean的lambda表達式進去就好了。 Java編譯器會搞定剩下的事。
前面那個例子可以簡單的用一個lambda表達式替換掉匿名內部就好了,但是還有進一步優化的空間。新的DirectoryStream工具可以幫助我們更有效率的遍歷大的目錄結構。我們來試下這種方法。這是newDirectoryStream()方法的變種,它接受一個額外的過濾器。
複製代碼代碼如下:
Files.newDirectoryStream(
Paths.get("fpij"), path -> path.toString().endsWith(".java"))
.forEach(System.out::println);
這樣我們去掉了匿名內部類別並把繁瑣的程式碼變得簡潔明了。這兩個版本的輸出結果是一樣的。我們來列印下指定的文件。
這段程式碼只會輸出指定目錄下的.java文件,以下是它的部分輸出結果:
複製代碼代碼如下:
fpij/Compare.java
fpij/IterateString.java
fpij/ListDirs.java
…
我們基於文件名稱來篩選文件,同樣也可以很容易通過文件屬性,例如文件是不是可執行文件,是否可讀,可寫入等來進行篩選。這麼做的話得需要一個listFiles()方法,它接受一個FileFilter類型的參數。我們仍然使用lambda表達式來實作而不是去建立匿名內部類別。現在來看一個列出目前目錄下所有隱藏檔案的範例。
複製代碼代碼如下:
final File[] files = new File(".").listFiles(file -> file.isHidden());
如果我們操作的是一個很大的目錄,可以使用DirectoryStream而不是直接呼叫File上面的方法。
我們傳給listFiles()方法的lambda表達式的簽章和FileFilter介面的accept()方法的簽章是一樣的。這個lambda表達式接受的是一個File實例的參數,在這個例子中參數名稱是file。如果檔案是隱藏屬性的話,剛回傳true,否則回傳false.
這裡其實還可以再精簡程式碼,我們不傳lambda表達式了,傳一個方法引用會讓程式碼看起來會更簡潔一些:
複製代碼代碼如下:
new File(".").listFiles(File::isHidden);
我們先用lambda表達式實現,接著再使用方法引用將它重構得更簡潔。如果我們再寫新的程式碼的話,當然應該採用這種簡潔的方式。如果可以早點發現這種簡潔的實現,我們當然要優先使用它。有一句話叫做」先讓它能運作,然後再去優化(make it work, then make it better)",先讓程式碼能跑起來,等我們理清楚了,才去考慮簡潔性和性能等進行優化。
我們透過一個範例來從目錄中過濾出了指定的檔案。下面我們來看看如何去遍歷指定目錄下的子目錄。