使用流式编程和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 }
View Code

 

posted @ 2018-11-25 20:20  wangchuanli  阅读(2103)  评论(0编辑  收藏  举报