一、需求
遍歷目錄是操作文件時的一個常見需求。比如寫一個程序,需要找到並處理指定目錄下的所有JS文件時,就需要遍歷整個目錄。該項目使用流式編程和lambda表達式,幫助你進一步熟悉java8特性,並且通過它實現目錄遍歷。
二、項目源代碼
1 package com.wordcount.demo; 2 3 import java.io.File; 4 import java.nio.file.FileSystems; 5 import java.nio.file.Files; 6 import java.nio.file.Path; 7 import java.util.Scanner; 8 9 /** 10 * 遍歷目錄是操作文件時的一個常見需求。比如寫一個程序,需要找到並處理指定目錄下的所有JS文件時, 11 * 就需要遍歷整個目錄。該項目教會你如何使用流式編程和lambda表達式, 幫助你進一步熟悉java8特性,並且通過它實現目錄遍歷。 12 * 13 * @author Administrator 14 */ 15 public class DirFile { 16 public static void main(String[] args) throws Exception { 17 Scanner sc = new Scanner(System.in); 18 System.out.println("請輸入文件的絕對路徑:"); 19 String path = sc.next(); 20 // String EndName = sc.next(); 21 oldMethod(path); 22 System.out.println("----------"); 23 newMethod(path); 24 } 25 //傳統方法-用遞歸處理 26 public static void oldMethod(String path) { 27 if (new File(path).isDirectory()) { 28 String[] childs = new File(path).list(); 29 for (String child : childs) { 30 31 oldMethod(path + "\\" + child); 32 System.out.println(path + "\\" + child); 33 } 34 } 35 } 36 //利用Stream流式和lambda表達式完成 37 public static void newMethod(String path) throws Exception { 38 Path start = FileSystems.getDefault().getPath(path); 39 Files.walk(start).filter(childpath -> childpath.toFile().isFile()) 40 // .filter(path -> path.toString().endsWith(EndName)) 41 .forEach(System.out::println); 42 } 43 }
三、項目知識點學習
1、File工具類基礎
構造方法
public File(String pathname){}
在pathname路徑下創建文件對象
public File(String path,String name){}
在path參數指定的目錄中創建具有給定名字的File對象,如果path為null,構造器將使用當前目錄創建對象
public File(File dir, String name){}
File對象dir表示一個目錄,在dir參數指定的目錄中創建具有給定名字的File對象,如果dir為null,
構造器將使用當前目錄創建對象
File類的方法:http://www.runoob.com/java/java-file.html
2、流式編程Stream?
Stream 不是集合元素,它不是數據結構並不保存數據,它是有關算法和計算的,它更像一個高級版本的 Iterator。原始版本的 Iterator,用戶只能顯式地一個一個遍歷元素並對其執行某些操作;高級版本的 Stream,用戶只要給出需要對其包含的元素執行什么操作,比如 “過濾掉長度大於 10 的字符串”、“獲取每個字符串的首字母”等,Stream 會隱式地在內部進行遍歷,做出相應的數據轉換。
Stream 就如同一個迭代器(Iterator),單向,不可往復,數據只能遍歷一次,遍歷過一次后即用盡了,就好比流水從面前流過,一去不復返。而和迭代器又不同的是,Stream 可以並行化操作,迭代器只能命令式地、串行化操作。顧名思義,當使用串行方式去遍歷時,每個 item 讀完后再讀下一個 item。而使用並行去遍歷時,數據會被分成多個段,其中每一個都在不同的線程中處理,然后將結果一起輸出。Stream 的並行操作依賴於 Java7 中引入的 Fork/Join 框架(JSR166y)來拆分任務和加速處理過程。
Stream是用函數編程的方式在集合類上進行復雜操作的工具
3、流的知識
當我們使用一個流的時候,通常包括三個基本步驟:
獲取一個數據源(source)→ 數據轉換→執行操作獲取想要的結果,每次轉換原有 Stream 對象不改變,返回一個新的 Stream 對象(可以有多次轉換),這就允許對其操作可以像鏈條一樣排列,變成一個管道,如下圖所示。
有多種方式生成 Stream Source:
從 Collection 和數組
Collection.stream()
Collection.parallelStream()
Arrays.stream(T array) or Stream.of()
從 BufferedReader
java.io.BufferedReader.lines()
靜態工廠
java.util.stream.IntStream.range()
java.nio.file.Files.walk()
自己構建
java.util.Spliterator
其它
Random.ints()
BitSet.stream()
Pattern.splitAsStream(java.lang.CharSequence)
JarFile.stream()
流的操作類型分為兩種:
- Intermediate:一個流可以后面跟隨零個或多個 intermediate 操作。其目的主要是打開流,做出某種程度的數據映射/過濾,然后返回一個新的流,交給下一個操作使用。這類操作都是惰性化的(lazy),就是說,僅僅調用到這類方法,並沒有真正開始流的遍歷。
- Terminal:一個流只能有一個 terminal 操作,當這個操作執行后,流就被使用“光”了,無法再被操作。所以這必定是流的最后一個操作。Terminal 操作的執行,才會真正開始流的遍歷,並且會生成一個結果,或者一個 side effect。
在這里用的操作:
filter
filter 對原始 Stream 進行某項測試,通過測試的元素被留下來生成一個新 Stream。
Integer[] sixNums = {1, 2, 3, 4, 5, 6}; Integer[] evens = Stream.of(sixNums).filter(n -> n%2 == 0).toArray(Integer[]::new);
經過條件“被 2 整除”的 filter,剩下的數字為 {2, 4, 6}。(留下偶數)
List<String> output = reader.lines(). flatMap(line -> Stream.of(line.split(REGEXP))). filter(word -> word.length() > 0). collect(Collectors.toList());
這段代碼首先把每行的單詞用 flatMap 整理到新的 Stream,然后保留長度不為 0 的,就是整篇文章中的全部單詞了。(把單詞挑出來)
forEach
forEach 方法接收一個 Lambda 表達式,然后在 Stream 的每一個元素上執行該表達式。
// Java 8 roster.stream() .filter(p -> p.getGender() == Person.Sex.MALE) .forEach(p -> System.out.println(p.getName())); // Pre-Java 8 for (Person p : roster) { if (p.getGender() == Person.Sex.MALE) { System.out.println(p.getName()); } }
對一個人員集合遍歷,找出男性並打印姓名。可以看出來,forEach 是為 Lambda 而設計的,保持了最緊湊的風格。而且 Lambda 表達式本身是可以重用的,非常方便。當需要為多核系統優化時,可以 parallelStream().forEach(),只是此時原有元素的次序沒法保證,並行的情況下將改變串行時操作的行為,此時 forEach 本身的實現不需要調整,而 Java8 以前的 for 循環 code 可能需要加入額外的多線程邏輯。(打印姓名)
總之,Stream 的特性可以歸納為:
- 不是數據結構
- 它沒有內部存儲,它只是用操作管道從 source(數據結構、數組、generator function、IO channel)抓取數據。
- 它也絕不修改自己所封裝的底層數據結構的數據。例如 Stream 的 filter 操作會產生一個不包含被過濾元素的新 Stream,而不是從 source 刪除那些元素。
- 所有 Stream 的操作必須以 lambda 表達式為參數
- 不支持索引訪問
- 你可以請求第一個元素,但無法請求第二個,第三個,或最后一個。不過請參閱下一項。
- 很容易生成數組或者 List
- 惰性化
- 很多 Stream 操作是向后延遲的,一直到它弄清楚了最后需要多少數據才會開始。
- Intermediate 操作永遠是惰性化的。
- 並行能力
- 當一個 Stream 是並行化的,就不需要再寫多線程代碼,所有對它的操作會自動並行進行的。
- 可以是無限的
- 集合有固定大小,Stream 則不必。limit(n) 和 findFirst() 這類的 short-circuiting 操作可以對無限的 Stream 進行運算並很快完成。
4、lambda表達式
Lambda 表達式,也可稱為閉包,它是推動 Java 8 發布的最重要新特性。
Lambda 允許把函數作為一個方法的參數(函數作為參數傳遞進方法中)。
使用 Lambda 表達式可以使代碼變的更加簡潔緊湊。
以下是lambda表達式的重要特征:
- 可選類型聲明:不需要聲明參數類型,編譯器可以統一識別參數值。
- 可選的參數圓括號:一個參數無需定義圓括號,但多個參數需要定義圓括號。
- 可選的大括號:如果主體包含了一個語句,就不需要使用大括號。
- 可選的返回關鍵字:如果主體只有一個表達式返回值則編譯器會自動返回值,大括號需要指定明表達式返回了一個數值。
使用 Lambda 表達式需要注意以下兩點:
- Lambda 表達式主要用來定義行內執行的方法類型接口,例如,一個簡單方法接口。在上面例子中,我們使用各種類型的Lambda表達式來定義MathOperation接口的方法。然后我們定義了sayMessage的執行。
- Lambda 表達式免去了使用匿名方法的麻煩,並且給予Java簡單但是強大的函數化的編程能力。
lambda表達式示例:

1 public class Java8Tester { 2 public static void main(String args[]){ 3 Java8Tester tester = new Java8Tester(); 4 5 // 類型聲明 6 MathOperation addition = (int a, int b) -> a + b; 7 8 // 不用類型聲明 9 MathOperation subtraction = (a, b) -> a - b; 10 11 // 大括號中的返回語句 12 MathOperation multiplication = (int a, int b) -> { return a * b; }; 13 14 // 沒有大括號及返回語句 15 MathOperation division = (int a, int b) -> a / b; 16 17 System.out.println("10 + 5 = " + tester.operate(10, 5, addition)); 18 System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction)); 19 System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication)); 20 System.out.println("10 / 5 = " + tester.operate(10, 5, division)); 21 22 // 不用括號 23 GreetingService greetService1 = message -> 24 System.out.println("Hello " + message); 25 26 // 用括號 27 GreetingService greetService2 = (message) -> 28 System.out.println("Hello " + message); 29 30 greetService1.sayMessage("Runoob"); 31 greetService2.sayMessage("Google"); 32 } 33 34 interface MathOperation { 35 int operation(int a, int b); 36 } 37 38 interface GreetingService { 39 void sayMessage(String message); 40 } 41 42 private int operate(int a, int b, MathOperation mathOperation){ 43 return mathOperation.operation(a, b); 44 } 45 }