Featured image of post 计算机基础 数据结构与算法 Stream API

计算机基础 数据结构与算法 Stream API

🌏数据结构与算法实践 🎯 这篇文章用于记录有关 stream API的使用;在后端开发的实践中 我越来越频繁地看到Stream API的身影 Stream的使用在暑期PySpark的时候也用到了; 如果要进行数据分析相关的开发 则Stream是必知必会的 在这里整理一下入门的知识

🎄Stream API Java8引入

🍭概述

在Java中,流(Stream)是指Java 8中引入的Stream API,它是一种用于操作集合数据的高级抽象。

Java Stream API基于函数式编程的思想,提供了一组丰富的操作方法,用于对集合数据进行过滤、映射、排序、归约等操作,以及支持并行处理。

Java Stream API的使用步骤如下:

  1. 创建流:通过集合类的stream()方法或parallelStream()方法创建流对象。也可以通过Stream.of()方法从一组对象创建流,或者通过Arrays.stream()方法从数组创建流。
  2. 中间操作:通过调用流对象上的各种中间操作方法,对流进行操作。常见的中间操作方法包括filter()map()distinct()sorted()等,用于对流进行过滤、映射、去重、排序等操作。这些操作可以顺序地串联起来,形成一个操作链。
  3. 终端操作:通过调用流对象上的终端操作方法,对流进行最终的计算和结果返回。常见的终端操作方法包括forEach()collect()reduce()count()等,用于对流进行遍历、收集结果、归约计算等操作。

Java Stream API的使用优势包括:

  1. 函数式编程风格:Java Stream API利用Lambda表达式和方法引用,提供了一种简洁、直观的集合操作方式,增强了代码的可读性和可维护性。
  2. 延迟执行:流操作通常是延迟执行的,只有在终端操作调用时才会进行实际的计算,这样可以节省计算资源。
  3. 并行处理:Java Stream API支持并行处理,可以自动将流的操作分配到多线程执行,提高处理数据的效率。

🍭使用场景

  1. 数据处理与转换:Stream API提供了丰富的操作方法,可以方便地对数据进行过滤、映射、排序、归约等操作。这些操作可以灵活组合,使得数据处理和转换更加简洁高效。例如,可以通过流操作筛选出符合某个条件的数据,然后对结果进行进一步的转换和计算。
  2. 数据统计与分析:利用Stream API可以快速对数据集合进行统计分析。通过调用count()sum()max()min()等终端操作方法,可以获取数据的数量、总和、最大值、最小值等结果。同时,可以结合中间操作方法如filter()map()等进行数据筛选和转换,用于特定的统计需求。
  3. 并行处理:Stream API提供了并行处理的支持,可以自动将流的操作分配到多线程执行,提高数据处理的效率。适用于对大规模数据集合进行并行计算的场景,例如大数据处理、图像处理等。
  4. 集合操作优化:Stream API提供了优化的集合操作,可以避免传统的迭代方式对集合进行繁琐的遍历和操作。通过链式调用操作方法,可以将一系列的集合操作简化为一行代码,提高代码的可读性和可维护性。同时,Stream API也提供了延迟执行的特性,只在需要返回结果时才进行实际计算,可以节省计算资源。
  5. 流式处理和管道操作:Stream API支持流式处理的方式,利用管道将一系列的操作链接在一起,实现一种流程化的数据处理方式。这种方式更加直观和易于理解,适用于数据处理管道、数据流处理等场景。

🎄filter过滤数据 & foreach遍历数据

Stream -> filter & foreach函数的使用

public static void main(String[] args) {
    // 创建一个ArrayList集合 并填充数据
    ArrayList<String> list = new ArrayList<>();
    list.add("张无忌"); list.add("周芷若"); list.add("赵敏");
    list.add("张强");   list.add("张三丰");

    // 1 获取流对象 Stream<String> stream = list.stream();
    list.stream() // 当前list = ["张无忌","周芷若","赵敏","张强","张三丰"]
        // 2 过滤数据 只留下以"张"开头的字符串
        .filter(str -> str.startsWith("张")) // 当前list = ["张无忌","张强","张三丰"]
        // 3 继续过滤数据 只留下list当中长度为2的字符串
        .filter(str -> str.length() == 2) // 当前list = ["张强"]
        // 4 遍历list中当前的所有字符串并进行打印
        .forEach(str-> System.out.println(str));
        /*打印结果(只有一行):
           张强
         */
}

🎄Lambda表达式简化

