当前位置: 首页 > article >正文

Java的Stream流:文件处理、排序与串并行流的全面指南

Java的Stream流:文件处理、排序与串并行流的全面指南

Java 8 引入了 Stream API,这是一个用于处理集合数据的强大工具,它提供了一种声明式的方式来进行聚合操作。Stream 不是一个数据结构,而是一种对数据进行操作的抽象,允许开发者以一种更简洁、易读的方式来表达复杂的查询逻辑。下面我们将详细介绍 Java Stream 的概念、特性以及如何使用它。

1 Stream 的基本概念

Stream 是一个来自源的数据元素序列,支持顺序和并行聚合操作。它可以看作是高级版本的 Iterator,但是与 Iterator 不同的是,Stream 操作可以链式调用,从而形成一系列的操作流水线。此外,Stream 的操作不会修改源数据,而是生成新的结果。

1.2 Stream 的创建

创建 Stream 有多种方式:

  • 从集合创建:大多数集合类都提供了 stream()parallelStream() 方法来创建串行流或并行流。
  • 通过静态方法创建Stream.of() 可以接受不定数量的参数来创建流;Stream.generate()Stream.iterate() 可以生成无限流,通常需要结合 limit() 来限制大小。
  • 从数组创建:可以通过 Arrays.stream(array) 或者直接调用数组上的 stream() 方法来创建流。
  • 文件读取BufferedReader.lines() 可以将文件的每一行转换成流中的元素。
  • 正则表达式分割字符串Pattern.splitAsStream() 可以根据指定的分隔符将字符串拆分成流。
1.3 中间操作

中间操作是指那些返回另一个 Stream 的操作,它们本身不会触发任何计算,只有当终端操作被执行时才会真正开始处理数据。常见的中间操作包括:

  • filter:过滤掉不符合条件的元素。
  • map:对每个元素应用一个函数,并返回一个新的 Stream。
  • flatMap:对每个元素应用一个函数,该函数返回一个 Stream,然后将所有这些 Stream 扁平化为一个单独的 Stream。
  • distinct:去除重复元素。
  • sorted:对元素排序,可以选择自然排序或自定义比较器。
  • peek:对每个元素执行副作用操作(如打印),但不改变流的内容。
1.4 终端操作

一旦执行了终端操作,Stream 就会被消耗掉,不能再被使用。常见的终端操作有:

  • forEach:遍历流中的每一个元素。
  • collect:将流中的元素收集到集合中,如 List 或 Set。
  • reduce:通过某种方式减少流中的元素,例如求和或乘积。
  • count:统计流中元素的数量。
  • min/max:找到最小值/最大值。
  • anyMatch/allMatch/noneMatch:检查是否至少有一个/所有/没有元素满足给定的谓词。

2 基本使用示例

示例1 筛选大于等于 10 的整数
List<Integer> numbers = Arrays.asList(5, 10, 15, 20, 25, 30);
numbers.stream()
       .filter(num -> num >= 10)
       .forEach(System.out::println);

这段代码会输出 10, 15, 20, 25, 30,因为这些都是大于等于 10 的数字。

示例2 提取员工姓名
List<Employee> employees = Arrays.asList(
        new Employee("Alice", 25),
        new Employee("Bob", 30),
        new Employee("Charlie", 35)
);
employees.stream()
         .map(Employee::getName)
         .forEach(System.out::println);

这里我们使用 map 操作提取了每个员工的名字,并打印出来。

示例3 去重
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 2, 3, 5, 1, 6);
numbers.stream().distinct()
       .forEach(System.out::println);

这段代码会输出 1, 2, 3, 4, 5, 6,因为 distinct 操作已经移除了重复的元素。

3 文件读取与 Stream 结合

在 Java 8 中,Files.lines() 方法提供了一种简单而有效的方式来逐行读取文件内容,并将其转换为 Stream。这使得我们可以利用 Stream 的强大功能来处理文件中的每一行数据,例如过滤、映射、排序等。下面我们将通过几个具体的示例来展示如何结合文件读取操作使用 Stream。

示例 4:逐行读取文件并打印
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;

public class 读取文件示例 {
    public static void main(String[] args) {
        try (Stream<String> 行流 = Files.lines(Paths.get("data.txt"))) {
            行流.forEach(System.out::println);
        } catch (IOException e) {
            System.err.println("读取文件时发生错误: " + e.getMessage());
        }
    }
}

这段代码展示了如何使用 Files.lines() 方法逐行读取文件 data.txt 并打印每一行的内容。try-with-resources 语句确保了流在使用完毕后会被自动关闭,避免资源泄露。

示例 5:查找包含特定关键词的行
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Optional;

public class 查找关键词示例 {
    public static void main(String[] args) {
        try (Stream<String> 行流 = Files.lines(Paths.get("data.txt"))) {
            Optional<String> 包含密码的行 = 行流.filter(->.contains("密码"))
                                              .findFirst();
            if (包含密码的行.isPresent()) {
                System.out.println("找到包含 '密码' 的行: " + 包含密码的行.get());
            } else {
                System.out.println("没有行包含 '密码'.");
            }
        } catch (IOException e) {
            System.err.println("读取文件时发生错误: " + e.getMessage());
        }
    }
}

此示例展示了如何使用 filterfindFirst 方法来查找文件中包含特定关键词(如 “密码”)的第一行,并将其打印出来。如果找不到符合条件的行,则输出相应的提示信息。

示例 6:统计文件中单词的数量
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class 统计单词数量示例 {
    public static void main(String[] args) throws IOException {
        long 单词总数 = Files.lines(Paths.get("data.txt"))
                              .flatMap(Pattern.compile("\\s+")::splitAsStream)
                              .count();
        System.out.println("总共有 " + 单词总数 + " 个单词");
    }
}

