Java中的Stream流与IO流完整实战指南(从零掌握)
作者:努力中的马喽
Java新手必会:Stream流与IO流完整实战指南
核心观点:掌握Java的Stream流和IO流,就像学会了两把"数据处理神器"——Stream流让集合操作变得优雅高效,IO流让文件读写不再头疼。本文将用最接地气的方式,带你从零到一掌握这两大核心技能。
1. 什么是Stream流和IO流?一句话说清楚
Stream流(集合流)
一句话理解:把集合中的数据想象成流水线上的产品,Stream流就是这条流水线,可以对数据进行筛选、加工、组装。
生活例子:就像奶茶店制作奶茶的过程——原料(集合)→ 筛选茶叶(filter)→ 加糖(map)→ 打包(collect),一气呵成!
IO流(输入输出流)
一句话理解:IO流是Java读写文件、网络传输数据的通道,就像水管输送水一样。
生活例子:你用U盘拷贝文件,IO流就是那根"看不见的数据线",负责把数据从硬盘"运"到U盘。
2. Stream流:让集合操作像流水线一样顺畅
2.1 Stream流的三大核心优势
- 代码简洁:一行代码搞定原本需要好几个for循环的操作
- 链式调用:filter、map、sorted一气呵成,逻辑清晰
- 延迟执行:只有调用终止操作(如collect)时才真正执行,性能更优
2.2 核心API实战
案例1:筛选出成绩大于60分的学生
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class StreamDemo1 {
public static void main(String[] args) {
// 学生成绩列表
List<Integer> scores = new ArrayList<>();
scores.add(85);
scores.add(45);
scores.add(92);
scores.add(58);
scores.add(73);
// 使用Stream流筛选及格的成绩
List<Integer> passedScores = scores.stream()
.filter(score -> score >= 60) // 筛选:保留>=60的成绩
.collect(Collectors.toList()); // 收集结果
System.out.println("及格成绩:" + passedScores);
// 输出:及格成绩:[85, 92, 73]
}
}代码讲解:
stream():将集合转换为流filter():过滤数据,参数是Lambda表达式collect(Collectors.toList()):将流收集回List
案例2:将所有学生姓名转为大写
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamDemo2 {
public static void main(String[] args) {
List<String> names = Arrays.asList("zhangsan", "lisi", "wangwu");
// 将姓名转为大写
List<String> upperNames = names.stream()
.map(String::toUpperCase) // 转换:每个元素调用toUpperCase()
.collect(Collectors.toList());
System.out.println("大写姓名:" + upperNames);
// 输出:大写姓名:[ZHANGSAN, LISI, WANGWU]
}
}代码讲解:
map():转换操作,将每个元素映射成新的值String::toUpperCase:方法引用,等价于name -> name.toUpperCase()
案例3:统计平均分
import java.util.Arrays;
import java.util.List;
public class StreamDemo3 {
public static void main(String[] args) {
List<Integer> scores = Arrays.asList(85, 92, 78, 90, 88);
// 计算平均分
double average = scores.stream()
.mapToInt(Integer::intValue) // 转换为IntStream
.average() // 求平均值
.orElse(0.0); // 如果流为空,返回0.0
System.out.println("平均分:" + average);
// 输出:平均分:86.6
}
}代码讲解:
mapToInt():将Stream转为IntStream,可以使用数值操作average():返回OptionalDouble,避免空指针orElse():提供默认值
案例4:去重并排序
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamDemo4 {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(5, 2, 8, 2, 9, 1, 5, 8);
// 去重、排序、收集
List<Integer> result = numbers.stream()
.distinct() // 去重
.sorted() // 排序(默认升序)
.collect(Collectors.toList());
System.out.println("去重排序后:" + result);
// 输出:去重排序后:[1, 2, 5, 8, 9]
}
}3. 字节流 vs 字符流:该用哪个?看这里就够了
3.1 核心区别一览表
| 对比项 | 字节流 | 字符流 |
|---|---|---|
| 处理单位 | 字节(byte,8位) | 字符(char,16位) |
| 适用场景 | 图片、视频、音频等二进制文件 | 文本文件(.txt、.java等) |
| 核心类 | InputStream / OutputStream | Reader / Writer |
| 是否有缓冲 | 无(需手动包装) | 无(需手动包装) |
3.2 字节流实战:复制图片
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ByteStreamDemo {
public static void main(String[] args) {
// 使用 try-with-resources 自动关闭资源
try (
FileInputStream fis = new FileInputStream("source.jpg");
FileOutputStream fos = new FileOutputStream("target.jpg")
) {
byte[] buffer = new byte[1024]; // 缓冲区
int len;
// 循环读取并写入
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
System.out.println("图片复制成功!");
} catch (IOException e) {
e.printStackTrace();
}
}
}代码讲解:
FileInputStream:文件字节输入流,用于读取FileOutputStream:文件字节输出流,用于写入read(buffer):一次读取最多buffer.length个字节,返回实际读取的字节数-1表示读到文件末尾try-with-resources:JDK7新特性,自动关闭资源
3.3 字符流实战:读写文本文件
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class CharStreamDemo {
public static void main(String[] args) {
// 写入文本
try (FileWriter fw = new FileWriter("hello.txt")) {
fw.write("你好,Java IO流!\n");
fw.write("这是第二行内容。");
System.out.println("文件写入成功!");
} catch (IOException e) {
e.printStackTrace();
}
// 读取文本
try (FileReader fr = new FileReader("hello.txt")) {
int ch;
while ((ch = fr.read()) != -1) {
System.out.print((char) ch);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}代码讲解:
FileWriter:字符输出流,适合写文本FileReader:字符输入流,适合读文本read():一次读一个字符,返回int(-1表示结束)
4. 缓冲流:性能提升10倍的秘密武器
4.1 为什么需要缓冲流?
问题:每次读写都直接访问硬盘,就像你去超市买菜,买一根葱就跑一趟,效率极低!
解决方案:缓冲流内置一个缓冲区(默认8KB),相当于带个购物车,攒够一车再去收银台,大大减少IO次数。
4.2 缓冲字节流实战
import java.io.*;
public class BufferedByteStreamDemo {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
try (
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("large_file.zip")
);
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("large_file_copy.zip")
)
) {
byte[] buffer = new byte[8192]; // 8KB缓冲区
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
long endTime = System.currentTimeMillis();
System.out.println("复制完成,耗时:" + (endTime - startTime) + "ms");
} catch (IOException e) {
e.printStackTrace();
}
}
}性能对比:
- 普通字节流:1000ms
- 缓冲字节流:100ms(提升10倍)
4.3 缓冲字符流实战:按行读写
import java.io.*;
public class BufferedCharStreamDemo {
public static void main(String[] args) {
// 按行写入
try (BufferedWriter bw = new BufferedWriter(
new FileWriter("students.txt")
)) {
bw.write("张三,85");
bw.newLine(); // 写入换行符(跨平台)
bw.write("李四,92");
bw.newLine();
bw.write("王五,78");
System.out.println("学生信息写入成功!");
} catch (IOException e) {
e.printStackTrace();
}
// 按行读取
try (BufferedReader br = new BufferedReader(
new FileReader("students.txt")
)) {
String line;
while ((line = br.readLine()) != null) { // 按行读取
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}代码讲解:
BufferedWriter.newLine():写入换行符,自动适配不同操作系统BufferedReader.readLine():按行读取,读到末尾返回null
5. 资源释放:不注意这点会出大问题 ⚠️
5.1 为什么必须关闭流?
后果:
- 内存泄漏:流对象一直占用内存
- 文件锁定:文件被占用,无法删除或重命名
- 数据丢失:缓冲区数据可能未写入文件
5.2 正确的资源释放方式
❌ 错误示范(JDK7之前)
FileInputStream fis = null;
try {
fis = new FileInputStream("data.txt");
// 读取操作...
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close(); // 繁琐!
} catch (IOException e) {
e.printStackTrace();
}
}
}✅ 正确示范(JDK7+)
try (FileInputStream fis = new FileInputStream("data.txt")) {
// 读取操作...
} catch (IOException e) {
e.printStackTrace();
}
// 无需手动关闭,try-with-resources自动处理原理:实现了AutoCloseable接口的类,在try代码块结束时会自动调用close()方法。
6. 实战案例与常见避坑指南
案例1:读取配置文件并过滤有效配置
import java.io.*;
import java.util.List;
import java.util.stream.Collectors;
public class ConfigReaderDemo {
public static void main(String[] args) {
try (BufferedReader br = new BufferedReader(
new FileReader("config.properties")
)) {
// 使用Stream流过滤空行和注释行
List<String> validConfigs = br.lines() // 将所有行转为Stream
.map(String::trim) // 去除首尾空格
.filter(line -> !line.isEmpty()) // 过滤空行
.filter(line -> !line.startsWith("#")) // 过滤注释
.collect(Collectors.toList());
System.out.println("有效配置:");
validConfigs.forEach(System.out::println);
} catch (IOException e) {
e.printStackTrace();
}
}
}亮点:将IO流和Stream流结合,代码简洁优雅!
案例2:统计文本文件中每个单词的出现次数
import java.io.*;
import java.util.*;
import java.util.stream.Collectors;
public class WordCountDemo {
public static void main(String[] args) {
try (BufferedReader br = new BufferedReader(
new FileReader("article.txt")
)) {
Map<String, Long> wordCount = br.lines()
.flatMap(line -> Arrays.stream(line.split("\\s+"))) // 按空格分词
.map(String::toLowerCase) // 转小写
.filter(word -> !word.isEmpty()) // 过滤空字符串
.collect(Collectors.groupingBy(
word -> word, // 按单词分组
Collectors.counting() // 统计数量
));
// 打印词频前10的单词
wordCount.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.limit(10)
.forEach(entry ->
System.out.println(entry.getKey() + ": " + entry.getValue())
);
} catch (IOException e) {
e.printStackTrace();
}
}
}知识点整合:
- BufferedReader读取文件
- Stream流的flatMap、filter、map
- Collectors的高级用法(分组、计数)
常见避坑指南 🚨
坑1:忘记关闭流导致文件锁定
现象:程序运行后无法删除文件,提示"文件正在使用"
解决:始终使用try-with-resources
坑2:字节流读取文本出现乱码
原因:字节流不处理字符编码
解决:
// ❌ 错误:用字节流读中文
FileInputStream fis = new FileInputStream("中文.txt");
// ✅ 正确:用字符流或指定编码
BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream("中文.txt"), "UTF-8")
);坑3:缓冲流未flush导致数据丢失
BufferedWriter bw = new BufferedWriter(new FileWriter("data.txt"));
bw.write("重要数据");
// 如果程序崩溃,数据可能丢失!
// 解决:手动flush或关闭流
bw.flush(); // 强制刷新缓冲区坑4:Stream流多次消费
Stream<String> stream = list.stream(); stream.forEach(System.out::println); // 第一次消费 stream.forEach(System.out::println); // ❌ 报错:stream已被消费
解决:Stream只能消费一次,需要重新创建。
7. 核心要点总结 📝
Stream流(集合流)核心要点
✅ 记住这三步:创建流 → 中间操作(filter/map/sorted)→ 终止操作(collect)
✅ 常用API速查:
filter():筛选map():转换distinct():去重sorted():排序limit():限制数量collect():收集结果
✅ 注意事项:Stream只能消费一次,链式调用更高效
IO流核心要点
✅ 选择流的黄金法则:
- 文本文件 → 字符流(Reader/Writer)
- 二进制文件 → 字节流(InputStream/OutputStream)
- 需要高性能 → 加Buffered包装
✅ 四大核心类记忆:
| 场景 | 输入 | 输出 |
|---|---|---|
| 字节流 | FileInputStream | FileOutputStream |
| 字符流 | FileReader | FileWriter |
| 缓冲字节流 | BufferedInputStream | BufferedOutputStream |
| 缓冲字符流 | BufferedReader | BufferedWriter |
✅ 资源管理铁律:永远使用try-with-resources,避免内存泄漏
实践建议
- 读写文本优先用缓冲字符流,性能好且支持按行操作
- 复制文件优先用缓冲字节流,通用性强
- 处理集合数据优先用Stream流,代码简洁
- 记得指定字符编码,避免中文乱码(推荐UTF-8)
- 大文件分块读取,避免内存溢出
结语
掌握Stream流和IO流,你就解锁了Java开发的两大核心技能:
- Stream流让你的集合操作代码像诗一样优雅
- IO流让你的文件处理能力如虎添翼
记住:多动手实践,把本文的代码全部运行一遍,你会发现原来流的世界如此简单!
到此这篇关于Java中的Stream流与IO流完整实战指南(从零掌握)的文章就介绍到这了,更多相关java stream流与io流内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
