(1)Java序列化就是指把Java对象转换为字节序列的过程
Java反序列化就是指把字节序列恢复为Java对象的过程。
(2)序列化最重要的作用:在传递和保存对象时.保证对象的完整性和可传递性。对象转换为有序字节流,以便在网络上传输或者保存在本地文件中。
反序列化的最重要的作用:根据字节流中保存的对象状态及描述信息,通过反序列化重建对象。
我们知道,当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。那么当两个Java进程进行通信时,能否实现进程间的对象传送呢?答案是可以的。如何做到呢?这就需要Java序列化与反序列化了。换句话说,一方面,发送方需要把这个Java对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出Java对象。
当我们明晰了为什么需要Java序列化和反序列化后,我们很自然地会想Java序列化的好处。其好处一是实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里),二是,利用序列化实现远程通信,即在网络上传送对象的字节序列。
① 想把内存中的对象保存到一个文件中或者数据库中时候;
② 想用套接字在网络上传送对象的时候;
③ 想通过RMI传输对象的时候
XML&SOAP
JSON(Javascript Object Notation)
Protobuf
要实现对象的序列化,最直接的操作就是实现Serializable接口
使用IO流中的对象流可以实现序列化操作,将对象保存到文件,再读取出来。
首先创建一个对象,并实现Serializable接口:
import java.io.Serializable;
public class User implements Serializable{
private static final long serialVersionUID = 1L;
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User [name=" + name + ", age=" + age + "]";
}
}
用对象流写一个保存对象与读取对象的工具类:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SerializeUtil {
// 保存对象,序列化
public static void saveObject(Object object) throws Exception {
ObjectOutputStream out = null;
FileOutputStream fout = null;
try {
fout = new FileOutputStream("D:/1.txt");
out = new ObjectOutputStream(fout);
out.writeObject(object);
} finally {
fout.close();
out.close();
}
}
// 读取对象,反序列化
public static Object readObject() throws Exception {
ObjectInputStream in = null;
FileInputStream fin = null;
try {
fin = new FileInputStream("D:/1.txt");
in = new ObjectInputStream(fin);
Object object = in.readObject();
return object;
} finally {
fin.close();
in.close();
}
}
}
测试:
public class Main {
public static void main(String[] args) {
User user = new User();
user.setName("hhh");
user.setAge(33);
// 保存
try {
SerializeUtil.saveObject(user);
} catch (Exception e) {
System.out.println("保存时异常:" + e.getMessage());
}
// 读取
User userObject;
try {
userObject = (User) SerializeUtil.readObject();
System.out.println(userObject);
} catch (Exception e) {
System.out.println("读取时异常:" + e.getMessage());
}
}
}
这里我们成功的进行了一次将对象保存到文件中,再读取了出来。如果此时,我们不实现序列化接口,就会出现异常了。我们取消实现的Serialiable接口代码:
public class User {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User [name=" + name + ", age=" + age + "]";
}
}
ObjectOutputStream 构造函数
public ObjectOutputStream(OutputStream out) throws IOException {
verifySubclass();
bout = new BlockDataOutputStream(out);//①
handles = new HandleTable(10, (float) 3.00);
subs = new ReplaceTable(10, (float) 3.00);
enableOverride = false;//②
writeStreamHeader();//③
bout.setBlockDataMode(true);
if (extendedDebugInfo) {
debugInfoStack = new DebugTraceInfoStack();
} else {
debugInfoStack = null;
}
}
①bout:用于写入一些类元数据还有对象中基本数据类型的值,在下面会分析。
②enableOverride :false 表示不支持重写序列化过程,如果为 true ,那么需要重写 writeObjectOverride 方法。这个一般不用管它。
③writeStreamHeader() 写入头信息,具体看下面分析。
ObjectOutputStream#writeStreamHeader()
protected void writeStreamHeader() throws IOException {
bout.writeShort(STREAM_MAGIC);//①
bout.writeShort(STREAM_VERSION);//②
}
①STREAM_MAGIC 声明使用了序列化协议,bout 就是一个流,将对应的头数据写入该流中
②STREAM_VERSION 指定序列化协议版本
ObjectOUtStream#writeObject(obj);
上面是 ObjectOutStream 构造中做的事,下面来看看具体 writeObject 方法内部做了什么事?
public final void writeObject(Object obj) throws IOException {
if (enableOverride) {//一般不会走这里,因为在 ObjectOutputStream 构造设置为 false 了
writeObjectOverride(obj);
return;
}
try {//代码会执行这里
writeObject0(obj, false);
} catch (IOException ex) {
...
}
}
ObjectOutStream#writeObject0()
private void writeObject0(Object obj, boolean unshared)
throws IOException
{
...
try {
Object orig = obj;
Class> cl = obj.getClass();
ObjectStreamClass desc;
//①
desc = ObjectStreamClass.lookup(cl, true);
...
//②
if (obj instanceof Class) {
writeClass((Class) obj, unshared);
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
// END Android-changed: Make Class and ObjectStreamClass replaceable.
} else 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());
}
}
}
...
}
① lookup 函数用于查找当前类的 ObjectStreamClass ,它是用于描述一个类的结构信息的,通过它就可以获取对象及其对象属性的相关信息,并且它内部持有该对象的父类的 ObjectStreamClass 实例。其内部大量使用了反射,读者可以去看看这个类的源码。下面看看它的构造函数
private ObjectStreamClass(final Class> cl) {
this.cl = cl;
name = cl.getName();
isProxy = Proxy.isProxyClass(cl);
isEnum = Enum.class.isAssignableFrom(cl);
serializable = Serializable.class.isAssignableFrom(cl);
externalizable = Externalizable.class.isAssignableFrom(cl);
Class> superCl = cl.getSuperclass();
//superDesc 表示需要序列化对象的父类的 ObjectStreamClass,如果为空,则调用 lookUp 查找
superDesc = (superCl != null) ? lookup(superCl, false) : null;
//localDesc 表示自己
localDesc = this;
...
}
② 根据 obj 的类型去执行序列化操作,如果不符合序列化要求,那么会③位置抛出 NotSerializableException
异常。
ObjectOutputStream#writeOrdinaryObject
private void writeOrdinaryObject(Object obj,
ObjectStreamClass desc,
boolean unshared)
throws IOException
{
...
try {
desc.checkSerialize();
//①
bout.writeByte(TC_OBJECT);
//②
writeClassDesc(desc, false);
handles.assign(unshared ? null : obj);
//③
if (desc.isExternalizable() && !desc.isProxy()) {
writeExternalData((Externalizable) obj);
} else {
//④
writeSerialData(obj, desc);
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
①写入类的元数据,TC_OBJECT. 声明这是一个新的对象,如果写入的是一个 String 类型的数据,那么就需要 TC_STRING 这个标识。
②writeClassDesc 方法主要作用就是自上而下(从父类写到子类,注意只会遍历那些实现了序列化接口的类)写入描述信息。该方法内部会不断的递归调用,我们只需要关系这个方法是写入描述信息就好了,读者可以查阅一下源码。
从这里可以知道,序列化过程需要额外的写入很多数据,例如描述信息,类数据等,因此序列化后占用的空间肯定会更大。
③ desc.isExternalizable() 判断需要序列化的对象是否实现了 Externalizable 接口,这个在上面已经演示过怎么使用的,在序列化过程就是在这个地方进行判断的。如果有,那么序列化的过程就会由程序员自己控制了哦,writeExternalData 方法会回调,在这里就可以愉快地编写需要序列化的数据拉。
④ writeSerialData 在没有实现 Externalizable 接口时,就执行这个方法
ObjectOutputstream#writeSerialData
private void writeSerialData(Object obj, ObjectStreamClass desc)
throws IOException
{
//①
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
for (int i = 0; i < slots.length; i++) {
ObjectStreamClass slotDesc = slots[i].desc;
if (slotDesc.hasWriteObjectMethod()) {//②
PutFieldImpl oldPut = curPut;
curPut = null;
SerialCallbackContext oldContext = curContext;
if (extendedDebugInfo) {
debugInfoStack.push(
"custom writeObject data (class \"" +
slotDesc.getName() + "\")");
}
try {
curContext = new SerialCallbackContext(obj, slotDesc);
bout.setBlockDataMode(true);
slotDesc.invokeWriteObject(obj, this);
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
} finally {
curContext.setUsed();
curContext = oldContext;
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
curPut = oldPut;
} else {
defaultWriteFields(obj, slotDesc);//③
}
}
}
① desc.getClassDataLayout 会返回 ObjectStreamClass.ClassDataSlot[] ,我们来看看 ClassDataSlot 类,可以看到它是封装了 ObjectStreamClass 而已,所以我们就简单的认为 ① 这一步就是用于返回序列化对象及其父类的 ClassDataSlot[] 数组,我们可以从 ClassDataSlot 中获取对应 ObjectStreamClass 描述信息。
static class ClassDataSlot {
/** class descriptor "occupying" this slot */
final ObjectStreamClass desc;
/** true if serialized form includes data for this slot's descriptor */
final boolean hasData;
ClassDataSlot(ObjectStreamClass desc, boolean hasData) {
this.desc = desc;
this.hasData = hasData;
}
}
② 开始遍历返回的数组,slotDesc 这个我们就简单将其看成对一个对象的描述吧。hasWriteObjectMethod 表示的是什么呢?这个其实就是你要序列化这个对象是否有 writeObject 这个 private 方法,注意哦,这个方法并不是任何接口的方法,而是我们手动写的,读者可以参考 ArrayList 代码,它内部就有这个方法。那么这个方法的作用是什么呢?这个方法我们在上面也演示过具体的使用,它就是用于自定义序列化过程的,读者可以返回到上面看看如果使用这个 writeObject 实现自定义序列化过程的。注意:其实这个过程不像实现 Externalizable 接口那样,自己完全去自定义序列化数据。
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException {
//执行 JVM 默认的序列化操作
s.defaultWriteObject();
//手动序列化 arr 前面30个元素
for (int i = 0; i < 30; i++) {
s.writeObject(arr[i]);
}
}
③ defaultWriteFields 这个方法就是 JVM 自动帮我们序列化了
private void defaultWriteFields(Object obj, ObjectStreamClass desc)
throws IOException
{
Class> cl = desc.forClass();
desc.checkDefaultSerialize();
int primDataSize = desc.getPrimDataSize();
if (primVals == null || primVals.length < primDataSize) {
primVals = new byte[primDataSize];
}
desc.getPrimFieldValues(obj, primVals);
//①
bout.write(primVals, 0, primDataSize, false);
ObjectStreamField[] fields = desc.getFields(false);
Object[] objVals = new Object[desc.getNumObjFields()];
int numPrimFields = fields.length - objVals.length;
desc.getObjFieldValues(obj, objVals);
//②
for (int i = 0; i < objVals.length; i++) {
if (extendedDebugInfo) {
debugInfoStack.push(
"field (class \"" + desc.getName() + "\", name: \"" +
fields[numPrimFields + i].getName() + "\", type: \"" +
fields[numPrimFields + i].getType() + "\")");
}
try {
writeObject0(objVals[i],
fields[numPrimFields + i].isUnshared());
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
}
这个方法主要分为以下两步