学习目标
掌握三种高级流的特点、使用场景及其相互关系
高级流的核心价值
Java I/O流分为节点流和处理流(包装流):
节点流:直接与数据源/目的地连接
├── FileInputStream / FileOutputStream
├── FileReader / FileWriter
└── ...
处理流:包装节点流或其他处理流,增加功能
├── BufferedInputStream / BufferedOutputStream
├── BufferedReader / BufferedWriter
├── InputStreamReader / OutputStreamWriter
└── PrintStream / PrintWriter
缓冲流
- 提供缓冲功能,减少I/O操作次数
- BufferedInputStream/BufferedOutputStream
- BufferedReader/BufferedWriter
- 核心优势:提高I/O效率
转换流
- 字节流与字符流间的桥梁
- InputStreamReader/OutputStreamWriter
- 核心功能:字符编码转换
打印流
- 提供格式化的输出能力
- PrintStream/PrintWriter
- 核心功能:方便的数据打印方法
重要概念:装饰器模式
Java I/O流的设计基于装饰器模式(Decorator Pattern),允许通过包装流的方式动态添加功能:
// 多层包装示例
Reader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream("data.txt"),
StandardCharsets.UTF_8)
);
学习目标
理解缓冲机制,能够正确使用缓冲流提升I/O效率
缓冲机制原理
缓冲流通过在内存中创建缓冲区减少实际I/O操作次数:
无缓冲:每次读写=1次I/O操作
使用缓冲:多次读写=1次I/O操作
缓冲流核心类
缓冲流类 |
包装的流类型 |
主要用途 |
特有方法 |
BufferedInputStream |
InputStream |
字节输入缓冲 |
- |
BufferedOutputStream |
OutputStream |
字节输出缓冲 |
flush() |
BufferedReader |
Reader |
字符输入缓冲 |
readLine() |
BufferedWriter |
Writer |
字符输出缓冲 |
newLine() |
缓冲流使用实例:文件高效复制
import java.io.*;
public class BufferedStreamExample {
public static void main(String[] args) {
// 测试文件路径
String source = "large_data.bin";
String destination = "copy.bin";
try {
long startTime = System.currentTimeMillis();
// 使用缓冲流复制文件
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(source));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destination))) {
byte[] buffer = new byte[4096]; // 4KB缓冲区
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
// 手动刷新缓冲区确保数据写入
bos.flush();
}
long endTime = System.currentTimeMillis();
System.out.println("使用缓冲流复制耗时: " + (endTime - startTime) + "ms");
} catch (IOException e) {
e.printStackTrace();
}
}
}
缓冲策略优化
缓冲区大小对性能影响:
- 默认缓冲区大小:8KB
- 一般设置4KB-64KB范围
- 超大文件可考虑1MB缓冲区
// 自定义缓冲区大小
BufferedInputStream bis = new BufferedInputStream(
new FileInputStream("data.bin"), 81920); // 80KB缓冲区
学习目标
理解字符编码机制,熟练使用转换流处理不同编码文本
转换流核心类
转换流类 |
包装的流类型 |
目标流类型 |
核心功能 |
InputStreamReader |
InputStream |
Reader |
字节流→字符流 |
OutputStreamWriter |
OutputStream |
Writer |
字符流→字节流 |
字符编码处理
警告: 不指定编码时使用平台默认编码,易导致跨平台乱码问题!
import java.io.*;
import java.nio.charset.*;
public class ConversionStreamExample {
public static void main(String[] args) {
// 写入UTF-8编码的文件
try (OutputStreamWriter writer = new OutputStreamWriter(
new FileOutputStream("utf8_file.txt"), StandardCharsets.UTF_8)) {
writer.write("你好,世界!");
writer.write("\n");
writer.write("Hello, World! ");
} catch (IOException e) {
e.printStackTrace();
}
// 读取GBK编码的文件
try (InputStreamReader reader = new InputStreamReader(
new FileInputStream("gbk_file.txt"), "GBK")) {
char[] buffer = new char[1024];
int charsRead;
while ((charsRead = reader.read(buffer)) != -1) {
System.out.print(new String(buffer, 0, charsRead));
}
} catch (IOException | UnsupportedEncodingException e) {
System.err.println("处理文件出错: " + e.getMessage());
}
}
}
常见字符编码
编码名称 |
描述 |
使用区域 |
UTF-8 |
8位Unicode编码,兼容ASCII |
国际化首选,网页标准 |
GBK |
汉字内码扩展规范 |
简体中文环境 |
ISO-8859-1 |
Latin-1,西欧字符 |
早期Web标准 |
UTF-16 |
16位Unicode编码 |
Java内部字符串表示 |
学习目标
熟练使用PrintStream和PrintWriter进行格式化输出
打印流的特点
- 自动处理字符串转换(toString())
- 不会抛出I/O异常(需通过checkError()检查)
- 支持自动刷新机制(autoFlush)
- 提供丰富的print()/println()方法
PrintStream vs PrintWriter
特性 |
PrintStream |
PrintWriter |
处理对象 |
字节流 |
字符流 |
编码支持 |
有限 |
完全支持字符编码 |
异常处理 |
protected(不常用) |
更现代化 |
推荐使用 |
System.out/System.err |
文本输出首选 |
打印流应用实例
import java.io.*;
public class PrintStreamExample {
public static void main(String[] args) {
// 创建格式化的日志文件
try (PrintWriter writer = new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream("application.log"),
StandardCharsets.UTF_8)))) {
// 设置自动刷新(每次println后自动flush)
writer = new PrintWriter(writer, true);
// 写入不同格式的数据
writer.println("====== 应用启动日志 ======");
writer.printf("启动时间: %tF %tT%n", System.currentTimeMillis(), System.currentTimeMillis());
writer.println("系统属性:");
System.getProperties().list(writer); // 打印所有系统属性
writer.printf("可用内存: %.2f MB%n", Runtime.getRuntime().freeMemory() / (1024.0 * 1024));
// 检查错误
if (writer.checkError()) {
System.err.println("写入日志时发生错误");
}
} catch (IOException e) {
e.printStackTrace();
}
// System.out重定向到文件
try (PrintStream fileOut = new PrintStream("console_output.txt")) {
// 保存原始System.out
PrintStream originalOut = System.out;
// 重定向标准输出
System.setOut(fileOut);
// 这些输出将写入文件
System.out.println("这是重定向的输出");
System.out.println(3.1415926);
System.out.printf("%2d月优惠活动开始!%n", 8);
// 恢复原始System.out
System.setOut(originalOut);
System.out.println("输出已恢复");
} catch (IOException e) {
e.printStackTrace();
}
}
}
学习目标
掌握多层流包装技术,实现高效、可靠的I/O处理
典型流组合模型
// 输入流组合:文件→缓冲→转换
BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream("data.txt"),
StandardCharsets.UTF_8)
);
// 输出流组合:缓冲→转换→打印→文件
PrintWriter writer = new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream("output.txt"),
StandardCharsets.UTF_8)),
true); // 启用自动刷新
综合应用实例:文件编码转换工具
import java.io.*;
import java.nio.charset.*;
public class EncodingConverter {
/**
* 转换文件编码格式
* @param sourceFile 源文件路径
* @param srcEncoding 源文件编码
* @param targetFile 目标文件路径
* @param targetEncoding 目标编码
* @param useBuffer 是否使用缓冲
*/
public static void convertEncoding(String sourceFile, String srcEncoding,
String targetFile, String targetEncoding,
boolean useBuffer) throws IOException {
try (Reader reader = createReader(sourceFile, srcEncoding, useBuffer);
Writer writer = createWriter(targetFile, targetEncoding, useBuffer)) {
char[] buffer = new char[8192]; // 8K字符缓冲区
int charsRead;
while ((charsRead = reader.read(buffer)) != -1) {
writer.write(buffer, 0, charsRead);
}
// 确保所有内容写出
writer.flush();
}
}
private static Reader createReader(String file, String encoding, boolean useBuffer)
throws IOException {
InputStream is = new FileInputStream(file);
Reader reader = new InputStreamReader(is, Charset.forName(encoding));
if (useBuffer) {
reader = new BufferedReader(reader, 81920); // 80KB缓冲
}
return reader;
}
private static Writer createWriter(String file, String encoding, boolean useBuffer)
throws IOException {
OutputStream os = new FileOutputStream(file);
Writer writer = new OutputStreamWriter(os, Charset.forName(encoding));
if (useBuffer) {
writer = new BufferedWriter(writer, 81920); // 80KB缓冲
}
return writer;
}
public static void main(String[] args) {
try {
// 将GBK文件转换为UTF-8,使用缓冲
convertEncoding("source_gbk.txt", "GBK",
"target_utf8.txt", "UTF-8", true);
System.out.println("文件转换完成!");
} catch (IOException e) {
System.err.println("转换失败: " + e.getMessage());
}
}
}
学习目标
识别并解决缓冲流、转换流和打印流的常见问题
缓冲流常见问题
问题: 写入数据后文件内容缺失或为空
原因: 未调用flush()或close()导致数据仍在缓冲区
解决: 手动调用flush()确保数据写出,或使用try-with-resources自动关闭
转换流常见问题
问题: 读取文本文件出现乱码或特殊字符错误
原因: 未正确指定或误判源文件编码
解决:
- 使用工具检测实际文件编码
- 尝试常见编码:UTF-8、GBK、ISO-8859-1
- 使用编码探测库(如juniversalchardet)
打印流常见问题
问题: 文件内容不正确但未报错
原因: 打印流捕获异常但未通知用户
解决: 定期调用checkError()检查错误状态
PrintWriter writer = ...;
writer.println(data);
// 检查错误状态
if (writer.checkError()) {
System.err.println("写入过程中发生错误!");
}
错误诊断工具类
import java.io.*;
import java.nio.charset.*;
public class StreamDebugger {
public static void diagnoseEncodingIssue(String filePath) {
System.out.println("诊断文件编码问题: " + filePath);
// 尝试使用不同编码读取
String[] encodings = {"UTF-8", "GBK", "ISO-8859-1", "Windows-1252"};
for (String encoding : encodings) {
System.out.println("\n尝试使用编码: " + encoding);
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream(filePath), encoding))) {
char[] buffer = new char[1024];
int count;
boolean hasError = false;
while ((count = reader.read(buffer)) != -1) {
// 显示前256字符内容
if (reader.getPosition() < 256) {
System.out.println("内容预览: " + new String(buffer, 0, Math.min(count, 50)));
}
// 检查非法字符
for (int i = 0; i < count; i++) {
if (buffer[i] == '\ufffd') {
hasError = true;
}
}
}
if (hasError) {
System.out.println("检测到替换字符(�),可能编码不匹配");
} else {
System.out.println("此编码没有检测到替换字符");
}
} catch (IOException | UnsupportedEncodingException e) {
System.err.println(encoding + " 编码测试失败: " + e.getMessage());
}
}
}
}
学习目标
综合应用缓冲流、转换流、打印流解决实际问题
练习1:日志分析器
编写一个日志分析工具:
- 从多个日志文件(不同编码格式)读取日志
- 按时间排序所有日志条目
- 提取包含"ERROR"的关键条目
- 生成HTML格式的报告,高亮错误条目
要求:
- 使用缓冲流提高读取效率
- 使用转换流处理不同编码
- 使用PrintWriter生成格式化的HTML输出
练习2:配置文件转换器
实现一个配置文件格式转换工具:
- 读取.properties配置文件
- 转换为JSON格式输出
- 支持编码自动检测与转换
- 保留原始注释内容
功能点:
- 使用缓冲流和转换流读取不同编码的.properties文件
- 使用PrintWriter精确控制JSON格式
- 实现注释检测与保留机制
参考实现:简单日志分析器
import java.io.*;
import java.nio.charset.*;
import java.util.*;
import java.util.regex.*;
public class LogAnalyzer {
public static void main(String[] args) {
String[] logFiles = {"system.log", "app_gbk.log", "network.log"};
String output = "report.html";
// 收集所有日志条目
List
entries = new ArrayList<>();
for (String file : logFiles) {
try (BufferedReader reader = detectEncodingReader(file)) {
entries.addAll(parseLogEntries(reader));
} catch (IOException e) {
System.err.println("处理日志失败: " + file + " - " + e.getMessage());
}
}
// 按时间排序
Collections.sort(entries);
// 生成HTML报告
try (PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(output)))) {
generateHtmlReport(writer, entries);
System.out.println("日志报告生成完成: " + output);
} catch (IOException e) {
e.printStackTrace();
}
}
// 自动检测编码创建Reader(简化版)
private static BufferedReader detectEncodingReader(String file) throws IOException {
// 实际项目中应使用juniversalchardet等库
String encoding = file.contains("gbk") ? "GBK" : "UTF-8";
return new BufferedReader(
new InputStreamReader(new FileInputStream(file), encoding));
}
// 解析日志条目(简化实现)
private static List
parseLogEntries(BufferedReader reader) throws IOException {
List
entries = new ArrayList<>();
Pattern pattern = Pattern.compile("^\\[(.+?)\\]\\s+(\\w+):\\s+(.*)");
String line;
int lineNum = 1;
while ((line = reader.readLine()) != null) {
Matcher matcher = pattern.matcher(line);
if (matcher.find()) {
entries.add(new LogEntry(
matcher.group(1), // 时间戳
matcher.group(2), // 级别
matcher.group(3), // 消息
lineNum,
reader
));
}
lineNum++;
}
return entries;
}
// 生成HTML报告
private static void generateHtmlReport(PrintWriter writer, List
entries) {
writer.println("
");
writer.println("
日志分析报告");
writer.println("
");
writer.println("系统日志分析报告
");
writer.println("
");
writer.println("
时间 | 级别 | 消息 |
");
for (LogEntry entry : entries) {
String rowClass = "";
if ("ERROR".equals(entry.level)) rowClass = "error";
else if ("WARN".equals(entry.level)) rowClass = "warn";
writer.printf("
%s | %s | %s |
%n",
rowClass, entry.timestamp, entry.level, entry.message);
}
writer.println("
");
writer.println("
");
}
static class LogEntry implements Comparable
{
String timestamp;
String level;
String message;
int lineNumber;
BufferedReader source;
public LogEntry(String timestamp, String level, String message, int lineNumber, BufferedReader source) {
this.timestamp = timestamp;
this.level = level;
this.message = message;
this.lineNumber = lineNumber;
this.source = source;
}
@Override
public int compareTo(LogEntry o) {
return timestamp.compareTo(o.timestamp);
}
}
}