java序列化和反序列化理解,java序列化和反序列化两种方式

  java序列化和反序列化理解,java序列化和反序列化两种方式

  00-1010基本概念序列化反序列化序列化和反序列化摘要自定义序列化策略可具体化瞬态静态变量序列化ID销毁单一案例摘要

  00-1010当在Java,创建对象时,一旦程序终止,所创建的对象可能不存在。如果希望对象即使在程序不运行时也能保存对象的信息,那么就需要使用序列化机制序列化机制:.一个对象可以表示为一个字节序列。包括:个对象的数据对象的类型信息;存储在对象中的数据类型;可序列化对象写入文件后,可以根据对象的各种信息从文件中读取并在内存中创建。这里读取和创建一个对象的过程就是反序列化,整个反序列化过程是独立于JVM.的也就是说,一个JVM中序列化的对象可以在另一个完全不同的JVM.中反序列化一般,序列化需要实现java.io.Serializable接口,使用ObjectInputStreamObjectOutputStream读写对象也可以实现java.io.Externalizable接口,标准序列化或者自定义二进制格式。用来满足不同场景的需求。Java序列化场景:Java对象的字节序列保存到硬盘上,并在网络上把对象的字节序列传输给RMI(Remote Method Invocation)JVM.进行远程方法调用。在运行结束时,你仍然需要使用创建的对象。您需要保存创建的对象以供后续传输,以便旧JVM创建的对象可以在新JVM.中运行Java序列化注意点:对象的序列化保存对象成员变量对象,对象的序列化不注意类中静态变量的序列化。为了确保类的所有属性都可以序列化,如果不想序列化某个属性,可以将其声明为瞬态transient.

  00-1010Java对象序列化:使可序列化对象实现Serializable接口,创建ObjectOutputStream输出流来调用ObjectOutputStream对象的writeObject()。方法输出可序列化的对象,即序列化示例:公共类person实现可序列化的{ private stringname私人年龄;public person(){ system . out . println(不带参数的构造.);}public Person(String name,int age){ this . name=name;this.age=年龄;System.out.println(带参数的构造.);} @ override public String to String(){ return Person { name= name ,age= age }} } public class serializable test { public static void main(String[]args)抛出IOException,ClassNotFoundException { Person Person=new Person( Lily ,20);ObjectOutputStream OOS=new ObjectOutputStream(new file output stream( person . txt ));OOS . writeobject(person);OOS . close();}}

  00-1010Java对象反序列化:创建一个ObjectInputStream输入流,调用ObjectInputStream对象的readObject()方法获取序列化对象公共类反序列化测试{ public static void main(string[]args)抛出io exc。

  eption, 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自定义序列化示例:
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("有参构造...");}@Overridepublic void writeExternal(ObjectOutput out) throws IOException {System.out.println("执行writeExternal()方法...");out.writeObject(name);out.writeInt(code);} @Overridepublic void readExternal(ObjectInput in) throws IOException,ClassNotFoundException {System.out.println("执行readExternal()方法...");name = (String) in.readObject();code = in.readInt();}@Overridepublic 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

  

使用Externalizable自定义序列化时,为了保证序列化和反序列化的正确性,需要在writeExternal() 方法中将信息写入,并且在readExternal() 方法中恢复数据

  

transient

可以使用transient关键字配置一些重要的信息比如密码等不进行序列化transient关键字修饰的属性不会参与到序列化过程中transient关键字修饰的属性在反序列化过程中,如果是引用数据类型,则返回null. 如果是基本数据类型,则返回默认值.不一定是基本数据类型序列化之前的值因为实现Externalizable接口的对象默认情况下不会保存任何字段,所以transient关键字只能和Serializable对象一起使用transient关键字的使用场景:服务器端给客户端发送序列化对象数据时,对象中存在敏感数据比如密码字符串,在序列化时进行加密,客户端拥有解密的密钥,只有在客户端进行反序列化时,才会对密码进行读取这时就可以对密码字符串对象使用transient修饰,这样可以一定程度上保证序列化对象的数据安全

  

静态变量

序列化时不会序列化静态变量静态变量属于类的状态,序列化中保存的是对象,也就是类的实例的状态序列化操作的是序列化中对象,也就是类的实例的状态,静态变量属于类的状态.所以序列化时不会对静态变量进行序列化

  

序列化ID

Java虚拟机进行反序列化:两个类的类路径和功能代码一致两个类的序列化ID,也就是serialVersionUID一致功能代码一致:序列化的类和反序列化的类所实现的功能和功能相关的代码是一样的示例:客户端A将类对象序列化客户端B, 客户端B进行反序列化这时要求AB都有这样的一个类文件,功能代码一致,并且都实现了Serializable接口serialVersionUID的两种生成方式:默认值 1L通过类名,接口名和方法名以及属性随机生成的一个不重复的long类型的值在序列化ID,serialVersionUID相同的情况下,即使序列化对象的序列化属性修改,序列化对象也可以进行反序列化.因此如果只是修改了方法或者修改了静态变量或transient变量,只要不修改序列化ID, 那么反序列化就不会受到影响显式声明序列化ID,serialVersionUID的场景:如果需要类的不同版本对序列化兼容,要确保类的不同版本具有相同的serialVersionUID如果不需要类的不同版本对序列化兼容,要确保类的不同版本具有不同的serialVersionUID序列化一个类的实例后,如果修改一个字段或者增加一个字段,如果没有设置类的serialVersionUID, 就会导致无法反序列化旧的实例,会在反序列化时抛出异常序列化类添加SerialVersionUID后,如果修改一个字段或者增加一个字段,反序列化旧的实例时,修改的或者增加的字段的值会设置为初始化的值

  

破坏单例

除了反射可以破坏单例模式外,序列化和反序列化后会得到一个新的对象,也可以破坏单例模式序列化和反序列化破坏单例模式:反序列化时,使用ObjectInputStream对象中的readObject() 方法readObject() 的方法中调用readObject0() 方法
 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))
 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, 数组或者枚举常量这些对象
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() 方法就返回trueinvokeReadResolve() 方法会通过反射的方式调用要被反序列化的类的readResolve() 方法

  

总结

到此这篇关于Java基础入门之序列化和反序列化的文章就介绍到这了,更多相关Java序列化和反序列化内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: