什么是对象序列化和对象反序列化?
序列化是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。对象序列化和反序列化的过程就是将对象写入字节流和从字节流中读取对象的过程。将对象状态转换成字节流后,可以用java.io包中的各种I/O流类将其保存到文件中,或者应用NIO技术将其传输到另一线程中或通过网络连接将对象数据发送到另一主机。对象序列化功能非常强大,在RMI、Socket、JMS和EJB中都有应用。
对象序列化机制是为了解决对象在磁盘上读写操作和在网络中传递出现的问题而提出的,通过流化后的对象可以通过调用该类的writeObject()方法方便地向特定的文件或者网络输出对象,另一方也可以通过readObject()方法方便的接口该对象。
序列化的实现场景有以下几种:
1. 永久性保存对象,保存对象的字节序列到本地文件中;
2. 对象序列化可以实现分布式对象。例如RMI要利用对象序列化运行远程主机上的服务,与在本地机上运行对象时相同;
3. 通过序列化对象在网络中传递对象;
4. 通过序列化在进程中传递对象。
如何实现序列化和反序列化?
Java中序列化有两种实现方法,分别是实现Serializable
接口和Externalizable
接口。一般选择使用前者来实现序列化,因为该接口不需要实现任何方法;而后者接口定义了writeExternal()
和readExternal()
方法,实现该接口的类必须要实现这两个方法。
为了保障对象能够实现序列化,对象不仅需要实现Serializable
接口,而且被序列化的类的属性类型也都要具有可序列化的特性。如果某个实行不具有可序列化特性,并且又没有使用static
和transient
关键字修饰,在使用是将出现错误。被static
和transient
关键字修饰的成员将不会被序列化。
如何实现对象在磁盘(文件)中的存取操作?
对象序列化的过程分为序列化和反序列化两大部分。序列化是该过程的第一部分,将数据分解成字节流,以便存储在文件中火灾网络上传输。反序列化就是打开字节流并重构对象。具体可以通过Java.io.ObjectOutputStream
类中的writeObject(Oject obj)
方法和Java.io.ObjectInputStream
类中的readObject()
方法来实现。
实例代码如下:
被序列化的类:
public class SerializableClass implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private String name;
private int age;
private Date birthday;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
序列化类:
public class Serializable {
public static void main(String[] args) {
SerializableClass object = new SerializableClass();
object.setName("class1");
object.setAge(10);
object.setBirthday(new Date());
Serializable serializable = new Serializable();
serializable.writeObject(object);
System.out.format("%s", "write object done\n");
SerializableClass o = (SerializableClass) serializable.readObject();
System.out.println(o.getName());
System.out.println(o.getAge());
System.out.println(o.getBirthday());
}
public void writeObject(Object o) {
try {
FileOutputStream fos = new FileOutputStream("object");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(o);
oos.flush();
oos.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public Object readObject() {
try {
FileInputStream fis = new FileInputStream("object");
ObjectInputStream ois = new ObjectInputStream(fis);
Object o = ois.readObject();
ois.close();
fis.close();
return o;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
对象中的成员哪些参与序列化?哪些不参与序列化?
Java对象序列化时参与序列化的内容包含以下几个方面:
- 属性,包括基本数据类型、数组以及其他对象的引用;
- 类型。
不能被序列化的内容有以下几个方面:
- 被static修饰的属性;
- 被transient修饰的属性。
在序列化的过程中,不仅保留当前类对象的数据,而且递归保存对象中其他对象的引用数据,既“深复制”。
在序列化的过程中,由于有些属性值比较敏感(例如密码),则不需要被序列化。对于此类属性只需要在定义时为其添加transient关键字即可,对于transient修饰的属性序列化机制会跳过而不会将其写入文件,同时在反序列化时,该属性值保持默认初始化值。
在序列化类中添加serialVersionUID
属性有什么作用?
serialVersionUID
是一个私有的静态final属性,用于表明类之间不同版本的兼容性,该属性不是必须的。当一个类实现了java.io.Serializabe
接口,但是没有显示定义一个Long类型的serialVersionUID
属性时,Java序列化机制会根据编译的class自动生成一个serialVersionUID
作为该类的序列化版本ID号,只有同一次编译生成的class才会生成相同的serialVersionUID
。在反序列化过程中,JVM会把传来的字节流中的serialVersionUID
与本地类的serialVersionUID
进行比较,如果相同就认为版本一致,则可以进行反序列化,否则就会出现序列化版本不一致的InvalidClassException
异常。如果不希望通过编译器在强制划分软件版本,既实现序列化接口的类能够兼容以前的版本,只需要显示地定义一个类型为Long,名为serialVersionUID
的final属性即可保证相同的版本号,且在进行序列化和反序列化时不会出现前面提到的异常。