标签:摘要算法 数据类型 服务 ret session zhang lis 它的 二进制
对象序列化:把对象转换为字节序列的过程
对象反序列化:把字节序列恢复为对象的过程
1. 为何要进行序列化
我们知道当虚拟机停止运行之后,内存中的对象就会消失。在很多应用中,需要对某些对象进行序列化,让他们离开内存空间,进入物理硬盘,便于长期保存。例如,最常见的是WEB服务器中的Session对象,当有10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些Session先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。
当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。
即对象序列化主要有两种用途:
(1)把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
(2)在网络上传送对象的字节序列。
1. 前提条件
如果要让每个对象支持序列化机制,比如让它的类是可序列化的,则该类必须实现如下两个接口之一:
2. 相关API
有两个类常常用于序列化和反序列化:java.io.ObjectOutputStream和java.io.ObjectInputStream
(1)java.io.ObjectOutputStream
java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可以对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
(2)java.io.ObjectInputStream
java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把他们反序列化为一个对象,并将其返回。
3. 对象序列化步骤
(1)创建一个对象输出流,它可以包装一个其它类型的目标输出流,如文件输出流;
(2)通过对象输出流的writeObject()方法写对象
4. 对象反序列化的步骤
(1)创建一个对象输入流,它可以包装一个其它类型的源输入流,如文件输入流;
(2)通过对象输入流的readObject()方法读取对象。
5. 重要原则
1. 将一个对象序列化之后存储到文件中
1 package com.test.a; 2 3 import java.io.Serializable; 4 5 public class Person implements Serializable{ 6 private static final long serialVersionUID = 1L; 7 public String name; 8 public int age; 9 10 public Person(String name, int age) { 11 this.name = name; 12 this.age = age; 13 } 14 15 public String getName() { 16 return name; 17 } 18 19 public void setName(String name) { 20 this.name = name; 21 } 22 23 public int getAge() { 24 return age; 25 } 26 27 public void setAge(int age) { 28 this.age = age; 29 } 30 31 }
1 package com.test.a; 2 3 import java.io.FileNotFoundException; 4 import java.io.FileOutputStream; 5 import java.io.IOException; 6 import java.io.ObjectOutputStream; 7 8 9 public class Test { 10 public static void main(String args[]) throws FileNotFoundException, IOException{ 11 Person person=new Person("zhangsan", 23); 12 String path="C:\\Users\\hermioner\\Desktop\\test.txt"; 13 ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream(path)); 14 objectOutputStream.writeObject(person); 15 objectOutputStream.close(); 16 } 17 }
说明:上面是执行结果。对象序列化之后,写入的是一个二进制文件,所以打开乱码是正常现象,不过透过乱码我们还是能够知道就是我们序列化的哪个对象。Person对象实现了Serializable接口,这个接口没有任何方法需要被实现,只是一个标记接口,表示这个类的对象可以被序列化,如果没有明确写了实现这个接口,就会抛出异常。
2. 从文件中反序列化对象
1 package com.test.a; 2 3 import java.io.FileInputStream; 4 import java.io.FileNotFoundException; 5 import java.io.FileOutputStream; 6 import java.io.IOException; 7 import java.io.ObjectInputStream; 8 import java.io.ObjectOutputStream; 9 10 11 public class Test { 12 public static void main(String args[]) throws FileNotFoundException, IOException, ClassNotFoundException{ 13 Person person=new Person("zhangsan", 23); 14 String path="C:\\Users\\hermioner\\Desktop\\test.txt"; 15 ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream(path)); 16 objectOutputStream.writeObject(person); 17 objectOutputStream.close(); 18 19 ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(path)); 20 Person person2=(Person) objectInputStream.readObject(); 21 System.out.println(person2.getName()); 22 System.out.println(person2.getAge()); 23 objectInputStream.close(); 24 } 25 } 26 27 zhangsan 28 23
说明:在调用readObject()方法的时候,有一个强转的动作。所以在反序列化时,要提供java对象所属类的class文件。
3. 多个对象的反序列化
1 package com.test.a; 2 3 import java.io.FileInputStream; 4 import java.io.FileNotFoundException; 5 import java.io.FileOutputStream; 6 import java.io.IOException; 7 import java.io.ObjectInputStream; 8 import java.io.ObjectOutputStream; 9 10 11 public class Test { 12 public static void main(String args[]) throws FileNotFoundException, IOException, ClassNotFoundException{ 13 Person person=new Person("zhangsan", 23); 14 Person person2=new Person("lisi", 13); 15 String path="C:\\Users\\hermioner\\Desktop\\test.txt"; 16 ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream(path)); 17 objectOutputStream.writeObject(person); 18 objectOutputStream.writeObject(person2); 19 objectOutputStream.close(); 20 21 ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(path)); 22 Person person4=(Person) objectInputStream.readObject(); 23 System.out.println(person4.getName()); 24 System.out.println(person4.getAge()); 25 Person person5=(Person) objectInputStream.readObject(); 26 System.out.println(person5.getName()); 27 System.out.println(person5.getAge()); 28 objectInputStream.close(); 29 } 30 } 31 32 33 zhangsan 34 23 35 lisi 36 13
说明:如果使用序列化机制向文件中写入了多个对象,在反序列化时,需要按实际写入的顺序读取。
4. 对象引用的序列化
1 package com.test.a; 2 3 import java.io.Serializable; 4 5 public class Person implements Serializable { 6 private static final long serialVersionUID = 1L; 7 public String name; 8 public int age; 9 public Man man; 10 11 public Person(String name, int age, Man man) { 12 this.name = name; 13 this.age = age; 14 this.man = man; 15 } 16 17 public String getName() { 18 return name; 19 } 20 21 public void setName(String name) { 22 this.name = name; 23 } 24 25 public int getAge() { 26 return age; 27 } 28 29 public void setAge(int age) { 30 this.age = age; 31 } 32 33 public Man getMan() { 34 return man; 35 } 36 37 public void setMan(Man man) { 38 this.man = man; 39 } 40 41 }
1 package com.test.a; 2 3 import java.io.Serializable; 4 5 public abstract class Man implements Serializable{ //必须实现Serializable 6 public abstract void getInfo(); 7 } 8 9 10 package com.test.a; 11 12 public class Femal extends Man{ 13 14 @Override 15 public void getInfo() { 16 System.out.println("Femal"); 17 18 } 19 20 } 21 22 package com.test.a; 23 24 public class Male extends Man{ 25 26 @Override 27 public void getInfo() { 28 System.out.println("male"); 29 30 } 31 32 }
1 package com.test.a; 2 3 import java.io.FileInputStream; 4 import java.io.FileNotFoundException; 5 import java.io.FileOutputStream; 6 import java.io.IOException; 7 import java.io.ObjectInputStream; 8 import java.io.ObjectOutputStream; 9 10 11 public class Test { 12 public static void main(String args[]) throws FileNotFoundException, IOException, ClassNotFoundException{ 13 Person person=new Person("zhangsan",24, new Femal()); 14 String path="C:\\Users\\hermioner\\Desktop\\test.txt"; 15 ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream(path)); 16 objectOutputStream.writeObject(person); 17 objectOutputStream.close(); 18 19 ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(path)); 20 Person person4=(Person) objectInputStream.readObject(); 21 System.out.println(person4.getName()); 22 System.out.println(person4.getAge()); 23 Femal man=(Femal) person4.getMan(); 24 man.getInfo(); 25 objectInputStream.close(); 26 } 27 }
1 zhangsan 2 24 3 Femal
说明:上面介绍对象的成员变量都是基本数据类型,如果对象的成员变量是引用类型,这个引用类型的成员变量必须也是可序列化的,否则拥有该类型成员变量的类的对象不可序列化。
serialVersionUID:字面意思是序列化的版本号,凡是实现了Seriallizable接口的类都有一个表示序列化版本标识符的静态变量:
private static final long serialVersionUID = 1L;
实现Serializable接口的类如果类中没有添加serialVersionUID,那么就会出现如下的警告提示
看上面的提示信息,可以知道有两种方式来生成serial version ID
(1)采用默认方式
private static final long serialVersionUID = 1L;
这种方式生成的serialVersionUID是1L.
(2)采用第二种
private static final long serialVersionUID = -6587084022709540081L;
这种方式生成的serialVersionUID是根据类名,接口名,方法和属性等来生成的。
1. serialVersionUID的作用
(1)假设如下Person对象进行序列化和反序列化----没有加入serialVersionUID
1 package com.test.a; 2 3 import java.io.Serializable; 4 5 public class Person implements Serializable { 6 7 public String name; 8 public int age; 9 10 11 public Person(String name,int age) { 12 this.name=name; 13 this.age=age; 14 } 15 16 public String getName() { 17 return name; 18 } 19 20 public void setName(String name) { 21 this.name = name; 22 } 23 24 public int getAge() { 25 return age; 26 } 27 28 public void setAge(int age) { 29 this.age = age; 30 } 31 }
1 package com.test.a; 2 3 import java.io.FileInputStream; 4 import java.io.FileNotFoundException; 5 import java.io.FileOutputStream; 6 import java.io.IOException; 7 import java.io.ObjectInputStream; 8 import java.io.ObjectOutputStream; 9 10 11 public class Test { 12 public static void main(String args[]) throws FileNotFoundException, IOException, ClassNotFoundException{ 13 Person person=new Person("zhangsan",24); 14 String path="C:\\Users\\hermioner\\Desktop\\test.txt"; 15 ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream(path)); 16 objectOutputStream.writeObject(person); 17 objectOutputStream.close(); 18 19 ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(path)); 20 Person person4=(Person) objectInputStream.readObject(); 21 System.out.println(person4); 22 System.out.println(person4.getName()); 23 System.out.println(person4.getAge()); 24 25 objectInputStream.close(); 26 } 27 } 28 29 com.test.a.Person@448139f0 30 zhangsan 31 24
com.test.a.Person@448139f0
zhangsan
24
(2)修改Person,添加一个新的属性
1 package com.test.a; 2 3 import java.io.Serializable; 4 5 public class Person implements Serializable { 6 7 public String name; 8 public int age; 9 public String sex;/////new added 10 11 12 public Person(String name,int age) { 13 this.name=name; 14 this.age=age; 15 } 16 17 public Person(String name,int age,String sex)/////new added 18 { 19 this.name=name; 20 this.age=age; 21 this.sex=sex; 22 } 23 24 public String getName() { 25 return name; 26 } 27 28 public void setName(String name) { 29 this.name = name; 30 } 31 32 public int getAge() { 33 return age; 34 } 35 36 public void setAge(int age) { 37 this.age = age; 38 } 39 }
此时只执行反序列化,(因为生成的对象还是调用的之前两个参数的哪个对象)
1 package com.test.a; 2 3 import java.io.FileInputStream; 4 import java.io.FileNotFoundException; 5 import java.io.FileOutputStream; 6 import java.io.IOException; 7 import java.io.ObjectInputStream; 8 import java.io.ObjectOutputStream; 9 10 11 public class Test { 12 public static void main(String args[]) throws FileNotFoundException, IOException, ClassNotFoundException{ 13 // Person person=new Person("zhangsan",24); 14 String path="C:\\Users\\hermioner\\Desktop\\test.txt"; 15 // ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream(path)); 16 // objectOutputStream.writeObject(person); 17 // objectOutputStream.close(); 18 19 ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(path)); 20 Person person4=(Person) objectInputStream.readObject(); 21 System.out.println(person4); 22 System.out.println(person4.getName()); 23 System.out.println(person4.getAge()); 24 25 objectInputStream.close(); 26 } 27 }
1 Exception in thread "main" java.io.InvalidClassException: com.test.a.Person; local class incompatible: stream classdesc serialVersionUID = 4647091331428092166, local class serialVersionUID = -7232940355925514760 2 at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:687) 3 at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1883) 4 at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1749) 5 at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2040) 6 at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1571) 7 at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431) 8 at com.test.a.Test.main(Test.java:20)
说明:此时用的还是之前序列化的结果进行反序列化,只是此次反序列化的之前,还修改了Person。因此会造成不兼容的现象。根据上面错误提示我们知道:文件流中的class和classpath中的class,也就是修改过后的class,不兼容了,处于安全机制考虑,程序抛出了错误,并且拒绝载入。那么如果我们真的有需求要在序列化后添加一个字段或者方法呢?应该怎么办?那就是自己去指定serialVersionUID。在Test例子中,没有指定Person类的serialVersionUID的,那么java编译器会自动给这个class进行一个摘要算法,类似于指纹算法,只要这个文件 多一个空格,得到的UID就会截然不同的,可以保证在这么多类中,这个编号是唯一的。所以,添加了一个字段后,由于没有显指定 serialVersionUID,编译器又为我们生成了一个UID,当然和前面保存在文件中的那个不会一样了,于是就出现了2个序列化版本号不一致的错误。因此,只要我们自己指定了serialVersionUID,就可以在序列化后,去添加一个字段,或者方法,而不会影响到后期的还原,还原后的对象照样可以使用,而且还多了方法或者属性可以用。
因此总结Class不兼容解决办法:
显式地定义serialVersionUID有两种用途:
SerialVersionUID其实是JAVA的序列化机制采取的一种特殊的算法:
1. transient关键字
用transient关键字来修饰实例变量,该变量就会被完全隔离在序列化机制之外。
1 package com.test.a; 2 3 import java.io.Serializable; 4 5 public class Person implements Serializable { 6 private static final long serialVersionUID = -7232940355925514760L; 7 public String name; 8 public transient int age; 9 10 public Person(String name,int age) { 11 this.name=name; 12 this.age=age; 13 } 14 15 public String getName() { 16 return name; 17 } 18 19 public void setName(String name) { 20 this.name = name; 21 } 22 23 public int getAge() { 24 return age; 25 } 26 27 public void setAge(int age) { 28 this.age = age; 29 } 30 }
1 package com.test.a; 2 3 import java.io.FileInputStream; 4 import java.io.FileNotFoundException; 5 import java.io.FileOutputStream; 6 import java.io.IOException; 7 import java.io.ObjectInputStream; 8 import java.io.ObjectOutputStream; 9 10 11 public class Test { 12 public static void main(String args[]) throws FileNotFoundException, IOException, ClassNotFoundException{ 13 Person person=new Person("zhangsan",24); 14 String path="C:\\Users\\hermioner\\Desktop\\test.txt"; 15 ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream(path)); 16 objectOutputStream.writeObject(person); 17 objectOutputStream.close(); 18 19 ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(path)); 20 Person person4=(Person) objectInputStream.readObject(); 21 System.out.println(person4); 22 System.out.println(person4.getName()); 23 System.out.println(person4.getAge()); 24 25 objectInputStream.close(); 26 } 27 }
1 com.test.a.Person@448139f0 2 zhangsan 3 0
说明:age的打印值为0,是默认初始化值。如果是字符串,就为null。这说明使用tranisent修饰的变量,在经过序列化和反序列化之后,JAVA对象会丢失该实例变量的值。因此,Java提供了一种自定义序列化机制,这样程序就可以自己来控制如何序列化各实例变量,甚至不序列化实例变量。
2. 自定义序列化
在序列化和反序列化过程中需要特殊处理的类应该提供如下的方法,这些方法用于实现自定义的序列化。
writeObject()
readObject()
这两个方法并不属于任何的类和接口,只要在要序列化的类中提供这两个方法,就会在序列化机制中自动被调用。
其中writeObject方法用于写入特定类的实例状态,以便相应的readObject方法可以恢复它。通过重写该方法,程序员可以获取对序列化的控制,可以自主决定可以哪些实例变量需要序列化,怎样序列化。该方法调用out.defaultWriteObject来保存JAVA对象的实例变量,从而可以实现序列化java对象状态的目的。
1 package com.test.a; 2 import java.io.IOException; 3 import java.io.ObjectInputStream; 4 import java.io.ObjectOutputStream; 5 import java.io.Serializable; 6 7 public class Person implements Serializable 8 { 9 private static final long serialVersionUID = 1L; 10 int age; 11 String address; 12 double height; 13 public Person(int age, String address, double height) 14 { 15 this.age = age; 16 this.address = address; 17 this.height = height; 18 } 19 20 //JAVA BEAN自定义的writeObject方法 21 private void writeObject(ObjectOutputStream out) throws IOException 22 { 23 System.out.println("writeObejct ------"); 24 out.writeInt(age); 25 out.writeObject(new StringBuffer(address).reverse()); 26 out.writeDouble(height); 27 } 28 29 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException 30 { 31 System.out.println("readObject ------"); 32 this.age = in.readInt(); 33 this.address = ((StringBuffer)in.readObject()).reverse().toString(); 34 this.height = in.readDouble(); 35 } 36 }
1 package com.test.a; 2 import java.io.FileInputStream; 3 import java.io.FileOutputStream; 4 import java.io.IOException; 5 import java.io.ObjectInputStream; 6 import java.io.ObjectOutputStream; 7 8 public class Test 9 { 10 public static void main(String[] args) throws IOException, IOException, 11 ClassNotFoundException 12 { 13 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream( 14 "C:\\Users\\hermioner\\Desktop\\test.txt")); 15 Person p = new Person(25, "China", 180); 16 oos.writeObject(p); 17 oos.close(); 18 19 ObjectInputStream ois = new ObjectInputStream(new FileInputStream( 20 "C:\\Users\\hermioner\\Desktop\\test.txt")); 21 Person p1 = (Person) ois.readObject(); 22 System.out.println("age=" + p1.age + ";address=" + p1.address 23 + ";height=" + p1.height); 24 ois.close(); 25 } 26 }
1 writeObejct ------ 2 readObject ------ 3 age=25;address=China;height=180.0
1、这个地方跟前面的区别就是在Person类中提供了writeObject方法和readObject方法,并且提供了具体的实现。
2、在ObjectOutputStream调用writeObject方法执行过程,肯定调用了Person类的writeObject方法
3、Person中自定义的writeObject和readObject只能是private才可以被调用,如果是public则不会被调用。???WHY
4. 自定义序列API
比如ArrayList和HashMap都自定义了序列化和反序列化中的核心方法
标签:摘要算法 数据类型 服务 ret session zhang lis 它的 二进制
原文地址:https://www.cnblogs.com/Hermioner/p/9776434.html