《深入探索 Java IO 流进阶:缓冲流、转换流、序列化与工具类引言》

目录

一、缓冲流:提升 IO 效率的利器        

1.1 缓冲流概述

1.2 字节缓冲流

构造方法

效率测试对比

1.3 字符缓冲流

构造方法

特有功能方法

1.4 实战练习:文本排序

二、转换流:解决字符编码问题

2.1 字符编码与字符集基础

2.2 编码问题的产生

2.3 InputStreamReader:字节到字符的桥梁

构造方法

示例:指定编码读取

2.4 OutputStreamWriter:字符到字节的桥梁

构造方法

示例:指定编码写出

2.5 实战练习:文件编码转换

三、序列化流:对象的持久化存储

3.1 序列化概述

3.2 ObjectOutputStream:对象序列化

构造方法

序列化条件

示例:序列化对象

3.3 ObjectInputStream:对象反序列化

构造方法

示例:反序列化对象

序列化版本号的重要性

3.4 实战练习:序列化集合


在 Java 编程中,IO(输入 / 输出)操作是与外部设备进行数据交互的核心环节。前文中我们学习了基础的 File 流操作,但在实际开发中,面对高效读写、编码转换、对象持久化等复杂需求,基础流就显得力不从心了。本文将系统讲解 Java IO 流的进阶知识,包括缓冲流、转换流、序列化流、打印流、压缩 / 解压缩流以及实用的 IO 工具包,帮助开发者掌握更强大的 IO 操作技能。

一、缓冲流:提升 IO 效率的利器        

1.1 缓冲流概述

缓冲流(也称为高效流)是对 4 个基本 File 流(FileInputStreamFileOutputStreamFileReaderFileWriter)的增强。它通过内置缓冲区减少系统 IO 次数,从而显著提高读写效率。缓冲流按照数据类型可分为两类:

  • 字节缓冲流BufferedInputStreamBufferedOutputStream
  • 字符缓冲流BufferedReaderBufferedWriter

缓冲流的核心原理是在创建流对象时,内置一个默认大小的缓冲区数组(字节缓冲流默认 8KB,字符缓冲流默认 8192 个字符)。当进行读写操作时,数据先在缓冲区中暂存,当缓冲区满或手动刷新时才与磁盘交互,大大减少了直接操作磁盘的次数。

1.2 字节缓冲流

构造方法

字节缓冲流通过包装基本字节流创建,构造方法如下:

  • public BufferedInputStream(InputStream in):创建缓冲输入流,包装指定的输入流
  • public BufferedOutputStream(OutputStream out):创建缓冲输出流,包装指定的输出流

构造示例代码:

// 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));
效率测试对比

为了直观展示缓冲流的高效性,我们通过复制 375MB 的大文件进行对比测试:

基本流复制(单字节读写)

long start = System.currentTimeMillis();
try (FileInputStream fis = new FileInputStream("jdk9.exe");
     FileOutputStream fos = new FileOutputStream("copy.exe")) {
    int b;
    while ((b = fis.read()) != -1) {
        fos.write(b);
    }
} catch (IOException e) {
    e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("普通流复制时间:" + (end - start) + " 毫秒"); // 耗时极长(通常十几分钟)

缓冲流复制(单字节读写)

long start = System.currentTimeMillis();
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("jdk9.exe"));
     BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.exe"))) {
    int b;
    while ((b = bis.read()) != -1) {
        bos.write(b);
    }
} catch (IOException e) {
    e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("缓冲流复制时间:" + (end - start) + " 毫秒"); // 约8016毫秒

缓冲流 + 数组复制(最优方案)

long start = System.currentTimeMillis();
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("jdk9.exe"));
     BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.exe"))) {
    int len;
    byte[] bytes = new byte[8 * 1024]; // 8KB缓冲区
    while ((len = bis.read(bytes)) != -1) {
        bos.write(bytes, 0, len);
    }
} catch (IOException e) {
    e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("缓冲流使用数组复制时间:" + (end - start) + " 毫秒"); // 约666毫秒

效率分析:单字节读写时,缓冲流因减少了 IO 次数而远快于基本流;而缓冲流结合数组读写时,通过批量处理数据进一步提升效率,是大文件复制的推荐方案。

1.3 字符缓冲流

构造方法

字符缓冲流通过包装基本字符流创建,构造方法如下:

  • public BufferedReader(Reader in):创建缓冲字符输入流,包装指定的字符输入流
  • public BufferedWriter(Writer out):创建缓冲字符输出流,包装指定的字符输出流

构造示例代码:

// 创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
// 创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
特有功能方法

字符缓冲流在继承基本字符流方法的基础上,提供了更便捷的特有方法:

  1. BufferedReaderreadLine()方法

    • 功能:读取一行文字(不包含换行符)
    • 返回值:读取到的行内容,若到达流末尾则返回null

    示例代码:

    BufferedReader br = new BufferedReader(new FileReader("in.txt"));
    String line = null;
    while ((line = br.readLine()) != null) { // 循环读取每行内容
        System.out.println(line); // 处理每行数据
    }
    br.close();
    
  2. BufferedWriternewLine()方法

    • 功能:写入一个平台无关的换行符(Windows 为\r\n,Linux 为\n
    • 优势:无需手动拼接换行符,保证跨平台兼容性

    示例代码:

    BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"));
    bw.write("Hello");
    bw.newLine(); // 写入换行
    bw.write("World");
    bw.close(); // 关闭时自动刷新缓冲区
    

1.4 实战练习:文本排序

需求:将乱序的文本按行首数字恢复顺序(如 "3.xxx"、"8.xxx" 需排序为 "1.xxx"、"2.xxx"...)。

实现思路

  1. 使用BufferedReader逐行读取文本
  2. 将读取的内容存储到ArrayList集合
  3. 使用Collections.sort()结合自定义比较器排序
  4. 通过BufferedWriter按顺序写出排序后的内容

代码实现

import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class TextSortDemo {
    public static void main(String[] args) throws IOException {
        // 1.创建集合存储文本行
        ArrayList<String> list = new ArrayList<>();
        
        // 2.创建缓冲流对象
        BufferedReader br = new BufferedReader(new FileReader("in.txt"));
        BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"));
        
        // 3.读取文本行并存储
        String line;
        while ((line = br.readLine()) != null) {
            list.add(line);
        }
        
        // 4.自定义排序规则(按行首数字升序)
        Collections.sort(list, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                // 提取行首数字(如"3.xxx"提取"3")
                int num1 = Integer.parseInt(o1.split("\\.")[0]);
                int num2 = Integer.parseInt(o2.split("\\.")[0]);
                return num1 - num2; // 升序排序
            }
        });
        
        // 5.写出排序后的内容
        for (String s : list) {
            bw.write(s);
            bw.newLine(); // 换行
        }
        
        // 6.释放资源
        bw.close();
        br.close();
    }
}

