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

Java基础快速入门: 转换流与对象操作流

本文纲要

  1. 转换流概念
    底层读取机制回顾
    转换流的桥梁作用
    体系结构与API解读

  2. 转换流指定编码读写
    乱码问题成因
    使用InputStreamReader指定码表读取
    使用OutputStreamWriter指定码表写出
    JDK11后字符流直接指定编码

  3. 对象操作流基本特点
    传统写入对象属性的弊端
    对象流整体写入思想

  4. 对象序列化——ObjectOutputStream
    序列化定义
    Serializable接口与标记性接口
    序列化代码示例

  5. 对象反序列化——ObjectInputStream
    反序列化读取对象
    强转与异常处理

  6. 对象操作流的两个注意点
    serialVersionUID序列号不一致问题
    手动指定序列号 & 解决异常
    transient瞬态关键字

  7. 对象操作流练习
    多个对象的序列化与反序列化
    EOFException的处理
    利用集合整体序列化

转换流概念

复习字符流底层读取

字符流底层其实也是字节流,按字节逐个读取数据。

  • 纯英文或数字(如ABC,对应码表值97,98,99):字节流读取97 → 98 → 99。
  • 包含中文(UTF‑8编码,一个中文占3字节,例如-23, -69, -111表示一个汉字):
    • 同样逐字节读取,第一个中文字节的第一个字节是负数;
    • 检测到负数,就知道遇到了中文,会按当前编码一次读取多个字节(GBK读2个,UTF‑8读3个),再将这多个字节转换为字符。

真正在工作的一直是字节流,但上层我们看到的是字符流。转换流就是负责在字节流和字符流之间做转换。

文件 (字节形式)

字节输入流

转换流
InputStreamReader

字符流

内存 (字符形式)

内存 (字符形式)

字符流

转换流
OutputStreamWriter

字节输出流

文件 (字节形式)

  • :字节流 → 转换流 → 字符流(字节 → 字符)
  • :字符流 → 转换流 → 字节流(字符 → 字节)

分类

类型输入流输出流
转换流InputStreamReaderOutputStreamWriter
别称字符输入流(实质是字节→字符)字符输出流(实质是字符→字节)

命名非常直观:InputStream(字节输入) +Reader(字符) →InputStreamReader
OutputStream(字节输出) +Writer(字符) →OutputStreamWriter

API文档中的描述:

  • InputStreamReader:从字节流到字符流的桥梁,读取字节并使用指定编码将其解码为字符。
  • OutputStreamWriter:从字符流到字节流的桥梁,使用指定编码将写入的字符编码为字节。

底层源码验证

在 Java 中,FileReader继承自InputStreamReader,其构造方法内部实际上创建了字节流并传递给父类转换流:

// FileReader 的构造publicFileReader(StringfileName)throwsFileNotFoundException{super(newFileInputStream(fileName));}

可见,字符文件读取依赖的底层就是转换流 + 字节流。

转换流指定编码读写

乱码之源

文件编码与IDE(或程序)编码不一致时会产生乱码。
例如,Windows 记事本默认编码为GBK,而 IDEA 默认使用UTF‑8

直接使用FileReader读取GBK文件:

// 方法1:直接读取会产生乱码// 因为文件是GBK码表,而idea默认的是UTF-8编码格式privatestaticvoidmethod1()throwsIOException{FileReaderfr=newFileReader("C:\\Users\\apple\\Desktop\\a.txt");intch;while((ch=fr.read())!=-1){System.out.println((char)ch);}fr.close();}

解决思路: 文件是什么编码,就用什么编码去读。

JDK11 之前:使用转换流指定编码
使用InputStreamReader指定GBK读取

// 如何解决乱码?// 文件是什么码表,那么咱们就必须使用什么码表去读取privatestaticvoidmethod2()throwsIOException{// 指定使用GBK码表去读取文件InputStreamReaderisr=newInputStreamReader(newFileInputStream("C:\\Users\\apple\\Desktop\\a.txt"),"GBK");intch;while((ch=isr.read())!=-1){System.out.println((char)ch);}isr.close();}

使用OutputStreamWriter指定UTF‑8写出

OutputStreamWriterosw=newOutputStreamWriter(newFileOutputStream("C:\\Users\\apple\\Desktop\\b.txt"),"UTF-8");osw.write("我爱学习,谁也别打扰我");osw.close();

注意:用 IDEA 以 UTF‑8 写出的文件,Windows 记事本打开时也能正确显示,因为它会自动识别编码;若另存为 ANSI(GBK),字节数会变化。

JDK11 之后:字符流直接指定编码

// 在JDK11之后,字符流新推出了一个构造,也可以指定编码表FileReaderfr=newFileReader("C:\\Users\\apple\\Desktop\\a.txt",Charset.forName("gbk"));intch;while((ch=fr.read())!=-1){System.out.println((char)ch);}fr.close();

FileReader新增的两参数构造,直接接受Charset对象,无需再使用转换流。

对象操作流基本特点

