java-流式API:Stream API

Java8引入了一个全新的流式API:Stream API,它位于java.util.stream包中。
这个Stream不同于java.ioInputStreamOutputStream,它代表的是任意Java对象的序列

Stream和List也不一样,List存储的每个元素都是已经存储在内存中的某个Java对象,而Stream输出的元素可能并没有预先存储在内存中,而是实时计算出来的。

Stream可以表示无限序列,元素有可能已经全部存储在内存中,也有可能是根据需要实时计算出来的。一个Stream可以轻易地转换为另一个Stream,而不是修改原Stream本身。真正的计算通常发生在最后结果的获取,也就是惰性计算。

1.创建Stream

最简单的方式是直接用Stream.of()

public class StreamDemo {
    public static void main(String[] args) {
        Stream<Integer> integerStream=Stream.of(1,2,3,4,5);
        integerStream.forEach(System.out::print);
    }
}

再就是基于一个数组或者Collection

public class StreamDemo {
    public static void main(String[] args) {
        Integer[] array = {1, 2, 3, 4, 5};
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            list.add(i + 1);
        }

        Stream<Integer> stream1 = Arrays.stream(array);
        Stream<Integer> stream2 = list.stream();

        stream1.forEach(System.out::print);
        stream2.forEach(System.out::print);

    }
}

还可以通过Stream.generate()方法,它需要传入一个Supplier对象

public class StreamDemo {
    public static void main(String[] args) {
        //基于Supplier创建的Stream会不断调用Supplier.get()方法来不断产生下一个元素,
        // 这种Stream保存的不是元素,而是算法,它可以用来表示无限序列。
        Stream<Integer> integerStream = Stream.generate(new Supplier<Integer>() {
            int n = 1;

            @Override
            public Integer get() {
                return n++;
            }
        });
        //无限序列必须先变成有限序列
        integerStream.limit(20).forEach(System.out::println);
    }
}

上述代码我们用一个Supplier模拟了一个无限序列(当然受int范围限制不是真的无限大)。如果用List表示,即便在int范围内,也会占用巨大的内存,而Stream几乎不占用空间,因为每个元素都是实时计算出来的,用的时候再算

另外,Files类的lines()方法可以把一个文件变成一个Stream,每个元素代表文件的一行内容:

public class StreamDemo {
    public static void main(String[] args) throws IOException {
        Stream<String> lines = Files.lines(Paths.get("D:\\test.txt"));
        lines.forEach(System.out::println);
    }
}

此方法对于按行遍历文本文件十分有用。


因为Java的泛型不支持基本类型,所以我们无法用Stream这样的类型。为了保存int,只能使用Stream,但这样会产生频繁的装箱、拆箱操作,会降低代码执行效率。为了提高效率,Java标准库提供了IntStreamLongStreamDoubleStream这三种使用基本类型的Stream,例如:

public class StreamDemo {
    public static void main(String[] args) throws IOException {
        IntStream intStream=IntStream.generate(new IntSupplier(){
            int n=1;
            @Override
            public int getAsInt() {
                return n++;
            }
        });

        intStream.limit(20).forEach(System.out::println);
    }
}

2.使用map

Stream.map()是Stream最常用的一个转换方法,它把一个Stream转换为另一个Stream。
例如:
通过map对字符集合去空格、变小写:

/**
 * 通过map对字符集合去空格、变小写
 **/
public class StreamDemo {
    public static void main(String[] args) throws IOException {
        List<String> list=new ArrayList<>();
        list.add(" apple");
        list.add("Banana");
        list.add("ORANGE");
        Stream<String> stream = list.stream();

        stream.map(String::toLowerCase)
                .map(String::trim)
                .forEach(System.out::println);
    }
}

去除日期当中的空格并输出该日是本年的第几天:

/**
 * 去除日期当中的空格并输出该日是本年的第几天
 */
public class StreamDemo {
    public static void main(String[] args) throws IOException {
        String[] times = new String[]{"2020 - 04 - 01", "2022 - 04 - 14"};
        Stream<String> timesStream = Arrays.stream(times);
        timesStream.map(t->t.replaceAll(" ",""))
                .map(LocalDate::parse)
                .map(LocalDate::getDayOfYear)
                .forEach(System.out::println);
    }
}

