【Java】各种IO流知识详解

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;
        }
    }
}
  1. 内存映射文件优化:
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;
        }
    }
}
  1. 异步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流最佳实践总结

    性能优化原则:

    1. 总是使用缓冲流,减少IO次数
    2. 合理设置缓冲区大小(8KB-32KB)
    3. 批量操作代替单字节操作
    4. 考虑使用NIO处理大文件

    错误处理原则:

    1. 使用try-with-resources确保资源释放
    2. 区分不同类型的IOException
    3. 提供有意义的错误信息
    4. 实现适当的错误恢复机制

    编码规范原则:

    1. 明确指定字符编码,避免平台依赖
    2. 实现Serializable时提供serialVersionUID
    3. 合理使用transient关键字
    4. 文件操作完成后及时关闭流

    安全原则:

    1. 验证文件路径,防止路径遍历攻击
    2. 限制文件大小,防止磁盘空间耗尽
    3. 敏感数据使用加密流
    4. 定期清理临时文件

    掌握这些技巧不仅能帮助在面试中展现专业能力,更能在实际项目中开发出高质量的IO处理程序。

相关推荐
Mr.朱鹏1 小时前
SQL深度分页问题案例实战
java·数据库·spring boot·sql·spring·spring cloud·kafka
小张快跑。2 小时前
【Java企业级开发】(十一)企业级Web应用程序Servlet框架的使用(上)
java·前端·servlet
星星不打輰2 小时前
SSM项目--SweetHouse 甜蜜蛋糕屋
java·spring·mybatis·ssm·springmvc
Victor3562 小时前
Netty(19)Netty的性能优化手段有哪些?
后端
傻啦嘿哟2 小时前
实战:用Splash搞定JavaScript密集型网页渲染
开发语言·javascript·ecmascript
Knight_AL2 小时前
Java 线程池预热(Warm-up)实战:开启与不开启到底差多少?
java·开发语言
爬山算法2 小时前
Netty(15)Netty的线程模型是什么?它有哪些线程池类型?
java·后端
C++业余爱好者2 小时前
公司局域网访问外网的原理
java
liwulin05062 小时前
【PYTHON】COCO数据集中的物品ID
开发语言·python