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分别如下
public class Home implements Serializable {
private String address; public String getAddress() {
return address;
} public void setAddress(String address) {
this.address = address;
}
}
public class School {
private String schoolName; public String getSchoolName() {
return schoolName;
} public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
}
public class Person implements Serializable { public static String parentType = "Person"; //父类静态变量 private String sex;//性别 public String getSex() {
return sex;
} public void setSex(String sex) {
this.sex = sex;
}
}
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的对象属性,序列化测试代码如下:
public class MainTest {
public static void main(String[] args) throws Exception{
User user = new User();
user.setAge(10);
user.setBirth(new Date());
user.setPassword("123456");
user.setUserName("Jack");
user.setUserId(100L);
user.setSex("男");
user.setIDCard("131313131313113");
user.parentType = "son";//修改父类静态变量
user.alive = true; //修改User类的静态变量 Home home = new Home();
home.setAddress("中国浙江");
School school = new School();
school.setSchoolName("清华大学");
user.setHome(home);//设置对象属性
// user.setSchool(school);//设置对象属性 (因为School类没有实现Seriliazable接口,所以如果设置就会报错) List<User> friends = new ArrayList<User>();
User userF = new User();
userF.setUserId(101L);
userF.setUserName("Friend");
friends.add(userF);
user.setFriends(friends); //序列化
serializer(user);
//反序列化
User newUser = derializer();
//验证
System.out.println("验证两个对象是否相等");
System.out.println("原对象地址:"+user.toString());
System.out.println("新对象地址:"+newUser.toString());
System.out.println("******************");
System.out.println("打印两个对象");
System.out.println("原对象数据:"+JSON.toJSON(user).toString());
System.out.println("新对象数据:"+JSON.toJSON(newUser).toString());
} /**序列化对象*/
private static void serializer(User user)throws Exception{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(new File("/Users/xxw/testlog/user.txt")));
outputStream.writeObject(user);
outputStream.close();
} /**反序列化对象*/
private static User derializer()throws Exception{
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(new File("/Users/xxw/testlog/user.txt")));
User user = (User) inputStream.readObject();
inputStream.close();
return user;
}
}
测试结果为:
验证两个对象是否相等
原对象地址:com.lucky.demo.base.seralizer.demo.User@3764951d
新对象地址:com.lucky.demo.base.seralizer.demo.User@4783da3f
******************
打印两个对象
原对象数据:{"iDCard":"131313131313113","password":"123456","sex":"男","birth":1573203467764,"userName":"Jack","userId":100,"age":10,"friends":[{"userName":"Friend","userId":101,"age":0}],"home":{"address":"中国浙江"}}
新对象数据:{"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接口,那么序列化的时候不会报错,但是父类中的属性在反序列化之后字段就会没有,结果如下:
打印两个对象
原对象数据:{"iDCard":"131313131313113","password":"123456","sex":"男","birth":1573203839905,"userName":"Jack","userId":100,"age":10,"friends":[{"userName":"Friend","userId":101,"age":0}],"home":{"address":"中国浙江"}}
新对象数据:{"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接口的作用就是起到一个标识的作用,源码如下
public interface Serializable {
}
Serializable接口既然是标识的作用,那么就需要在实际序列化操作的时候进行识别,而实际的序列化操作是通过ObjectOutputStream 和 ObjectInputStream实现的,那么接下来就看下这两个类的是如何实现序列化和反序列化的
3.1.2.1、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;
}
}
OutoutStream表示保存的二进制流,也就是将序列化的对象保存到这个二进制流中,再看下具体的序列化方法源码如下:
public final void writeObject(Object obj) throws IOException {
if (enableOverride) {//enableOverride 表示是否可以被覆盖,默认为false
writeObjectOverride(obj);
return;
}
try {
//执行具体的序列化操作
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0) {
writeFatalException(ex);
}
throw ex;
}
}
最终执行了writeObject0(obj, false)方法,代码如下:
private void writeObject0(Object obj, boolean unshared)
throws IOException
{
boolean oldMode = bout.setBlockDataMode(false);
depth++;
try {
// handle previously written and non-replaceable objects
// 处理已经处理过的和不可替换的对象,这些是不可序列化的
int h;
if ((obj = subs.lookup(obj)) == null) {
writeNull();
return;
} else if (!unshared && (h = handles.lookup(obj)) != -1) {
writeHandle(h);
return;
} else if (obj instanceof Class) {
writeClass((Class) obj, unshared);
return;
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
return;
} // check for replacement object
Object orig = obj;
//获取对象的Class对象
Class<?> cl = obj.getClass();
ObjectStreamClass desc;
for (;;) {
// REMIND: skip this check for strings/arrays?
Class<?> repCl;
//获取Class的描述信息,并且判断是否是Serializable接口
desc = ObjectStreamClass.lookup(cl, true);
if (!desc.hasWriteReplaceMethod() ||
(obj = desc.invokeWriteReplace(obj)) == null ||
(repCl = obj.getClass()) == cl)
{
break;
}
cl = repCl;
} //如果允许被替换的情况
if (enableReplace) {
Object rep = replaceObject(obj);
if (rep != obj && rep != null) {
cl = rep.getClass();
desc = ObjectStreamClass.lookup(cl, true);
}
obj = rep;
} // if object replaced, run through original checks a second time
// 如果对象被替换了,只有是ObjectOutputStream的子类才会出现
if (obj != orig) {
subs.assign(orig, obj);
if (obj == null) {
writeNull();
return;
} else if (!unshared && (h = handles.lookup(obj)) != -1) {
writeHandle(h);
return;
} else if (obj instanceof Class) {
writeClass((Class) obj, unshared);
return;
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
return;
}
} // remaining cases
/**序列化不同类型的对象,分别序列化String、数组、枚举类型
* 对于Integer、Long等都属于实现了Serializable接口的数据类型*/
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) {//序列化实现了Serializable接口的数据类型
writeOrdinaryObject(obj, desc, unshared);
} else {//抛出不可序列化异常
if (extendedDebugInfo) {
throw new NotSerializableException(
cl.getName() + "\n" + debugInfoStack.toString());
} else {
throw new NotSerializableException(cl.getName());
}
}
} finally {
depth--;
bout.setBlockDataMode(oldMode);
}
}
}
前面都是在做各种检查,实际有效的代码就是从75行开始,分别针对不同类型的对象分别执行不同的序列化方法
writeString方法的逻辑就是将字符串按字节的方式进行序列化,底层就是通过数组复制的方式获取到char[],然后写入到缓存的序列化的byte[]数组中
System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);
writeArray方法的逻辑就是先判断数组的数据类型是什么,如果是基本数据类型之间写入byte数字,如果是对象类型就调用writeObject0方法
writeEnum方法的逻辑是直接写入枚举的值
而对于对象类型是比较复杂的,也就是writeOrdinaryObject方法,逻辑如下:
private void writeOrdinaryObject(Object obj,
ObjectStreamClass desc,
boolean unshared)
throws IOException
{
if (extendedDebugInfo) {
debugInfoStack.push(
(depth == 1 ? "root " : "") + "object (class \"" +
obj.getClass().getName() + "\", " + obj.toString() + ")");
}
try {
//检查ObjectStreamClass对象
desc.checkSerialize(); //写入标记表示是Object类型
bout.writeByte(TC_OBJECT);
//写入Class对象的描述信息
writeClassDesc(desc, false);
handles.assign(unshared ? null : obj);
if (desc.isExternalizable() && !desc.isProxy()) {
//写入实现了Externalizable接口的对象
writeExternalData((Externalizable) obj);
} else {
//写入实现了Serializable
writeSerialData(obj, desc);
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
最终按实现了Externalizable接口或Serializable接口分别执行writeExternalData和writeSerialData方法,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;
//判断该类是否自定义类writeObject方法,如果重写了该方法则按重写的逻辑处理
if (slotDesc.hasWriteObjectMethod()) {
ObjectOutputStream.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);
//通过反射的方式执行自定义的writeObejct方法
slotDesc.invokeWriteObject(obj, this);
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
} finally {
curContext.setUsed();
curContext = oldContext;
if (extendedDebugInfo) {
debugInfoStack.pop();
}
} curPut = oldPut;
} else {
//如果没有自定义writeObject方法则按默认的方法写入属性数据
defaultWriteFields(obj, slotDesc);
}
}
}
先是根据类的描述信息判断是否自定义了序列化方法writeObejct方法,如果自定义了就通过反射执行invokeWriteObejct方法,如果没有自定义则执行defaultWriteFields方法,defaultWriteFields方法逻辑如下:
private void defaultWriteFields(Object obj, ObjectStreamClass desc)
throws IOException
{
Class<?> cl = desc.forClass();
//校验对象的类信息是否和类描述信息一致
if (cl != null && obj != null && !cl.isInstance(obj)) {
throw new ClassCastException();
} //
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();
}
}
}
}
总结:
序列化的整体逻辑就是遍历对象的所有属性,递归执行序列化方法,直到序列化的对象是String、Array或者是Eunm类,则按String、Array、Enum的序列化方式写入字节流中。
3.1.2.2、ObjectInputStream源码解析
ObjectInputStream的逻辑不用看也能猜到是和ObjectOutputStream相反的,反序列化的readObject方法具体逻辑如下:
public final Object readObject()
throws IOException, ClassNotFoundException
{
if (enableOverride) {
return readObjectOverride();
} // if nested read, passHandle contains handle of enclosing object
int outerHandle = passHandle;
try {
//执行反序列化方法
Object obj = readObject0(false);
handles.markDependency(outerHandle, passHandle);
ClassNotFoundException ex = handles.lookupException(passHandle);
if (ex != null) {
throw ex;
}
if (depth == 0) {
vlist.doCallbacks();
}
return obj;
} finally {
passHandle = outerHandle;
if (closed && depth == 0) {
clear();
}
}
}
private Object readObject0(boolean unshared) throws IOException {
boolean oldMode = bin.getBlockDataMode();
if (oldMode) {
int remain = bin.currentBlockRemaining();
if (remain > 0) {
throw new OptionalDataException(remain);
} else if (defaultDataEnd) {
/*
* Fix for 4360508: stream is currently at the end of a field
* value block written via default serialization; since there
* is no terminating TC_ENDBLOCKDATA tag, simulate
* end-of-custom-data behavior explicitly.
*/
throw new OptionalDataException(true);
}
bin.setBlockDataMode(false);
} byte tc;
//从流中读取int数据,序列化时候写入的时候写入属性之前都会写入int值表示属性的类型
while ((tc = bin.peekByte()) == TC_RESET) {
bin.readByte();
handleReset();
} depth++;
totalObjectRefs++;
try {
/**判断读取的int数据表示当前读取的是什么类型的数据结构
* 不同的类型数据分别执行不同的解析方法*/
switch (tc) {
case TC_NULL:
return readNull(); case TC_REFERENCE:
return readHandle(unshared); case TC_CLASS:
return readClass(unshared); case TC_CLASSDESC:
case TC_PROXYCLASSDESC:
return readClassDesc(unshared); case TC_STRING:
case TC_LONGSTRING:
return checkResolve(readString(unshared)); case TC_ARRAY:
return checkResolve(readArray(unshared)); case TC_ENUM:
return checkResolve(readEnum(unshared)); case TC_OBJECT:
return checkResolve(readOrdinaryObject(unshared)); case TC_EXCEPTION:
IOException ex = readFatalException();
throw new WriteAbortedException("writing aborted", ex); case TC_BLOCKDATA:
case TC_BLOCKDATALONG:
if (oldMode) {
bin.setBlockDataMode(true);
bin.peek(); // force header read
throw new OptionalDataException(
bin.currentBlockRemaining());
} else {
throw new StreamCorruptedException(
"unexpected block data");
} case TC_ENDBLOCKDATA:
if (oldMode) {
throw new OptionalDataException(true);
} else {
throw new StreamCorruptedException(
"unexpected end of block data");
} default:
throw new StreamCorruptedException(
String.format("invalid type code: %02X", tc));
}
} finally {
depth--;
bin.setBlockDataMode(oldMode);
}
}
private Object checkResolve(Object obj) throws IOException {
if (!enableResolve || handles.lookupException(passHandle) != null) {
return obj;
}
Object rep = resolveObject(obj);
if (rep != obj) {
// The type of the original object has been filtered but resolveObject
// may have replaced it; filter the replacement's type
if (rep != null) {
if (rep.getClass().isArray()) {
filterCheck(rep.getClass(), Array.getLength(rep));
} else {
filterCheck(rep.getClass(), -1);
}
}
handles.setObject(passHandle, rep);
}
return rep;
}
解析的方法分别是readString()、readArray()、readEnum()、readOrdinaryObject()方法
解析字符串的方法就是直接从流中读取字节,解析数组和枚举的过程差不多,重点是解析对象的逻辑,代码如下:
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
//判断是否是对象类型
if (bin.readByte() != TC_OBJECT) {
throw new InternalError();
} //读取类描述信息对象
ObjectStreamClass desc = readClassDesc(false);
desc.checkDeserialize(); Class<?> cl = desc.forClass();
if (cl == String.class || cl == Class.class
|| cl == ObjectStreamClass.class) {
throw new InvalidClassException("invalid class descriptor");
} Object obj;
try {
/**
* isInstantiable方法是判断是否有public的无参构造方法表示是否可以初始化对象
* 然后通过Constructor的newInstance方法初始化对象
* */
//通过反射执行Class的newInstance方法构造对象
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
} passHandle = handles.assign(unshared ? unsharedMarker : obj);
ClassNotFoundException resolveEx = desc.getResolveException();
if (resolveEx != null) {
handles.markException(passHandle, resolveEx);
} if (desc.isExternalizable()) {
//反序列化实现了Externalizable接口的对象
readExternalData((Externalizable) obj, desc);
} else {
//反序列化实现了Serializable接口的对象
readSerialData(obj, desc);
} handles.finish(passHandle); if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
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);
}
} return obj;
}
和序列化的时候一样,先判断是实现了Externalizable接口还是Serializable接口分别执行不同的逻辑,readSerialData方法逻辑如下:
private void readSerialData(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 (slots[i].hasData) {
if (obj == null || handles.lookupException(passHandle) != null) {
defaultReadFields(null, slotDesc); //没有数据可以解析
} else if (slotDesc.hasReadObjectMethod()) {//如果有自定义的readObejct方法则按自定义的逻辑执行
ThreadDeath t = null;
boolean reset = false;
SerialCallbackContext oldContext = curContext;
if (oldContext != null)
oldContext.check();
try {
curContext = new SerialCallbackContext(obj, slotDesc); bin.setBlockDataMode(true);
slotDesc.invokeReadObject(obj, this);
} catch (ClassNotFoundException ex) {
/*
* In most cases, the handle table has already
* propagated a CNFException to passHandle at this
* point; this mark call is included to address cases
* where the custom readObject method has cons'ed and
* thrown a new CNFException of its own.
*/
handles.markException(passHandle, ex);
} finally {
do {
try {
curContext.setUsed();
if (oldContext!= null)
oldContext.check();
curContext = oldContext;
reset = true;
} catch (ThreadDeath x) {
t = x; // defer until reset is true
}
} while (!reset);
if (t != null)
throw t;
} /*
* defaultDataEnd may have been set indirectly by custom
* readObject() method when calling defaultReadObject() or
* readFields(); clear it to restore normal read behavior.
*/
defaultDataEnd = false;
} else {
//没有自定义的readObject方法则按默认的解析方法进行解析
defaultReadFields(obj, slotDesc);
} if (slotDesc.hasWriteObjectData()) {
skipCustomData();
} else {
bin.setBlockDataMode(false);
}
} else {
if (obj != null &&
slotDesc.hasReadObjectNoDataMethod() &&
handles.lookupException(passHandle) == null)
{
slotDesc.invokeReadObjectNoData(obj);
}
}
}
}
这里也是先判断是否自定义了反序列化的方法readObject,如果有就按自定义的执行,如果没有就执行默认的反序列化方法defaultReadFields方法执行
private void defaultReadFields(Object obj, ObjectStreamClass desc)
throws IOException
{
Class<?> cl = desc.forClass();
if (cl != null && obj != null && !cl.isInstance(obj)) {
throw new ClassCastException();
} int primDataSize = desc.getPrimDataSize();
if (primVals == null || primVals.length < primDataSize) {
primVals = new byte[primDataSize];
}
bin.readFully(primVals, 0, primDataSize, false);
if (obj != null) {
//设置基本数据类型的属性,只包括八大基本数据类型Integer、Long等不在内
desc.setPrimFieldValues(obj, primVals);
} int objHandle = passHandle;
ObjectStreamField[] fields = desc.getFields(false);
Object[] objVals = new Object[desc.getNumObjFields()];
int numPrimFields = fields.length - objVals.length;
for (int i = 0; i < objVals.length; i++) {
ObjectStreamField f = fields[numPrimFields + i];
objVals[i] = readObject0(f.isUnshared());//递归设置对象类型属性
if (f.getField() != null) {
handles.markDependency(objHandle, passHandle);
}
}
if (obj != null) {
desc.setObjFieldValues(obj, objVals);
}
passHandle = objHandle;
}
基本上反序列化和序列化的整体逻辑是相反的
3.2、自定义序列化和反序列化
JDK除了支持默认的序列化逻辑,还可以自行定义序列化的方式,只需要对应的类中重写writeObject方法和readObject方法即可,如下案例:
/**自定义序列化方法*/
private void writeObject(ObjectOutputStream outputStream)
throws IOException
{
outputStream.writeLong(this.userId);
outputStream.writeObject(this.userName);
outputStream.writeObject(this.password);
} /**自定义反序列化方法*/
private void readObject(ObjectInputStream inputStream){
try {
this.userId = inputStream.readLong();
this.password = (String)inputStream.readObject();
this.userName = (String)inputStream.readObject();
// this.addr = (String)inputStream.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}
Tips:采用自定义的序列化方式有以下几点必须满足
1、writeObject和readObject方法必须同时重写,如果只重写一个,那么会抛异常,因为序列化之后的是字节序列,是严格按字节序列的顺序来解析的
2、writeObject和readObject方法中的序列化和反序列化字段必须完全一致,也就是序列化字段的数量和顺序(writeObejct可以多write几个字段,但是顺序必须在后面,readObject不可多字段,否则解析会抛异常)
3、writeObejct和readObject都必须是private修饰的,public修饰的不起作用
3.3、Externalizable接口实现序列化
除了Serializable接口可以实现序列化,实现了Externalizable接口同样可以实现序列化,但是需要实现接口的序列化writeExternal和反序列化方法readExternal方法,如下示例:
public class User extends Person implements Externalizable
public void writeExternal(ObjectOutput out) throws IOException {
out.writeLong(this.userId);
out.writeObject(this.password);
out.writeObject(this.userName);
out.writeObject(this.addr);
} public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
this.userId = in.readLong();
this.userName = (String) in.readObject();
this.password= (String) in.readObject();//顺序和序列化顺序不一致,两个字段的值会错乱
this.addr = (String) in.readObject();
}
3.4、问题总结
3.4.1、序列化之前和反序列化之后的对象可能是同一个对象吗?
可能,反序列化是通过对象的构造方法进行初始化的,所以正常情况下不会是之前的同一个对象,而且序列化之前的对象可能都已经被垃圾回收的;
但是在单例模式下是可以通过自定义反序列化逻辑进行操作的,详情如下第二条。
3.4.2、序列化和反序列化破坏单例模式的解决?
public class SingletonUser implements Serializable{ private static SingletonUser user = new SingletonUser(); public static SingletonUser getInstance(){
return user;
} public static void main(String[] args){
//1.通过单例类的静态方法获取单例
SingletonUser user = SingletonUser.getInstance();
try {
//2.序列化单例对象
serializer(user);
//3.反序列化获取单例对象
SingletonUser user2 = derializer();
System.out.println(user);
System.out.println(user2);
} catch (Exception e) {
e.printStackTrace();
}
} /**序列化对象*/
private static void serializer(SingletonUser user)throws Exception{
ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(new File("/Users/xingwuxu/testlog/user.txt")));
outputStream.writeObject(user);
outputStream.close();
} /**反序列化对象*/
private static SingletonUser derializer()throws Exception{
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(new File("/Users/xingwuxu/testlog/user.txt")));
SingletonUser user = (SingletonUser) inputStream.readObject();
inputStream.close();
return user;
}
}
测试结果为:
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文件有版本号在序列化和反序列化时不一致就会导致反序列化失败,会抛异常提示版本号不一致,如:
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
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
at com.lucky.demo.base.seralizer.demo.MainTest.derializer(MainTest.java:68)
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序列化都保存了对象的哪些内容?
对象的属性、父类的可序列化属性、类的描述信息(类名称、字段、构造方法、自定义序列化相关方法等)
除了JDK自带的序列化方式,还有其他的几种序列化方式分别整理如下:
JAVA基础4---序列化和反序列化深入整理(JSON序列化)
JAVA基础4---序列化和反序列化深入整理(Hessian序列化)
JAVA基础4---序列化和反序列化深入整理(Kryo序列化)
JAVA基础4---序列化和反序列化深入整理(JDK序列化)的更多相关文章
- java基础(三):反射、反序列化破解单列模式和解决方式
单例模式指的是一个类只有一个对象,通过一些措施达到达到这个目的.但是反射和反序列化可以获得多个不同的对象. 先简单的认识一下单例模式 一:单例模式 通过私有构造器,声明一个该类的静态对象成员,提供一个 ...
- PHP序列化以及反序列化系列[1]--PHP序列化格式的写法
反序列化:对单一的已序列化的变量进行操作,将其转换回 PHP 的值(zval). PHP序列化方式 PHP在序列化的时候会将相应的变量以对应的键值进行储存. 将一个类序列化的话,处理代码主要的 文件: ...
- Java基础知识➣环境搭建与类型整理(一)
概述 公司业务需要,产品既要有.NET又需要Java,没得选择,只能业余时间学习Java,整体觉得Java也.NET还是很相似的,只是语法有差别,差别也不是很大,这就将学习Java的基础知识整理下,以 ...
- Java基础语法面试题50题整理(带答案)
嗯,之前某些原因整理了这份面试题,加油生活 (: 0,嗯,先做简单的,那个不对() 1,int [] sy = {0,9,2,3}; 2,int [] sy1 = new int[4]; 3,in ...
- Java基础构造方法和this关键字整理
构造方法 8.1构造方法介绍 构造方法的格式: 修饰符 构造方法名(参数列表) { } l 构造方法的体现: n 构造方法没有返回值类型.也不需要写返回值.因为它是为构建对象的,对象创建完,方法就 ...
- JAVA基础学习——1.1 环境搭建 之jdk安装,环境变量配置 (系统Win10,64bit)
大学里虽然老师教过JAVA,但我没学.后来工作了,断断续续的也碰到了JAVA的项目,都是拉过来就干的节奏.也没有好好系统的学习一下. 从今天开始系统学习整理一下JAVA,以后再碰到JAVA项目的时候, ...
- json数组的序列化和反序列化json数组的序列化和反序列化
如题,我就不多说了,自己看代码的,很好理解 using System; using System.Collections.Generic; using System.Web; using System ...
- 【Java基础】序列化与反序列化深入分析
一.前言 复习Java基础知识点的序列化与反序列化过程,整理了如下学习笔记. 二.为什么需要序列化与反序列化 程序运行时,只要需要,对象可以一直存在,并且我们可以随时访问对象的一些状态信息,如果程序终 ...
- Java基础IO流(四)序列化与反序列化
对象的序列化与反序列化: 对象的序列化,就是将Object转换成byte序列,反之叫对象的反序列化. 序列化流(ObjectOutInputStream),是过滤流 -------writeObjec ...
- Java基础—序列化与反序列化(转载)
转载自: Java序列化与反序列化 1.Java序列化与反序列化 Java序列化是指把Java对象转换为字节序列的过程:而Java反序列化是指把字节序列恢复为Java对象的过程. 2.为什么需要序列化 ...
随机推荐
- jenkins及Maven介绍
一.环境介绍 随着软件开发需求及复杂度的不断提高,团队开发成员之间如何更好地协同工作以确保软件开发的质量已经慢慢成为开发过程中不可回避的问题.Jenkins自动化部署可以解决集成.测试.部署等重复性的 ...
- jdk 的 安装以及环境变量配置
第一步:下载jdk 下载地址:https://www.oracle.com/technetwork/java/javase/downloads/index.html 第二步:安装jdk 第三步:配置环 ...
- SpringBoot系列(十二)过滤器配置详解
SpringBoot(十二)过滤器详解 往期精彩推荐 SpringBoot系列(一)idea新建Springboot项目 SpringBoot系列(二)入门知识 springBoot系列(三)配置文件 ...
- 标准库sys
sys模块的主要函数介绍,结合官方文档说明和实例.This module provides access to some variables used or maintained by the int ...
- 【Linux常见命令】ls命令
ls - list directory contents ls命令用于显示指定工作目录下之内容(列出目前工作目录所含之文件及子目录). 语法: ls [OPTION]... [FILE]... l ...
- dhcpd.conf(5) - Linux man page
http://linux.die.net/man/5/dhcpd.conf Name dhcpd.conf - dhcpd configuration file Description The d ...
- [转]Git详解之四 服务器上的Git
服务器上的 Git 到目前为止,你应该已经学会了使用 Git 来完成日常工作.然而,如果想与他人合作,还需要一个远程的 Git 仓库.尽管技术上可以从个人的仓库里推送和拉取修改内容,但我们不鼓励这样做 ...
- 硬纪元AI峰会前瞻:如何才能做好智能家居?用户体验最重要
用户体验不到位,市场就不能说真的发展起来. 可以明显的感觉到,随着人工智能.物联网等技术的发展和应用,我们的生活正在发生翻天覆地的变化,其中感觉最为明显的就是智能家居. 据前瞻产业研究院的数据统计,我 ...
- ACM及各类程序竞赛专业术语
AC (Accepted) 程序通过 WA (Wrong Answer) 错误的答案 PE (Presentation Error) 输出格式错误 RE (Runtime Error) 程序执行错误 ...
- CF--思维练习--CodeForces - 219C Color Stripe (思维)
ACM思维题训练集合 A colored stripe is represented by a horizontal row of n square cells, each cell is paine ...