Java-Day-26( 节点流和处理流 ( 序列化 + 对象流 + 标准输入输出流 + 转换流 + 打印流 ) )
Java-Day-26
节点流和处理流
序列化和反序列化
- 序列化就是在保存数据时,保存数据的值和数据类型
- 反序列化就是在恢复数据时,恢复数据的值和数据类型
- 需要让某个对象支持序列化机制,则必须让其类是可序列化的
- 其类必须实现如下两个接口之一:
- Serializable ( 标记接口:声明性质,没有任何方法 )
- Externalizable ( 该接口有方法需要去实现,因此一般实现上面的接口 )
- 其类必须实现如下两个接口之一:
对象流 ObjectInputStream 和 ObjectOutputStream
- 专门处理对象的处理流
需求引出
-
将 int num = 100 这个 int 数据保存到文件中,注意不是 100 数字,而是 int 100 ( 连带数据类型 ),而且能够从文件中直接恢复 int 100 ( 而不是String 类型 )
- 以前是只保存值,在过程中不保证数据类型不改变
-
将 Dog dog = new Dog( " 小黄 ", 3 ) 这个 dog 对象保存到文件中,并能够从文件恢复 ( 反序列化为 Dog 对象 )
- 保存值和数据类型,这样才能把小黄和 3 联系起来
-
上面的要求就是,能够将基本数据类型或者对象进行序列化和反序列化操作
基本介绍
- 提供了对基本类型或对象类型的序列化和反序列化的方法
- ObjectOutputStream 提供序列化功能
- 可见仍是构造器引入 OutputStream 实现类
- ObjectInputStream 提供反序列化功能
练习
- 使用 ObjectOutputStream 序列化基本数据类型和一个 Dog 对象 ( name, age ),并保存到 data.dat 文件中
- 序列化后,保存的文件格式不是文本,而是按照他的格式来保存 ( 文本打开会乱码 )
- 注意之前讲过的 Integer 等一系列包装类、String 就已实现了 Serializable 接口
public class Test {
public static void main(String[] args) throws Exception {
String filePath = "e:\\data.dat";
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath));
// 序列化数据到 filePath
oos.writeInt(100); // int —> Integer (实现了 Serializable)
oos.writeBoolean(true); // boolean —> Boolean (实现了 Serializable)
oos.writeChar('a'); // char —> Character...
oos.writeDouble(9.5); // double —> Double...
oos.writeUTF("zyz哇");
// 存放字符串的方法:writerUTF,String本就实现了 Serializable
oos.writeObject(new Dog("土豆", 10));
oos.close();
System.out.println("数据保存完毕 —> 序列化形式");
}
}
// 如果需要序列化某个类的对象,必须实现接口
class Dog implements Serializable {
private String name;
private int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
-
使用 ObjectInputStream 读取 data.dat 并反序列化恢复数据、
- 如果我们希望调用 Dog 的方法,需要向下转型 ( 存是 writeObject(),获取是 readObject() )
- 需要我们将 Dog 类的定义拷贝到都可以引用的位置,做成公共的包,即做到类要统一
- 若是读取的时候更改了 Dog,就要再写入 Output ( 会顺带 Dog 的包路径 ) 一次,才能再读取 Input
public class Test { public static void main(String[] args) throws Exception { String filePath = "e:\\data.dat"; ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath)); // 读取: // 读取的(反序列化)的顺序必须要和你保存数据(序列化)的顺序一致 // 否则异常 System.out.println(ois.readInt()); System.out.println(ois.readBoolean()); System.out.println(ois.readChar()); System.out.println(ois.readDouble()); System.out.println(ois.readUTF()); System.out.println(ois.readObject()); // 底层Object —> Dog // 关闭外层流 ois.close(); System.out.println("数据取出完毕 —> 反序列化形式"); } } // 此处的Dog为公共类,Input和Output的都是统一路径!才能不报错调用类中的方法! class Dog implements Serializable { private String name; private int age; public Dog(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Dog{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
- Dog为公共类,Input和Output的都是统一路径!才能不报错调用类中的方法!
注意细节
-
读写顺序要一致 ( 顺序但凡不同便报错 )
-
要求实现序列化或反序列化对象,需要实现 Serializable
-
序列化的类中建议添加 SerialVersionUID,为了提高版本的兼容性
-
如:
class Dog implements Serializable { private String name; private int age; // 序列化的版本号,可以提高兼容性 private static final long serialVersionUID = 1L; // 这样的话,再添加一个private的属性时,就不会认为是一个新的类,而是一个新的版本(升级版) // ..... }
-
-
序列化对象时,默认将里面所有属性都进行序列化,但除了 static 或 transient ( 短暂的 ) 修饰的成员以外
- 即,static 和 transient 修饰的属性值不会序列化成功 ( 反序列的时候没有值,是默认的 null )
-
序列化对象时,要求里面属性的类型也要实现序列化接口
- 例如 Dog 类中的 name 是 String 类型的 ( String 本身就实现了 Serializable 接口 ) 就可以序列化,若是添加一个新的类作为 Dog 属性,这个类要是不实现 Serializable 接口,那就会在序列化时报错
-
序列化具备可继承性,也就是如果某类已经实现了序列化,则它的所有子类也已经默认实现了序列化
- 如:Integer 间接实现了序列化,继承的类才是直接实现序列化的
标准输入输出流
介绍
编译类型 | 默认设备 | |
---|---|---|
System.in 标准输入 | InputStream | 键盘 |
System.out 标准输出 | PrintStream | 显示器 |
-
in:System里
public final static InputStream in = null;
-
编译类型:InputStream
-
运行类型 getClass :BufferedInputStream ( 字节处理流 )
-
常配 Scanner(),scanner.next() 到键盘得到输入
-
-
out:System里
public final static PrintStream out = null;
- 翻译类型:PrintStream
- 运行类型:PrintStream
转换流 InputStreamReader 和 OutputStreamWriter
引出
- 实则将字节流转换成字符流
String filePath = "e:\\note.txt";
BufferedReader br = new BufferedReader(new FileReader(filePath));
// 默认情况下读取文件是按照utf-8编码来,若是另存为时更换编码,换成别的后输出就是乱码
String s = br.readLine();
System.out.println("读取到的文件" + s); // 编码方式同,不会出现乱码
br.close();
- 所以去指定文件的编码方式,所以用到转换流,好去指定字节流的编码方式
介绍
-
InputStreamReader:Reader 的子类,可以将 InputStream ( 字节流 ) 包装 ( 转换 ) 成 Reader ( 字符流 )
- 包装 ( 转换 ):即用的仍是字节流,只不过处理时按照字符方式来处理
- 重点所用构造器:可以传入一个 InputStream 对象,然后用 Charset 用来指定处理的编码方式
-
OutputStreamWriter:Writer 的子类,实现将 OutputStream ( 字节流 ) 包装成 Writer ( 字符流 )
- 当处理纯文本数据时,如果使用字符流效率更高,并且可以有效解决中文问题,所以建议将字节流转换成字符流
- 可以在使用时指定编码格式 ( 如:utf-8, gbk, ISO8859-1 等 )
练习
-
编程将字节流 FileInputStream 包装 ( 转换 ) 成字符流 InputStreamReader,对文件进行读取 ( 按照 utf-8 / gbk 格式 ),进而再包装成 BufferedReader
- 有点 gbk 还是乱码,那文件就是 utf-8 编码
public class Test { public static void main(String[] args) throws IOException { String filePath = "e:\\note.txt"; // FileInputStream 转成了 InputStreamReader //// 指定编码 gbk / utf-8 (utf8) // InputStreamReader isr = new InputStreamReader(new FileInputStream(filePath), "utf-8"); //// BufferedReader包装流(处理) // BufferedReader br = new BufferedReader(isr); // 一般常用的就把上两部一起写 BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath), "utf-8")); String s = br.readLine(); System.out.println("读取所得 " + s); // 关闭外层流 br.close(); } }
-
编程将字节流 FileOutputStream 包装 ( 转换 ) 成字符流OutputStreamWriter,对文件进行写入 ( 按照 utf-8 / gbk 格式 )
public class Test { public static void main(String[] args) throws IOException { String filePath = "e:\\note.txt"; String charSer = "utf8"; OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(filePath, true), charSer); // 勿忘不想覆盖就加 true osw.write("wa老朱呀"); // 关闭外层流 osw.close(); System.out.println("按照 " + charSer + " 编码方式保存文件成功"); } }
打印流 PrintStream 和 PrintWriter
- 打印流只有输出流,没有输入流
PrintStream 字节打印流
- 构造器可直接传文件路径 ( String ) 或文件 ( File )、OutputStream
-
练习
public class Test { public static void main(String[] args) throws IOException { // System.out 实际就是 public final static PrintStream out = null; PrintStream out = System.out; // 默认情况下,PrintStream 输出数据的位置是标准输出,即显示器(控制台里) /* public void print(String s) { if (s == null) { s = "null"; } write(s); // 真正打印输出所在,所有也可以直接用 write } */ // out.print("wawa朱"); out.write("wawa朱也".getBytes()); // 把字符串转成字节数组 out.close(); // 构造器可见,可以去修改打印流输出的位置/设备 // 修改到 e:\f1.txt 文件中了 System.setOut(new PrintStream("e:\\f1.txt")); // 显示器上就不可见内容了 System.out.println("哈罗呀,zyz~"); } }
PrintWriter 字符打印流
- 构造器可传 Writer,OutputStream,String,File ( Chatset )
-
练习
public class Test { public static void main(String[] args) throws IOException { // out是PrintStream,其继承FilterOutputStream,其继承FilterOutputStream再继承着OutputStream // 此处传的是一个标准输出 // PrintWriter printWriter = new PrintWriter(System.out); // 想让输出位置在文件里 // FileWriter:super(new FileOutputStream(fileName)); // super:OutputStreamWriter (Writer) PrintWriter printWriter = new PrintWriter(new FileWriter("e:\\f2.txt")); printWriter.print("hi,你吼哇~"); printWriter.close(); // flush + 关闭流。(不写就不会刷新写入) } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义