public static void main(String[] args) {
    ArrayList<String> list = new ArrayList<>();
    Collections.addAll(list,"a","b","c","d","e");
    // 获取到一条流水线 并把集合中的数据放到流水线上
    Stream<String> stream1 = list.stream();
    /**
     * 使用终结方法打印一下流水线上的所有数据
     */
    System.out.print("原始代码表达方式遍历:");
    stream1.forEach(new Consumer<String>() {
        @Override
        public void accept(String s) {
            //s:依次表示流水线上的每一个数据
            System.out.print(s + " ");
        }
    });
    /**
     * Lambda简化表达方式
     */
    System.out.print("\nLambda简化代码表达方式遍历:");
    list.stream().forEach(s -> System.out.print(s + " "));
}

运行结果:

🎄双列集合HashMap使用流

public static void main(String[] args) {
    // 1.创建双列集合
    HashMap<String,Integer> hm = new HashMap<>();
    // 2.添加数据
    hm.put("aaa",111);
    hm.put("bbb",222);
    hm.put("ccc",333);
    hm.put("ddd",444);
    // 3.第一种获取stream流
    System.out.println("hm.keySet().stream()获取流:");
    hm.keySet().stream().forEach(s -> System.out.println(s));
    // 4.第二种获取stream流
    System.out.println("hm.entrySet().stream()获取流:");
    hm.entrySet().stream().forEach(s-> System.out.println(s));
}

运行结果:

🎄数组使用流

public static void main(String[] args) {
    //1.创建数组
    int[] arr1 = {1,2,3,4,5,6,7,8,9,10};
    String[] arr2 = {"a","b","c"};
    //2.获取stream流
    Arrays.stream(arr1).forEach(s-> System.out.print(s + " "));
    System.out.println("\n============================");
    Arrays.stream(arr2).forEach(s-> System.out.print(s + " "));
    System.out.println("\n============================");
    /**
     * 【注意 Stream接口中静态方法of的细节】
     *  方法的形参是一个可变参数 可以传递一堆零散的数据 也可以传递数组
     *  但是数组必须是引用数据类型的 如果传递基本数据类型 是会把整个数组当做一个元素 放到Stream当中
     *  比如下面的例子 因为arr1是基本数据类型的数组 所以只打印出了对象名
     */
    Stream.of(arr1).forEach(s-> System.out.println(s));
}

运行结果:

🎄limit限制数量 && skip跳过元素

public static void main(String[] args) {
    ArrayList<String> list = new ArrayList<>();
    Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰", "张翠山", "张良", "王二麻子", "谢广坤");
    System.out.print("只输出前3个元素 -> ");
    list.stream().limit(3).forEach(s -> System.out.print(s + " "));
    System.out.print("\n跳过前4个元素 -> ");
    list.stream().skip(4) .forEach(s -> System.out.print(s + " "));
    System.out.print("\n先拿出前6个元素 再跳过3个元素 -> ");
    list.stream().limit(6).skip(3).forEach(s -> System.out.print(s + " "));
    System.out.print("\n先跳过3个元素 再拿出前3个元素 -> ");
    list.stream().skip(3).limit(3).forEach(s -> System.out.print(s + " "));
}

运行结果:

🎄distinct去重 & concat合并

public static void main(String[] args) {
    ArrayList<String> list1 = new ArrayList<>();
    Collections.addAll(list1, "张无忌","张无忌","张无忌", "张三丰", "张三丰");
    ArrayList<String> list2 = new ArrayList<>();
    Collections.addAll(list2, "周芷若", "赵敏");
    System.out.print("distinct去重 -> ");
    list1.stream().distinct().forEach(s -> System.out.print(s + " "));
    System.out.print("\n去重 + concat合并 -> ");
    Stream.concat(list1.stream(),list2.stream()).distinct().forEach(s -> System.out.print(s + " "));
}

运行结果:

🎄map -> 转换(v) 流中的数据类型

通过使用map链式编程来转换流中的数据类型

修改Stream流中的数据 不会影响原来集合或者数组中的数据

/**
 * 需求:只获取里面的年龄并进行打印 String->int
 */
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌-15", "周芷若-14", "赵敏-13", "张强-20", "张三丰-100", "张翠山-40", "张良-35", "王二麻子-37", "谢广坤-41");

/**
 * 当map方法执行完毕之后 流上的数据就变成了整数
 * 所以在下面forEach当中 s依次表示流里面的每一个数据 这个数据现在就是整数了
 */
list.stream().map(new Function<String, Integer>() {
    /**
     * @param s 表示流里面的每一个数据
     * @return  表示转换之后的数据
     */
    @Override
    public Integer apply(String s) {
        String[] arr = s.split("-");
        String ageString = arr[1];
        int age = Integer.parseInt(ageString);
        return age;
    }
}).forEach(s-> System.out.print(s + " "));

