Java IO流详解
一、Java IO流概述
Java IO流是Java编程中用于处理输入输出操作的核心机制,它提供了一套统一的接口来处理不同类型的数据源和目标。无论是文件操作、网络通信还是内存数据处理,IO流都扮演着关键角色。
1.1 什么是Java IO流
IO流(Input/Output Stream)是Java中用于数据传输的抽象概念。流代表了一个有序的数据序列,数据可以从一个地方流向另一个地方。IO流的设计模式采用了装饰器模式,通过组合不同的流来实现复杂的功能。
流的核心特点
- 单向性:流只能单向流动,分为输入流和输出流
- 顺序性:数据按照顺序读取和写入
- 抽象性:通过抽象类和接口提供统一的操作方式
- 组合性:可以组合多个流来实现复杂功能
1.2 IO流的分类体系
Java IO流主要分为两大类:字节流和字符流,每种又分为输入流和输出流。
字节流基类:InputStream(输入)、OutputStream(输出)
字符流基类:Reader(输入)、Writer(输出)
具体分类
- 字节流:处理二进制数据,如图片、音频、视频等
- 字符流:处理文本数据,自动处理字符编码
- 节点流:直接连接数据源的流,如FileInputStream
- 处理流:包装其他流提供额外功能,如BufferedReader
1.3 IO流的应用场景
在实际项目中,IO流广泛应用于:
- 文件操作:读写配置文件、日志文件、数据文件
- 网络通信:Socket编程、HTTP请求响应
- 数据序列化:对象存储、数据传输
- 多媒体处理:图片处理、音频视频操作
- 系统交互:控制台输入输出、进程间通信
二、字节流详解
字节流是Java IO中最基础的流类型,用于处理8位的字节数据,是所有流类型的基础。
2.1 InputStream(字节输入流)
InputStream是所有字节输入流的抽象基类,定义了读取字节的基本方法。
基本读取操作
java
// 这段代码演示了FileInputStream的基本使用
// 在实际项目中,我们经常需要读取配置文件、图片文件等二进制数据
// 这里展示了如何安全地读取文件并处理异常情况
import java.io.*;
public class BasicFileReader {
public static void main(String[] args) {
String filePath = "config.properties";
try (InputStream inputStream = new FileInputStream(filePath)) {
// 读取文件内容到字节数组
byte[] buffer = new byte[1024];
int bytesRead = inputStream.read(buffer);
// 将字节转换为字符串显示
String content = new String(buffer, 0, bytesRead, "UTF-8");
System.out.println("文件内容:");
System.out.println(content);
} catch (FileNotFoundException e) {
System.err.println("文件不存在:" + filePath);
// 在实际项目中,这里应该记录日志并返回默认配置
} catch (IOException e) {
System.err.println("读取文件时发生错误:" + e.getMessage());
// 实际项目中应该进行错误恢复或通知用户
}
}
}
缓冲读取优化
java
// 这段代码演示了使用缓冲流优化文件读取性能
// 在处理大文件时,直接使用FileInputStream效率较低
// BufferedInputStream通过内部缓冲区显著提高了读取性能
import java.io.*;
public class BufferedFileReader {
public static void readFileWithBuffer(String filePath) {
long startTime = System.currentTimeMillis();
try (InputStream fileInput = new FileInputStream(filePath);
BufferedInputStream bufferedInput = new BufferedInputStream(fileInput)) {
byte[] buffer = new byte[8192]; // 8KB缓冲区
int bytesRead;
long totalBytes = 0;
while ((bytesRead = bufferedInput.read(buffer)) != -1) {
totalBytes += bytesRead;
// 这里可以处理读取的数据,比如计算MD5、分析内容等
}
long endTime = System.currentTimeMillis();
System.out.printf("读取完成,总字节数:%d,耗时:%d ms%n", totalBytes, (endTime - startTime));
} catch (IOException e) {
System.err.println("读取文件失败:" + e.getMessage());
}
}
// 实际应用:图片文件处理
public static byte[] readImageFile(String imagePath) {
try (InputStream inputStream = new BufferedInputStream(new FileInputStream(imagePath))) {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
return outputStream.toByteArray();
} catch (IOException e) {
System.err.println("读取图片失败:" + e.getMessage());
return null;
}
}
}
2.2 OutputStream(字节输出流)
OutputStream是所有字节输出流的抽象基类,用于将字节数据写入目标。
文件写入操作
java
// 这段代码演示了文件写入的基本操作和最佳实践
// 在实际项目中,我们经常需要保存日志、配置、用户上传的文件等
// 这里展示了如何安全高效地进行文件写入
import java.io.*;
import java.nio.charset.StandardCharsets;
public class FileWriteOperations {
// 基本文件写入
public static void writeBasicFile(String filePath, String content) {
try (OutputStream outputStream = new FileOutputStream(filePath)) {
byte[] data = content.getBytes(StandardCharsets.UTF_8);
outputStream.write(data);
System.out.println("文件写入成功:" + filePath);
} catch (IOException e) {
System.err.println("文件写入失败:" + e.getMessage());
}
}
// 追加写入模式
public static void appendToFile(String filePath, String content) {
try (OutputStream outputStream = new FileOutputStream(filePath, true)) { // true表示追加模式
byte[] data = content.getBytes(StandardCharsets.UTF_8);
outputStream.write(data);
System.out.println("内容追加成功");
} catch (IOException e) {
System.err.println("追加写入失败:" + e.getMessage());
}
}
// 缓冲写入优化
public static void writeWithBuffer(String filePath, String content) {
try (OutputStream fileOutput = new FileOutputStream(filePath);
BufferedOutputStream bufferedOutput = new BufferedOutputStream(fileOutput)) {
byte[] data = content.getBytes(StandardCharsets.UTF_8);
bufferedOutput.write(data);
bufferedOutput.flush(); // 确保数据写入磁盘
System.out.println("缓冲写入成功");
} catch (IOException e) {
System.err.println("缓冲写入失败:" + e.getMessage());
}
}
// 实际应用:日志文件写入
public static void writeLog(String logFile, String logMessage) {
try (OutputStream outputStream = new FileOutputStream(logFile, true);
BufferedOutputStream bufferedOutput = new BufferedOutputStream(outputStream)) {
String timestamp = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date());
String logEntry = String.format("[%s] %s%n", timestamp, logMessage);
bufferedOutput.write(logEntry.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
System.err.println("日志写入失败:" + e.getMessage());
}
}
}
文件复制操作
java
// 这段代码演示了文件复制的多种实现方式
// 在实际项目中,文件复制是一个常见需求,如备份文件、上传文件处理等
// 这里展示了不同复制方式的性能对比
import java.io.*;
public class FileCopyOperations {
// 基本文件复制(单字节读取)
public static boolean copyFileBasic(String source, String destination) {
try (InputStream input = new FileInputStream(source);
OutputStream output = new FileOutputStream(destination)) {
int byteRead;
while ((byteRead = input.read()) != -1) {
output.write(byteRead);
}
return true;
} catch (IOException e) {
System.err.println("基本复制失败:" + e.getMessage());
return false;
}
}
// 缓冲文件复制(推荐方式)
public static boolean copyFileWithBuffer(String source, String destination) {
try (InputStream input = new BufferedInputStream(new FileInputStream(source));
OutputStream output = new BufferedOutputStream(new FileOutputStream(destination))) {
byte[] buffer = new byte[8192]; // 8KB缓冲区
int bytesRead;
while ((bytesRead = input.read(buffer)) != -1) {
output.write(buffer, 0, bytesRead);
}
return true;
} catch (IOException e) {
System.err.println("缓冲复制失败:" + e.getMessage());
return false;
}
}
// 使用NIO进行文件复制(Java 7+)
public static boolean copyFileNIO(String source, String destination) {
try {
java.nio.file.Path sourcePath = java.nio.file.Paths.get(source);
java.nio.file.Path destPath = java.nio.file.Paths.get(destination);
java.nio.file.Files.copy(sourcePath, destPath, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
return true;
} catch (java.io.IOException e) {
System.err.println("NIO复制失败:" + e.getMessage());
return false;
}
}
// 实际应用:图片上传处理
public static boolean processUploadedImage(String tempPath, String targetPath, String thumbnailPath) {
try {
// 复制原图到目标位置
if (!copyFileWithBuffer(tempPath, targetPath)) {
return false;
}
// 这里可以添加图片处理逻辑,如生成缩略图
// 为了演示,我们简单复制一份作为缩略图
copyFileWithBuffer(targetPath, thumbnailPath);
// 删除临时文件
new File(tempPath).delete();
return true;
} catch (Exception e) {
System.err.println("图片处理失败:" + e.getMessage());
return false;
}
}
}
三、字符流详解
字符流专门用于处理文本数据,自动处理字符编码转换,是处理文本文件的首选。
3.1 Reader(字符输入流)
Reader是所有字符输入流的抽象基类,用于读取字符数据。
文本文件读取
java
// 这段代码演示了字符流读取文本文件的各种方式
// 在实际项目中,经常需要读取配置文件、CSV数据、JSON文件等
// 字符流会自动处理编码转换,比字节流更适合处理文本
import java.io.*;
import java.nio.charset.StandardCharsets;
public class TextFileReader {
// 基本文本读取
public static String readBasicText(String filePath) {
StringBuilder content = new StringBuilder();
try (Reader reader = new FileReader(filePath, StandardCharsets.UTF_8)) {
char[] buffer = new char[1024];
int charsRead;
while ((charsRead = reader.read(buffer)) != -1) {
content.append(buffer, 0, charsRead);
}
} catch (IOException e) {
System.err.println("读取文本文件失败:" + e.getMessage());
}
return content.toString();
}
// 缓冲文本读取(推荐)
public static void readTextWithBuffer(String filePath) {
try (Reader fileReader = new FileReader(filePath, StandardCharsets.UTF_8);
BufferedReader bufferedReader = new BufferedReader(fileReader)) {
String line;
int lineNumber = 1;
while ((line = bufferedReader.readLine()) != null) {
System.out.printf("%d: %s%n", lineNumber++, line);
// 这里可以对每行进行处理,如解析CSV、JSON等
}
} catch (IOException e) {
System.err.println("缓冲读取失败:" + e.getMessage());
}
}
// 实际应用:配置文件读取
public static java.util.Map<String, String> readConfigFile(String configPath) {
java.util.Map<String, String> config = new java.util.HashMap<>();
try (BufferedReader reader = new BufferedReader(
new FileReader(configPath, StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
// 跳过注释和空行
line = line.trim();
if (line.isEmpty() || line.startsWith("#")) {
continue;
}
// 解析键值对
int equalIndex = line.indexOf('=');
if (equalIndex > 0) {
String key = line.substring(0, equalIndex).trim();
String value = line.substring(equalIndex + 1).trim();
// 移除引号
if (value.startsWith("\"") && value.endsWith("\"")) {
value = value.substring(1, value.length() - 1);
}
config.put(key, value);
}
}
} catch (IOException e) {
System.err.println("读取配置文件失败:" + e.getMessage());
}
return config;
}
// 实际应用:CSV文件读取
public static java.util.List<java.util.Map<String, String>> readCSVFile(String csvPath) {
java.util.List<java.util.Map<String, String>> data = new java.util.ArrayList<>();
try (BufferedReader reader = new BufferedReader(
new FileReader(csvPath, StandardCharsets.UTF_8))) {
// 读取表头
String headerLine = reader.readLine();
if (headerLine == null) {
return data;
}
String[] headers = headerLine.split(",");
// 读取数据行
String dataLine;
while ((dataLine = reader.readLine()) != null) {
String[] values = dataLine.split(",");
if (values.length == headers.length) {
java.util.Map<String, String> row = new java.util.HashMap<>();
for (int i = 0; i < headers.length; i++) {
row.put(headers[i].trim(), values[i].trim());
}
data.add(row);
}
}
} catch (IOException e) {
System.err.println("读取CSV文件失败:" + e.getMessage());
}
return data;
}
}
3.2 Writer(字符输出流)
Writer是所有字符输出流的抽象基类,用于写入字符数据。
文本文件写入
java
// 这段代码演示了字符流写入文本文件的各种方式
// 在实际项目中,经常需要生成报告、保存日志、导出数据等
// 字符流提供了更方便的文本操作方法
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
public class TextFileWriter {
// 基本文本写入
public static void writeBasicText(String filePath, String content) {
try (Writer writer = new FileWriter(filePath, StandardCharsets.UTF_8)) {
writer.write(content);
System.out.println("文本写入成功:" + filePath);
} catch (IOException e) {
System.err.println("文本写入失败:" + e.getMessage());
}
}
// 缓冲文本写入(推荐)
public static void writeWithBuffer(String filePath, String content) {
try (Writer fileWriter = new FileWriter(filePath, StandardCharsets.UTF_8);
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter)) {
bufferedWriter.write(content);
bufferedWriter.flush(); // 确保数据写入磁盘
System.out.println("缓冲写入成功");
} catch (IOException e) {
System.err.println("缓冲写入失败:" + e.getMessage());
}
}
// 逐行写入
public static void writeLines(String filePath, List<String> lines) {
try (BufferedWriter writer = new BufferedWriter(
new FileWriter(filePath, StandardCharsets.UTF_8))) {
for (String line : lines) {
writer.write(line);
writer.newLine(); // 写入换行符,跨平台兼容
}
System.out.printf("写入 %d 行数据到:%s%n", lines.size(), filePath);
} catch (IOException e) {
System.err.println("逐行写入失败:" + e.getMessage());
}
}
// 实际应用:日志文件写入
public static void writeLog(String logFile, String level, String message) {
try (BufferedWriter writer = new BufferedWriter(
new FileWriter(logFile, true))) { // 追加模式
String timestamp = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date());
String logEntry = String.format("[%s] [%s] %s%n", timestamp, level, message);
writer.write(logEntry);
} catch (IOException e) {
System.err.println("日志写入失败:" + e.getMessage());
}
}
// 实际应用:CSV文件写入
public static void writeCSVFile(String csvPath, List<String> headers, List<Map<String, String>> data) {
try (BufferedWriter writer = new BufferedWriter(
new FileWriter(csvPath, StandardCharsets.UTF_8))) {
// 写入表头
writer.write(String.join(",", headers));
writer.newLine();
// 写入数据行
for (Map<String, String> row : data) {
StringBuilder line = new StringBuilder();
for (String header : headers) {
String value = row.getOrDefault(header, "");
// 如果包含逗号或引号,需要用引号包围
if (value.contains(",") || value.contains("\"")) {
value = "\"" + value.replace("\"", "\"\"") + "\"";
}
line.append(value).append(",");
}
// 移除最后的逗号
if (line.length() > 0) {
line.setLength(line.length() - 1);
}
writer.write(line.toString());
writer.newLine();
}
System.out.printf("CSV文件写入完成:%d 行数据%n", data.size());
} catch (IOException e) {
System.err.println("CSV写入失败:" + e.getMessage());
}
}
// 实际应用:生成HTML报告
public static void generateHTMLReport(String reportPath, String title, List<String> content) {
try (BufferedWriter writer = new BufferedWriter(
new FileWriter(reportPath, StandardCharsets.UTF_8))) {
// HTML模板
writer.write("<!DOCTYPE html>");
writer.newLine();
writer.write("<html>");
writer.newLine();
writer.write("<head>");
writer.newLine();
writer.write("<meta charset=\"UTF-8\">");
writer.newLine();
writer.write(String.format("<title>%s</title>", title));
writer.newLine();
writer.write("<style>");
writer.newLine();
writer.write("body { font-family: Arial, sans-serif; margin: 40px; }");
writer.newLine();
writer.write("h1 { color: #333; }");
writer.newLine();
writer.write("p { line-height: 1.6; }");
writer.newLine();
writer.write("</style>");
writer.newLine();
writer.write("</head>");
writer.newLine();
writer.write("<body>");
writer.newLine();
writer.write(String.format("<h1>%s</h1>", title));
writer.newLine();
// 写入内容
for (String paragraph : content) {
writer.write(String.format("<p>%s</p>", paragraph));
writer.newLine();
}
// HTML结束标签
writer.write("</body>");
writer.newLine();
writer.write("</html>");
writer.newLine();
System.out.println("HTML报告生成成功:" + reportPath);
} catch (IOException e) {
System.err.println("HTML生成失败:" + e.getMessage());
}
}
}
四、缓冲流详解
缓冲流通过内部缓冲区来优化IO性能,是Java IO中最常用的性能优化手段。
4.1 BufferedInputStream和BufferedOutputStream
字节缓冲流通过减少实际的IO操作次数来提高性能。
大文件处理优化
java
// 这段代码演示了缓冲流在大文件处理中的性能优势
// 在实际项目中,处理大文件时缓冲流是必需的,可以显著提高性能
// 这里通过对比测试展示缓冲流的效果
import java.io.*;
public class BufferStreamDemo {
// 非缓冲读取(性能较差)
public static long readFileWithoutBuffer(String filePath) throws IOException {
long startTime = System.currentTimeMillis();
try (InputStream input = new FileInputStream(filePath)) {
byte[] buffer = new byte[1024];
int bytesRead;
long totalBytes = 0;
while ((bytesRead = input.read(buffer)) != -1) {
totalBytes += bytesRead;
// 模拟数据处理
for (int i = 0; i < bytesRead; i++) {
buffer[i] = (byte) (buffer[i] ^ 0xFF); // 简单的字节操作
}
}
long endTime = System.currentTimeMillis();
System.out.printf("非缓冲读取:%d 字节,耗时:%d ms%n", totalBytes, (endTime - startTime));
return totalBytes;
}
}
// 缓冲读取(性能较好)
public static long readFileWithBuffer(String filePath) throws IOException {
long startTime = System.currentTimeMillis();
try (InputStream input = new BufferedInputStream(new FileInputStream(filePath))) {
byte[] buffer = new byte[1024];
int bytesRead;
long totalBytes = 0;
while ((bytesRead = input.read(buffer)) != -1) {
totalBytes += bytesRead;
// 模拟数据处理
for (int i = 0; i < bytesRead; i++) {
buffer[i] = (byte) (buffer[i] ^ 0xFF);
}
}
long endTime = System.currentTimeMillis();
System.out.printf("缓冲读取:%d 字节,耗时:%d ms%n", totalBytes, (endTime - startTime));
return totalBytes;
}
}
// 自定义缓冲区大小测试
public static void testDifferentBufferSizes(String filePath) throws IOException {
int[] bufferSizes = {1024, 4096, 8192, 16384, 32768};
for (int size : bufferSizes) {
long startTime = System.currentTimeMillis();
try (InputStream input = new BufferedInputStream(
new FileInputStream(filePath), size)) {
byte[] buffer = new byte[1024];
int bytesRead;
long totalBytes = 0;
while ((bytesRead = input.read(buffer)) != -1) {
totalBytes += bytesRead;
}
long endTime = System.currentTimeMillis();
System.out.printf("缓冲区大小 %d:%d 字节,耗时:%d ms%n",
size, totalBytes, (endTime - startTime));
}
}
}
// 实际应用:文件加密/解密
public static void encryptFile(String inputPath, String outputPath, byte key) {
try (InputStream input = new BufferedInputStream(new FileInputStream(inputPath));
OutputStream output = new BufferedOutputStream(new FileOutputStream(outputPath))) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = input.read(buffer)) != -1) {
// 简单的XOR加密
for (int i = 0; i < bytesRead; i++) {
buffer[i] ^= key;
}
output.write(buffer, 0, bytesRead);
}
System.out.printf("文件加密完成:%s -> %s%n", inputPath, outputPath);
} catch (IOException e) {
System.err.println("文件加密失败:" + e.getMessage());
}
}
// 实际应用:文件压缩(简单的RLE算法)
public static void compressFile(String inputPath, String outputPath) {
try (InputStream input = new BufferedInputStream(new FileInputStream(inputPath));
OutputStream output = new BufferedOutputStream(new FileOutputStream(outputPath))) {
byte currentByte;
byte previousByte = -1;
int count = 0;
while ((currentByte = (byte) input.read()) != -1) {
if (currentByte == previousByte) {
count++;
if (count == 255) { // 防止计数溢出
output.write(previousByte);
output.write((byte) 255);
count = 0;
}
} else {
if (previousByte != -1) {
output.write(previousByte);
if (count > 1) {
output.write((byte) count);
}
}
previousByte = currentByte;
count = 1;
}
}
// 写入最后的数据
if (previousByte != -1) {
output.write(previousByte);
if (count > 1) {
output.write((byte) count);
}
}
System.out.printf("文件压缩完成:%s -> %s%n", inputPath, outputPath);
} catch (IOException e) {
System.err.println("文件压缩失败:" + e.getMessage());
}
}
}
4.2 BufferedReader和BufferedWriter
字符缓冲流提供了更高效的文本处理能力,特别是readLine()方法非常实用。
文本处理优化
java
// 这段代码演示了字符缓冲流在文本处理中的优势
// 在实际项目中,处理大文本文件、日志文件时,字符缓冲流是必需的
// readLine()方法让逐行处理变得非常简单
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
public class CharacterBufferDemo {
// 逐行处理大文件
public static void processLargeTextFile(String filePath) {
try (BufferedReader reader = new BufferedReader(
new FileReader(filePath, StandardCharsets.UTF_8))) {
String line;
int lineNumber = 0;
long totalCharacters = 0;
long totalWords = 0;
while ((line = reader.readLine()) != null) {
lineNumber++;
totalCharacters += line.length();
totalWords += line.split("\\s+").length;
// 模拟处理:找到包含特定关键词的行
if (line.contains("error") || line.contains("exception")) {
System.out.printf("第 %d 行发现问题:%s%n", lineNumber, line);
}
// 每处理1000行输出一次进度
if (lineNumber % 1000 == 0) {
System.out.printf("已处理 %d 行%n", lineNumber);
}
}
System.out.printf("文件处理完成:%d 行,%d 字符,%d 单词%n",
lineNumber, totalCharacters, totalWords);
} catch (IOException e) {
System.err.println("文件处理失败:" + e.getMessage());
}
}
// 实际应用:日志文件分析
public static Map<String, Integer> analyzeLogFile(String logPath) {
Map<String, Integer> logLevels = new HashMap<>();
Map<String, Integer> ipCounts = new HashMap<>();
Set<String> errorMessages = new HashSet<>();
try (BufferedReader reader = new BufferedReader(
new FileReader(logPath, StandardCharsets.UTF_8))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty()) continue;
// 提取日志级别(假设格式为:[INFO] message)
if (line.startsWith("[")) {
int endIndex = line.indexOf(']');
if (endIndex > 0) {
String level = line.substring(1, endIndex);
logLevels.put(level, logLevels.getOrDefault(level, 0) + 1);
// 收集错误信息
if ("ERROR".equals(level) || "FATAL".equals(level)) {
errorMessages.add(line.substring(endIndex + 1).trim());
}
}
}
// 简单的IP地址统计
String[] parts = line.split("\\s+");
for (String part : parts) {
if (part.matches("\\d+\\.\\d+\\.\\d+\\.\\d+")) {
ipCounts.put(part, ipCounts.getOrDefault(part, 0) + 1);
break;
}
}
}
} catch (IOException e) {
System.err.println("日志分析失败:" + e.getMessage());
}
// 输出分析结果
System.out.println("日志级别统计:");
logLevels.forEach((level, count) ->
System.out.printf(" %s: %d%n", level, count));
System.out.println("访问最多的IP地址:");
ipCounts.entrySet().stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
.limit(5)
.forEach(entry ->
System.out.printf(" %s: %d 次%n", entry.getKey(), entry.getValue()));
System.out.println("错误信息样例:");
errorMessages.stream().limit(3).forEach(msg ->
System.out.printf(" %s%n", msg));
return logLevels;
}
// 实际应用:大文件分割
public static void splitLargeFile(String inputPath, String outputDir, int linesPerFile) {
try (BufferedReader reader = new BufferedReader(
new FileReader(inputPath, StandardCharsets.UTF_8))) {
int fileNumber = 1;
int currentLineCount = 0;
BufferedWriter currentWriter = new BufferedWriter(
new FileWriter(outputDir + "/part_" + fileNumber + ".txt", StandardCharsets.UTF_8));
String line;
while ((line = reader.readLine()) != null) {
currentWriter.write(line);
currentWriter.newLine();
currentLineCount++;
// 达到指定行数,创建新文件
if (currentLineCount >= linesPerFile) {
currentWriter.close();
fileNumber++;
currentLineCount = 0;
currentWriter = new BufferedWriter(
new FileWriter(outputDir + "/part_" + fileNumber + ".txt", StandardCharsets.UTF_8));
System.out.printf("创建第 %d 个文件%n", fileNumber);
}
}
// 关闭最后一个文件
currentWriter.close();
System.out.printf("文件分割完成,共 %d 个文件%n", fileNumber);
} catch (IOException e) {
System.err.println("文件分割失败:" + e.getMessage());
}
}
// 实际应用:数据去重
public static void deduplicateFile(String inputPath, String outputPath) {
Set<String> uniqueLines = new HashSet<>();
try (BufferedReader reader = new BufferedReader(
new FileReader(inputPath, StandardCharsets.UTF_8));
BufferedWriter writer = new BufferedWriter(
new FileWriter(outputPath, StandardCharsets.UTF_8))) {
String line;
int totalLines = 0;
int uniqueLinesCount = 0;
while ((line = reader.readLine()) != null) {
totalLines++;
if (uniqueLines.add(line)) { // add返回true表示是新行
writer.write(line);
writer.newLine();
uniqueLinesCount++;
}
}
System.out.printf("去重完成:原文件 %d 行,去重后 %d 行,减少 %d 行%n",
totalLines, uniqueLinesCount, totalLines - uniqueLinesCount);
} catch (IOException e) {
System.err.println("文件去重失败:" + e.getMessage());
}
}
}
五、对象流详解
对象流支持对象的序列化和反序列化,用于将对象状态保存到文件或网络传输。
5.1 ObjectInputStream和ObjectOutputStream
对象流可以读写实现了Serializable接口的对象。
对象序列化操作
java
// 这段代码演示了对象流的序列化和反序列化操作
// 在实际项目中,对象序列化用于缓存、会话管理、深拷贝等场景
// 这里展示了如何安全地进行对象序列化
import java.io.*;
import java.util.Date;
// 必须实现Serializable接口
class User implements Serializable {
private static final long serialVersionUID = 1L; // 版本号,建议总是提供
private String username;
private String email;
private transient String password; // transient字段不会被序列化
private Date registrationDate;
private int[] preferences;
public User(String username, String email, String password) {
this.username = username;
this.email = email;
this.password = password;
this.registrationDate = new Date();
this.preferences = new int[]{1, 2, 3};
}
// 自定义序列化方法
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject(); // 默认序列化
// 可以在这里添加自定义序列化逻辑
}
// 自定义反序列化方法
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject(); // 默认反序列化
// 可以在这里添加自定义反序列化逻辑
this.password = "******"; // 重新设置密码
}
@Override
public String toString() {
return String.format("User{username='%s', email='%s', registrationDate=%s}",
username, email, registrationDate);
}
}
class Product implements Serializable {
private static final long serialVersionUID = 2L;
private String id;
private String name;
private double price;
private String category;
public Product(String id, String name, double price, String category) {
this.id = id;
this.name = name;
this.price = price;
this.category = category;
}
@Override
public String toString() {
return String.format("Product{id='%s', name='%s', price=%.2f, category='%s'}",
id, name, price, category);
}
}
public class ObjectStreamDemo {
// 序列化单个对象
public static void serializeObject(String filePath, Object obj) {
try (ObjectOutputStream output = new ObjectOutputStream(
new FileOutputStream(filePath))) {
output.writeObject(obj);
System.out.printf("对象序列化成功:%s%n", filePath);
} catch (IOException e) {
System.err.println("对象序列化失败:" + e.getMessage());
}
}
// 反序列化单个对象
public static Object deserializeObject(String filePath) {
try (ObjectInputStream input = new ObjectInputStream(
new FileInputStream(filePath))) {
Object obj = input.readObject();
System.out.printf("对象反序列化成功:%s%n", filePath);
return obj;
} catch (IOException | ClassNotFoundException e) {
System.err.println("对象反序列化失败:" + e.getMessage());
return null;
}
}
// 序列化对象列表
public static void serializeObjectList(String filePath, List<?> objects) {
try (ObjectOutputStream output = new ObjectOutputStream(
new FileOutputStream(filePath))) {
output.writeInt(objects.size()); // 先写入列表大小
for (Object obj : objects) {
output.writeObject(obj);
}
System.out.printf("对象列表序列化成功:%d 个对象%n", objects.size());
} catch (IOException e) {
System.err.println("对象列表序列化失败:" + e.getMessage());
}
}
// 反序列化对象列表
public static <T> List<T> deserializeObjectList(String filePath, Class<T> clazz) {
List<T> objects = new ArrayList<>();
try (ObjectInputStream input = new ObjectInputStream(
new FileInputStream(filePath))) {
int size = input.readInt(); // 读取列表大小
for (int i = 0; i < size; i++) {
Object obj = input.readObject();
if (clazz.isInstance(obj)) {
objects.add(clazz.cast(obj));
}
}
System.out.printf("对象列表反序列化成功:%d 个对象%n", objects.size());
} catch (IOException | ClassNotFoundException e) {
System.err.println("对象列表反序列化失败:" + e.getMessage());
}
return objects;
}
// 实际应用:用户会话管理
public static class SessionManager {
private String sessionFile = "sessions.dat";
public void saveSession(String sessionId, User user, long lastActivity) {
Map<String, Object> session = new HashMap<>();
session.put("sessionId", sessionId);
session.put("user", user);
session.put("lastActivity", lastActivity);
serializeObject(sessionFile + "_" + sessionId, session);
}
public Map<String, Object> loadSession(String sessionId) {
String filePath = sessionFile + "_" + sessionId;
Object obj = deserializeObject(filePath);
if (obj instanceof Map) {
return (Map<String, Object>) obj;
}
return null;
}
}
// 实际应用:数据缓存
public static class DataCache {
private String cacheFile = "cache.dat";
public void cacheProducts(List<Product> products) {
serializeObjectList(cacheFile, products);
}
public List<Product> getCachedProducts() {
return deserializeObjectList(cacheFile, Product.class);
}
public boolean isCacheValid(long maxAgeMillis) {
File cacheFileObj = new File(cacheFile);
if (cacheFileObj.exists()) {
long lastModified = cacheFileObj.lastModified();
return (System.currentTimeMillis() - lastModified) <= maxAgeMillis;
}
return false;
}
}
// 实际应用:深拷贝对象
public static <T extends Serializable> T deepCopy(T obj) {
try {
// 使用序列化进行深拷贝
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(byteOut);
out.writeObject(obj);
out.flush();
ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
ObjectInputStream in = new ObjectInputStream(byteIn);
@SuppressWarnings("unchecked")
T copy = (T) in.readObject();
return copy;
} catch (IOException | ClassNotFoundException e) {
System.err.println("深拷贝失败:" + e.getMessage());
return null;
}
}
// 演示使用
public static void main(String[] args) {
// 创建测试对象
User user = new User("admin", "admin@example.com", "password123");
List<Product> products = Arrays.asList(
new Product("P001", "Laptop", 999.99, "Electronics"),
new Product("P002", "Mouse", 29.99, "Electronics"),
new Product("P003", "Keyboard", 79.99, "Electronics")
);
// 序列化操作
serializeObject("user.dat", user);
serializeObjectList("products.dat", products);
// 反序列化操作
User loadedUser = (User) deserializeObject("user.dat");
System.out.println("加载的用户:" + loadedUser);
List<Product> loadedProducts = deserializeObjectList("products.dat", Product.class);
System.out.println("加载的产品:");
loadedProducts.forEach(System.out::println);
// 深拷贝测试
Product original = products.get(0);
Product copy = deepCopy(original);
System.out.println("原始对象:" + original);
System.out.println("拷贝对象:" + copy);
System.out.println("是否相同对象:" + (original == copy)); // false
// 缓存演示
DataCache cache = new DataCache();
cache.cacheProducts(products);
if (cache.isCacheValid(60000)) { // 1分钟内有效
List<Product> cachedProducts = cache.getCachedProducts();
System.out.println("从缓存加载了 " + cachedProducts.size() + " 个产品");
}
// 会话管理演示
SessionManager sessionManager = new SessionManager();
sessionManager.saveSession("session123", user, System.currentTimeMillis());
Map<String, Object> session = sessionManager.loadSession("session123");
if (session != null) {
System.out.println("会话用户:" + session.get("user"));
}
}
}
六、面试技巧和最佳实践
6.1 面试常见问题及回答策略
问题1:请解释Java IO流的分类体系,并说明字节流和字符流的区别。
优秀回答:
Java IO流按照处理数据类型分为字节流和字符流两大类:
字节流:
- 基类:InputStream和OutputStream
- 处理8位字节数据
- 适合处理二进制文件:图片、音频、视频等
- 不关心字符编码
字符流:
- 基类:Reader和Writer
- 处理16位字符数据
- 自动处理字符编码转换
- 适合处理文本文件
在我之前的项目中,开发文件上传功能时:
- 用户上传的图片、文档用字节流处理,避免编码问题
- 配置文件、日志文件用字符流处理,自动处理UTF-8编码
关键区别:字符流本质上是字节流+编码转换器,Reader/Writer内部包含InputStream/OutputStream。
问题2:为什么要使用缓冲流?它的工作原理是什么?
优秀回答:
缓冲流通过内部缓冲区减少实际IO操作次数来提高性能。
工作原理:
1. 每次读取时,从数据源读取一块数据到缓冲区
2. 后续读取直接从缓冲区获取
3. 缓冲区空了才再次访问数据源
性能提升:
- 减少系统调用次数(磁盘IO很慢)
- 内存操作比磁盘操作快几个数量级
- 批量操作比单字节操作效率高
在我负责的大数据处理项目中:
- 处理10GB日志文件,非缓冲方式需要2小时
- 使用8KB缓冲区后只需20分钟,性能提升6倍
最佳实践:
- 文件操作总是使用缓冲流
- 缓冲区大小一般设置为8KB-32KB
- 注意及时flush确保数据写入磁盘
6.2 展示解决问题能力的实战案例
案例:高性能文件传输系统设计
在开发云存储系统的文件传输模块时,我遇到了大文件传输性能问题。
问题场景:
- 需要支持GB级文件的上传下载
- 网络环境不稳定,需要断点续传
- 服务器资源有限,需要控制内存使用
我的解决方案:
1. 分块传输设计:
```java
public class ChunkedFileTransfer {
private static final int CHUNK_SIZE = 1024 * 1024; // 1MB块大小
// 上传文件
public boolean uploadFile(String filePath, OutputStream networkOutput) {
try (InputStream input = new BufferedInputStream(
new FileInputStream(filePath))) {
byte[] chunk = new byte[CHUNK_SIZE];
int bytesRead;
long totalTransferred = 0;
while ((bytesRead = input.read(chunk)) != -1) {
networkOutput.write(chunk, 0, bytesRead);
totalTransferred += bytesRead;
// 进度回调
notifyProgress(totalTransferred, new File(filePath).length());
// 网络检查点
if (shouldCheckpoint(totalTransferred)) {
saveCheckpoint(totalTransferred);
}
}
return true;
} catch (IOException e) {
handleTransferError(e);
return false;
}
}
}
- 内存映射文件优化:
java
// 对于超大文件,使用NIO的内存映射
public void copyLargeFile(String source, String target) {
try (RandomAccessFile sourceFile = new RandomAccessFile(source, "r");
RandomAccessFile targetFile = new RandomAccessFile(target, "rw")) {
FileChannel sourceChannel = sourceFile.getChannel();
FileChannel targetChannel = targetFile.getChannel();
long size = sourceChannel.size();
long position = 0;
while (position < size) {
long transferred = sourceChannel.transferTo(
position, Math.min(CHUNK_SIZE, size - position), targetChannel);
position += transferred;
}
}
}
- 异步IO处理:
java
// 使用异步IO提高并发处理能力
public CompletableFuture<Boolean> asyncFileCopy(Path source, Path target) {
return CompletableFuture.supplyAsync(() -> {
try {
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
return true;
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
这个解决方案实现了:
-
传输速度提升300%
-
内存使用控制在1MB以内
-
支持断点续传,可靠性高
-
支持并发传输,系统吞吐量提升5倍
6.3 IO流最佳实践总结
性能优化原则:
- 总是使用缓冲流,减少IO次数
- 合理设置缓冲区大小(8KB-32KB)
- 批量操作代替单字节操作
- 考虑使用NIO处理大文件
错误处理原则:
- 使用try-with-resources确保资源释放
- 区分不同类型的IOException
- 提供有意义的错误信息
- 实现适当的错误恢复机制
编码规范原则:
- 明确指定字符编码,避免平台依赖
- 实现Serializable时提供serialVersionUID
- 合理使用transient关键字
- 文件操作完成后及时关闭流
安全原则:
- 验证文件路径,防止路径遍历攻击
- 限制文件大小,防止磁盘空间耗尽
- 敏感数据使用加密流
- 定期清理临时文件
掌握这些技巧不仅能帮助在面试中展现专业能力,更能在实际项目中开发出高质量的IO处理程序。