11.Java I/O 流:文件读写与数据持久化

在 Java 编程世界里,I/O 流是实现数据输入输出的基础组件,广泛应用于文件操作、网络通信、数据持久化等场景。本文将深入剖析 Java I/O 流的核心概念,区分字节流与字符流的使用场景,结合文件读写、图片复制等实战案例,帮助开发者掌握数据存储与读取的关键技术。

一、Java I/O 流核心概念​

1. 流的分类​

Java I/O 流按方向分为输入流(读取数据)和输出流(写入数据),按处理单元分为字节流和字符流:​

  • 字节流:以字节(byte,8 位)为单位处理数据,适用于二进制文件(如图片、视频、可执行文件),能够处理任意类型的数据。
  • 字符流:以字符(char,16 位 Unicode)为单位处理数据,自动处理字符编码,适用于文本文件(如.txt、.properties)。

2. 流的顶层抽象类​

字节流:​

  • 输入流顶层类:InputStream(抽象类,定义字节读取方法)
  • 输出流顶层类:OutputStream(抽象类,定义字节写入方法)

字符流:​

  • 输入流顶层类:Reader(抽象类,定义字符读取方法)
  • 输出流顶层类:Writer(抽象类,定义字符写入方法)

3. 流的装饰器模式​

Java 通过装饰器模式增强流的功能,核心思想是通过包装类为基础流添加缓冲、编码转换、数据格式处理等功能。例如:​

  • BufferedInputStream/BufferedOutputStream:添加缓冲功能,减少磁盘 / 网络交互次数,提升读写效率。
  • InputStreamReader/OutputStreamWriter:实现字节流与字符流的桥梁,处理字符编码转换。
  • DataInputStream/DataOutputStream:支持基本数据类型(如int、String)的读写。

二、字节流:处理二进制数据​

1. 文件字节流:FileInputStream与FileOutputStream​

使用场景: 读取 / 写入二进制文件(如图片、音频、压缩包),适合处理非文本类型的数据。​

示例 1: 单字节读取(原理演示,性能较低)

java 复制代码
import java.io.FileInputStream;  
import java.io.FileOutputStream;  
import java.io.IOException;  

/**  
 * 图片复制示例(单字节读取)  
 * 演示基本字节流操作,适合理解底层原理,但不推荐用于实际项目  
 */  
public class ImageCopyExample {  
    public static void main(String[] args) {  
        String sourcePath = "source.jpg";   // 源文件路径  
        String targetPath = "target.jpg";  // 目标文件路径  

        // try-with-resources自动关闭流,避免资源泄漏(Java 7+特性)  
        try (FileInputStream fis = new FileInputStream(sourcePath);  
             FileOutputStream fos = new FileOutputStream(targetPath)) {  

            int byteData;  
            // read()方法:读取一个字节,返回-1表示文件结束  
            while ((byteData = fis.read()) != -1) {  
                fos.write(byteData);  // 写入单个字节  
            }  
            System.out.println("图片复制完成(单字节模式)!");  

        } catch (IOException e) {  
            System.err.println("文件操作失败:" + e.getMessage());  
            e.printStackTrace();  
        }  
    }  
}  

示例 2: 缓冲区读取(推荐实现,性能提升 10 倍 +)​

java 复制代码
import java.io.FileInputStream;  
import java.io.FileOutputStream;  
import java.io.IOException;  

/**  
 * 图片复制示例(缓冲区读取)  
 * 使用字节数组批量读写,减少系统调用次数  
 */  
public class BufferedImageCopyExample {  
    public static void main(String[] args) {  
        String sourcePath = "source.jpg";  
        String targetPath = "target.jpg";  
        byte[] buffer = new byte[8192];  // 8KB缓冲区(典型值,可根据内存调整)  

        try (FileInputStream fis = new FileInputStream(sourcePath);  
             FileOutputStream fos = new FileOutputStream(targetPath)) {  

            int bytesRead;  
            // read(byte[]):读取数据到缓冲区,返回实际读取的字节数  
            while ((bytesRead = fis.read(buffer)) != -1) {  
                fos.write(buffer, 0, bytesRead);  // 写入缓冲区中的有效数据  
            }  
            System.out.println("图片复制完成(缓冲区模式)!");  

        } catch (IOException e) {  
            System.err.println("文件操作失败:" + e.getMessage());  
        }  
    }  
}  

