使用流式编程和lambda表达式实现java遍历文件目录
一、需求
遍历目录是操作文件时的一个常见需求。比如写一个程序,需要找到并处理指定目录下的所有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 }