基本概念
Java 中创建对象时,一旦程序终止,创建的对象可能就不存在.要想使得对象能够在程序不运行的状态下依然能够保存对象的信息,这时就需要用到序列化机制 序列化机制: 一个对象可以被表示为一个字节序列,包括: 对象的数据 对象的类型信息 存储在对象中的数据类型 将可序列化对象写入文件后,可以从文件中读取出来,根据对象的各种信息在内存中创建该对象. 这里的读取并创建对象的过程就是反序列化 序列化和反序列化的整个过程都是 JVM 独立的.也就是说,在一个JVM中的序列化对象可以在另一个完全不同的 JVM 中反序列化对象 一般情况下,序列化需要实现 java.io.Serializable 接口,使用 ObjectInputStream 和 ObjectOutputStream 进行对象的读写操作 还可以实现 java.io.Externalizable 接口,进行标准的序列化或者自定义的二进制格式.用来满足不同场景下的需求 Java序列化场景: 将 Java 对象的字节序列持久化到硬盘中 在网络上传输对象的字节序列 进行远程方法调用 RMI(Remote Method Invocation) JVM 运行结束时,还需要使用创建的对象 需要将创建的对象保存下来以便后续的传输 使得旧 JVM 创建的对象能够在一个新的 JVM 中运行 Java序列化注意点: 对象的序列化保存的是对象成员变量对象,对象的序列化不会关注类中的静态变量 类的序列化要保证类的所有属性都是可以序列化的,如果想要某个属性不被序列化.可以声明为瞬时态 transient
序列化
Java对象序列化: 使得可序列化的对象实现 Serializable 接口 创建一个 ObjectOutputStream 输出流 调用 ObjectOutputStream 对象的 writeObject() 方法进行输出可序列化对象即可 序列化示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class Person implements Serializable { private String name; private int age;
public Person() { System.out.println( "无参构造..." ); }
public Person(String name, int age) { this .name = name; this .age = age; System.out.println( "有参构造..." ); }
@Override public String toString() { return "Person{" + "name='" + name + "\'" + ", age='" + age + "\'" "}" ; } } |
1 2 3 4 5 6 7 8 9 |
public class SerializableTest { public static void main(String[] args) throws IOException, ClassNotFoundException { Person person = new Person( "Lily" , 20 );
ObjectOutputStream oos = new ObjectOutputStream( new FileOutputStream( "Person.txt" )); oos.writeObject(person); oos.close(); } } |
反序列化
Java对象反序列化: 创建一个 ObjectInputStream 输入流 调用 ObjectInputStream 对象的 readObject() 方法得到序列化对象
1 2 3 4 5 6 7 |
public class DeserializableTest { public static void main(String[] args) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream( new FileInputStream( "Person.txt" )); Pesron person = (Person)ois.readObject(ois); System.out.println(person); } } |
反序列化的对象是由 JVM 生成的对象,而不是通过类的构造函数生成的: 反序列化对象时 ,JVM 中要存在对象对应的类,否则会抛出 ClassNotFoundException 异常 如果一个可序列化的类的成员不是基本类型,而是一个引用类型时,那么这个引用类型必须实现 Serializable 接口,否则会抛出 NotSerializableException 异常
序列化和反序列化总结
实现 Serializable 接口就可以进行序列化的原因: writeObject(): 首先会处理之前被编写的以及不可替换的对象 如果有的对象被替换了,则检查被替换的对象 最后如果对象都被替换了,则进行原始的检查 原始的检查即检查被替换的对象类型是否为 String 类型,数组类型 ,Enum 类型或者实现了 Serializable 接口,符合条件就可以对检查的对象执行相应的序列化操作,否则将会抛出 NotSerializableException 异常 对于序列化的机制来说,如果对同一个对象执行多次序列化操作时,不会得到多个对象 保存到磁盘的对象都有一个序列化编号,当程序试图进行序列化时,会检查该对象是否已经序列化 只有对象从未被序列化过时,才会将此对象序列化为字节序列,如果对象已经序列化过,那么直接输出序列化编号 Java 序列化机制不会重复序列化同一个对象,会记录已经序列化对象的编号,此时如果序列化了一个可变对象后,如果更改了对象的内容会再次进行序列化.如果没有更改内容,则不会将此对象转换为字节序列,只会保存序列化编号 实现 Serializable 接口时可以重写 writeObject() 方法和 readObject() 方法: 重写 writeObject() 方法和 readObject() 方法后,对象进行序列化和反序列化时,就会自动调用重写的 writeObject() 方法和 readObject() 方法 实现 Externalizable 接口时可以重写 writeExternal() 方法和 readExternal() 方法: 重写 writeExternal() 方法和 readExternal() 方法后,对象进行序列化和反序列化时,就会自动调用重写的 writeExternal() 方法和 readExternal() 方法
自定义序列化策略
Externalizable 如果需要使得对象的一部分可以被序列化,另一部分数据不被序列化,此时可以自定义实现 Externalizable 接口,并且实现 writeExternal() 和 readExternal() 方法,可以在序列化和反序列化过程中自动调用来执行一些特殊的操作 注意点: Serializable 接口实现的对象是与二进制的构建有关的,不会调用构造器 Externalizable 接口实现的对象的所有构造函数都会被调用,所以要编写出类的无参和有参构造函数 使用 Externalizable 自定义序列化示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
public class CustomExternal implements Externalizable {
private String name;
private int code;
public CustomExternal() { System.out.println( "无参构造..." ); }
public CustomExternal(String name, int code) { this .name = name; this .code = code; System.out.println( "有参构造..." ); }
@Override public void writeExternal(ObjectOutput out) throws IOException { System.out.println( "执行writeExternal()方法..." ); out.writeObject(name); out.writeInt(code); }
@Override public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException { System.out.println( "执行readExternal()方法..." ); name = (String) in.readObject(); code = in.readInt(); }
@Override public String toString() { return "类:" + name + code; }
public static void main(String[] args) throws IOException, ClassNotFoundException { CustomExternal custom = new CustomeExternal( "oxford" , 666 ); System.out.println(custom);
// 序列化 ObjectOutputStream out = new ObjectOutputStream( new FileInputStream( "oxford.txt" )); System.out.println( "序列化对象..." ); out.writeObject(custom); out.close();
// 反序列化 ObjectInputStream in = new ObjectInputStream( new FileInputStream( "oxford.txt" )); System.out.println( "反序列化对象..." ); custom = (CustomExternal) in.readObject(); System.out.println(custom); in.close(); } } |
有参构造...
类:oxford666
序列化对象...
执行writeExternal()方法...
反序列化对象...
无参构造...
执行readExternal()方法...
类:oxford666
transient 可以使用 transient 关键字配置一些重要的信息比如密码等不进行序列化 transient 关键字修饰的属性不会参与到序列化过程中 transient 关键字修饰的属性在反序列化过程中,如果是引用数据类型,则返回 null. 如果是基本数据类型,则返回默认值.不一定是基本数据类型序列化之前的值 因为实现 Externalizable 接口的对象默认情况下不会保存任何字段,所以 transient 关键字只能和 Serializable 对象一起使用 transient 关键字的使用场景: 服务器端给客户端发送序列化对象数据时,对象中存在敏感数据 比如密码字符串,在序列化时进行加密,客户端拥有解密的密钥,只有在客户端进行反序列化时,才会对密码进行读取 这时就可以对密码字符串对象使用 transient 修饰,这样可以一定程度上保证序列化对象的数据安全
静态变量
序列化时不会序列化静态变量 静态变量属于类的状态,序列化中保存的是对象,也就是类的实例的状态 序列化操作的是序列化中对象,也就是类的实例的状态,静态变量属于类的状态.所以序列化时不会对静态变量进行序列化
序列化ID
Java 虚拟机进行反序列化: 两个类的类路径和功能代码一致 两个类的序列化ID,也就是serialVersionUID一致 功能代码一致: 序列化的类和反序列化的类所实现的功能和功能相关的代码是一样的 示例: 客户端 A 将类对象序列化客户端 B, 客户端 B 进行反序列化 这时要求 A 和 B 都有这样的一个类文件,功能代码一致,并且都实现了 Serializable 接口 serialVersionUID 的两种生成方式: 默认值 1L 通过类名,接口名和方法名以及属性随机生成的一个不重复的 long 类型的值 在序列化 ID, 即 serialVersionUID 相同的情况下,即使序列化对象的序列化属性修改,序列化对象也可以进行反序列化.因此如果只是修改了方法或者修改了静态变量或 transient 变量,只要不修改序列化 ID, 那么反序列化就不会受到影响 显式声明序列化 ID, 即 serialVersionUID 的场景: 如果需要类的不同版本对序列化兼容,要确保类的不同版本具有相同的 serialVersionUID 如果不需要类的不同版本对序列化兼容,要确保类的不同版本具有不同的 serialVersionUID 序列化一个类的实例后,如果修改一个字段或者增加一个字段,如果没有设置类的 serialVersionUID, 就会导致无法反序列化旧的实例,会在反序列化时抛出异常 序列化类添加 SerialVersionUID 后,如果修改一个字段或者增加一个字段,反序列化旧的实例时,修改的或者增加的字段的值会设置为初始化的值
破坏单例
除了反射可以破坏单例模式外,序列化和反序列化后会得到一个新的对象,也可以破坏单例模式 序列化和反序列化破坏单例模式: 反序列化时,使用 ObjectInputStream 对象中的 readObject() 方法 readObject() 的方法中调用 readObject0() 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public final Object readObject() throws IOException, ClassNotFoundException { if (enableOverride) { return readObjectOverride(); }
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(); } } } |
readObject0() 方法中会返回一个 checkResolve(readOrdinaryObject(unshared))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
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) { throw new OptionalDataException( true ); } bin.setBlockDataMode( false ); }
byte tc; while ((tc = bin.peekByte()) == TC_RESET) { bin.readByte(); handleReset(); }
depth++; totalObjectRefs++; try { 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(); 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); } } |
readOrdinaryObject() 方法用于读取并返回普通对象. 这里的普通对象不包括 String, Class, ObjectStreamClass, 数组或者枚举常量这些对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
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 { 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()) { readExternalData((Externalizable) obj, desc); } else { 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; } |
isInstantiable() 方法表示如果一个实现了 Serializable 接口或者 Externalizable 接口的类可以在运行时实例化,那么该方法就返回 true 如果可以在运行时序列化,就会调用 desc.newInstance() 方法使用反射的方式调用无参构造方法新建一个对象,创建一个类的新的实例 如果类实现的是 Serializable 接口,就调用第一个不可进行序列化超类的无参构造方法数创建新的实例 如果类实现的 Externalizable 接口,就调用公共的无参构造方法创建新的实例 因为序列化过程中会通过反射调用无参构造函数创建一个新的对象,所以序列化会破坏单例模式 为了防止序列化破坏单例模式,可以在 Singleton.java 中添加 readResolve() 方法并且指定要返回的对象的生成策略 因为 readOrdinaryObject() 方法源码中的 hasResolveMethod() 表示如果实现了 Serializable 或者 Externalizable 接口的类中包含 readResolve() 方法就返回 true i nvokeReadResolve() 方法会通过反射的方式调用要被反序列化的类的 readResolve() 方法
总结
到此这篇关于Java基础入门之序列化和反序列化的文章就介绍到这了,更多相关Java序列化和反序列化内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!
原文链接:https://juejin.cn/post/7068277615689367589
查看更多关于Java基础入门总结之序列化和反序列化的详细内容...