JAVA基础4---序列化和反序列化深入整理(JDK序列化)
一、什么是序列化和反序列化?
序列化:将对象状态信息转化成可以存储或传输的形式的过程(Java中就是将对象转化成字节序列的过程)
反序列化:从存储文件中恢复对象的过程(Java中就是通过字节序列转化成对象的过程)
二、为什么要序列化和反序列化?
Java中对象都是存储在内存中,准确地说是JVM的堆或栈内存中,可以各个线程之间进行对象传输,但是无法在进程之间进行传输。另外如果需要在网络传输中传输对象也没有办法,同样内存中的对象也没有办法直接保存成文件。
所以需要对对象进行序列化,序列化对象之后一个个的Java对象就变成了字节序列,而字节序列是可以传输和存储的。而反序列化就可以通过序列化生产的字节序列再恢复成序列化之前的对象状态及信息。
总结:
1、进程之间传输对象(如RPC、RMI通信)
2、网络通信时进行传输对象
3、持久化对象时需要将对象序列化
三、怎么序列化和反序列化?
实现序列化的方式有很多种,常用的方式有如下几种:
3.1、JDK序列化
JDK序列化时JDK自带的序列化方式,使用其他也比较方便,只需要序列化的类实现了Serializable接口即可,Serializable接口没有定义任何方法和属性,所以只是起到了标识的作用,表示这个类是可以被序列化的。
如果没有实现Serializable接口而进行序列化操作就会抛出NotSerializableException异常。
能够序列化的字段:属性变量、父类的属性变量(父类也需要实现Serializablie接口)
不能序列化的字段:静态变量、父类的属性变量、关键字transient修饰的变量、没有实现Serializable接口的对象属性
3.1.1、Serializable接口案例
定义类User、Person、Home、School分别如下
1 public class Home implements Serializable { 2 private String address; 3 4 public String getAddress() { 5 return address; 6 } 7 8 public void setAddress(String address) { 9 this.address = address; 10 } 11 }
1 public class School { 2 private String schoolName; 3 4 public String getSchoolName() { 5 return schoolName; 6 } 7 8 public void setSchoolName(String schoolName) { 9 this.schoolName = schoolName; 10 } 11 }
1 public class Person implements Serializable { 2 3 public static String parentType = "Person"; //父类静态变量 4 5 private String sex;//性别 6 7 public String getSex() { 8 return sex; 9 } 10 11 public void setSex(String sex) { 12 this.sex = sex; 13 } 14 }
public class User extends Person implements Serializable { public static boolean alive; //静态变量 private Long userId;//Long 类型 private int age; //int 类型 private String userName; //string 类型 private String password; //string 类型 private transient String IDCard; //不序列化的字段 private Date birth; //Date类型 private Home home; // 可以序列化的对象类型 private School school; //不可以序列化的对象类型 List<User> friends; //List类型 /** set get 方法省略*/ }
案例中序列化的类为User,继承类Person,分别含有类Home和School的对象属性,序列化测试代码如下:
1 public class MainTest { 2 public static void main(String[] args) throws Exception{ 3 User user = new User(); 4 user.setAge(10); 5 user.setBirth(new Date()); 6 user.setPassword("123456"); 7 user.setUserName("Jack"); 8 user.setUserId(100L); 9 user.setSex("男"); 10 user.setIDCard("131313131313113"); 11 user.parentType = "son";//修改父类静态变量 12 user.alive = true; //修改User类的静态变量 13 14 Home home = new Home(); 15 home.setAddress("中国浙江"); 16 School school = new School(); 17 school.setSchoolName("清华大学"); 18 user.setHome(home);//设置对象属性 19 // user.setSchool(school);//设置对象属性 (因为School类没有实现Seriliazable接口,所以如果设置就会报错) 20 21 List<User> friends = new ArrayList<User>(); 22 User userF = new User(); 23 userF.setUserId(101L); 24 userF.setUserName("Friend"); 25 friends.add(userF); 26 user.setFriends(friends); 27 28 //序列化 29 serializer(user); 30 //反序列化 31 User newUser = derializer(); 32 //验证 33 System.out.println("验证两个对象是否相等"); 34 System.out.println("原对象地址:"+user.toString()); 35 System.out.println("新对象地址:"+newUser.toString()); 36 System.out.println("******************"); 37 System.out.println("打印两个对象"); 38 System.out.println("原对象数据:"+JSON.toJSON(user).toString()); 39 System.out.println("新对象数据:"+JSON.toJSON(newUser).toString()); 40 } 41 42 /**序列化对象*/ 43 private static void serializer(User user)throws Exception{ 44 ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(new File("/Users/xxw/testlog/user.txt"))); 45 outputStream.writeObject(user); 46 outputStream.close(); 47 } 48 49 /**反序列化对象*/ 50 private static User derializer()throws Exception{ 51 ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(new File("/Users/xxw/testlog/user.txt"))); 52 User user = (User) inputStream.readObject(); 53 inputStream.close(); 54 return user; 55 } 56 }
测试结果为:
1 验证两个对象是否相等 2 原对象地址:com.lucky.demo.base.seralizer.demo.User@3764951d 3 新对象地址:com.lucky.demo.base.seralizer.demo.User@4783da3f 4 ****************** 5 打印两个对象 6 原对象数据:{"iDCard":"131313131313113","password":"123456","sex":"男","birth":1573203467764,"userName":"Jack","userId":100,"age":10,"friends":[{"userName":"Friend","userId":101,"age":0}],"home":{"address":"中国浙江"}} 7 新对象数据:{"password":"123456","sex":"男","birth":1573203467764,"userName":"Jack","userId":100,"age":10,"friends":[{"userName":"Friend","userId":101,"age":0}],"home":{"address":"中国浙江"}}
这里User类的School属性没有实现Serializable接口,所以如果给school属性赋值然后进行序列化就会报错,结果如下:
1 Exception in thread "main" java.io.NotSerializableException: com.lucky.demo.base.seralizer.demo.School
2 at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
3 at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
4 at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
5 at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
6 at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
7 at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
8 at com.lucky.demo.base.seralizer.demo.MainTest.serializer(MainTest.java:59)
9 at com.lucky.demo.base.seralizer.demo.MainTest.main(MainTest.java:43)
而如果User类的父类Person没有实现Serializable接口,那么序列化的时候不会报错,但是父类中的属性在反序列化之后字段就会没有,结果如下:
1 打印两个对象 2 原对象数据:{"iDCard":"131313131313113","password":"123456","sex":"男","birth":1573203839905,"userName":"Jack","userId":100,"age":10,"friends":[{"userName":"Friend","userId":101,"age":0}],"home":{"address":"中国浙江"}} 3 新对象数据:{"password":"123456","birth":1573203839905,"userName":"Jack","userId":100,"age":10,"friends":[{"userName":"Friend","userId":101,"age":0}],"home":{"address":"中国浙江"}}
这里就没有了父类的属性sex字段
3.1.2、Serializable接口实现原理
Serializable接口是一个空接口,没有定义任何的方法和属性,所以Serialiazable接口的作用就是起到一个标识的作用,源码如下
1 public interface Serializable { 2 }
Serializable接口既然是标识的作用,那么就需要在实际序列化操作的时候进行识别,而实际的序列化操作是通过ObjectOutputStream 和 ObjectInputStream实现的,那么接下来就看下这两个类的是如何实现序列化和反序列化的
3.1.2.1、ObjectOutputStream源码解析
构造函数如下
1 public ObjectOutputStream(OutputStream out) throws IOException { 2 verifySubclass(); 3 bout = new BlockDataOutputStream(out); 4 handles = new HandleTable(10, (float) 3.00); 5 subs = new ReplaceTable(10, (float) 3.00); 6 enableOverride = false; 7 writeStreamHeader(); 8 bout.setBlockDataMode(true); 9 if (extendedDebugInfo) { 10 debugInfoStack = new DebugTraceInfoStack(); 11 } else { 12 debugInfoStack = null; 13 } 14 }
OutoutStream表示保存的二进制流,也就是将序列化的对象保存到这个二进制流中,再看下具体的序列化方法源码如下:
1 public final void writeObject(Object obj) throws IOException { 2 if (enableOverride) {//enableOverride 表示是否可以被覆盖,默认为false 3 writeObjectOverride(obj); 4 return; 5 } 6 try { 7 //执行具体的序列化操作 8 writeObject0(obj, false); 9 } catch (IOException ex) { 10 if (depth == 0) { 11 writeFatalException(ex); 12 } 13 throw ex; 14 } 15 }
最终执行了writeObject0(obj, false)方法,代码如下:
1 private void writeObject0(Object obj, boolean unshared) 2 throws IOException 3 { 4 boolean oldMode = bout.setBlockDataMode(false); 5 depth++; 6 try { 7 // handle previously written and non-replaceable objects 8 // 处理已经处理过的和不可替换的对象,这些是不可序列化的 9 int h; 10 if ((obj = subs.lookup(obj)) == null) { 11 writeNull(); 12 return; 13 } else if (!unshared && (h = handles.lookup(obj)) != -1) { 14 writeHandle(h); 15 return; 16 } else if (obj instanceof Class) { 17 writeClass((Class) obj, unshared); 18 return; 19 } else if (obj instanceof ObjectStreamClass) { 20 writeClassDesc((ObjectStreamClass) obj, unshared); 21 return; 22 } 23 24 // check for replacement object 25 Object orig = obj; 26 //获取对象的Class对象 27 Class<?> cl = obj.getClass(); 28 ObjectStreamClass desc; 29 for (;;) { 30 // REMIND: skip this check for strings/arrays? 31 Class<?> repCl; 32 //获取Class的描述信息,并且判断是否是Serializable接口 33 desc = ObjectStreamClass.lookup(cl, true); 34 if (!desc.hasWriteReplaceMethod() || 35 (obj = desc.invokeWriteReplace(obj)) == null || 36 (repCl = obj.getClass()) == cl) 37 { 38 break; 39 } 40 cl = repCl; 41 } 42 43 //如果允许被替换的情况 44 if (enableReplace) { 45 Object rep = replaceObject(obj); 46 if (rep != obj && rep != null) { 47 cl = rep.getClass(); 48 desc = ObjectStreamClass.lookup(cl, true); 49 } 50 obj = rep; 51 } 52 53 // if object replaced, run through original checks a second time 54 // 如果对象被替换了,只有是ObjectOutputStream的子类才会出现 55 if (obj != orig) { 56 subs.assign(orig, obj); 57 if (obj == null) { 58 writeNull(); 59 return; 60 } else if (!unshared && (h = handles.lookup(obj)) != -1) { 61 writeHandle(h); 62 return; 63 } else if (obj instanceof Class) { 64 writeClass((Class) obj, unshared); 65 return; 66 } else if (obj instanceof ObjectStreamClass) { 67 writeClassDesc((ObjectStreamClass) obj, unshared); 68 return; 69 } 70 } 71 72 // remaining cases 73 /**序列化不同类型的对象,分别序列化String、数组、枚举类型 74 * 对于Integer、Long等都属于实现了Serializable接口的数据类型*/ 75 if (obj instanceof String) {//序列化字符串类型对象 76 writeString((String) obj, unshared); 77 } else if (cl.isArray()) {//序列化数组类型对象 78 writeArray(obj, desc, unshared); 79 } else if (obj instanceof Enum) {//序列化枚举类型对象 80 writeEnum((Enum<?>) obj, desc, unshared); 81 } else if (obj instanceof Serializable) {//序列化实现了Serializable接口的数据类型 82 writeOrdinaryObject(obj, desc, unshared); 83 } else {//抛出不可序列化异常 84 if (extendedDebugInfo) { 85 throw new NotSerializableException( 86 cl.getName() + "\n" + debugInfoStack.toString()); 87 } else { 88 throw new NotSerializableException(cl.getName()); 89 } 90 } 91 } finally { 92 depth--; 93 bout.setBlockDataMode(oldMode); 94 } 95 } 96 }
前面都是在做各种检查,实际有效的代码就是从75行开始,分别针对不同类型的对象分别执行不同的序列化方法
writeString方法的逻辑就是将字符串按字节的方式进行序列化,底层就是通过数组复制的方式获取到char[],然后写入到缓存的序列化的byte[]数组中
1 System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
writeArray方法的逻辑就是先判断数组的数据类型是什么,如果是基本数据类型之间写入byte数字,如果是对象类型就调用writeObject0方法
writeEnum方法的逻辑是直接写入枚举的值
而对于对象类型是比较复杂的,也就是writeOrdinaryObject方法,逻辑如下:
1 private void writeOrdinaryObject(Object obj, 2 ObjectStreamClass desc, 3 boolean unshared) 4 throws IOException 5 { 6 if (extendedDebugInfo) { 7 debugInfoStack.push( 8 (depth == 1 ? "root " : "") + "object (class \"" + 9 obj.getClass().getName() + "\", " + obj.toString() + ")"); 10 } 11 try { 12 //检查ObjectStreamClass对象 13 desc.checkSerialize(); 14 15 //写入标记表示是Object类型 16 bout.writeByte(TC_OBJECT); 17 //写入Class对象的描述信息 18 writeClassDesc(desc, false); 19 handles.assign(unshared ? null : obj); 20 if (desc.isExternalizable() && !desc.isProxy()) { 21 //写入实现了Externalizable接口的对象 22 writeExternalData((Externalizable) obj); 23 } else { 24 //写入实现了Serializable 25 writeSerialData(obj, desc); 26 } 27 } finally { 28 if (extendedDebugInfo) { 29 debugInfoStack.pop(); 30 } 31 } 32 }
最终按实现了Externalizable接口或Serializable接口分别执行writeExternalData和writeSerialData方法,writeSerialData方法如下:
1 private void writeSerialData(Object obj, ObjectStreamClass desc) 2 throws IOException 3 { 4 //获取类的描述信息对象 5 ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout(); 6 for (int i = 0; i < slots.length; i++) { 7 ObjectStreamClass slotDesc = slots[i].desc; 8 //判断该类是否自定义类writeObject方法,如果重写了该方法则按重写的逻辑处理 9 if (slotDesc.hasWriteObjectMethod()) { 10 ObjectOutputStream.PutFieldImpl oldPut = curPut; 11 curPut = null; 12 SerialCallbackContext oldContext = curContext; 13 14 if (extendedDebugInfo) { 15 debugInfoStack.push( 16 "custom writeObject data (class \"" + 17 slotDesc.getName() + "\")"); 18 } 19 try { 20 curContext = new SerialCallbackContext(obj, slotDesc); 21 bout.setBlockDataMode(true); 22 //通过反射的方式执行自定义的writeObejct方法 23 slotDesc.invokeWriteObject(obj, this); 24 bout.setBlockDataMode(false); 25 bout.writeByte(TC_ENDBLOCKDATA); 26 } finally { 27 curContext.setUsed(); 28 curContext = oldContext; 29 if (extendedDebugInfo) { 30 debugInfoStack.pop(); 31 } 32 } 33 34 curPut = oldPut; 35 } else { 36 //如果没有自定义writeObject方法则按默认的方法写入属性数据 37 defaultWriteFields(obj, slotDesc); 38 } 39 } 40 }
先是根据类的描述信息判断是否自定义了序列化方法writeObejct方法,如果自定义了就通过反射执行invokeWriteObejct方法,如果没有自定义则执行defaultWriteFields方法,defaultWriteFields方法逻辑如下:
1 private void defaultWriteFields(Object obj, ObjectStreamClass desc) 2 throws IOException 3 { 4 Class<?> cl = desc.forClass(); 5 //校验对象的类信息是否和类描述信息一致 6 if (cl != null && obj != null && !cl.isInstance(obj)) { 7 throw new ClassCastException(); 8 } 9 10 // 11 desc.checkDefaultSerialize(); 12 13 int primDataSize = desc.getPrimDataSize(); 14 if (primVals == null || primVals.length < primDataSize) { 15 primVals = new byte[primDataSize]; 16 } 17 desc.getPrimFieldValues(obj, primVals); 18 bout.write(primVals, 0, primDataSize, false); 19 20 ObjectStreamField[] fields = desc.getFields(false);//获取所有属性 21 Object[] objVals = new Object[desc.getNumObjFields()];//获取对象类型属性 22 int numPrimFields = fields.length - objVals.length; 23 desc.getObjFieldValues(obj, objVals); 24 for (int i = 0; i < objVals.length; i++) {//遍历对象类型属性数组 25 if (extendedDebugInfo) { 26 debugInfoStack.push( 27 "field (class \"" + desc.getName() + "\", name: \"" + 28 fields[numPrimFields + i].getName() + "\", type: \"" + 29 fields[numPrimFields + i].getType() + "\")"); 30 } 31 try { 32 //递归写入对象类型的属性 33 writeObject0(objVals[i], 34 fields[numPrimFields + i].isUnshared()); 35 } finally { 36 if (extendedDebugInfo) { 37 debugInfoStack.pop(); 38 } 39 } 40 } 41 }
总结:
序列化的整体逻辑就是遍历对象的所有属性,递归执行序列化方法,直到序列化的对象是String、Array或者是Eunm类,则按String、Array、Enum的序列化方式写入字节流中。
3.1.2.2、ObjectInputStream源码解析
ObjectInputStream的逻辑不用看也能猜到是和ObjectOutputStream相反的,反序列化的readObject方法具体逻辑如下:
1 public final Object readObject() 2 throws IOException, ClassNotFoundException 3 { 4 if (enableOverride) { 5 return readObjectOverride(); 6 } 7 8 // if nested read, passHandle contains handle of enclosing object 9 int outerHandle = passHandle; 10 try { 11 //执行反序列化方法 12 Object obj = readObject0(false); 13 handles.markDependency(outerHandle, passHandle); 14 ClassNotFoundException ex = handles.lookupException(passHandle); 15 if (ex != null) { 16 throw ex; 17 } 18 if (depth == 0) { 19 vlist.doCallbacks(); 20 } 21 return obj; 22 } finally { 23 passHandle = outerHandle; 24 if (closed && depth == 0) { 25 clear(); 26 } 27 } 28 }
1 private Object readObject0(boolean unshared) throws IOException { 2 boolean oldMode = bin.getBlockDataMode(); 3 if (oldMode) { 4 int remain = bin.currentBlockRemaining(); 5 if (remain > 0) { 6 throw new OptionalDataException(remain); 7 } else if (defaultDataEnd) { 8 /* 9 * Fix for 4360508: stream is currently at the end of a field 10 * value block written via default serialization; since there 11 * is no terminating TC_ENDBLOCKDATA tag, simulate 12 * end-of-custom-data behavior explicitly. 13 */ 14 throw new OptionalDataException(true); 15 } 16 bin.setBlockDataMode(false); 17 } 18 19 byte tc; 20 //从流中读取int数据,序列化时候写入的时候写入属性之前都会写入int值表示属性的类型 21 while ((tc = bin.peekByte()) == TC_RESET) { 22 bin.readByte(); 23 handleReset(); 24 } 25 26 depth++; 27 totalObjectRefs++; 28 try { 29 /**判断读取的int数据表示当前读取的是什么类型的数据结构 30 * 不同的类型数据分别执行不同的解析方法*/ 31 switch (tc) { 32 case TC_NULL: 33 return readNull(); 34 35 case TC_REFERENCE: 36 return readHandle(unshared); 37 38 case TC_CLASS: 39 return readClass(unshared); 40 41 case TC_CLASSDESC: 42 case TC_PROXYCLASSDESC: 43 return readClassDesc(unshared); 44 45 case TC_STRING: 46 case TC_LONGSTRING: 47 return checkResolve(readString(unshared)); 48 49 case TC_ARRAY: 50 return checkResolve(readArray(unshared)); 51 52 case TC_ENUM: 53 return checkResolve(readEnum(unshared)); 54 55 case TC_OBJECT: 56 return checkResolve(readOrdinaryObject(unshared)); 57 58 case TC_EXCEPTION: 59 IOException ex = readFatalException(); 60 throw new WriteAbortedException("writing aborted", ex); 61 62 case TC_BLOCKDATA: 63 case TC_BLOCKDATALONG: 64 if (oldMode) { 65 bin.setBlockDataMode(true); 66 bin.peek(); // force header read 67 throw new OptionalDataException( 68 bin.currentBlockRemaining()); 69 } else { 70 throw new StreamCorruptedException( 71 "unexpected block data"); 72 } 73 74 case TC_ENDBLOCKDATA: 75 if (oldMode) { 76 throw new OptionalDataException(true); 77 } else { 78 throw new StreamCorruptedException( 79 "unexpected end of block data"); 80 } 81 82 default: 83 throw new StreamCorruptedException( 84 String.format("invalid type code: %02X", tc)); 85 } 86 } finally { 87 depth--; 88 bin.setBlockDataMode(oldMode); 89 } 90 }
1 private Object checkResolve(Object obj) throws IOException { 2 if (!enableResolve || handles.lookupException(passHandle) != null) { 3 return obj; 4 } 5 Object rep = resolveObject(obj); 6 if (rep != obj) { 7 // The type of the original object has been filtered but resolveObject 8 // may have replaced it; filter the replacement's type 9 if (rep != null) { 10 if (rep.getClass().isArray()) { 11 filterCheck(rep.getClass(), Array.getLength(rep)); 12 } else { 13 filterCheck(rep.getClass(), -1); 14 } 15 } 16 handles.setObject(passHandle, rep); 17 } 18 return rep; 19 }
解析的方法分别是readString()、readArray()、readEnum()、readOrdinaryObject()方法
解析字符串的方法就是直接从流中读取字节,解析数组和枚举的过程差不多,重点是解析对象的逻辑,代码如下:
1 private Object readOrdinaryObject(boolean unshared) 2 throws IOException 3 { 4 //判断是否是对象类型 5 if (bin.readByte() != TC_OBJECT) { 6 throw new InternalError(); 7 } 8 9 //读取类描述信息对象 10 ObjectStreamClass desc = readClassDesc(false); 11 desc.checkDeserialize(); 12 13 Class<?> cl = desc.forClass(); 14 if (cl == String.class || cl == Class.class 15 || cl == ObjectStreamClass.class) { 16 throw new InvalidClassException("invalid class descriptor"); 17 } 18 19 Object obj; 20 try { 21 /** 22 * isInstantiable方法是判断是否有public的无参构造方法表示是否可以初始化对象 23 * 然后通过Constructor的newInstance方法初始化对象 24 * */ 25 //通过反射执行Class的newInstance方法构造对象 26 obj = desc.isInstantiable() ? desc.newInstance() : null; 27 } catch (Exception ex) { 28 throw (IOException) new InvalidClassException( 29 desc.forClass().getName(), 30 "unable to create instance").initCause(ex); 31 } 32 33 passHandle = handles.assign(unshared ? unsharedMarker : obj); 34 ClassNotFoundException resolveEx = desc.getResolveException(); 35 if (resolveEx != null) { 36 handles.markException(passHandle, resolveEx); 37 } 38 39 if (desc.isExternalizable()) { 40 //反序列化实现了Externalizable接口的对象 41 readExternalData((Externalizable) obj, desc); 42 } else { 43 //反序列化实现了Serializable接口的对象 44 readSerialData(obj, desc); 45 } 46 47 handles.finish(passHandle); 48 49 if (obj != null && 50 handles.lookupException(passHandle) == null && 51 desc.hasReadResolveMethod()) 52 { 53 Object rep = desc.invokeReadResolve(obj); 54 if (unshared && rep.getClass().isArray()) { 55 rep = cloneArray(rep); 56 } 57 if (rep != obj) { 58 // Filter the replacement object 59 if (rep != null) { 60 if (rep.getClass().isArray()) { 61 filterCheck(rep.getClass(), Array.getLength(rep)); 62 } else { 63 filterCheck(rep.getClass(), -1); 64 } 65 } 66 handles.setObject(passHandle, obj = rep); 67 } 68 } 69 70 return obj; 71 }
和序列化的时候一样,先判断是实现了Externalizable接口还是Serializable接口分别执行不同的逻辑,readSerialData方法逻辑如下:
1 private void readSerialData(Object obj, ObjectStreamClass desc) 2 throws IOException 3 { 4 ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout(); 5 for (int i = 0; i < slots.length; i++) {//遍历类描述信息 6 ObjectStreamClass slotDesc = slots[i].desc; 7 if (slots[i].hasData) { 8 if (obj == null || handles.lookupException(passHandle) != null) { 9 defaultReadFields(null, slotDesc); //没有数据可以解析 10 } else if (slotDesc.hasReadObjectMethod()) {//如果有自定义的readObejct方法则按自定义的逻辑执行 11 ThreadDeath t = null; 12 boolean reset = false; 13 SerialCallbackContext oldContext = curContext; 14 if (oldContext != null) 15 oldContext.check(); 16 try { 17 curContext = new SerialCallbackContext(obj, slotDesc); 18 19 bin.setBlockDataMode(true); 20 slotDesc.invokeReadObject(obj, this); 21 } catch (ClassNotFoundException ex) { 22 /* 23 * In most cases, the handle table has already 24 * propagated a CNFException to passHandle at this 25 * point; this mark call is included to address cases 26 * where the custom readObject method has cons'ed and 27 * thrown a new CNFException of its own. 28 */ 29 handles.markException(passHandle, ex); 30 } finally { 31 do { 32 try { 33 curContext.setUsed(); 34 if (oldContext!= null) 35 oldContext.check(); 36 curContext = oldContext; 37 reset = true; 38 } catch (ThreadDeath x) { 39 t = x; // defer until reset is true 40 } 41 } while (!reset); 42 if (t != null) 43 throw t; 44 } 45 46 /* 47 * defaultDataEnd may have been set indirectly by custom 48 * readObject() method when calling defaultReadObject() or 49 * readFields(); clear it to restore normal read behavior. 50 */ 51 defaultDataEnd = false; 52 } else { 53 //没有自定义的readObject方法则按默认的解析方法进行解析 54 defaultReadFields(obj, slotDesc); 55 } 56 57 if (slotDesc.hasWriteObjectData()) { 58 skipCustomData(); 59 } else { 60 bin.setBlockDataMode(false); 61 } 62 } else { 63 if (obj != null && 64 slotDesc.hasReadObjectNoDataMethod() && 65 handles.lookupException(passHandle) == null) 66 { 67 slotDesc.invokeReadObjectNoData(obj); 68 } 69 } 70 } 71 }
这里也是先判断是否自定义了反序列化的方法readObject,如果有就按自定义的执行,如果没有就执行默认的反序列化方法defaultReadFields方法执行
1 private void defaultReadFields(Object obj, ObjectStreamClass desc) 2 throws IOException 3 { 4 Class<?> cl = desc.forClass(); 5 if (cl != null && obj != null && !cl.isInstance(obj)) { 6 throw new ClassCastException(); 7 } 8 9 int primDataSize = desc.getPrimDataSize(); 10 if (primVals == null || primVals.length < primDataSize) { 11 primVals = new byte[primDataSize]; 12 } 13 bin.readFully(primVals, 0, primDataSize, false); 14 if (obj != null) { 15 //设置基本数据类型的属性,只包括八大基本数据类型Integer、Long等不在内 16 desc.setPrimFieldValues(obj, primVals); 17 } 18 19 int objHandle = passHandle; 20 ObjectStreamField[] fields = desc.getFields(false); 21 Object[] objVals = new Object[desc.getNumObjFields()]; 22 int numPrimFields = fields.length - objVals.length; 23 for (int i = 0; i < objVals.length; i++) { 24 ObjectStreamField f = fields[numPrimFields + i]; 25 objVals[i] = readObject0(f.isUnshared());//递归设置对象类型属性 26 if (f.getField() != null) { 27 handles.markDependency(objHandle, passHandle); 28 } 29 } 30 if (obj != null) { 31 desc.setObjFieldValues(obj, objVals); 32 } 33 passHandle = objHandle; 34 }
基本上反序列化和序列化的整体逻辑是相反的
3.2、自定义序列化和反序列化
JDK除了支持默认的序列化逻辑,还可以自行定义序列化的方式,只需要对应的类中重写writeObject方法和readObject方法即可,如下案例:
1 /**自定义序列化方法*/ 2 private void writeObject(ObjectOutputStream outputStream) 3 throws IOException 4 { 5 outputStream.writeLong(this.userId); 6 outputStream.writeObject(this.userName); 7 outputStream.writeObject(this.password); 8 } 9 10 /**自定义反序列化方法*/ 11 private void readObject(ObjectInputStream inputStream){ 12 try { 13 this.userId = inputStream.readLong(); 14 this.password = (String)inputStream.readObject(); 15 this.userName = (String)inputStream.readObject(); 16 // this.addr = (String)inputStream.readObject(); 17 } catch (Exception e) { 18 e.printStackTrace(); 19 } 20 }
Tips:采用自定义的序列化方式有以下几点必须满足
1、writeObject和readObject方法必须同时重写,如果只重写一个,那么会抛异常,因为序列化之后的是字节序列,是严格按字节序列的顺序来解析的
2、writeObject和readObject方法中的序列化和反序列化字段必须完全一致,也就是序列化字段的数量和顺序(writeObejct可以多write几个字段,但是顺序必须在后面,readObject不可多字段,否则解析会抛异常)
3、writeObejct和readObject都必须是private修饰的,public修饰的不起作用
3.3、Externalizable接口实现序列化
除了Serializable接口可以实现序列化,实现了Externalizable接口同样可以实现序列化,但是需要实现接口的序列化writeExternal和反序列化方法readExternal方法,如下示例:
1 public class User extends Person implements Externalizable
1 public void writeExternal(ObjectOutput out) throws IOException { 2 out.writeLong(this.userId); 3 out.writeObject(this.password); 4 out.writeObject(this.userName); 5 out.writeObject(this.addr); 6 } 7 8 public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { 9 this.userId = in.readLong(); 10 this.userName = (String) in.readObject(); 11 this.password= (String) in.readObject();//顺序和序列化顺序不一致,两个字段的值会错乱 12 this.addr = (String) in.readObject(); 13 }
3.4、问题总结
3.4.1、序列化之前和反序列化之后的对象可能是同一个对象吗?
可能,反序列化是通过对象的构造方法进行初始化的,所以正常情况下不会是之前的同一个对象,而且序列化之前的对象可能都已经被垃圾回收的;
但是在单例模式下是可以通过自定义反序列化逻辑进行操作的,详情如下第二条。
3.4.2、序列化和反序列化破坏单例模式的解决?
1 public class SingletonUser implements Serializable{ 2 3 private static SingletonUser user = new SingletonUser(); 4 5 public static SingletonUser getInstance(){ 6 return user; 7 } 8 9 public static void main(String[] args){ 10 //1.通过单例类的静态方法获取单例 11 SingletonUser user = SingletonUser.getInstance(); 12 try { 13 //2.序列化单例对象 14 serializer(user); 15 //3.反序列化获取单例对象 16 SingletonUser user2 = derializer(); 17 System.out.println(user); 18 System.out.println(user2); 19 } catch (Exception e) { 20 e.printStackTrace(); 21 } 22 } 23 24 /**序列化对象*/ 25 private static void serializer(SingletonUser user)throws Exception{ 26 ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(new File("/Users/xingwuxu/testlog/user.txt"))); 27 outputStream.writeObject(user); 28 outputStream.close(); 29 } 30 31 /**反序列化对象*/ 32 private static SingletonUser derializer()throws Exception{ 33 ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(new File("/Users/xingwuxu/testlog/user.txt"))); 34 SingletonUser user = (SingletonUser) inputStream.readObject(); 35 inputStream.close(); 36 return user; 37 } 38 }
测试结果为:
1 com.lucky.demo.base.seralizer.demo.SingletonUser@266474c2
2 com.lucky.demo.base.seralizer.demo.SingletonUser@2f4d3709
不出所料,通过序列化之后再反序列化之后的对象不是同一个对象,但是这就破坏了单例模式,因为单例模式就是应该只能创建一个对象的,但是通过序列化操作之后对象就会可以被创建多个。所以此时就需要通过自定义序列化的方式来解决破坏单例模式。
解决办法如下:在单例类中定义方法readResolve方法,并且返回Object对象
private Object readResolve(){ return user; }
测试结果如下:
com.lucky.demo.base.seralizer.demo.SingletonUser@266474c2
com.lucky.demo.base.seralizer.demo.SingletonUser@266474c2
但是如果返回返回的不是Object对象,而是SingletonUser对象,则会无效,如下:
private SingletonUser readResolve(){ return user; }
测试结果为:
com.lucky.demo.base.seralizer.demo.SingletonUser@266474c2
com.lucky.demo.base.seralizer.demo.SingletonUser@2f4d3709
接下来就分析下原理是什么,在反序列化关键类ObjectInputStream类中的readOrdinaryObject方法中在反序列化之后有如下逻辑:
if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod())//判断是否含有readResolve()方法 { Object rep = desc.invokeReadResolve(obj);//通过反射的方式执行对象的readResolve方法 if (unshared && rep.getClass().isArray()) { rep = cloneArray(rep); } if (rep != obj) { // Filter the replacement object if (rep != null) { if (rep.getClass().isArray()) { filterCheck(rep.getClass(), Array.getLength(rep)); } else { filterCheck(rep.getClass(), -1); } } handles.setObject(passHandle, obj = rep);//将通过readResolve创建的对象赋值给obj对象 } }
先判断该类中是否包含有readResolveMethod方法,如果有就通过readResolve方法返回结果,再将返回的结果赋值给通过反序列化生成的对象,所以只需要readResolve方法返回的也是同样的单例对象即可。
3.4.3、实现了Serializable接口的serialVersionUID作用是什么?
在反序列化时,JVM需要知道所属的class文件,在序列化的时候JVM会记录class文件的版本号,默认是JVM自动生成,也可以手动定义。反序列化时JVM会按版本号找指定版本的class文件进行反序列化,
如果class文件有版本号在序列化和反序列化时不一致就会导致反序列化失败,会抛异常提示版本号不一致,如:
1 Exception in thread "main" java.io.InvalidClassException: com.lucky.demo.base.seralizer.demo.User; local class incompatible: stream classdesc serialVersionUID = 512, local class serialVersionUID = 2276725928587165175 2 at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699) 3 at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885) 4 at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751) 5 at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042) 6 at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573) 7 at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431) 8 at com.lucky.demo.base.seralizer.demo.MainTest.derializer(MainTest.java:68) 9 at com.lucky.demo.base.seralizer.demo.MainTest.main(MainTest.java:47)
3.4.4、JDK序列化的整体逻辑流程?
序列化流程:
1、调用ObjectStreamClass.lookup方法得到类的描述对象(保护类的基本数据属性、对象属性、是否包含readObject、writeObject等和序列化相关的方法等)
2、判断对象的类型如果是String、Array、Eunm则直接转化为字节序列,如果是对象类型(实现了Serializable接口的属性)则执行writeOrdinaryObject方法
3、如果属性所属的类自定义了writeObject方法则通过反射的方式invoke自定义的writeObject方法,如果没有自定义则按默认的序列化方法defaultWriteFields方法
4、递归所有属性,如果是基本数据类型则直接写入流中,如果不是基本数据类型则递归属性继续执行writeObject方法写入流中
5、最终的结果是将对象转化成二进制流中,所以需要依次按顺序将对象的信息写入到流中
反序列化流程:
1、从字节流中读取字节,判断字节对应所属的类型(Class、String、Object等等)
2、根据不同的类型,继续读取对应的字节数据,如果不是对象类型那么就可以直接解析出基本数据类型的数据
3、如果是对象类型,先读取字节TC_CLASSDESC,从而解析读取并解析出ObjectStreamClass对象,也就是类的描述信息对象
4、通过ObjectStreamClass可以得到类的构造方法,并通过构造器的newInstance初始化对象,然后开始给属性赋值
5、判断是否有自定义readObject,有的话通过反射执行readObject方法反序列化,如果没有则按默认的反序列化方法defaultReadFields方法
6、依次从流中读取属性的值进行赋值,如果是对象类型则递归继续执行readObject方法进行读取流操作
3.4.5、JDK序列化都保存了对象的哪些内容?
对象的属性、父类的可序列化属性、类的描述信息(类名称、字段、构造方法、自定义序列化相关方法等)
3.4.6、实现细节
序列化主要使用到了ObjectOutputStream类的writeObject方法来实现,ObjectOutputStream内部有一个BlockDataOutputStream类型,该类是真正存储序列化数据的类。
BlockDataOutoutStream内部有一个byte[] buf属性用于存储序列化的二进制字节数组。
当进行序列化对象时,依次向字节数组写入数据,写入之前会先写入标记。比如当对象是字符串时,则开始就需要写入标记 TC_STRING,表示当前序列化数据是一个字符串。如果是对象,就会先写入标记TC_OBJECT。
另外如果需要写入类的描述时同样会写入标记TC_CLASSDESC,这样再解析的时候就可以通过解析标记来进行读取对应的字节数组进行反序列化。
四、各种序列化方式的对比
目前主流的序列化方式包括JDK序列化、Hessian、JSON、Kryo、ProtoBuf等,对比如下:
JDK | Hessian | JSON | Kryo | ProtoBuf | |
序列化数据大小 | 很大 | 较小 | 较大 | 很小 | 很小 |
序列化速度 | 很慢 | 较快 | 较快 | 很快 | 很快 |
总结 |
无论是序列化的效率还是序列化的数据大小都没有竞争力, 另外也不支持跨语言,唯一的优点是不需要依赖第三方Jar包就可以实现 |
效率比JDK要好,数据大小也比JDK要小, 且均比JSON要好。且可以跨语言 |
效率和数据量大小相比于JDK序列化优化了很多,但是整体效率不高。 优点是序列化后的数据是字符串,可观性较好且支持跨语言 |
效率非常高,序列化数据非常小,基本上和ProtoBuf整体性能一致,但是不支持跨语言。 |
效率非常高,序列化数据非常小, 但是需要提前定义序列化的数据结构,且提前静态编译, 使用成本变高 |
总结:
JDK序列化:JDK序列化会把对象类的描述和所有属性的元数据都序列化为字节流,另外继承的元数据也会序列化,所以导致序列化的元素较多且字节流很大,但是由于序列化了所有信息所以相对而言更可靠。但是如果只需要序列化属性的值时就比较浪费。
Hessian序列化:Hessian序列化将基本数据类型进行了标记,如Integer类型标记为I,字符串类型标记为S,Map类型标记为M,类描述信息标记为C,通过各种标记大大简化了属性类信息的序列化过程。另外对于自定义类型如果已经序列化过,会用R进行标记,并
加上引用的位置。通常类的描述信息比字段的值信息要大,所以通过简化类的描述信息来大幅度提升了序列化的效果以及字节流的减少。
Kryo序列化:Kryo序列化使用之前可以对涉及到的Class进行注册,注册之后Kryo对象会缓存Class的注册信息,后续序列化和反序列化时都只需要写入Class注册之后绑定的ID即可,比完整的Class信息会效率高很多。但是在注册的时候必须保证注册的顺序不允许发生改变,特别是在集群部署的情况下。A服务器注册的顺序和B服务器注册的顺序不一样,那么A服务器序列化的数据B服务器是无法反序列化的。如果需要新增注册的类只能在后面继续添加注册的类信息。