二、转换流:解决字符编码问题

2.1 字符编码与字符集基础

计算机中所有数据都以二进制存储,字符编码是字符与二进制的对应规则,而字符集(Charset)是系统支持的所有字符的集合。常见字符集及编码:

字符集编码方式特点
ASCII单字节编码仅支持英文字符和控制字符,共 128 个字符
ISO-8859-1单字节编码支持欧洲语言,兼容 ASCII,共 256 个字符
GB2312双字节编码支持简体中文,收录约 7000 汉字
GBK双字节编码扩展 GB2312,支持繁体、日韩汉字,共 21003 个汉字
UnicodeUTF-8/UTF-16支持全球所有字符,UTF-8 为变长编码(1-4 字节),互联网首选编码

编码与解码

  • 编码:字符 → 字节(如 "中"→0xE4B8AD(UTF-8)或0xD6D0(GBK))
  • 解码:字节 → 字符(需使用与编码相同的规则,否则会乱码)

2.2 编码问题的产生

当使用FileReader读取 Windows 系统默认 GBK 编码的文件时,若 IDEA 默认编码为 UTF-8,会因解码规则不匹配导致乱码:

FileReader fr = new FileReader("gbk.txt"); // gbk.txt为GBK编码的"大家好"
int c;
while ((c = fr.read()) != null) {
    System.out.print((char) c); // 输出乱码:���
}

2.3 InputStreamReader:字节到字符的桥梁

InputStreamReader是字节流到字符流的转换流,可指定字符集解码字节数据。

构造方法
  • InputStreamReader(InputStream in):使用默认字符集(通常为 UTF-8)
  • InputStreamReader(InputStream in, String charsetName):使用指定字符集(如 "GBK")
示例:指定编码读取
public class EncodingDemo {
    public static void main(String[] args) throws IOException {
        String fileName = "gbk.txt"; // GBK编码的文件
        
        // 使用默认编码(UTF-8)读取GBK文件,乱码
        InputStreamReader isrUtf8 = new InputStreamReader(new FileInputStream(fileName));
        // 使用GBK编码读取,正常解码
        InputStreamReader isrGbk = new InputStreamReader(new FileInputStream(fileName), "GBK");
        
        // 测试默认编码读取
        int c;
        System.out.println("默认编码读取:");
        while ((c = isrUtf8.read()) != -1) {
            System.out.print((char) c); // 乱码
        }
        
        // 测试指定编码读取
        System.out.println("\nGBK编码读取:");
        while ((c = isrGbk.read()) != -1) {
            System.out.print((char) c); // 正常输出:大家好
        }
        
        isrUtf8.close();
        isrGbk.close();
    }
}

2.4 OutputStreamWriter:字符到字节的桥梁

OutputStreamWriter是字符流到字节流的转换流,可指定字符集编码字符数据。

