Java对象序列化与文件I/O全解析
立即解锁
发布时间: 2025-08-18 01:24:45 阅读量: 1 订阅数: 7 

### Java对象序列化与文件I/O全解析
在Java编程中,对象序列化和文件I/O是非常重要的概念。它们允许我们将对象的状态保存到文件中,并且在需要的时候恢复对象的状态。下面我们将详细介绍这些内容。
#### 1. 反序列化:恢复对象
对象序列化的主要目的是在以后的某个时间,在不同的JVM运行中(甚至可能不是对象序列化时运行的同一个JVM)将对象恢复到其原始状态。反序列化与序列化的过程相反。
以下是反序列化的步骤:
1. **创建FileInputStream对象**:
```java
FileInputStream fileStream = new FileInputStream("MyGame.ser");
```
FileInputStream知道如何连接到现有的文件。
2. **创建ObjectInputStream对象**:
```java
ObjectInputStream os = new ObjectInputStream(fileStream);
```
ObjectInputStream可以读取对象,但它不能直接连接到文件,需要与连接流(如FileInputStream)链在一起。
3. **读取对象**:
```java
Object one = os.readObject();
Object two = os.readObject();
Object three = os.readObject();
```
每次调用`readObject()`方法,会按写入的顺序读取流中的下一个对象。如果尝试读取的对象数量超过写入的数量,会抛出异常。
4. **转换对象类型**:
```java
GameCharacter elf = (GameCharacter) one;
GameCharacter troll = (GameCharacter) two;
GameCharacter magician = (GameCharacter) three;
```
`readObject()`方法的返回类型是`Object`,所以需要将反序列化的对象转换为实际类型。
5. **关闭ObjectInputStream**:
```java
os.close();
```
关闭顶部的流会自动关闭下面的流,所以FileInputStream(以及文件)会自动关闭。
#### 2. 反序列化过程中发生了什么?
当对象被反序列化时,JVM会尝试在堆上创建一个新对象,使其具有与序列化时相同的状态。不过,瞬态变量(用`transient`关键字标记的变量)会被恢复为`null`(对于对象引用)或默认的基本类型值。
反序列化对象的具体步骤如下:
1. 从流中读取对象。
2. JVM通过存储在序列化对象中的信息确定对象的类类型。
3. JVM尝试查找并加载对象的类。如果JVM无法找到和/或加载该类,会抛出异常,反序列化失败。
4. 在堆上为新对象分配空间,但序列化对象的构造函数不会运行。因为如果构造函数运行,会将对象的状态恢复到其原始的“新”状态,而我们希望对象恢复到序列化时的状态。
5. 如果对象的继承树中有不可序列化的类,该不可序列化类的构造函数以及其上方的任何构造函数(即使它们是可序列化的)都会运行。一旦构造函数链开始,就无法停止,这意味着从第一个不可序列化的类开始,所有超类都会重新初始化其状态。
6. 对象的实例变量被赋予序列化状态中的值。瞬态变量被赋予`null`(对于对象引用)或默认值(对于基本类型)。
#### 3. 常见问题解答
- **问题**:为什么类不作为对象的一部分保存?这样就不会有找不到类的问题了。
- **回答**:虽然可以这样设计序列化,但这会造成巨大的浪费和开销。在将对象写入本地硬盘文件时,可能影响不大,但序列化也用于通过网络连接发送对象。如果每个序列化对象都捆绑一个类,带宽问题会变得更加严重。不过,对于通过网络传输的序列化对象,实际上有一种机制可以为序列化对象“标记”其类的URL。这在Java的远程方法调用(RMI)中使用,这样可以将序列化对象作为方法参数发送,如果接收调用的JVM没有该类,它可以使用URL从网络获取并加载该类,一切都是自动完成的。
- **问题**:静态变量会被序列化吗?
- **回答**:不会。静态变量意味着“每个类一个”,而不是“每个对象一个”。静态变量不会被保存,当对象被反序列化时,它将具有该类当前的任何静态变量值。所以,不要让可序列化对象依赖于动态变化的静态变量,因为对象恢复时可能会不同。
#### 4. 保存和恢复游戏角色示例
以下是一个保存和恢复游戏角色的示例代码:
```java
import java.io.*;
public class GameSaverTest {
public static void main(String[] args) {
GameCharacter one = new GameCharacter(50, "Elf", new String[] {"bow", "sword", "dust"});
GameCharacter two = new GameCharacter(200, "Troll", new String[] {"bare hands", "big ax"});
GameCharacter three = new GameCharacter(120, "Magician", new String[] {"spells", "invisibility"});
try {
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("Game.ser"));
os.writeObject(one);
os.writeObject(two);
os.writeObject(three);
os.close();
} catch(IOException ex) {
ex.printStackTrace();
}
one = null;
two = null;
three = null;
try {
ObjectInputStream is = new ObjectInputStream(new FileInputStream("Game.ser"));
GameCharacter oneRestore = (GameCharacter) is.readObject();
GameCharacter twoRestore = (GameCharacter) is.readObject();
GameCharacter threeRestore = (GameCharacter) is.readObject();
System.out.println("One’s type: " + oneRestore.getType());
System.out.println("Two’s type: " + twoRestore.getType());
System.out.println("Three’s type: " + threeRestore.getType());
} catch(Exception ex) {
ex.printStackTrace();
}
}
}
import java.io.*;
public class GameCharacter implements Serializable {
int power;
String type;
String[] weapons;
public GameCharacter(int p, String t, String[] w) {
power = p;
type = t;
weapons = w;
}
public int getPower() {
return power;
}
public String getType() {
return type;
}
public String getWeapons() {
String weaponList = "";
for (int i = 0; i < weapons.length; i++) {
weaponList += weapons[i] + " ";
}
return weaponList;
}
}
```
这个示例展示了如何将游戏角色对象序列化到文件中,然后再从文件中反序列化恢复对象。
#### 5. 序列化相关要点总结
- 可以通过序列化对象来保存其状态。
- 要序列化对象,需要使用`ObjectOutputStream`(来自`java.io`包)。
- 流分为连接流和链流。
- 连接流可以表示与源或目的地的连接,通常是文件、网络套接字连接或控制台。
- 链流不能直接连接到源或目的地,必须与连接流(或其他流)链在一起。
- 要将对象序列化到文件,需要创建`FileOutputStream`并将其链入`ObjectOutputStream`。
- 要序列化对象,在`ObjectOutputStream`上调用`writeObject(theObject)`方法,不需要在`FileOutputStream`上调用方法。
- 要被序列化,对象必须实现`Serializable`接口。如果类的超类实现了`Serializable`接口,子类即使没有明确声明实现`Serializable`接口,也会自动可序列化。
- 当对象被序列化时,其整个对象图都会被序列化。这意味着序列化对象的实例变量引用的任何对象都会被序列化,依此类推。
- 如果图中的任何对象不可序列化,除非引用该对象的实例变量被跳过,否则运行时会抛出异常。
- 如果希望序列化跳过某个实例变量,可以使用`transient`关键字标记它。该变量将被恢复为`null`(对于对象引用)或默认值(对于基本类型)。
- 在反序列化期间,图中所有对象的类必须对JVM可用。
- 使用`readObject()`方法按原始写入的顺序读取对象。
- `readObject()`方法的返回类型是`Object`,所以反序列化的对象必须转换为其实际类型。
- 静态变量不会被序列化,因为将静态变量值作为特定对象状态的一部分保存没有意义,因为该类型的所有对象共享同一个值。
#### 6. 将字符串写入文本文件
虽然通过序列化保存对象是在Java程序运行之间保存和恢复数据的最简单方法,但有时需要将数据保存到普通的文本文件中。例如,Java程序可能需要将数据写入一个简单的文本文件,供其他(可能是非Java)程序读取。
以下是一个将字符串写入文本文件的示例:
```java
import java.io.*;
class WriteAFile {
public static void main (String[] args) {
try {
FileWriter writer = new FileWriter("Foo
```
0
0
复制全文
相关推荐