2. 缓冲字节流:BufferedInputStream与BufferedOutputStream​

原理: 内部维护一个默认大小(通常 8KB)的缓冲区,批量读取 / 写入数据,减少与底层设备(如磁盘)的交互次数,显著提升性能。​

示例: 缓冲流高效复制图片

java 复制代码
import java.io.BufferedInputStream;  
import java.io.BufferedOutputStream;  
import java.io.FileInputStream;  
import java.io.FileOutputStream;  
import java.io.IOException;  

public class BufferedStreamExample {  
    public static void main(String[] args) {  
        String sourcePath = "source.jpg";  
        String targetPath = "target.jpg";  

        // 装饰器模式:将FileInputStream包装为BufferedInputStream  
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sourcePath));  
             BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(targetPath))) {  

            byte[] buffer = new byte[8192];  
            int bytesRead;  
            while ((bytesRead = bis.read(buffer)) != -1) {  
                bos.write(buffer, 0, bytesRead);  
            }  
            System.out.println("使用缓冲流复制完成,性能提升约10倍!");  

        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
}  

三、字符流:处理文本数据​

1. 文件字符流:FileReader与FileWriter​

使用场景: 读取 / 写入文本文件(如日志、配置文件),自动处理字符编码(默认使用系统编码,可能导致乱码)。​

示例 1: 单字符读取(原理演示)

java 复制代码
import java.io.FileReader;  
import java.io.FileWriter;  
import java.io.IOException;  

/**  
 * 文本文件复制示例(单字符读取)  
 * 注意:默认使用系统编码(如Windows的GBK),处理中文可能乱码  
 */  