将一组序列转换为它的立方:

/**
 * 将一组序列转换为它的立方
 */
public class StreamDemo {
    public static void main(String[] args) {
        IntStream intStream = IntStream.generate(new IntSupplier() {
            int n = 1;

            @Override
            public int getAsInt() {
                return n++;
            }
        });

        IntStream intStream1 = intStream.map(n -> n * n * n);
        intStream1.limit(5).forEach(System.out::println);
    }
}

3.使用filter

filter()操作就是对一个Stream的所有元素根据条件进行过滤,满足条件的元素就构成了一个新的Stream
例如:
判断元素是否是奇数,过滤掉偶数,只剩下奇数:

/**
 * 判断元素是否是奇数,过滤掉偶数,只剩下奇数
 */
public class StreamDemo {
    public static void main(String[] args) {
        IntStream intStream = IntStream.generate(new IntSupplier() {
            int n = 1;

            @Override
            public int getAsInt() {
                return n++;
            }
        });

        IntStream intStream1 = intStream.filter(n -> n % 2 != 0);
        intStream1.limit(5).forEach(System.out::println);
    }
}

从一组给定的LocalDate中过滤掉工作日,以便得到休息日:

/**
 * 从一组给定的LocalDate中过滤掉工作日,以便得到休息日
 */
public class StreamDemo {
    public static void main(String[] args) {
        Stream<LocalDate> stream = Stream.generate(new Supplier<LocalDate>() {
            LocalDate start = LocalDate.of(2022, 4, 1);
            int n = 0;

            @Override
            public LocalDate get() {
                return start.plusDays(n++);
            }
        });
        stream.limit(31)
                .filter(d -> d.getDayOfWeek() == DayOfWeek.SATURDAY
                        || d.getDayOfWeek() == DayOfWeek.SUNDAY)
                .forEach(System.out::println);
    }
}

4.使用reduce

reduce()则是Stream的一个聚合方法,它可以把一个Stream的所有元素按照聚合函数聚合成一个结果。
例如:
返回累加的结果

/**
 * 返回累加的结果
 */
public class StreamDemo {
    public static void main(String[] args) {
        List<Integer> list=new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            list.add(i+1);
        }

        Stream<Integer> stream = list.stream();
        Integer acc = stream.reduce(0, (sum, i) -> sum + i);
        System.out.println(acc);
    }
}

以上代码可能不太好理解,我们用另一种等价的形式改写一下
Stream stream = list.stream();
int sum = 0;
for (n : stream) {
sum = (sum, n) -> sum + n;
}

举个例子,从配置文件读取数据并准换成Map:

public class StreamDemo {
    public static void main(String[] args) throws IOException {
    	//读取配置文件
        Stream<String> lines = Files.lines(Paths.get("D:\\properties.yaml"), Charset.forName("UTF-8"));
        //将读取到由每一行组成的Stream转成单个Map组成的Stream,然后聚合
        Map<String, String> result = lines.map(line -> {
                                        Map<String, String> map = new HashMap<>();
                                        String[] split = line.split("=");
                                        map.put(split[0], split[1]);
                                        return map;
                                    }).reduce(new HashMap<String, String>(), (map, it) -> {
                                        map.putAll(it);
                                        return map;
                                    });
        System.out.println(result);
    }
}

5.特性

执行以下代码:

    public static void main(String[] args) throws IOException, InterruptedException {
       IntStream stream=IntStream.generate(new IntSupplier() {
           int n=0;
           @Override
           public int getAsInt() {
               return n++;
           }
       });
        IntStream intStream = stream.limit(100000000).map(n -> n * n);
//        List list = stream.limit(100000000).mapToObj(i -> i).collect(Collectors.toList());

        Thread.sleep(10000000);
    }

会发现没有太高的内存占用,也没有CPU占用
java-流式API:Stream API_第1张图片
因为转换操作只是保存了转换规则,无论我们对一个Stream转换多少次,都不会有任何实际计算发生。

但如果运行以下代码:

