一 java序列化操作的基本原理
1,什么序列化和反序列化
serialization(序列化),是一种将对象按照一定规则生成出一连串有序的字符的过程。这个一连串有序的字符 可以是二进制, xml,json,或特定的字符串 等等。
Deserialization(反序列化) 将 一连串有序的字符 --》对象
2,什么情况下需要序列化
a) 当你想把一个内存中对象保存到一个文件中或数据库中的时候(持久化)
b)在跨平台的环境下,通过网络传输对象时(WebSerivce SOAP)
c)当通过RMI传出对象的时候(仅限于java环境)
3,如何实现序列化
需要实现序列化的类实现 serialization接口。 Serialization接口中无任何方法,所以我们可以理解为一个标记,表明这个类可以被序列化
4,Serializable的作用
仅仅是个标记而已
5,序列化和反序列化例子
public class demo {
//序列化Student对象至outFile文件
public static void serialize(Object obj, String outFile) {
try {//通过对象输出流 的writeObject()方法序列化 obj对象
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(outFile));
oos.writeObject(obj);
oos.flush();
oos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
//将outFile文件中的内容反序列化为Student对象
public static Object deserialize(String readFile) {
try {
ObjectInputStream oos=new ObjectInputStream(new FileInputStream(readFile));
Object obj=oos.readObject();
oos.close();
return obj;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
Student stu=new Student("tengxvincent",18);
String stuFileName="student.out";
//serialize(stu,stuFileName);
Student student= (Student)deserialize(stuFileName);
System.out.println(student);
}
}
6,默认序列化机制
7,影响序列化
7.1 transient关键字
对于一些敏感的关键字(用户密码)可以用 transient 修饰该字段 不去序列化
7.21 静态变量是否能序列化 为什么?
不能序列化 序列化并不保存静态变量的状态
静态成员属于类级别的,所以不能序列化,序列化只是序列化了对象而已
7.22父子类问题
如果父类没有实现序列化,而子类实现列序列化。那么父类中的成员没办法做序列化操作
7.3 writeObject()方法与readObject()方法
//jdk自动调用, 扩展序列化规则的一个方法
//打破serialization接口序列化规则
private void wirteObject(ObjectOutputStream out) throws IOException {
// TODO Auto-generated method stub
out.defaultWriteObject();
out.writeInt(age);
}
private void readObject(ObjectInputStream in)throws IOException,ClassNotFoundException {
// TODO Auto-generated method stub
in.defaultReadObject();
age=in.readInt();
}
对于transient关键字修饰的字段 可以使用这两个方法 序列化
7.4 Externalizable接口
/**
*
* 如果实现这个接口,jdk将不会调用默认的序列化规则,完全使用自定义的序列化规则
*/
public class Person implements Externalizable {
private String name;
transient private int age;
//扩展序列化规则的方法
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
// TODO Auto-generated method stub
name=(String)in.readObject();
age=in.readInt();
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
// TODO Auto-generated method stub
out.writeObject(name);
out.writeInt(age);
}
7.5 readResolve()方法
当我们使用单例模式时,应该是期望这个类的实例是唯一的,但是如果该类是可实例化的,那么情况可能就不一样,
此时,我能可以使用 readResove()方法的实现,来确保在同一jvm中只有一个单例对象的引用
public class Message implements Serializable{
private String name;
private int age;
private Message(String name,int age) {
this.name=name;
this.age=age;
}
/**
* @return 获取一个单例对象
*/
public static Message getInstance() {
return InstanceHolder.instance;
}
private static class InstanceHolder{
private static final Message instance=new Message("呵呵",18);
}
private Object readResolve() {
return InstanceHolder.instance;
}
}
8 序列化ID
主要是针对跨平台,跨服务器,如果序列化ID不一致,会导致无法反序列化
serialVersionUID适用于Java的序列化机制。简单来说,Java的序列化机制是通过判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException。
具体的序列化过程是这样的:序列化操作的时候系统会把当前类的serialVersionUID写入到序列化文件中,当反序列化时系统会去检测文件中的serialVersionUID,判断它是否与当前类的serialVersionUID一致,如果一致就说明序列化类的版本与当前类版本是一样的,可以反序列化成功,否则失败。
serialVersionUID有两种显示的生成方式:
一是默认的1L,比如:private static final long serialVersionUID = 1L;
二是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:
private static final long serialVersionUID = xxxxL;
文件流中的class和classpath中的class,也就是修改过后的class,不兼容了,处于安全机制考虑,程序抛出了错误,并且拒绝载入。从错误结果来看,如果没有为指定的class配置serialVersionUID,那么java编译器会自动给这个class进行一个摘要算法,类似于指纹算法,只要这个文件有任何改动,得到的UID就会截然不同的,可以保证在这么多类中,这个编号是唯一的。所以,由于没有显指定 serialVersionUID,编译器又为我们生成了一个UID,当然和前面保存在文件中的那个不会一样了,于是就出现了2个序列化版本号不一致的错误。因此,只要我们自己指定了serialVersionUID,就可以在序列化后,去添加一个字段,或者方法,而不会影响到后期的还原,还原后的对象照样可以使用,而且还多了方法或者属性可以用。
9 序列化前和序列化后的对象的关系?
是==还是 equals 是浅克隆还是深克隆?
msgpack序列化与json序列化方式的比较
java序列化机制Serialize接口
java本身的序列化机制存在的问题
1. 序列化数据结果比较大、传输效率比较低
2. 不能跨语言对接
以至于在后来的很长一段时间,基于XML格式编码的对象序列化机制成为了主流,一方面解决了多语言兼容问题,另一方面比二进制的序列化方式更容易理解。以至于基于XML的SOAP协议及对应的WebService框架在很长一段时间内成为各个主流开发语言的必备的技术。
再到后来,基于JSON的简单文本格式编码的HTTP REST接口又基本上取代了复杂的Web Service接口,成为分布式架构中远程通信的首要选择。但是JSON序列化存储占用的空间大、性能低等问题,同时移动客户端应用需要更高效的传输数据来提升用户体验。在这种情况下与语言无关并且高效的二进制编码协议就成为了大家追求的热点技术之一。首先诞生的一个开源的二进制序列化框架-MessagePack。它比google的Protocol Buffers出现得还要早
恰当的序列化协议不仅可以提高系统的通用性、强壮型、安全性、优化性能。同时还能让系统更加易于调试和扩展
序列化实现深度克隆
浅拷贝(浅复制、浅克隆):被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。
换言之,浅拷贝仅仅复制所拷贝的对象,而不复制它所引用的对象。
深拷贝(深复制、深克隆):被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。
那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。
换言之,深拷贝把要复制的对象所引用的对象都复制了一遍
主流的序列化技术
json/Hession(2)/xml/ protobuf /protostuff / kryo/ MsgPack/ FST /thrift
json序列化
1 jackson(google出品) spring4.2 之后的默认序列化方式
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.31</version>
</dependency>
public class JsonDemo {
//初始化
private static Person init(){
Person person=new Person();
person.setName("mic");
person.setAge(18);
return person;
}
public static void main(String[] args) throws IOException {
// excuteWithJack();
excuteWithFastJson();
// excuteWithProtoBuf();
//
// excuteWithHessian();
}
private static void excuteWithJack() throws IOException {
Person person=init();
ObjectMapper mapper=new ObjectMapper();
byte[] writeBytes=null;
Long start=System.currentTimeMillis();
for(int i=0;i<10000;i++){
writeBytes=mapper.writeValueAsBytes(person);
}
System.out.println("Json序列化:"+(System.currentTimeMillis()-start)+"ms : " +
"总大小->"+writeBytes.length);
Person person1=mapper.readValue(writeBytes,Person.class);
System.out.println(person1);
}
2 fastjson (阿里 出品) 效率比Jackson略低
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.35</version>
</dependency>
private static void excuteWithFastJson() throws IOException {
Person person=init();
String text=null;
Long start=System.currentTimeMillis();
for(int i=0;i<10000;i++){
text=JSON.toJSONString(person);
}
System.out.println("fastjson序列化:"+(System.currentTimeMillis()-start)+"ms : " +
"总大小->"+text.getBytes().length);
Person person1=JSON.parseObject(text,Person.class);
System.out.println(person1);
}
protobuf 序列化
(google 内部的序列化方式 实现起来比较麻烦,我们使用百度为我们提供的jprotobuf的jar)
protobuf的性能比较高 原因?
1 字节压缩-->字节比较少 传输中占用的带宽就比较少
2 缓存
<dependency>
<groupId>com.baidu</groupId>
<artifactId>jprotobuf</artifactId>
<version>2.1.2</version>
</dependency>
private static void excuteWithProtoBuf() throws IOException {
Person person=init();
Codec<Person> personCodec= ProtobufProxy.create(Person.class,false);
Long start=System.currentTimeMillis();
byte[] bytes=null;
for(int i=0;i<10000;i++){
bytes=personCodec.encode(person);
}
System.out.println("protobuf序列化:"+(System.currentTimeMillis()-start)+"ms : " +
"总大小->"+bytes.length);
Person person1=personCodec.decode(bytes);
System.out.println(person1);
}
Hession 序列化
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.38</version>
</dependency>
private static void excuteWithHessian() throws IOException {
Person person=init();
ByteArrayOutputStream os=new ByteArrayOutputStream();
HessianOutput ho=new HessianOutput(os);
Long start=System.currentTimeMillis();
for(int i=0;i<10000;i++){
ho.writeObject(person);
if(i==0){
System.out.println(os.toByteArray().length);
}
}
System.out.println("Hessian序列化:"+(System.currentTimeMillis()-start)+"ms : " +
"总大小->"+os.toByteArray().length);
HessianInput hi=new HessianInput(new ByteArrayInputStream(os.toByteArray()));
Person person1=(Person)hi.readObject();
System.out.println(person1);
}