public class TextCopyExample {  
    public static void main(String[] args) {  
        String sourcePath = "source.txt";  
        String targetPath = "target.txt";  

        try (FileReader reader = new FileReader(sourcePath);  
             FileWriter writer = new FileWriter(targetPath)) {  

            int character;  
            // read():读取一个字符(Unicode码点),返回-1表示文件结束  
            while ((character = reader.read()) != -1) {  
                writer.write(character);  // 写入单个字符  
            }  
            System.out.println("文本复制完成(单字符模式)!");  

        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
}  

示例 2: 显式指定 UTF-8 编码(推荐)​

java 复制代码
import java.io.BufferedReader;  
import java.io.BufferedWriter;  
import java.io.FileInputStream;  
import java.io.FileOutputStream;  
import java.io.IOException;  
import java.io.InputStreamReader;  
import java.io.OutputStreamWriter;  

/**  
 * 文本文件复制(指定UTF-8编码)  
 * 解决中文乱码问题,支持跨平台文件读写  
 */  
public class EncodingExample {  
    public static void main(String[] args) {  
        String sourcePath = "source.txt";  
        String targetPath = "target.txt";  

        try (BufferedReader reader = new BufferedReader(  
                new InputStreamReader(new FileInputStream(sourcePath), "UTF-8"));  
             BufferedWriter writer = new BufferedWriter(  
                new OutputStreamWriter(new FileOutputStream(targetPath), "UTF-8"))) {  

            int character;  
            while ((character = reader.read()) != -1) {  
                writer.write(character);  
            }  
            System.out.println("UTF-8编码文本复制完成!");  

        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
}  

2. 缓冲字符流:BufferedReader与BufferedWriter​

优势:​

  • BufferedReader支持按行读取(readLine()方法),无需手动处理换行符。
  • BufferedWriter支持高效写入,并提供平台无关的换行符(newLine()方法)。

示例: 按行读取文本文件

java 复制代码
import java.io.BufferedReader;  
import java.io.BufferedWriter;  
import java.io.FileReader;  
import java.io.FileWriter;  
import java.io.IOException;  

/**  
 * 按行处理文本文件(推荐实践)  
 * 适用于日志分析、配置文件解析等场景  
 */  
public class LineBasedExample {  
    public static void main(String[] args) {  
        String sourcePath = "access.log";  
        String targetPath = "clean.log";  

        try (BufferedReader reader = new BufferedReader(new FileReader(sourcePath));  
             BufferedWriter writer = new BufferedWriter(new FileWriter(targetPath))) {  

            String line;  
            // 逐行读取(自动过滤换行符,返回null表示文件结束)  
            while ((line = reader.readLine()) != null) {  
                // 处理逻辑:例如过滤空行或敏感信息  
                if (!line.trim().isEmpty()) {  
                    writer.write(line);  
                    writer.newLine();  // 写入平台适配的换行符(Windows为\r\n,Linux为\n)  
                }  
            }  
            System.out.println("按行处理文本完成!");  

        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
}  

四、实战案例:综合应用​

案例 1: 统计文本文件单词出现次数​

需求: 读取文本文件,统计每个单词的出现次数,忽略大小写和标点符号。

java 复制代码
import java.io.BufferedReader;  
import java.io.FileReader;  
import java.io.IOException;  
import java.util.HashMap;  
import java.util.Map;  
import java.util.TreeMap;  

public class WordCountTool {  
    public static void main(String[] args) {  
        String filePath = "input.txt";  
        Map<String, Integer> wordCounts = new HashMap<>();  

        try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {  
            String line;  
            while ((line = reader.readLine()) != null) {  
                // 预处理:转小写、去除首尾空格、分割单词  
                String[] words = line.trim().toLowerCase()  
                        .split("[\s\p{Punct}]+");  // 按空格和标点分割  

                for (String word : words) {  
                    if (!word.isEmpty()) {  
                        wordCounts.merge(word, 1, Integer::sum);  // 统计次数  
                    }  
                }  
            }  

            // 按单词字母顺序排序后输出  
            Map<String, Integer> sortedCounts = new TreeMap<>(wordCounts);  
            System.out.println("单词统计结果:");  
            sortedCounts.forEach((word, count) ->  
                System.out.printf("%-15s: %d%n", word, count)  
            );  

        } catch (IOException e) {  
            System.err.println("文件读取失败:" + e.getMessage());  
        }  
    }  
}  

案例 2:图片加密 / 解密(字节流应用)​

原理: 使用异或运算(XOR)实现简单加密,相同密钥可加密和解密。

java 复制代码
import java.io.BufferedInputStream;  
import java.io.BufferedOutputStream;  
import java.io.FileInputStream;  
import java.io.FileOutputStream;  
import java.io.IOException;  

public class ImageEncryptTool {  
    private static final byte ENCRYPT_KEY = 0x3B;  // 自定义加密密钥(1-255之间)  

    public static void main(String[] args) {  
        String sourcePath = "source.jpg";  
        String encryptedPath = "encrypted.jpg";  
        String decryptedPath = "decrypted.jpg";  

        try {  
            encryptFile(sourcePath, encryptedPath);  
            System.out.println("加密成功:" + encryptedPath);  
            encryptFile(encryptedPath, decryptedPath);  
            System.out.println("解密成功:" + decryptedPath);  

        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  

    /**  
     * 异或加密/解密文件  
     * @param source 源文件路径  
     * @param target 目标文件路径  
     */  
    private static void encryptFile(String source, String target) throws IOException {  
        try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(source));  
             BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(target))) {  

            byte[] buffer = new byte[8192];  
            int bytesRead;  
            while ((bytesRead = bis.read(buffer)) != -1) {  
                for (int i = 0; i < bytesRead; i++) {  
                    buffer[i] ^= ENCRYPT_KEY;  // 异或运算实现加密/解密  
                }  
                bos.write(buffer, 0, bytesRead);  
            }  
        }  
    }  
}  

五、Java I/O 流最佳实践​

1. 资源管理:强制使用try-with-resources

java 复制代码
// 反例:手动关闭流(易遗漏,导致资源泄漏)  
FileInputStream fis = null;  
try {  
    fis = new FileInputStream("file.txt");  
    // 业务逻辑  
} finally {  
    if (fis != null) {  
        fis.close();  // 可能抛出IOException,需再次处理  
    }  
}  

// 正例:自动关闭流(推荐,Java 7+)  
try (FileInputStream fis = new FileInputStream("file.txt")) {  
    // 业务逻辑(无需手动关闭,JVM自动处理)  
} catch (IOException e) {  
    e.printStackTrace();  
}  

2. 性能优化:永远使用缓冲流​

  • 字节流:优先使用BufferedInputStream/BufferedOutputStream,缓冲区大小建议 8KB(new byte[8192])。
  • 字符流:优先使用BufferedReader/BufferedWriter,避免直接使用FileReader/FileWriter。

3. 编码处理:显式指定字符集​

文本文件读写时,始终通过InputStreamReader/OutputStreamWriter显式指定编码(如UTF-8),避免依赖系统默认编码导致乱码。​

4. 大文件处理:分段读取写入

arduino 复制代码
// 大文件分段读取(例如1GB文件)  
long fileSize = new File("large.file").length();  
byte[] buffer = new byte[1024 * 1024];  // 1MB缓冲区  
while (fis.read(buffer) != -1 && totalRead < fileSize) {  
    fos.write(buffer, 0, (int) Math.min(buffer.length, fileSize - totalRead));  
    totalRead += buffer.length;  
}  

六、Java NIO:现代 I/O 解决方案

NIO提供了更简洁、高效的文件操作 API,基于Path和Files类,支持异步 IO、文件属性访问等高级功能。​

示例:NIO快速复制文件

java 复制代码
import java.io.IOException;  
import java.nio.file.Files;  
import java.nio.file.Path;  
import java.nio.file.Paths;  
import java.nio.file.StandardCopyOption;  

public class NioFileCopy {  
    public static void main(String[] args) {  
        Path source = Paths.get("source.txt");  
        Path target = Paths.get("target.txt");  

        try {  
            // 快速复制文件(支持覆盖已存在文件)  
            Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);  
            System.out.println("NIO复制完成,性能优于传统I/O!");  

            // 读取所有行(适合小文件)  
            Files.lines(source).forEach(System.out::println);  

        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
}  

七、总结:选择合适的 I/O 方案​​

场景​ 推荐 API​ 核心优势​
二进制文件读写​ BufferedInputStream/BufferedOutputStream​ 缓冲机制提升效率,支持任意二进制数据​
文本文件读写(小文件)​ BufferedReader/BufferedWriter​ 按行处理,自动处理编码和换行符​
文本文件读写(大文件)​ NIO.2 Files.lines()/Files.write()​ 流式处理,内存占用低,支持链式操作​
对象序列化​ ObjectInputStream/ObjectOutputStream​ 支持 Java 对象的持久化存储​

​掌握 Java I/O 流的核心原理与使用技巧,是实现数据持久化、文件处理和网络通信的基础。建议开发者在实际项目中:​

  1. 根据数据类型(二进制 / 文本)选择字节流或字符流;
  2. 始终使用缓冲流提升性能,避免直接操作基础流;
  3. 显式处理字符编码,优先使用UTF-8保证跨平台兼容性;
  4. 大文件处理时采用分段读写或 NIO.2 的高效 API。

对于高性能场景(如分布式文件系统、海量日志处理),可进一步学习 Java NIO 的通道(Channel)和缓冲区(Buffer)机制,以及异步 I/O 操作,提升系统吞吐量和响应速度。

相关推荐
zeijiershuai11 分钟前
SpringBoot Controller接收参数方式, @RequestMapping
java·spring boot·后端
zybsjn21 分钟前
后端项目中静态文案国际化语言包构建选型
java·后端·c#
L2ncE30 分钟前
ES101系列07 | 分布式系统和分页
java·后端·elasticsearch
枣伊吕波1 小时前
第十二节:第三部分:集合框架:List系列集合:特点、方法、遍历方式、ArrayList集合的底层原理
java·jvm·list
贺函不是涵1 小时前
【沉浸式求职学习day51】【发送邮件】【javaweb结尾】
java·学习
无限大61 小时前
《计算机“十万个为什么”》之前端与后端
前端·后端·程序员
初次见面我叫泰隆1 小时前
Golang——2、基本数据类型和运算符
开发语言·后端·golang
南风lof1 小时前
ReentrantLock与AbstractQueuedSynchronizer源码解析,一文读懂底层原理
后端
你不是我我2 小时前
【Java开发日记】基于 Spring Cloud 的微服务架构分析
java·开发语言
写bug写bug2 小时前
彻底搞懂 RSocket 协议
java·后端