Java 流(Stream)、文件(File)和IO(一文讲透)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观

在 Java 编程的世界中,处理数据流、文件操作和输入输出(IO)是开发者必须掌握的核心技能之一。无论是从文件中读取配置信息、处理网络传输的数据,还是构建复杂的数据处理管道,Java 提供的 Stream、File 和 IO 相关 API 都能为开发者提供强大而灵活的工具。本文将通过循序渐进的方式,结合实际案例和生动的比喻,帮助读者理解这些技术的核心概念,并掌握其在实际开发中的应用。


一、Java 流(Stream):数据处理的“流水线”

1.1 流的基本概念

Java 流可以类比为一条“数据流水线”,它将数据元素按顺序传输,并通过一系列操作(如过滤、映射、聚合)对数据进行处理。与传统的集合(如 List、Array)不同,流(Stream)本身不存储数据,而是描述“如何访问和操作数据”。

核心特性

  • 惰性计算:流的操作通常分为“中间操作”和“终端操作”,只有在调用终端操作时,流才会真正执行计算。
  • 函数式编程风格:流 API 深度结合了 Lambda 表达式和方法引用,使代码更简洁且易于阅读。

示例代码

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
long count = names.stream()  // 创建流
                  .filter(name -> name.startsWith("A"))  // 中间操作:过滤以"A"开头的名字
                  .count();  // 终端操作:计算数量
System.out.println("以A开头的名字数量:" + count);  // 输出:1

1.2 流的操作分类

中间操作(Intermediate Operations)

这些操作返回一个新的流,不会立即执行计算,而是构建一个操作链。

  • 过滤(filter):筛选符合条件的元素。
  • 映射(map):将元素转换为另一种形式(例如,字符串转为长度)。
  • 排序(sorted):按自然顺序或自定义规则排序。

示例:映射操作

List<Integer> lengths = names.stream()
                             .map(String::length)  // 将每个字符串转为长度
                             .collect(Collectors.toList());
System.out.println(lengths);  // 输出:[5, 3, 7, 5]

终端操作(Terminal Operations)

这些操作触发流的计算,并返回结果或副作用。

  • 收集(collect):将流结果收集到集合中。
  • 求和(sum):计算数值型元素的总和。
  • 查找(findFirst):获取流中的第一个元素。

示例:查找与求和

Optional<String> firstLongName = names.stream()
                                      .filter(name -> name.length() > 5)
                                      .findFirst();
System.out.println(firstLongName.orElse("无"));  // 输出:"Charlie"

int totalLength = names.stream()
                       .mapToInt(String::length)
                       .sum();
System.out.println("总长度:" + totalLength);  // 输出:20

1.3 流的并行处理:提升性能的“多车道”

Java 流支持并行处理(Parallel Stream),通过多线程加速大数据量的计算。例如:

List<Integer> numbers = IntStream.range(1, 1000000).boxed().collect(Collectors.toList());
long sequentialTime = measureTime(() -> numbers.stream().mapToLong(Math::abs).sum());
long parallelTime = measureTime(() -> numbers.parallelStream().mapToLong(Math::abs).sum());
System.out.println("串行耗时:" + sequentialTime + " ms, 并行耗时:" + parallelTime + " ms");

(注:measureTime 为假设的计时工具方法)


二、Java 文件(File):操作文件系统的“瑞士军刀”

2.1 文件与目录的基本操作

Java 的 java.io.File 类提供了对文件和目录的底层操作能力,例如创建、删除、重命名等。

核心方法示例

// 创建新文件
File newFile = new File("data.txt");
if (newFile.createNewFile()) {
    System.out.println("文件创建成功");
} else {
    System.out.println("文件已存在");
}

// 删除文件
if (newFile.delete()) {
    System.out.println("文件删除成功");
}

2.2 文件路径的处理:从“本地地址”到“标准化”

Java 7 引入的 java.nio.file.Path 接口提供了更现代的路径处理方式,支持跨平台操作。例如:

Path path = Paths.get("user", "documents", "report.txt");
System.out.println("绝对路径:" + path.toAbsolutePath());  // 输出绝对路径
System.out.println("文件名:" + path.getFileName());      // 输出:"report.txt"

