本文参考文章:http://mp.weixin.qq.com/s?__biz=MzIwOTE2MzU4NA==&mid=2247484021&idx=1&sn=d7760254bd36f3a39dc8705ad40b469f&chksm=9779465aa00ecf4cfb5513a0ac41a31f318df2565b3e4ac739e62abfedad21d5a1e483f63f63&scene=0&xtrack=1#rd
1.什么叫序列化
序列化:把Java对象转换成字节序列的过程
反序列化:把字节序列转换成对象的过程
2.为什么要序列化及序列化的用途
因为虚拟机一旦运行结束,Java对象的生命也就结束了,如果我们还是想继续在以后用该对象,或者想把该对象传输给另一台机器,就只能将其转换成字节序列存储在文件和数据库中,或者通过网络传输给另一台机器。
序列化的用途:
- 减轻内存压力的同时,持久化
比如 Web服务器中的Session对象,当有 10+万用户并发访问的,就有可能出现10万个Session对象,内存可能消化不良,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。
- 让Java对象可以在网络中进行传输。
我们在使用Dubbo远程调用服务框架时,需要把传输的Java对象实现Serializable接口,即让Java对象序列化,因为这样才能让对象在网络上传输。
3. 常用的API
3.1 Serializable接口
Serializable接口是一个标记接口,没有方法或字段。一旦实现了此接口,就标志该类的对象就是可序列化的。
public interface Serializable {
}
3.2 Externalizable 接口
Externalizable继承了Serializable接口,还定义了两个抽象方法:writeExternal()和readExternal(),如果开发人员使用Externalizable来实现序列化和反序列化,需要重写writeExternal()和readExternal()方法
public interface Externalizable extends java.io.Serializable{
void writeExternal(ObjectOutput out)throws IOException;
void readExternal(ObjectInput in)throws IOException,ClassNotFoundException;
}
3.3 java.io.ObjectOutputStream类
表示对象输出流,它的writeObject(Object obj)方法可以对指定obj对象参数进行序列化,再把得到的字节序列写到一个目标输出流中。
3.4 java.io.ObjectInputStream类
表示对象输入流, 它的readObject()方法,从输入流中读取到字节序列,反序列化成为一个对象,最后将其返回。
4.序列化的使用
首先创建一个对象User
package com.company;
import java.io.Serializable;
public class User implements Serializable {
private String name;
private Integer age;
private String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
'}';
}
}
然后使用ObjectOutputStream ObjectInputStream测试
ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream("D:\\text.out"));
User user=new User();
user.setAge(22);
user.setName("Daniel");
user.setSex("男");
objectOutputStream.writeObject(user);
objectOutputStream.flush();
objectOutputStream.close();
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream("D:\\text.out"));
User user1=(User) objectInputStream.readObject();
user1.setName("jjp");
System.out.println(user1);
输出结果:
User{name=‘jjp’, age=22, sex=‘男’}
5.序列化底层
writeObject的部分源码:
// remaining cases
if (obj instanceof String) {
writeString((String) obj, unshared);
} else if (cl.isArray()) {
writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
writeOrdinaryObject(obj, desc, unshared);
} else {
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
ObjectOutputStream 在序列化的时候,会判断被序列化的Object是哪一种类型,String?array?enum?还是 Serializable,如果都不是的话,抛出 NotSerializableException异常。所以呀,Serializable只是一个标志,一个序列化标志~
6.序列化的注意点
6.1 static静态变量和transient 修饰的字段是不会被序列化的
我们将age设为静态变量,,sex设为transient变量
package com.company;
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream("D:\\text.out"));
User user=new User();
user.setAge(22);
user.setName("Daniel");
user.setSex("男");
System.out.println(user);
objectOutputStream.writeObject(user);
objectOutputStream.flush();
objectOutputStream.close();
user.setAge(56);
ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream("D:\\text.out"));
User user1=(User) objectInputStream.readObject();
System.out.println(user1);
}
}
程序运行结果如下:
User{name=‘Daniel’, age=22, sex=‘男’}
User{name=‘Daniel’, age=56, sex=‘null’}
- 可以看出静态变量是不会写入序列化的文件中的,这是因为static变量是类级别的,而序列化针对的是对象。
- 被transient修饰的变量序列化时,其值变为初始值,如果时基本类型如int是0,如果是对象则是null**
6.2 SerialVerisonUID
JAVA序列化的机制是通过判断类的serialVersionUID来验证版本是否一致的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID和本地相应实体类的serialVersionUID进行比较,如果相同,反序列化成功,如果不相同,就抛出InvalidClassException异常。