序列化的最终目的就是为了对象可以跨平台存储及进行网络传输。
进行跨平台存储和网络传输的方式就是IO,IO支持的数据格式是字节数组。
并且我们还需要将字节数组还原回对象的原来模样,因此需要在对象转换成字节数组时就制定一种规则–序列化
把Java对象转换为字节序列的过程称为对象的序列化。
把字节序列恢复为对象的过程称为对象的反序列化。
核心作用就是对象状态的保存和重建–字节流中所保存的对象状态及描述信息
1、java.io.ObjectOutputStream 代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
对象序列化的步骤:
创建一个对象输出流,可以包装一个其他类型的目标输出流(如文件输出流)
通过对象输出流的writeObject()方法写对象
2、java.io.ObjectInputStream 代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。
对象反序列化的步骤:
创建一个对象输入流,它可以包装一个其他类型的源输入流(如文件输入流)
通过对象输入流的readObject()方法读取对象
只有实现了Serializable 和 Externalizable 接口的类的对象才能被序列化。
Externalizable接口继承自 Serializable接口,实现Externalizable接口的类完全由自身来控制序列化的行为,而仅实现Serializable接口的类可以采用默认的序列化方式 。
一个对象想要被序列化,那么它的类就要实现此接口或者它的子接口。
这个对象的所有属性(包括private属性、包括其引用的对象)都可以被序列化和反序列化来保存、传递。不想序列化的字段可以使用transient修饰。
由于Serializable对象完全以它存储的二进制位为基础来构造,因此并不会调用任何构造函数,因此Serializable类无需默认构造函数,但是当Serializable类的父类没有实现Serializable接口时,反序列化过程会调用父类的默认构造函数,因此该父类必需有默认构造函数,否则会抛异常。
使用transient关键字阻止序列化虽然简单方便,但被它修饰的属性被完全隔离在序列化机制之外,导致了在反序列化时无法获取该属性的值,而通过在需要序列化的对象的Java类里加入writeObject()方法与readObject()方法可以控制如何序列化各属性,甚至完全不序列化某些属性或者加密序列化某些属性。
它是Serializable接口的子类,用户要实现的writeExternal()和readExternal() 方法,用来决定如何序列化和反序列化。
因为序列化和反序列化方法需要自己实现,因此可以指定序列化哪些属性,而transient在这里无效。
对Externalizable对象反序列化时,会先调用类的无参构造方法,这是有别于默认反序列方式的。如果把类的不带参数的构造方法删除,或者把该构造方法的访问权限设置为private、默认或protected级别,会抛出java.io.InvalidException: no valid constructor异常,因此Externalizable对象必须有默认构造函数,而且必需是public的。
使用时,你只想隐藏一个属性,比如用户对象user的密码pwd,如果使用Externalizable,并除了pwd之外的每个属性都写在writeExternal()方法里,这样显得麻烦,可以使用Serializable接口,并在要隐藏的属性pwd前面加上transient就可以实现了。如果要定义很多的特殊处理,就可以使用Externalizable。
JDK(不支持跨语言)、JSON、XML、Hessian、Kryo(不支持跨语言)、Thrift、Protostuff、FST(不支持跨语言)
JDK 自带的序列化机制对使用者而言是非常简单的。
序列化具体的实现是由 ObjectOutputStream 完成的,而反序列化的具体实现是ObjectInputStream 完成的。
序列化过程就是在读取对象数据的时候,不断加入一些特殊分隔符,这些特殊分隔符用于在反序列化过程中截断用。
JSON 是典型的 Key-Value 方式,没有数据类型,是一种文本型序列化框架
缺点:
Hessian 是动态类型、二进制、紧凑的,并且可跨语言移植的一种序列化框架。Hessian 协议要比 JDK、JSON 更加紧凑,性能上要比 JDK、JSON 序列化高效很多,而且生成的字节数也更小。有非常好的兼容性和稳定性
缺点:
Protobuf 是 Google 内部的混合语言数据标准,是一种轻便、高效的结构化数据存储格式,可以用于结构化数据序列化,支持 Java、Python、C++、Go 等语言。Protobuf 使用的时候需要 定义 IDL(Interface description language),然后使用不同语言的 IDL编译器,生成序列化工具类,它的优点是:
缺点:
建议使用类型:
首选是 Hessian 与 Protobuf,因为他们在性能、时间开销、空间开销、通用性、兼容性和安全性上,都满足了我们的要求。其中 Hessian 在使用上更加方便,在对象的兼容性上更好;Protobuf则更加高效,通用性上更有优势。
字面意思是序列化的版本号;凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量。
这个serialVersionUID的取值是Java运行时环境根据类的内部细节自动生成的。
类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的 serialVersionUID,也有可能相同。为了提高serialVersionUID的独立性和确定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。
在完成序列化和反序列的测试后,修改对象类,添加一个新的属性,再执行反序列化操作就会抛出异常信息:
—文件流中的class和classpath中的class,也就是修改后的class与序列化的内容不兼容了,出于安全机制考虑,抛出错误,拒绝载入。
— 如果必须需要在序列化后添加一个字段或方法,需要自己去指定serialVersionUID,因为这个UID是唯一的
0、序列化时,只对对象的状态进行保存,而不管对象的方法
1、static属性不能被序列化–代表类的状态
–序列化保存的是对象的状态,静态变量属于类的状态,因此序列化不保存静态变量
2、Transient关键字修饰的属性不会被序列化–代表对象的临时数据
3、序列化版本号的作用:
对象通常需要根据业务的需求变化要新增、修改或者删除一些属性,在我们做了一些修改后,就通过修改版本号告诉 反序列化的那一方对象有了修改你需要同步修改。
4、当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化
5、当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口
序列化以正向递归的形式进行的,如果父类实现了序列化那么其子类都将被序列化;子类实现了序列化而父类没有实现序列化,那么只有子类的属性会进行序列化,而父类的属性是不会进行序列化的。
6、Java有很多基础类已经实现了serializable接口,比如String,Vector等。但是也有一些没有实现
7、如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存!这是能用序列化解决深拷贝的重要原因;
注意:浅拷贝请使用Clone接口的原型模式。
8、Serializable 中的writeObject()方法与readObject()方法 和 Externalizable 中的writeExternal()和readExternal() 方法有什么异同?
readExternal(),writeExternal()两个方法,这两个方法除了方法签名和readObject(),writeObject()两个方法的方法签名不同之外,其方法体完全一样。
需要指出的是,当使用Externalizable机制反序列化该对象时,程序会使用public的无参构造器创建实例,然后才执行readExternal()方法进行反序列化,因此实现Externalizable的序列化类必须提供public的无参构造。
虽然实现Externalizable接口能带来一定的性能提升,但由于实现ExternaLizable接口导致了编程复杂度的增加,所以大部分时候都是采用实现Serializable接口方式来实现序列化。
9、并非所有的对象都可以序列化,至于为什么不可以,有很多原因了,比如:
安全方面的原因,比如一个对象拥有private,public等field,对于一个要传输的对象,比如写到文件,或者进行RMI传输等等,在序列化进行传输的过程中,这个对象的private等域是不受保护的;
资源分配方面的原因,比如socket,thread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分配,而且,也是没有必要这样实现;
java 实现序列化很简单,只需要实现Serializable 接口即可。
public class User implements Serializable{
//年龄
private int age;
//名字
private String name ;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
// 把User对象设置值后写入文件
FileOutputStream fos = new FileOutputStream("D:\\temp.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
User user = new User();
user.setAge(18);
user.setName("sandy");
oos.writeObject(user);
oos.flush();
oos.close();
// 再把从文件读取出来的转换为对象
FileInputStream fis = new FileInputStream("D:\\temp.txt");
ObjectInputStream oin = new ObjectInputStream(fis);
User user = (User) oin.readObject();
System.out.println("name="+user.getName());
// 输出结果为:name=sandy
// 以上把User对象进行二进制的数据存储后,并从文件中读取数据出来转成User对象就是一个序列化和反序列化的过程。