对象序列化
对象序列化机制允许把内存中的Java对象转换成与平台无关的二进制流,从而可以保存到磁盘或者进行网络传输,其它程序获得这个二进制流后可以将其恢复成原来的Java对象。 序列化机制可以使对象可以脱离程序的运行而对立存在
序列化的含义和意义
序列化
序列化机制可以使对象可以脱离程序的运行而对立存在
序列化(Serialize)指将一个java对象写入IO流中,与此对应的是,对象的反序列化(Deserialize)则指从IO流中恢复该java对象
如果需要让某个对象可以支持序列化机制,必须让它的类是可序列化(serializable),为了让某个类可序列化的,必须实现如下两个接口之一:
-
- Serializable:标记接口,实现该接口无须实现任何方法,只是表明该类的实例是可序列化的
- Externalizable
所有在网络上传输的对象都应该是可序列化的,否则将会出现异常;所有需要保存到磁盘里的对象的类都必须可序列化;程序创建的每个JavaBean类都实现Serializable;
类自定义序列化方式一:实现Serializable接口
序列化
示例代码
1 /** 2 * ObjectInputStream和OjbectOutputSteam 3 * 用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可 4 * 以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。 5 */ 6 public class ObjectInputOutputStreamTest { 7 public static void main(String[] args) { 8 ObjectInputOutputStreamTest test = new ObjectInputOutputStreamTest(); 9 test.testObjectOutputStream(); 10 } 11 12 /** 13 * 序列化:用ObjectOutputStream类保存基本类型数据或对象的机制 14 */ 15 public void testObjectOutputStream(){ 16 ObjectOutputStream oos = null; 17 try { 18 oos = new ObjectOutputStream(new FileOutputStream("object.bat")); 19 oos.writeObject(new Person(12, "小白")); 20 oos.flush(); 21 } catch (IOException e) { 22 e.printStackTrace(); 23 } finally { 24 if(oos != null) { 25 try { 26 oos.close(); 27 } catch (IOException e) { 28 e.printStackTrace(); 29 } 30 } 31 } 32 } 33 34 } 35 36 37 class Person implements Serializable{ 38 39 public static final long serialVersionUID = 123321; 40 41 Integer age; 42 String name; 43 44 public Person(Integer age, String name) { 45 this.age = age; 46 this.name = name; 47 } 48 49 @Override 50 public String toString() { 51 return "Person{" + 52 "age=" + age + 53 ", name='" + name + '\'' + 54 '}'; 55 } 56 }
反序列化
从二进制流中恢复Java对象,则需要使用反序列化,程序可以通过如下两个步骤来序列化该对象:
示例代码
1 /** 2 * 反序列化:用ObjectInputStream类读取基本类型数据或对象的机制 3 */ 4 public void testObjectInputStream(){ 5 ObjectInputStream ois = null; 6 try { 7 ois = new ObjectInputStream(new FileInputStream("object.bat")); 8 Person p = (Person) ois.readObject(); 9 System.out.println(p); 10 } catch (IOException e) { 11 e.printStackTrace(); 12 } catch (ClassNotFoundException e) { 13 e.printStackTrace(); 14 } finally { 15 if(ois != null) { 16 try { 17 ois.close(); 18 } catch (IOException e) { 19 e.printStackTrace(); 20 } 21 } 22 } 23 }
反序列化读取的仅仅是Java对象的数据,而不是Java类,因此采用反序列化恢复Java对象时,必须提供Java对象所属的class文件,否则会引发ClassNotFoundException异常;反序列化机制无须通过构造器来初始化Java对象
如果使用序列化机制向文件中写入了多个Java对象,使用反序列化机制恢复对象必须按照实际写入的顺序读取。当一个可序列化类有多个父类时(包括直接父类和间接父类),这些父类要么有无参的构造器,要么也是可序列化的—否则反序列化将抛出InvalidClassException异常。如果父类是不可序列化的,只是带有无参数的构造器,则该父类定义的Field值不会被序列化到二进制流中
对象引用的序列化
如果某个类的Field类型不是基本类型或者String类型,而是另一个引用类型,那么这个引用类型必须是可序列化的,否则有用该类型的Field的类也是不可序列化的
serialVersionUID
1、凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:
private static final long serialVersionUID;
serialVersionUID用来表明类的不同版本间的兼容性。简言之,其目的是以序列化对象 进行版本控制,有关各版本反序列化时是否兼容。
如果类没有显示定义这个静态常量,它的值是Java运行时环境根据类的内部细节自 动生成的。若类的实例变量做了修改,serialVersionUID 可能发生变化。故建议, 显式声明。
2、简单来说,Java的序列化机制是通过在运行时判断类的serialVersionUID来验 证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的 serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同 就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异 常。(InvalidCastException)
Java序列化的特殊情况
1:静态变量和transient关键字修饰的变量不能被序列化;
2:反序列化时要按照序列化的顺序重构对象:如先序列化A后序列化B,则反序列化时也要先获取A后获取B,否则报错。
3:序列化ID的作用:虚拟机是否允许对象反序列化,不仅取决于该对象所属类路径和功能代码是否与虚拟机加载的类一致,而是主要取决于对象所属类与虚拟机加载的该类的序列化 ID 是否一致。
4:自定义序列化方法的应用场景:对某些敏感数据进行加密操作后再序列化;反序列化对加密数据进行解密操作。
5:重复序列化:同一个对象重复序列化时,不会把对象内容再次序列化,而是新增一个引用指向第一次序列化时的对象而已。
类自定义序列化方式二:实现Externalizable接口
实现Externalizable接口(继承自 Serializable接口),并且在类中实现readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,在方法中定义类对象自定义的序列化和反序列化操作。这样通过对象输出流和对象输入流的输入输出方法序列化和反序列化对象时会自动调用类中定义的readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法。
void readExternal(ObjectInput in):需要序列化的类实现readExternal()方法来实现反序列化。该方法调用DataInput(它是ObjectInput的父接口)的方法来恢复基本类型的实例变量值,调用ObjectInput的readObject()方法来恢复引用类型的实例变量值
void writeExternal(Object out):需要序列化的类实现该方法来保存对象的状态。该方法调用DataOutput(它是ObjectOutput的父接口)的方法来保存基本类型的实例变量值,调用ObjectOutput的writeObject()方法来保存引用类型的实例变量值
示例代码
1 public class ObjectInputOutputStreamTest { 2 public static void main(String[] args) { 3 ObjectInputOutputStreamTest test = new ObjectInputOutputStreamTest(); 4 test.testAnimal(); 5 } 6 7 8 public void testAnimal(){ 9 ObjectOutputStream oos = null; 10 try { 11 oos = new ObjectOutputStream(new FileOutputStream("object2.bat")); 12 oos.writeObject(new Animal(12, "小白")); 13 oos.flush(); 14 } catch (IOException e) { 15 e.printStackTrace(); 16 } finally { 17 if(oos != null) { 18 try { 19 oos.close(); 20 } catch (IOException e) { 21 e.printStackTrace(); 22 } 23 } 24 } 25 26 27 ObjectInputStream ois = null; 28 try { 29 ois = new ObjectInputStream(new FileInputStream("object2.bat")); 30 Animal a = (Animal) ois.readObject(); 31 System.out.println(a); 32 } catch (IOException e) { 33 e.printStackTrace(); 34 } catch (ClassNotFoundException e) { 35 e.printStackTrace(); 36 } finally { 37 if(ois != null) { 38 try { 39 ois.close(); 40 } catch (IOException e) { 41 e.printStackTrace(); 42 } 43 } 44 } 45 } 46 } 47 48 49 50 class Animal implements Externalizable{ 51 52 public static final long serialVersionUID = 12345; 53 54 Integer age; 55 String name; 56 57 public Animal() { 58 } 59 60 public Animal(Integer age, String name) { 61 this.age = age; 62 this.name = name; 63 } 64 65 public void writeExternal(ObjectOutput out) throws IOException { 66 out.writeInt(age); 67 out.writeUTF(name); 68 } 69 70 public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { 71 age = in.readInt(); 72 name = in.readUTF(); 73 } 74 75 76 @Override 77 public String toString() { 78 return "Animal{" + 79 "age=" + age + 80 ", name='" + name + '\'' + 81 '}'; 82 } 83 }
其他序列化手段
1:把对象包装成JSON格式进行序列化
2:用XML格式序列化
3:采用第三方插件(如:ProtoBuf)进行序列化