序列化
序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。一般将一个对象存储至一个储存媒介,例如档案或是记亿体缓冲等。在网络传输过程中,可以是字节或是XML等格式。而字节的或XML编码格式可以还原完全相等的对象。这个相反的过程又称为反序列化,关于对象创建有很多方法,创建出来的这些Java对象都是存在于JVM的堆内存中的。只有JVM处于运行状态的时候,这些对象才可能存在。一旦JVM停止运行,这些对象的状态也就随之而丢失了。我们需要将这些对象持久化下来,并且能够在需要的时候把对象重新读取出来。Java的对象序列化可以帮助我们实现该功能。
对象序列化机制(object serialization)是Java语言内建的一种对象持久化方式,通过对象序列化,可以把对象的状态保存为字节数组,并且可以在有需要的时候将这个字节数组通过反序列化的方式再转换成对象。对象序列化可以很容易的在JVM中的活动对象和字节数组(流)之间进行转换
java 中,序列化常用于RMI(远端方法调用)及网络传输中 相关类:
java.io.Serializable
java.io.Externalizable
ObjectOutput
ObjectInput
ObjectOutputStream
ObjectInputStream
Serializable
虽然Serializable接口中并没有定义任何属性和方法,但是如果一个类想要具备序列化能力也比必须要实现它。其实,主要是因为序列化在真正的执行过程中会使用instanceof判断一个类是否实现类Serializable,如果未实现则直接抛出异常。
一般都是通过实现Serializable方法来实现序列化 如果一个类的父类,需要将父类的变量持久化起来,父类也需要几次Serializable接口
public class User{
private String userName;
private int userAge;
get set......
@overwirte
public String toString(){
return "User{"+
"name'"+name+'\''+
",age="+age+
'}';
}
}
main实现方法
public class SeriableDemo{
public static void main(String[] args){
User user = new User();
user.setname("fourous");
user.setage("12");
user.toString();
//Write Obj to File
try (FileOutputStream fos = new FileOutputStream("tempFile"); ObjectOutputStream oos = new ObjectOutputStream(
fos)) {
oos.writeObject(user);
} catch (IOException e) {
e.printStackTrace();
}
//Read Obj from File
File file = new File("tempFile");
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) {
User1 newUser = (User1)ois.readObject();
System.out.println(newUser);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
}
Externalizable
Externalizable和Serializable接口的区别就是Externalizable继承了Serializable,该接口中定义了两个抽象方法:writeExternal()与readExternal()。当使用Externalizable接口来进行序列化与反序列化的时候需要开发人员重写writeExternal()与readExternal()方法。使用Externalizable进行序列化的时候,在读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被保存对象的字段的值分别填充到新对象中。所以,实现Externalizable接口的类必须要提供一个public的无参的构造,如果实现了Externalizable接口的类中没有无参数的构造函数,在运行时会抛出异常:java.io.InvalidClassException。如果一个Java类没有定义任何构造函数,编译器会帮我们自动添加一个无参的构造方法,可是,如果我们在类中定义了一个有参数的构造方法了,编译器便不会再帮我们创建无参构造方法,这点需要注意
public class User{
private String userName;
private int userAge;
get set......
//这里是和Serializable区别地方,需要重写这两个方法
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(age);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = (String) in.readObject();
age = in.readInt();
}
@overwirte
public String toString(){
return "User{"+
"name'"+name+'\''+
",age="+age+
'}';
}
}
ObjectOutput 和 ObjectInput接口
上面的writeExternal方法和readExternal方法分别接收ObjectOutput和ObjectInput类型参数。这两个类作用如下。
ObjectInput 扩展自 DataInput 接口以包含对象的读操作
DataInput 接口用于从二进制流中读取字节,并根据所有 Java 基本类型数据进行重构。同时还提供根据 UTF-8 修改版格式的数据重构 String 的工具。 对于此接口中的所有数据读取例程来说,如果在读取所需字节数之前已经到达文件末尾 (end of file),则将抛出 EOFException(IOException 的一种)。如果因为到达文件末尾以外的其他原因无法读取字节,则将抛出 IOException 而不是 EOFException。尤其是,在输入流已关闭的情况下,将抛出 IOException
ObjectOutput 扩展 DataOutput 接口以包含对象的写入操作
DataOutput 接口用于将数据从任意 Java 基本类型转换为一系列字节,并将这些字节写入二进制流。同时还提供了一个将 String 转换成 UTF-8 修改版格式并写入所得到的系列字节的工具。 对于此接口中写入字节的所有方法,如果由于某种原因无法写入某个字节,则抛出 IOException。
ObjectOutputStream 和ObjectInputStream
一般使用ObjectOutputStream的writeObject方法把一个对象进行持久化。再使用ObjectInputStream的readObject从持久化存储中把对象读取出来
transient
transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null
serialVersionUID
关于serialVersionUID,阿里巴巴开发手册中就有提及
- 默认的1L
- 通过类名、接口名、成员方法及属性等来生成一个64位的哈希字段,这个可以通过IDE自动生成
** 怎么保证修改抛出异常的原理 ** 在没有指定情况下,生成SerialVersionUID的流程
> ObjectInputStream.readObject -> readObject0 -> readOrdinaryObject -> readClassDesc -> readNonProxyDesc -> ObjectStreamClass.initNonProxy
在反序列化过程中,对serialVersionUID做了严格比较,如果发现不相等,则直接抛出异常
public long getSerialVersionUID() {
// REMIND: synchronize instead of relying on volatile?
if (suid == null) {
suid = AccessController.doPrivileged(
new PrivilegedAction<Long>() {
public Long run() {
return computeDefaultSUID(cl);
}
在没有定义serialVersionUID的时候,会调用 computeDefaultSUID 方法,生成一个默认的serialVersionUID