场景:将用户对象(用户名、密码)保存到本地文件。

传统方式:用缓冲字符流写入对象的属性值。

Useruser=newUser("zhangsan","qwer");BufferedWriterbw=newBufferedWriter(newFileWriter("a.txt"));bw.write(user.getUsername());bw.newLine();bw.write(user.getPassword());bw.close();

缺陷:任何人打开 a.txt 都能直接看到用户名和密码,数据不安全

对象操作流思想:

  • 不以属性值为单位写入,而是将整个对象以字节形式写入到文件
  • 再次打开文件看到的是乱码,只有用对象输入流再读回内存,才能还原对象。

对象序列化——ObjectOutputStream

将对象以字节形式写到本地文件(或网络传输),称为序列化
对应流:ObjectOutputStream(对象序列化流)。

序列化步骤

  1. 创建ObjectOutputStream,包装一个字节输出流(如FileOutputStream)。
  2. 调用writeObject(Object obj)写出对象。
  3. 关闭流。
Useruser=newUser("zhangsan","qwer");ObjectOutputStreamoos=newObjectOutputStream(newFileOutputStream("a.txt"));oos.writeObject(user);oos.close();

Serializable接口

直接运行上述代码会抛出NotSerializableException

抛出一个实例需要一个Serializable接口。

要求:要被序列化的类必须实现java.io.Serializable接口。

// 如果想要这个类的对象能被序列化,那么这个类必须要实现一个接口 Serializable// Serializable 接口的意义:// 称之为是一个标记性接口,里面没有任何的抽象方法// 只要一个类实现了这个Serializable接口,那么就表示这个类的对象可以被序列化publicclassUserimplementsSerializable{privateStringusername;privateStringpassword;// 构造 / getter / setter / toString...}

再次运行序列化代码,成功将对象写入 a.txt。

对象反序列化——ObjectInputStream

将文件中保存的对象读回到内存,称为反序列化。
对应流:ObjectInputStream(对象反序列化流)。

ObjectInputStreamois=newObjectInputStream(newFileInputStream("a.txt"));Usero=(User)ois.readObject();// readObject()返回Object,需要强转System.out.println(o);ois.close();

readObject()返回Object类型,需强转为原来的具体类,并处理ClassNotFoundException

对象操作流的两个注意点

1 ) 序列号 serialVersionUID

现象:对类进行修改(如将private改为public)后,再反序列化之前序列化的文件,会抛出InvalidClassException

异常关键信息:

local class incompatible: stream classdesc serialVersionUID = -5824992206458892149, local class serialVersionUID = 4900133124572371851

原因:

  1. 第一次序列化时,JVM 根据类信息(成员变量、方法等)自动计算一个序列号,并写入文件。
  2. 修改类之后,JVM 重新计算序列号,类中序列号与文件中的不一致,导致报错。

解决:手动固定serialVersionUID,不让 JVM 自动计算。

publicclassUserimplementsSerializable{// serialVersionUID 序列号// 如果我们自己没有定义,那么虚拟机会根据类中的信息自动的计算出一个序列号。// 问题:如果我们修改了类中的信息,那么虚拟机会再次计算出一个序列号。// 第一步:把User对象序列化到本地. --- -5824992206458892149// 第二步:修改了javabean类. 导致 --- 类中的序列号 4900133124572371851// 第三步:把文件中的对象读到内存. 本地中的序列号和类中的序列号不一致了.// 解决?// 不让虚拟机帮我们自动计算,我们自己手动给出.而且这个值不要变.privatestaticfinallongserialVersionUID=1L;// ...}

定义格式:private static final long serialVersionUID = <任意值>;

小技巧:很多 Java 自带类(如ArrayList)也实现了Serializable并手动指定了serialVersionUID,可以直接参考其写法。

2 )transient瞬态关键字

某些成员变量的值不希望被序列化(如密码),可以在属性前加transient关键字。

