Head First Java 读书笔记 14章

第14章:序列化与文件的输入输出

对象可以被序列化,也可以展开。对象有状态和行为两种属性,行为存在于类中,而状态存在于个别的对象中。本章将讨论以下两种选项:

1.如果只有自己写的Java程序会用到这些数据。用序列化(Serialization),将被序列化的对象写到文件中。然后就可以让你的程序去文件中读取序列化的对象,并把它们展开回到活生生的状态。

2.如果数据需要被其它程序引用。写一个纯文本文件,用其它程序可以解析的特殊字符写到文件中。

如何把序列化对象写入文件?

public class TestOutputStream {
public static void main(String[] args) {
try {
// 如果文件不存在,就自动创建该文件
FileOutputStream fileStream = new FileOutputStream("mygame.ser");
// 创建存取文件的 os 对象
ObjectOutputStream os = new ObjectOutputStream(fileStream);
// 把序列化对象写入文件
os.writeObject(new Dog()); // Dog必须是可序列化的类
os.writeObject(new Dog());
// 关闭所关联的输出串流
os.close();
} catch (Exception e) {
System.out.println(e);
}
}
}

什么是串流?

将串流( stream )连接起来代表来源与目的地(文件或网络端口)的连接。串流必须要连接到某处才能算是个串流。Java的输入输出API带有连接类型的串流,它代表来源与目的地之间的连接,连接串流即把串流与其它串流连接起来。

当对象被序列化时,被该对象引用的实例变量也会被序列化。且所有被引用的对象也会被序列化。最重要的是,这些操作都是自动完成的。

如果要让类能够序列化,就要实现Serializable

Serializable接口,又被称为marker或tab类的标记接口,因为此接口并没有任何方法需要被实现。它唯一的目的就是声明有实现它的类是可以被序列化的。也就是说,此类型的对象可以通过序列化的机制来存储。如果某个类是可以序列化的,则它的子类也自动地可以序列化。

// Serializable没有方法需要实现,它只是用来告诉Java虚拟机这个类可以被序列化
public class Box implements Serializable {
public Box() {}
}

注意:序列化是全有或全无的,即对象序列化时不存在“一部分序列化成功、另一部分序列化失败”的情况,如果对象有一部分序列化失败,则整个序列化过程就是失败的。只有可序列化的对象才能被写入到串流中。

那么在一个可序列化的类中,如何指定部分实例变量不执行序列化呢?

如果希望某个实例变量不能或不应该被序列化,就把它标记为 transient(瞬时)的,即可。

// Serializable没有方法需要实现,它只是用来告诉Java虚拟机这个类可以被序列化
public class Box implements Serializable {
public Box() {}

// 该id变量,就不会被序列化
transient String id;
String username;
}

如何从文件中读取序列化对象,并将其还原?

把对象恢复到存储时的状态。解序列化,可看成是序列化的反向操作。

public class TestInputStream {
public static void main(String[] args) {
try {
// 打开文件流,如果文件不存在就会报错
FileInputStream fileStream = new FileInputStream("mygame.ser");
// 创建 输入流
ObjectInputStream os = new ObjectInputStream(fileStream);
// 读取 序列化对象
Object one = os.readObject();
Object two = os.readObject();
// 类型转换,还原对象类型
Dog d1 = (Dog)one;
Dog d2 = (Dog)two;
// 关注 输入流
os.close();
} catch (Exception e) {
System.out.println(e);
}
}
}

序列化(存储)和解序列化(恢复)的过程,到底到生了什么事?

你可以通过序列化来存储对象的状态,使用ObjectOutputStream来序列化对象。Stream是连接串流或是链接用的串流,连接串流用来表示源或目的地、文件、网络套接字连接。链接用串流用来链接连接串流。使用FileOutputStream将对象序列化到文件上。静态变量不会被序列化,因为所有对象都是共享同一份静态变量值。

对象必须实现Serializable 这个接口,才能被序列化。如果父类实现了它,则子类就自动地有实现。
解序列化时,所有的类都必须能让Java虚拟机找到。读取对象的顺序必须与写入时的顺序一致。

如何把字符串写入文件文件?

try {
// FileWriter
FileWriter writer = new FileWriter("foo.txt");
writer.write("hello foo!");
writer.close();
} catch (IOException e) {
System.out.print(e);
}

什么是缓冲区?为什么使用缓冲区会提升数据读写的效率?

没有缓冲区,就好像逛超市没有推车一样,你只能一次拿一项商品去结账。缓冲区能让你暂时地摆一堆东西,直到装满为止。用了缓冲区,就可以省下好几趟的来回。

缓冲区的奥妙之处在于,使用缓冲区比没有使用缓冲区的效率更好。通过 BufferedWriter 和 FileWriter 的链接,BufferedWriter 可以暂存一堆数据,等到满的时候再实际写入磁盘,这样就可以减少对磁盘的操作次数。如果想要强制缓冲区立即写入,只要调用 writer.flush() 这个方法即可。

如何从文本文件中读取数据?

File对象表示文件,FileReader用于执行实际的数据读取,BufferedReader让读取更有效率。读取数据,使用 while 循环来逐行读取,直到 readLine() 的结果为 null 为止。这是最常见的数据读取方式(几乎所有的非序列化对象都是这样的)。

什么是 serialVersionUID?为什么要使用 serialVersionUID?

每当对象被序列化的同时,该对象(以及所有在其版图上的对象)都会被“盖”上一个类的版本识别ID,这个ID就被称为 serialVersionUID ,它是根据类的结构信息计算出来的。在对象被解序列化时,如果在对象被序列化之后类有了不同的 serialVersionUID,则解序列化会失败。虽然会失败,但你还可以有控制权。

如果你认为类有可能会深化,就把版本识别ID(serialVersionUID)放在类中。当Java尝试解序列化还原对象时,它会对比对象与Java虚拟机上的类的serialVersionUID 是否相同。如果相同,则还原成功;否则,还原将失败,Java虚拟机就会抛出异常。因此,把 serialVersionUID 放在类中,让类在演化过程中保持 serialVersionUID 不变。

public class Dog {
// 类的版本识别ID
private static final long serialVersionUID = -54662325652236L;
}

若想知道某个类的 serialVersionUID 是多少?则可以使用 Java Development Kit 里的 serialver 工具进行查询。

serialver Dog
Dog: static final long serialVersionUID = -54662325652236L;

posted @   就叫清风吧  阅读(29)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
点击右上角即可分享
微信分享提示