System.out.println("\n------------------------");

/**
 * 使用Lambda表达式 更简洁
 */
list.stream()
        .map(s-> Integer.parseInt(s.split("-")[1]))
        .forEach(s-> System.out.print(s + " "));

打印结果:

🎄count -> 统计数目

ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰", "张翠山");
long count = list.stream().count();
System.out.println(count); // 6

🎄toArray() -> 收集流中的数据 放到数组中

ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰", "张翠山");
Object[] a = list.stream().toArray();
System.out.println(Arrays.toString(a)); // [张无忌, 周芷若, 赵敏, 张强, 张三丰, 张翠山]

🎄collect -> 收集流中的数据放到 List Set Map

使用下面的数据来演示 collect的使用

ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "张无忌-男-15", "周芷若-女-14", "赵敏-女-13", "张强-男-20",
        "张三丰-男-100", "张翠山-男-40", "张良-男-35", "王二麻子-男-37", "谢广坤-男-41");

把所有的男性收集到List集合当中

List<String> newList1 = list.stream()
	.filter(s -> "男".equals(s.split("-")[1]))
	.collect(Collectors.toList());

把所有的男性收集到Set集合当中

Set<String> newList2 = list.stream().filter(s -> "男".equals(s.split("-")[1]))
	.collect(Collectors.toSet());

把所有的男性收集到Map集合当中 -> 需要指定键和值(原数据的那一部分作为键 那一部分作为值)

/**
 * 完整使用模式
 */
Map<String, Integer> map = list.stream()
    .filter(s -> "男".equals(s.split("-")[1]))
    .collect(Collectors.toMap(
        new Function<String, String>() {
            @Override
            public String apply(String s) {
              //张无忌-男-15
              return s.split("-")[0];
            }
        },
        new Function<String, Integer>() {
            @Override
            public Integer apply(String s) {
                return Integer.parseInt(s.split("-")[2]);
            }
        })
    );
/**
 * Lambda使用模式
 */
Map<String, Integer> map2 = list.stream()
    .filter(s -> "男".equals(s.split("-")[1]))
    .collect(Collectors.toMap(s -> s.split("-")[0], s -> Integer.parseInt(s.split("-")[2])));
/**
 * 【打印结果】
 * {张强=20, 张良=35, 张翠山=40, 王二麻子=37, 张三丰=100, 张无忌=15, 谢广坤=41}
 */

⚡使用顺序总结[Lambda]

filter -> [map] -> [limit|skip|distinct] -> [toArray()|count|collect]

⚡综合运用

注意下面的两种方式的收集结果的区别

(1)第一种使用map函数将两列数据封装到Actor类对应的字段 构建成Actor对象

(2)第二种使用collect(Collectors.toMap(..))

/*********************** 模拟数据准备 ***********************/
//1.创建两个ArrayList集合
ArrayList<String> manList = new ArrayList<>();
ArrayList<String> womenList = new ArrayList<>();
//2.添加数据
Collections.addAll(manList, "蔡坤坤,24", "叶齁咸,23", "刘不甜,22", "吴签,24", "谷嘉,30", "肖梁梁,27");
Collections.addAll(womenList, "赵小颖,35", "杨颖,36", "高元元,43", "张天天,31", "刘诗,35", "杨小幂,33");
//3.男演员只要名字为3个字的前两人
Stream<String> stream1 = manList.stream()
        .filter(s -> s.split(",")[0].length() == 3)
        .limit(2);
//4.女演员只要姓杨的,并且不要第一个
Stream<String> stream2 = womenList.stream()
        .filter(s -> s.split(",")[0].startsWith("杨"))
        .skip(1);
//5.把过滤后的男演员姓名和女演员姓名合并到一起
//演员信息封装成Actor对象。

/****** 把过滤后的男演员姓名和女演员姓名合并到一起 封装成Actor对象 *******/
// 【result -> 】 [Actor{name = 蔡坤坤, age = 24}, Actor{name = 叶齁咸, age = 23}, Actor{name = 杨小幂, age = 33}]
List<Actor> list = Stream.concat(stream1, stream2)
        .map(s -> new Actor(s.split(",")[0], Integer.parseInt(s.split(",")[1])))
        .collect(Collectors.toList());

/****** 使用 *******/
// 【result -> 】 {叶齁咸=23, 杨小幂=33, 蔡坤坤=24}
Map<String, Integer> map = Stream.concat(stream1, stream2)
    .collect(Collectors.toMap(s -> s.split(",")[0], s -> Integer.parseInt(s.split(",")[1]));