构造方法
  • OutputStreamWriter(OutputStream out):使用默认字符集
  • OutputStreamWriter(OutputStream out, String charsetName):使用指定字符集
示例:指定编码写出
public class EncodingWriteDemo {
    public static void main(String[] args) throws IOException {
        // 使用UTF-8编码写出(默认)
        OutputStreamWriter oswUtf8 = new OutputStreamWriter(new FileOutputStream("utf8.txt"));
        oswUtf8.write("你好"); // "你好"在UTF-8中占6字节
        oswUtf8.close();
        
        // 使用GBK编码写出
        OutputStreamWriter oswGbk = new OutputStreamWriter(new FileOutputStream("gbk.txt"), "GBK");
        oswGbk.write("你好"); // "你好"在GBK中占4字节
        oswGbk.close();
    }
}

2.5 实战练习:文件编码转换

需求:将 GBK 编码的文本文件转换为 UTF-8 编码。

实现思路

  1. 使用InputStreamReader指定 GBK 编码读取源文件
  2. 使用OutputStreamWriter默认 UTF-8 编码写出到目标文件

代码实现

import java.io.*;

public class CodeConvertDemo {
    public static void main(String[] args) throws IOException {
        String srcFile = "source_gbk.txt"; // 源GBK文件
        String destFile = "target_utf8.txt"; // 目标UTF-8文件
        
        // 创建转换流
        InputStreamReader isr = new InputStreamReader(new FileInputStream(srcFile), "GBK");
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(destFile));
        
        // 读写转换
        char[] cbuf = new char[1024];
        int len;
        while ((len = isr.read(cbuf)) != -1) {
            osw.write(cbuf, 0, len);
        }
        
        // 释放资源
        osw.close();
        isr.close();
    }
}

三、序列化流:对象的持久化存储

3.1 序列化概述

序列化是将对象转换为字节序列的过程,用于对象的持久化存储或网络传输;反序列化则是将字节序列恢复为对象的过程。Java 通过ObjectOutputStreamObjectInputStream实现序列化功能。

3.2 ObjectOutputStream:对象序列化

ObjectOutputStream用于将对象写入输出流,实现对象的持久化。

构造方法
  • public ObjectOutputStream(OutputStream out):创建序列化流,包装输出流
序列化条件
  1. 类必须实现java.io.Serializable接口(标记接口,无抽象方法)
  2. 类的所有属性必须可序列化(基本类型默认可序列化,引用类型需同样实现Serializable
  3. 无需序列化的属性可使用transient关键字修饰
示例:序列化对象
import java.io.*;

// 可序列化的Employee类
class Employee implements Serializable {
    // 序列版本号(保证序列化与反序列化版本一致)
    private static final long serialVersionUID = 1L;
    public String name;
    public String address;
    public transient int age; // transient修饰的属性不参与序列化
    
    public Employee(String name, String address, int age) {
        this.name = name;
        this.address = address;
        this.age = age;
    }
}

public class SerializeDemo {
    public static void main(String[] args) throws IOException {
        Employee emp = new Employee("张三", "北京", 30);
        
        // 创建序列化流
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("emp.ser"));
        oos.writeObject(emp); // 序列化对象
        oos.close();
        
        System.out.println("对象已序列化");
    }
}

3.3 ObjectInputStream:对象反序列化

ObjectInputStream用于从输入流读取字节序列,恢复为对象。

构造方法
  • public ObjectInputStream(InputStream in):创建反序列化流,包装输入流
示例:反序列化对象
import java.io.*;

public class DeserializeDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Employee emp = null;
        
        // 创建反序列化流
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("emp.ser"));
        emp = (Employee) ois.readObject(); // 反序列化对象
        ois.close();
        
        // 输出反序列化结果
        System.out.println("姓名:" + emp.name); // 张三
        System.out.println("地址:" + emp.address); // 北京
        System.out.println("年龄:" + emp.age); // 0(transient属性未序列化)
    }
}
序列化版本号的重要性

serialVersionUID用于验证序列化对象与类的版本一致性:

  • 若类未显式定义,JVM 会根据类结构自动生成
  • 类结构修改后,自动生成的版本号会变化,导致反序列化失败
  • 显式定义后,即使类结构修改(如新增属性),仍可反序列化(新增属性取默认值)

3.4 实战练习:序列化集合

需求:序列化存储多个自定义对象的集合,再反序列化并遍历。

实现思路

  1. 创建多个自定义对象(需实现Serializable
  2. 将对象存入ArrayList集合
  3. 序列化集合到文件
  4. 反序列化集合并遍历输出

代码实现

import java.io.*;
import java.util.ArrayList;

// 学生类(可序列化)
class Student implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private String pwd;
    
    public Student(String name, String pwd) {
        this.name = name;
        this.pwd = pwd;
    }
    
    public String getName() { return name; }
    public String getPwd() { return pwd; }
}

public class Serial

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值