public class StreamDemo {
    public static void main(String[] args) throws IOException, InterruptedException {
       IntStream stream=IntStream.generate(new IntSupplier() {
           int n=0;
           @Override
           public int getAsInt() {
               return n++;
           }
       });
//        IntStream intStream = stream.limit(100000000).map(n -> n * n);
        List<Integer> list = stream.limit(100000000).mapToObj(i -> i).collect(Collectors.toList());

        Thread.sleep(10000000);
    }
}

会发现占用了大量内存和CPU
java-流式API:Stream API_第2张图片
这是因为大量的数据存储在了List当中,占用了大量内存

6.输出为数组

    public static void main(String[] args) throws IOException, InterruptedException {
        IntStream stream = IntStream.generate(new IntSupplier() {
            int n = 0;

            @Override
            public int getAsInt() {
                return n++;
            }
        });
        int[] array = stream.limit(100).toArray();
        System.out.println(Arrays.toString(array));
    }

7.输出为List

    public static void main(String[] args) throws IOException, InterruptedException {
        Stream<Integer> stream = Stream.of(1, 2, 3, 4);
        List<Integer> list = stream.collect(Collectors.toList());
        System.out.println(list);
    }

调用collect()并传入Collectors.toList()对象,通过类似reduce()的操作,把每个元素添加到ArrayList当中。

类似的,collect(Collectors.toSet())可以把Stream的每个元素收集到Set中。

        Stream<Integer> stream = Stream.of(1, 2, 3, 4);
        Set<Integer> set = stream.collect(Collectors.toSet());
        System.out.println(set);

8.输出为Map

public class StreamDemo {
    public static void main(String[] args) throws IOException, InterruptedException {
        Stream<String> stream = Stream.of("a:A", "b:B");
        Map<String, String> map = stream.collect(Collectors.toMap(
                s -> s.substring(0, s.indexOf(':')),
                s -> s.substring(s.indexOf(':') + 1)
        ));
        System.out.println(map);
    }
}

如此一来,第四节将配置文件转换成map的d代码就可以简化为

public class StreamDemo {
    public static void main(String[] args) throws IOException, InterruptedException {
        Stream<String> lines = Files.lines(Paths.get("D:\\properties.yaml"), Charset.forName("UTF-8"));
        Map<String, String> map = lines.collect(Collectors.toMap(
                line -> line.substring(0, line.indexOf('=')), // 配置文件里的key
                line -> line.substring(line.indexOf('=') + 1) //配置文件里的value
        ));
        System.out.println(map);
    }
}

9.分组

分组输出使用Collectors.groupingBy(),它需要提供两个函数:一个是分组的依据,第二个是分组的结果

public class StreamDemo {
    public static void main(String[] args) throws IOException, InterruptedException {
        Stream<String> stream
                = Stream.of("Apple", "Banana", "Blackberry", "Coconut", "Avocado", "Cherry", "Apricots");
        Map<String, List<String>> result = stream.collect(Collectors.groupingBy(
                s -> s.substring(0, 1), // 表示按照首字母分组
                Collectors.toList() //表示表示输出为List
        ));
        System.out.println(result);
    }
}

10.其他操作

10.1 排序:

        Stream<Integer> stream=Stream.of(6,8,6,8,7,2,3,3,9,9,4,6,1);
        List<Integer> list = stream.sorted().collect(Collectors.toList());
        System.out.println(list);

此方法要求Stream的每个元素必须实现Comparable接口。
可自定义Comparable,传入sorted()。
以下sorted((i1, i2) -> i2.compareTo(i1))实现降序排序:

        Stream<Integer> stream = Stream.of(6, 8, 6, 8, 7, 2, 3, 3, 9, 9, 4, 6, 1);
        List<Integer> list = stream
                .sorted((i1, i2) -> i2.compareTo(i1))
                .collect(Collectors.toList());
        System.out.println(list);

10.2 去重

        Stream<Integer> stream = Stream.of(6, 8, 6, 8, 7, 2, 3, 3, 9, 9, 4, 6, 1);
        List<Integer> list = stream
                .distinct()  // 去重
                .collect(Collectors.toList());
        System.out.println(list);

你可能感兴趣的:(java)