2.3 文件遍历:递归搜索的“地毯式排查”

使用 Files.walk() 方法可以递归遍历目录中的所有文件:

try (Stream<Path> walk = Files.walk(Paths.get("/home/user"))) {
    walk.filter(Files::isRegularFile)
        .forEach(path -> System.out.println("文件路径:" + path));
} catch (IOException e) {
    e.printStackTrace();
}

三、Java IO:数据输入输出的“传输通道”

3.1 IO 的核心概念:字节流与字符流

Java 的 IO 系统分为两套独立的类库:

  • 字节流(InputStream/OutputStream):处理二进制数据(如图片、音频)。
  • 字符流(Reader/Writer):处理文本数据,自动处理字符编码(如 UTF-8)。

示例:文件复制

// 使用字节流复制文件
try (InputStream in = new FileInputStream("source.jpg");
     OutputStream out = new FileOutputStream("copy.jpg")) {
    byte[] buffer = new byte[1024];
    int bytesRead;
    while ((bytesRead = in.read(buffer)) != -1) {
        out.write(buffer, 0, bytesRead);
    }
} catch (IOException e) {
    e.printStackTrace();
}

3.2 缓冲技术:提升性能的“蓄水池”

直接使用 InputStreamOutputStream 可能导致效率低下,因此通常会结合缓冲流(如 BufferedInputStream):

// 使用缓冲流优化文件读取
try (BufferedReader reader = new BufferedReader(
        new FileReader("data.txt"))) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
}

3.3 对象序列化:数据持久化的“变形记”

通过 ObjectInputStreamObjectOutputStream,可以将对象转换为字节序列并保存到文件中:

// 序列化对象到文件
try (ObjectOutputStream oos = new ObjectOutputStream(
        new FileOutputStream("object.dat"))) {
    oos.writeObject(new User("Alice", 30));
} catch (IOException e) {
    e.printStackTrace();
}

四、综合案例:构建一个 CSV 文件处理器

4.1 需求分析

假设我们需要从 CSV 文件中读取用户数据,过滤出年龄大于 18 岁的用户,并将结果写入新文件。

4.2 实现步骤

步骤 1:读取 CSV 文件

使用 BufferedReader 逐行读取文件:

try (BufferedReader reader = Files.newBufferedReader(
        Paths.get("users.csv"), StandardCharsets.UTF_8)) {
    String line;
    while ((line = reader.readLine()) != null) {
        // 处理每一行数据
    }
} catch (IOException e) {
    e.printStackTrace();
}

步骤 2:解析 CSV 内容

将每行数据按逗号分隔,并映射为 User 对象:

public class User {
    String name;
    int age;
    // 构造函数、getter/setter 略
}

// 解析行数据
User parseUser(String line) {
    String[] parts = line.split(",");
    return new User(parts[0], Integer.parseInt(parts[1]));
}

步骤 3:使用流过滤数据

List<User> users = ... // 读取后的用户列表
List<User> filtered = users.stream()
                           .filter(user -> user.getAge() > 18)
                           .collect(Collectors.toList());

步骤 4:写入新文件

使用 BufferedWriter 将过滤后的数据写入 CSV 文件:

try (BufferedWriter writer = Files.newBufferedWriter(
        Paths.get("filtered_users.csv"), StandardCharsets.UTF_8)) {
    filtered.forEach(user -> {
        try {
            writer.write(user.getName() + "," + user.getAge());
            writer.newLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
    });
} catch (IOException e) {
    e.printStackTrace();
}

结论

通过本文的讲解,读者应已掌握 Java 流、文件和 IO 的核心概念与实践方法。流(Stream)提供了高效且优雅的数据处理方式,文件(File)操作则帮助开发者与操作系统交互,而 IO(Input/Output)技术则是数据传输的基础。在实际开发中,这些技术的结合能解决从简单文件读写到复杂数据处理的多种场景需求。

未来,随着 Java 版本的迭代,相关 API 会持续优化(例如 Java 17 的记录类(Records)简化了数据对象的定义),开发者需保持学习,善用这些工具提升代码效率与可维护性。希望本文能成为您技术成长路上的参考指南!

最新发布