Java基础-IO流对象之序列化(ObjectOutputStream)与反序列化(ObjectInputStream)
作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
一.对象的序列化与反序列化
ObjectOutputStream流用于将对象保存在磁盘中,或者通过网络传输到另一台主机上。保存在文件中的对象的二进制流可以用ObjectInputStream流在以后被还原成原来的对象。
对象输出流的对象可以永久的保存在磁盘上,使对象可以脱离程序而存在,此过程也称为“序列化”过程,反之,将磁盘的数据加载到内存中的就称为“反序列化”过程。换句话说,对象中的数据以流的形式写入到文件中保存的过程称为写出对象,也叫对象的序列化,在文件中以流的形式将对象读取出来,读取对象的过程也叫反序列化。
注意:反序列化是不走构造方法的哟!
二.ObjectOutputStream流写对象
类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。序列化接口没有方法或字段,仅用于标识可序列化的语义。
接下来我们定义一个实现Serializable类接口如下:
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 package cn.org.yinzhengjie.note6; 8 9 import java.io.Serializable; 10 11 public class Person implements Serializable{ 12 private String Name; 13 private int Age; 14 public Person(String name, int age) { 15 super(); 16 Name = name; 17 Age = age; 18 } 19 20 public String getName() { 21 return Name; 22 } 23 24 public void setName(String name) { 25 Name = name; 26 } 27 28 public int getAge() { 29 return Age; 30 } 31 32 public void setAge(int age) { 33 Age = age; 34 } 35 36 @Override 37 public String toString() { 38 return "Person [姓名=" + Name + ",年龄=" + Age+ "]"; 39 } 40 }
定义好需要序列化的对象之后,我们需要就来搞事情吧,看看ObjectOutputStream 到底是如何使用的,案例如下:
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 package cn.org.yinzhengjie.note6; 8 9 import java.io.File; 10 import java.io.FileOutputStream; 11 import java.io.IOException; 12 import java.io.ObjectOutputStream; 13 14 public class ObjectOutputStreamDemo { 15 public static void main(String[] args) throws IOException { 16 //创建字节输出流,封装文件 17 File file = new File("yinzhengjie.txt"); 18 if(!file.exists()) { 19 file.createNewFile(); 20 } 21 FileOutputStream fos = new FileOutputStream(file); 22 23 //创建写出对象的序列化流的对象,构造方法传递字节输出流 24 ObjectOutputStream oos = new ObjectOutputStream(fos); 25 Person p = new Person("yinzhengjie", 18); 26 //调用序列化流的方法writeObject,写出对象。需要实例对象p具有序列化接口,否则会抛出异常对象:java.io.NotSerializableException 27 oos.writeObject(p); 28 //别忘了释放资源哟! 29 oos.close(); 30 } 31 }
三.ObjectInputStream流读取对象
在反序列一个文件内容的时候可能会存在java.lang.ClassNotFoundException类异常,原因是缺少反序列的字节码(*.class)文件。因此,序列化的前提是:必须有反序列化相关的字节码文件。现在我们把之前序列化的文件进行反序列操作,如下:
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 package cn.org.yinzhengjie.note6; 8 9 import java.io.File; 10 import java.io.FileInputStream; 11 import java.io.IOException; 12 import java.io.ObjectInputStream; 13 14 public class ObjectInputSteamDemo { 15 public static void main(String[] args) throws IOException, ClassNotFoundException { 16 //创建字节输入流,封装文件 17 File file = new File("yinzhengjie.txt"); 18 FileInputStream fis = new FileInputStream(file); 19 //创建反序列化流,构造方法中,传递字节输入流 20 ObjectInputStream ois = new ObjectInputStream(fis); 21 //调用反序列化流的方法"readObject()"读取对象,要注意的是反序列话的对象需要存在相应的字节码文件。否则会抛异常 22 Object obj = ois.readObject(); 23 //记得释放资源 24 ois.close(); 25 //查看我们的想要看的内容。 26 System.out.println(obj); 27 } 28 } 29 30 31 /* 32 以上代码执行结果如下: 33 Person [姓名=yinzhengjie,年龄=18] 34 */
注意,在序列化和反序列化过程中,序列化用的什么方法写入,我们读取的时候也应该用对应的方法去读取,打个比方,我们上面序列化一个自定义类时,用的是wirteObject()方法,读取的时候应该用对应方法读取,我们上面的案例就是用readObject方法进行读取,如果你序列化使用的wirteInt()方法,那么反序列话的时候就应该用readInt()方法哟!谨记这一点,会让你少踩很多坑的,别问我为什么这么说,因为这个坑我已经踩了你就别去踩了哈!
四.关于序列化的面试题
1>.静态成员变量为什么不能被序列化?
答:序列化其实是将对象进行序列化操作,而static修饰的成员变量是属于类的,并非对象所有!因此静态修饰的成员变量无法被序列化。
2>.瞬态关键字transient的用法?
答:当一个类的对象需要被序列化时,某些属性不需要被反序列化,这时不需要序列化的属性可以使用关键字transient修饰,只要被transient修饰了,序列化时这个属性就不会被序列化啦!它的用法比较单一,只能用于修饰成员变量不被序列化!这样做的目的是可以节省空间,将不需要的数据不进行序列化操作。
3>.Serializable接口有上面含义?
答:Serializable并没有任何功能,只是一个标记性接口,就好像去菜市场买猪肉,安检人员会在猪肉上印上一个标记表示该猪肉检验合格可以食用!而在Java中用该接口只是标识该类是可以被序列化!
4>.分析序列化中的为什么会存在序列化冲突问题?
答:Java代码在执行之前需要经过javac命令对源代码进行编译生成字节码文件“*.class”,与此同时会给该“*.class”计算出来一个序列号(serialVersionUID),在序列化时会将该属性一并序列化到文件中。当我们对源代码再次进行编辑时,依然是需要javac命令进行编译该文件才能运行修改后的代码,此时会生成一个新的序列号(serialVersionUID)出来。这个时候当我们将序列化的文件进行反序列化操作时,首先会对比字节码文件的序列号是否一致,如果不一致,则会抛出异常:“java.io.InvalidClassException”。
5>.为什么要自定义序列号?
答:原因很简单,就是为了解决序列化冲突问题。我们只需要要让源代码修改前和修改后的序列号(serialVersionUID)保持一致,这样就可以正常进行序列化操作啦!我们可以看一下案例如下:
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 package cn.org.yinzhengjie.note6; 8 9 import java.io.Serializable; 10 11 public class Person implements Serializable{ 12 private String Name; 13 public /*transient关键字在这里加,就可以阻止成员变量进行序列化啦!*/int Age; 14 15 static final long serialVersionUid = 7758520L; //类自定义序列号,编译器就不会计算序列号。 16 17 public Person(String name, int age) { 18 super(); 19 Name = name; 20 Age = age; 21 } 22 23 public String getName() { 24 return Name; 25 } 26 27 public void setName(String name) { 28 Name = name; 29 } 30 31 public int getAge() { 32 return Age; 33 } 34 35 public void setAge(int age) { 36 Age = age; 37 } 38 39 @Override 40 public String toString() { 41 return "Person [姓名=" + Name + ",年龄=" + Age+ "]"; 42 } 43 44 45 }
五.小试牛刀
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 package cn.org.yinzhengjie.serializable; 7 8 9 import java.io.Serializable; 10 import java.util.ArrayList; 11 import java.util.List; 12 13 /** 14 * 客户类 15 */ 16 public class Customer implements Serializable { 17 private static final long serialVersionUID = 6327665722505706622L; 18 private String name ; 19 private int age ; 20 21 public int getAge() { 22 return age; 23 } 24 25 public void setAge(int age) { 26 this.age = age; 27 } 28 29 //临时的,不参与串行化 30 private transient List<Order> orders = new ArrayList<Order>() ; 31 32 public String getName() { 33 return name; 34 } 35 36 public void setName(String name) { 37 this.name = name; 38 } 39 40 public List<Order> getOrders() { 41 return orders; 42 } 43 44 public void setOrders(List<Order> orders) { 45 this.orders = orders; 46 } 47 }
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 package cn.org.yinzhengjie.serializable; 7 8 9 import java.io.Serializable; 10 11 /** 12 * 客户类 13 */ 14 public class Item implements Serializable { 15 private String itemname; 16 17 public String getItemname() { 18 return itemname; 19 } 20 21 public void setItemname(String itemname) { 22 this.itemname = itemname; 23 } 24 }
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 package cn.org.yinzhengjie.serializable; 7 8 9 import java.io.Serializable; 10 import java.util.ArrayList; 11 import java.util.List; 12 13 /** 14 * 客户类 15 */ 16 public class Order implements Serializable { 17 private String orderno; 18 19 private List<Item> items =new ArrayList<Item>() ; 20 21 public List<Item> getItems() { 22 return items; 23 } 24 25 public void setItems(List<Item> items) { 26 this.items = items; 27 } 28 29 public String getOrderno() { 30 return orderno; 31 } 32 33 public void setOrderno(String orderno) { 34 this.orderno = orderno; 35 } 36 }
1>.实现浅拷贝
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 package cn.org.yinzhengjie.serializable; 7 8 import org.junit.Test; 9 10 public class ShallowReplicas { 11 @Test 12 public void testDeeplyCopy() throws Exception { 13 Customer c = new Customer(); 14 c.setName("尹正杰"); 15 16 Order o1 = new Order(); 17 o1.setOrderno("0001"); 18 19 Order o2 = new Order(); 20 o2.setOrderno("0002"); 21 22 Item i1 = new Item(); 23 i1.setItemname("iphone 8 Plus"); 24 25 //建立关系 26 c.getOrders().add(o1); 27 c.getOrders().add(o2); 28 29 o1.getItems().add(i1); 30 31 //浅度复制 32 Customer cc = new Customer(); 33 cc.setName(c.getName()); 34 cc.setOrders(c.getOrders()); 35 36 String name = cc.getName(); 37 System.out.println(name); 38 } 39 }
2>.实现深度拷贝
1 /* 2 @author :yinzhengjie 3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/ 4 EMAIL:y1053419035@qq.com 5 */ 6 7 package cn.org.yinzhengjie.serializable; 8 9 import org.junit.Test; 10 11 import java.io.FileInputStream; 12 import java.io.FileOutputStream; 13 import java.io.ObjectInputStream; 14 import java.io.ObjectOutputStream; 15 16 public class DeepCopy { 17 String filePath = "D:\BigData\JavaSE\yinzhengjieData\c.dat"; 18 19 /** 20 * 深度拷贝,将对象序列化到文件 21 */ 22 @Test 23 public void testDeeplyCopy() throws Exception { 24 Customer c = new Customer(); 25 c.setName("尹正杰"); 26 27 Order o1 = new Order(); 28 o1.setOrderno("0001"); 29 30 Order o2 = new Order(); 31 o2.setOrderno("0002"); 32 33 Item i1 = new Item(); 34 i1.setItemname("iphone 8 Plus"); 35 36 //建立关系 37 c.getOrders().add(o1); 38 c.getOrders().add(o2); 39 40 o1.getItems().add(i1); 41 42 //深度拷贝,将对象序列化到文件 43 FileOutputStream fos = new FileOutputStream(filePath) ; 44 ObjectOutputStream oos = new ObjectOutputStream(fos) ; 45 oos.writeObject(c); 46 oos.close(); 47 fos.close(); 48 System.out.println(); 49 } 50 51 52 /** 53 * 定义反序列化的代码 54 */ 55 @Test 56 public void testDeserialize() throws Exception { 57 FileInputStream fis = new FileInputStream(filePath); 58 ObjectInputStream ois = new ObjectInputStream(fis); 59 Customer obj = (Customer)ois.readObject(); 60 ois.close(); 61 fis.close(); 62 System.out.println(obj.getName()); 63 } 64 } 65 66 /* 67 以上代码执行结果如下: 68 尹正杰 69 */