最近看到序列化的知识,觉得书上讲的不是很清楚,就去查了下资料,自己也进行了一些尝试,在这里记录下。
一、什么是序列化
首先可以下百度里的解释:
序列化 (Serialization)是将对象的状态信息转换为可以存储或传输的形式的过程。在序列化期间,对象将其当前状态写入到临时或持久性存储区。以后,可以通过从存储区中读取或反序列化对象的状态,重新创建该对象。
解释一下,我们在写程序的时候,如果我们想要将一个对象通过网络传输或者是长期保存到本地磁盘上方便下次使用时还是同样的内容,可以使用序列化和反序列化来实现。序列化是将对象转换为字节序列的过程,反序列化就是讲字节序列转换为对象的过程。很好理解。
二、java中如何实现序列化
1.实现Serializable接口或者Externalizable接口
2.使用ObjectOutputStream和ObjectInputStream
Serializable接口实例
1 import java.io.FileInputStream; 2 import java.io.FileNotFoundException; 3 import java.io.FileOutputStream; 4 import java.io.IOException; 5 import java.io.ObjectInputStream; 6 import java.io.ObjectOutputStream; 7 import java.io.Serializable; 8 9 /** 10 * 11 * @author Administrator 12 * @date 2019年3月18日 13 */ 14 15 class Product implements Serializable{ 16 17 private static final long serialVersionUID = 1L; 18 int a; 19 String b; 20 M m; 21 static int c = 1; 22 23 @Override 24 public String toString() { 25 // TODO Auto-generated method stub 26 return "a:"+a+" b:"+b+" M.a:"+m.a+" c:"+c; 27 } 28 29 public Product(int a, String b, M m) { 30 // TODO Auto-generated constructor stub 31 this.a = a; 32 this.b = b; 33 this.m = m; 34 } 35 } 36 37 class M implements Serializable{ 38 39 private static final long serialVersionUID = 1L; 40 int a; 41 } 42 43 public class SerializableTest1 { 44 45 public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException { 46 // TODO Auto-generated method stub 47 M m = new M(); 48 m.a = 2; 49 M m2 = new M(); 50 m2.a = 3; 51 Product p = new Product(4, "string", m); 52 Product p2 = new Product(5, "string2", m); 53 Product p3 = new Product(6, "string3", m2); 54 55 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("seria.txt")); 56 //m.a=4; 57 out.writeObject(p); 58 //Product.c = 2; 59 //m.a=4; 60 //p2.m.a=4; 61 out.writeObject(p2); 62 out.writeObject(p3); 63 out.close(); 64 65 ObjectInputStream in = new ObjectInputStream(new FileInputStream("seria.txt")); 66 Product p4 = (Product) in.readObject(); 67 //p4.m.a=5; 68 Product p5 = (Product) in.readObject(); 69 Product p6 = (Product) in.readObject(); 70 System.out.println(p4); 71 System.out.println(p5); 72 System.out.println(p6); 73 System.out.println("address:"+"m:"+m+" p4:"+p4.m+" p5:"+p5.m); 74 in.close(); 75 } 76 77 }
output: a:4 b:string M.a:2 c:1 a:5 b:string2 M.a:2 c:1 a:6 b:string3 M.a:3 c:1 address:m:thinking_in_java_18.M@12204a1 p4:thinking_in_java_18.M@99a589 p5:thinking_in_java_18.M@99a589
从上面代码我们很容易看出:
1.地址改变:当两个对象包含指向同一个其他对象的引用时,反序列化的结果也是同一个对象,但与原来的那个对象已经不同了,相当于是对象的深拷贝。
2.分别去掉56行或者59行注释,结果如下:
output by 56: a:4 b:string M.a:4 c:1 a:5 b:string2 M.a:4 c:1 a:6 b:string3 M.a:3 c:1 address:m:thinking_in_java_18.M@12204a1 p4:thinking_in_java_18.M@99a589 p5:thinking_in_java_18.M@99a589 output by 59: a:4 b:string M.a:2 c:1 a:5 b:string2 M.a:2 c:1 a:6 b:string3 M.a:3 c:1 address:m:thinking_in_java_18.M@12204a1 p4:thinking_in_java_18.M@99a589 p5:thinking_in_java_18.M@99a589
当发生序列化时,每个对象会有一个“序列化版本”,也就是字节序列,这个字节序列时不会发生更改的(第59行代码可以说明)。同时这个对象中包含的指向其他对象的引用也是会得到一个字节序列。如果这个对象同时被其他的引用指向,那么当这个引用所在的类序列化时,得到的会是同一个字节序列,即便这个对象中的数据发生了改变,字节序列是不会变的。
这里我做了下其他的尝试
1 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("seria.txt")); 2 out.writeObject(p); 3 //Product.c = 2; 4 out.writeObject(p); 5 out.writeObject(p2); 6 //out.writeObject(p3); 7 out.close(); 8 9 ObjectInputStream in = new ObjectInputStream(new FileInputStream("seria.txt")); 10 Product p4 = (Product) in.readObject(); 11 //p4.m.a=5; 12 Product p5 = (Product) in.readObject(); 13 Product p6 = (Product) in.readObject(); 14 System.out.println("address:"+p4.hashCode()+" p5:"+p5.hashCode()+" p6:"+p6.hashCode()); 15 in.close();
output:address: p4:13257035 p5:13257035 p6:27587151
由此可知同一个对象序列化后多次保存到本地,经过反序列化得到的对象相同。(可以尝试在第一保存后,改变对象内容,再次写入,发现结果仍是一样的)。
3.序列化时一整套的:一个对象序列化后,它所拥有的其他类的引用,这个类也要序列化
4.静态成员变量不受序列化影响
去掉第一段代码中第58行的注释,结果如下:
5.serialVersionUID 当你在反序列化之前,改变了类,比如新增了一个变量,这个时候就需要只用到serialVersionUID来判断反序列化得到的对象是不是这个类的。
Externalizable实例:
1 import java.io.*; 2 3 class Woods implements java.io.Externalizable{ 4 5 private static final long serialVersionUID = -5182532647273106745L; 6 int a; 7 String b; 8 9 @Override 10 public String toString() { 11 // TODO Auto-generated method stub 12 return "a:"+a+" b:"+b; 13 } 14 15 public Woods() { 16 // TODO Auto-generated constructor stub 17 this.a = 1; 18 this.b = "string"; 19 } 20 21 @Override 22 public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { 23 // TODO Auto-generated method stub 24 25 b = (String) in.readObject(); 26 a = in.readInt(); 27 } 28 29 @Override 30 public void writeExternal(ObjectOutput out) throws IOException { 31 // TODO Auto-generated method stub 32 out.writeObject(b); 33 out.writeInt(a); 34 } 35 } 36 37 38 public class Externalizable { 39 40 public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException { 41 // TODO Auto-generated method stub 42 43 Woods w = new Woods(); 44 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("extern.txt")); 45 out.writeObject(w); 46 out.close(); 47 48 ObjectInputStream in = new ObjectInputStream(new FileInputStream("extern.txt")); 49 Woods w2 = (Woods) in.readObject(); 50 in.close(); 51 52 System.out.println(w2); 53 } 54 55 }
实际上Externalizable与Serializable的差别在于,Serializable是默认的方式序列化。而Externalizabel可以实现部分序列化,来阻止一些敏感的信息泄露;同时Externalizable也可以使得反序列化的对象按照自己的想法重新初始化。
readObject->序列化
反序列化->初始化->writeObject
其次Externalizable必须有无参构造函数。
四、补充
1.Serializable也能实现Externalizable的功能,通过增加writeObject以及readObject两个方法,使得默认的序列化停止,转而执行新增的方法来实现序列化过程。与Externalizable类似。
2.父类未实现序列化,子类实现,此时要求父类必须有无参构造函数,因为在子类反序列化时,初始化需要调用父类的无参构造函数。
3.使用transient字段,该字段只能和Serializable一起使用,使用这个字段的变量不会通过默认方式序列化(关闭序列化)。