publicclassUserimplementsSerializable{privatestaticfinallongserialVersionUID=1L;privateStringusername;privatetransientStringpassword;// 不参与序列化// ...}

测试:

// 序列化时写入Useruser=newUser("zhangsan","qwer");oos.writeObject(user);// 反序列化读回Usero=(User)ois.readObject();System.out.println(o);// User{username='zhangsan', password='null'}

password未被序列化,因此读取时为null(默认值)。

对象操作流练习

需求:创建多个学生对象,序列化到文件,再反序列化到内存。
项目代码结构

otheriomodule/src/com/wb/convertedio/ ├── Student.java ├── User.java ├── ConvertedDemo1.java ├── ConvertedDemo2.java ├── ConvertedDemo3.java ├── ConvertedDemo4.java ├── ConvertedDemo5.java ├── ConvertedDemo6.java └── ConvertedDemo7.java

学生类定义

publicclassStudentimplementsSerializable{privatestaticfinallongserialVersionUID=2L;privateStringname;privateintage;publicStudent(){}publicStudent(Stringname,intage){this.name=name;this.age=age;}// getter / setter / toString ...}

写入多个对象

Students1=newStudent("杜子腾",16);Students2=newStudent("张三",23);Students3=newStudent("李四",24);ObjectOutputStreamoos=newObjectOutputStream(newFileOutputStream("a.txt"));oos.writeObject(s1);oos.writeObject(s2);oos.writeObject(s3);oos.close();

读取并处理 EOFException

错误示范(不能用null-1判断结尾):

// 对象输入流读到结束不会返回null或-1,会抛出EOFException/* while((obj = ois.readObject()) != null){ System.out.println(obj); } */

正确方式1:捕获EOFException

ObjectInputStreamois=newObjectInputStream(newFileInputStream("a.txt"));while(true){try{Objecto=ois.readObject();System.out.println(o);}catch(EOFExceptione){break;// 到达文件末尾}}ois.close();

方式2:利用集合整体序列化

一次写入一个集合对象,读取时也只需读一次,无需处理EOFException

Students1=newStudent("杜子腾",16);Students2=newStudent("张三",23);Students3=newStudent("李四",24);// 写入集合ObjectOutputStreamoos=newObjectOutputStream(newFileOutputStream("a.txt"));ArrayList<Student>list=newArrayList<>();list.add(s1);list.add(s2);list.add(s3);// 我们往本地文件中写的就是一个集合oos.writeObject(list);oos.close();// 读取集合ObjectInputStreamois=newObjectInputStream(newFileInputStream("a.txt"));ArrayList<Student>list2=(ArrayList<Student>)ois.readObject();for(Studentstudent:list2){System.out.println(student);}ois.close();

这种方式代码更简洁,推荐使用

总结

知识点关键类/接口要点
转换流InputStreamReader,OutputStreamWriter字节与字符流的桥梁;可指定编码读写
JDK11后的简化FileReader,FileWriter构造方法可直接传入Charset,无需显式使用转换流
对象序列化ObjectOutputStream实现Serializable接口,writeObject写出整体对象
对象反序列化ObjectInputStreamreadObject 读取并强转,注意ClassNotFoundException
序列号serialVersionUIDprivate static final long防止类修改后反序列化失败,需手动指定
transient关键字transient修饰的字段不参与序列化,用于敏感信息如密码
多对象的处理集合 + 序列化将多个对象放入集合,一次性序列化集合,避免处理EOFException

转换流打通了字节流与字符流的隔阂,对象操作流则为持久化对象提供了直接且安全的方案。掌握这些知识,Java I/O 的运用将更加灵活高效。

http://www.jsqmd.com/news/1099049/

相关文章:

  • ABAQUS磨损仿真全流程复现
  • python-122-节点可视化之基于AntVx6绘制节点
  • 算法入门(一):滑动窗口 之 可变窗口-求最短 / 最长-数值计算 (Leetcode 209 / 713 / 2875 / 1004 / 2024)
  • 如何5分钟搞定B站抢票:告别手速焦虑的自动化神器指南
  • 2026年全球范围内最佳高端品牌网站建设公司服务商排行榜,测评零代码、低代码、定制工具
  • 学长走心分享|在线动漫信息平台全套源码+论文,二次元特色毕设课设亮眼选题!
  • 5分钟掌握SVG-Edit:浏览器中创建专业矢量图形的终极解决方案
  • STM32 SPI控制器
  • 【MySQL】列的增删查改
  • 字幕编辑革命:如何用Subtitle Edit实现专业级字幕制作
  • Kafka-UI安全加固:如何解决生产环境权限失控问题
  • [QT]重载qdbug
  • 面向AI ASIC上全同态加密NTT加速的低成本多精度脉动阵列
  • 【RL】GRPO
  • VMware虚拟机安装Ubuntu完整指南:从零搭建安全可控的开发环境
  • MySQL数据分析实战:从零构建SQL查询到业务问题解决
  • 如何零基础掌握文本分析:KH Coder的完整新手指南
  • Mate Engine虚拟角色引擎:模块化VRM桌面伴侣的技术实现方案
  • 2026年循环提升机厂家综合实力排名:技术、服务与口碑的全方位较量
  • 性能数据从 CSV 到 Excel:移动端测试报表自动化处理思路
  • 【QT】模板如何使用
  • 2026年7月零代码网站搭建与企业无代码建站工具测评:谁更适合你,
  • MySQL实战指南:从SQL语法到索引优化与生产环境调优
  • 计算机毕业设计之基于SSM的校园共享单车管理系统设计与实现
  • 速来薅羊毛!8元免费得
  • Claude Code(15):CodeGraph - 给 AI 装上代码地图,少读文件、少烧 Token
  • VR-Reversal:3分钟将VR视频变成普通播放器可看的2D影片
  • UE 移动端 CPU、GPU、内存问题怎么归因:一套性能分析方法
  • RAG 真正让人头疼的地方,从来不是“搭不起来”
  • 抖音无水印下载技术解析:从录屏到原生文件获取的革命