当前位置: 首页 > news >正文

《深入探索 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>() {@Overridepublic 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

http://www.dtcms.com/a/342186.html

相关文章:

  • 事件驱动流程链——EPC
  • Metrics1:Intersection over union交并比
  • tail -f与less的区别
  • Python Excel 通用筛选函数
  • 【C++】模板(进阶)
  • Rancher 管理的 K8S 集群中部署常见应用(MySQL、Redis、RabbitMQ)并支持扩缩容的操作
  • ubuntu编译ijkplayer版本k0.8.8(ffmpeg4.0)
  • Spring Boot整合Amazon SNS实战:邮件订阅通知系统开发
  • 将windows 的路径挂载到Ubuntu上进行直接访问
  • C++---辗转相除法
  • VB.NET发送邮件给OUTLOOK.COM的用户,用OUTLOOK.COM邮箱账号登录给别人发邮件
  • Azure的迁移专业服务是怎么提供的
  • 带有 Angular V14 的 Highcharts
  • Transformer在文本、图像和点云数据中的应用——经典工作梳理
  • 【解决方案系列】大规模三维城市场景Web端展示方案
  • C++STL-stack和queue的使用及底层实现
  • 阿里云搭建flask服务器
  • 2021年ASOC SCI2区TOP,改进遗传算法+自主无人机目标覆盖路径规划,深度解析+性能实测
  • Java 16 新特性及具体应用
  • Redis 奇葩问题
  • Python break/continue
  • 嵌入式C语言和数据结构面试题
  • 2025-08-21 Python进阶3——模块
  • 信创自主可控新标杆:RustFS如何用内存安全架构重构数据主权防线?
  • Binlog Server守护MySQL数据0丢失
  • RabbitMQ:技巧汇总
  • Windows下RabbitMQ完整安装指南
  • 云原生俱乐部-k8s知识点归纳(6)
  • 活到老学到老之vue-vben-admin项目添加简单页面
  • 从YOLOv5到RKNN:零冲突转换YOLOv5模型至RK3588 NPU全指南