java序列化
java
中的序列化
经常听到关于序列化的话题,但是一直没有理解什么是序列化,为什么要序列化。
首先百度了一下序列化的定义:序列化(Serialization)将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入临时或持久性存储区。以后可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
在java中,存在于java虚拟机中的对象,他的内部状态只保存在内存中。如果jvm停止了这些状态就会丢失。在java中实现基本的对象序列化是件很简单的事。需要序列化的java类只需要实现java.io.Serializable
接口即可。这个接口只是作为一个标识,表示这个类可以进行序列化和反序列化。
序列化的特点:如果一个类能够被序列化,那么他的子类也可以被序列化。这里有个问题就是:如果子类需要序列化的话,还是要显式的声明serialVersionUID
。在反序列化的时候声明为static
和transient
类型的成员变量不能被序列化。static
表示类的状态,transient
表示对象的临时数据。
序列化的运用场景:
1.需要把内存中的对象状态保存到一个文件或者数据库中。
2.需要使用套接字在网络传输对象。
3.需要使用RMI传输对象。
java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。当然这些属性对象也需要实现接口java.io.Serializable
。
实际的序列化和反序列化工作是通过ObjectOutputStream
和ObjectInputStream
来完成。ObjectOutputStream
的writeObject
方法可以把一个java对象写到流中。ObjectInputStream
的readObject
方法可以从流中读取一个java对象。虽然用的参数或者返回值都是单个对象,但是实际操作的是一个对象图。包括了这个对象状态所引用的其他对象,以及具有相关关系的对象。会自动遍历这个对象图逐个序列化。基本数据类型和数组也是可以通过他们序列化的。
serialVersionUID
序列化版本号。
序列化运行时使用了serialVersionUID
与每个可序列化类相关联,该序列号在反序列化过程中用于验证序列化对象的发送者和接收者是否为该对象加载了与序列化兼容的类。为了保证序列化版本号的一致性,序列化类需要声明一个明确的版本号值。
private static final long serialVersionUID = 1L;
serialVersionUID
字段只是一个标识值。只用于当前这个类。
序列化机制:序列化分为两大部分:序列化和反序列化。序列化是这个过程的第一部分,将数据分解成字节流,以便存储在文件中或在网络上传输。反序列化就是打开字节流并重构对象。对象序列化不仅要将基本数据类型转换成字节 表示,有时还要恢复数据。恢复数据要求有恢复数据的对象实例。ObjectOutputStream中的序列化过程与字节流连接,包括对象类型和版本信 息。反序列化时,JVM用头信息生成对象实例,然后将对象字节流中的数据复制到对象数据成员中。
真正在需要序列化的时候都会根据实现的接口来进行操作。
当然也可以自己定义序列化的方法。
public interface Externalizable extends java.io.Serializable{}
一个类如果要完全负责自己的序列化,就可以实现Externalizable
接口,自己实现里面的两个方法。利用这些方法可以控制对象数据成员如何写入字节流.类实现 Externalizable时,头写入对象流中,然后类完全负责序列化和恢复数据成员,除了头以外,根本没有自动序列化。这里要注意了。声明类实现 Externalizable接口会有重大的安全风险。writeExternal()与readExternal()方法声明为public,恶意类可 以用这些方法读取和写入对象数据。如果对象包含敏感信息,则要格外小心。
public class SerializerUtils {
/**
* 序列化
* @param object
* @return
*/
public static byte[] serialize(Object object) {
ObjectOutputStream objectOutputStream = null;
ByteArrayOutputStream byteArrayOutputStream = null;
try {
byteArrayOutputStream = new ByteArrayOutputStream();
objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(object);
byte[] bytes = byteArrayOutputStream.toByteArray();
return bytes;
} catch (Exception e) {
return null;
} finally {
if (objectOutputStream != null) {
try {
objectOutputStream.close();
} catch (IOException e) {
//ignore
}
}
if (byteArrayOutputStream != null) {
try {
byteArrayOutputStream.close();
} catch (IOException e) {
//ignore
}
}
}
}
/**
* 反序列化
*/
@SuppressWarnings("unchecked")
public static <T> T unserialize(byte[] bytes) {
ByteArrayInputStream byteArrayInputStream = null;
ObjectInputStream objectInputStream = null;
try {
byteArrayInputStream = new ByteArrayInputStream(bytes);
objectInputStream = new ObjectInputStream(byteArrayInputStream);
return (T) objectInputStream.readObject();
} catch (Exception e) {
return null;
} finally {
if (objectInputStream != null) {
try {
objectInputStream.close();
} catch (IOException e) {
//ignore
}
}
if (byteArrayInputStream != null) {
try {
byteArrayInputStream.close();
} catch (IOException e) {
//ignore
}
}
}
}
}
对象序列化成byte[]
和byte[]
转换成对象方法。ObjectInputStream
和ObjectOutputStream
将对象从流中取出和写入到流中。这里使用的是ByteArrayInputStream
和ByteArrayOutputStream
作为容器然后�与byte[]
进行相互转换。
在通过ObjectInputStream
的readObject
方法读取到一个对象之后,这个对象是一个新的实例,但是其构造方法是没有被调用的,其中的�属性的初始化代码也没有被执行。对于那些没有被序列化的属性,在新创建出来的对象中的值都是默认的。也就是说,这个对象从某种角度上来说是不完备的。这有可能会造成一些隐含的错误。调用者并不知道对象是通过一般的new
操作符来创建的,还是通过反序列化所得到的。解决的办法就是在类的readObject
方法里面,再执行所需的对象初始化逻辑。对于一般的Java类来说,构造方法中包含了初始化的逻辑。可以把这些逻辑提取到一个方法中,在readObject
方法中调用此方法。
如果序列化对象后,这个对象的类发生了改变,这时需要注意是否需要有兼容性。一般来说,在新的版本中添加东西不会产生什么问题,而去掉一些属性则是不行的。