在这个例子中,我们使用 flatMap 方法结合正则表达式来分割每一行文本,从而得到一个包含所有单词的流。然后,我们使用 count 方法统计总共有多少个单词。这种方法非常适合处理大文件,因为它可以在不加载整个文件到内存的情况下完成任务。

示例 7:按字母顺序排序并去重后的单词列表
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class 排序去重单词示例 {
    public static void main(String[] args) throws IOException {
        List<String> 排序后的唯一单词 = Files.lines(Paths.get("data.txt"))
                                              .flatMap(Pattern.compile("\\s+")::splitAsStream)
                                              .distinct()
                                              .sorted()
                                              .collect(Collectors.toList());
        System.out.println("按字母顺序排序并去重后的单词: " + 排序后的唯一单词);
    }
}

这段代码展示了如何结合 flatMapdistinctsorted 方法来获取文件中按字母顺序排序且去重后的单词列表。最终结果被收集到一个 List<String> 中,并打印出来。这种方法可以有效地去除重复项,并对结果进行排序,非常适合用于文本分析等场景。

5 串行流与并行流

串行流是指所有操作都在单个线程上依次执行,而并行流则是指操作可以在多个线程上并发执行。并行流可以在多核处理器上提高效率,但是需要注意,并不是所有的操作都适合并行化,而且并行流可能会带来额外的开销。因此,在选择使用串行流还是并行流时,应该根据具体的应用场景做出权衡。

示例 8:使用串行流计算文件中单词的总长度
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.regex.Pattern;

public class 串行流计算单词总长度示例 {
    public static void main(String[] args) throws IOException {
        long 单词总长度 = Files.lines(Paths.get("data.txt"))
                                .flatMap(Pattern.compile("\\s+")::splitAsStream)
                                .mapToInt(String::length)
                                .sum();
        System.out.println("所有单词的总长度: " + 单词总长度);
    }
}

这段代码展示了如何使用串行流来计算文件中所有单词的总长度。flatMap 方法将每一行文本拆分为多个单词,mapToInt 方法将每个单词映射为其长度,最后使用 sum 方法计算所有单词长度的总和。

示例 9:使用并行流计算文件中单词的总长度
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.regex.Pattern;

public class 并行流计算单词总长度示例 {
    public static void main(String[] args) throws IOException {
        long 单词总长度 = Files.lines(Paths.get("data.txt"))
                                .parallel() // 使用并行流
                                .flatMap(Pattern.compile("\\s+")::splitAsStream)
                                .mapToInt(String::length)
                                .sum();
        System.out.println("所有单词的总长度: " + 单词总长度);
    }
}

在这段代码中,我们通过调用 parallel() 方法将串行流转换为并行流,从而允许 JVM 在多核处理器上并行处理文件中的每一行。需要注意的是,并行流的使用可能会导致结果的顺序发生变化,但在本例中,由于我们只关心单词长度的总和,因此顺序不影响最终结果。

6 性能考量

虽然 Stream 提供了非常方便的操作接口,但在某些情况下可能会影响性能,特别是对于大规模数据集。并行流可以在多核处理器上提高效率,但是需要注意,并不是所有的操作都适合并行化,而且并行流可能会带来额外的开销。因此,在选择使用串行流还是并行流时,应该根据具体的应用场景做出权衡。

7 使用 Stream 的注意事项

惰性求值:Stream 的中间操作是惰性的,只有遇到终端操作时才会触发实际的计算。
不可重用:Stream 的一旦被消费(即执行了终端操作),便不能再次使用。如果需要多次操作同一组数据,可以创建多个流对象。
线程安全:虽然并行流可以在多线程环境中工作,但这并不意味着它是线程安全的。对于非线程安全的操作,仍然需要采取适当的同步措施。

总结

通过上述示例,我们可以看到 Java Stream API 提供了一种简洁且强大的方式来处理集合数据。无论是文件读取、简单排序还是串行流与并行流的选择,Stream 都能够帮助开发者写出更加优雅和高效的代码。然而,在实际开发中,我们应该根据具体的需求和数据量来决定是否使用 Stream,以及选择合适的流类型,以确保最佳的性能和可维护性。


http://www.kler.cn/a/428830.html

相关文章:

  • springBoot项目使用Elasticsearch教程
  • Top期刊算法!RIME-CNN-BiLSTM-Attention系列四模型多变量时序预测
  • 【蜂巢——方向,数学】
  • 高效实现 Markdown 转 PDF 的跨平台指南20250117
  • windows配置 Conda 使用阿里云镜像源
  • 用 Python 自动化处理日常任务
  • 智能方法求解-圆环内传感器节点最大最小距离分布
  • 后端返回前端的数据量过大解决方案
  • 最新基于R语言森林生态系统结构、功能与稳定性分析与可视化实践高级应用
  • 低级爬虫实现-记录HCIP云架构考试
  • 数字图像处理(15):图像平移
  • Fiddler 5.21.0 使用指南:过滤浏览器HTTP(S)流量下(四)
  • 基于gitlab API刷新MR的commit的指定status
  • 【Unity高级】如何动态调整物体透明度
  • Linux-Regmap实验
  • Go database/sql包源码分析
  • ShardingSphere 数据库中间件
  • k8s 为什么需要Pod?
  • 高级java每日一道面试题-2024年12月05日-JVM篇-什么是TLAB?
  • 计算机键盘的演变 | 键盘键名称及其功能 | 键盘指法
  • 软件无线电安全之GNU Radio基础(下)
  • 英文论文翻译成中文,怎样翻译更地道?
  • 【开源免费】基于Vue和SpringBoot的高校学科竞赛平台(附论文)
  • 普通算法——一维前缀和
  • k8s-Informer概要解析(2)
  • Mybatis-plus 多租户插件