zoukankan      html  css  js  c++  java
  • Java序列化——实现Serializbale接口

    1. 理论

    Java 序列化是 JDK 1.1 时的特性:将 Java 对象转换为字节数组,便于存储或传输。此后,仍然可以将字节数组转换回 Java 对象原有的状态

      序列化的思想是“冻结”对象状态,传输对象状态(写到磁盘、通过网络传输等等);

      反序列化的思想是“解冻”对象状态,重新获得可用的 Java 对象。

      所有这些事情的发生要归功于 ObjectInputStream/ObjectOutputStream 类、完全保真的元数据以及程序员愿意用 Serializable 标识接口标记他们的类,从而 “参与” 这个过程。

      再来看看序列化 Serializbale 接口的定义:

    public interface Serializable {
    
    }

      明明就一个空的接口嘛,为什么能够保证实现了它的“类的对象”被序列化和反序列化?

      在回答上述问题之前,我们先来创建一个类(只有两个字段,和对应的 getter/setter),用于序列化和反序列化。

     1 public class UserInfo {
     2 
     3     private int UserId;
     4 
     5 
     6     private String UserName;
     7 
     8 
     9     public int getUserId() {
    10         return UserId;
    11     }
    12 
    13     public void setUserId(int userId) {
    14         UserId = userId;
    15     }
    16 
    17     public String getUserName() {
    18         return UserName;
    19     }
    20 
    21     public void setUserName(String userName) {
    22         UserName = userName;
    23     }
    24 }

      再来创建一个测试类,通过 ObjectOutputStream 将对象信息写入到文件当中,实际上就是一种序列化的过程;再通过 ObjectInputStream 将对象信息从文件中读出来,实际上就是一种反序列化的过程。

     1 import java.io.*;
     2 
     3 public class Main {
     4 
     5     public static void main(String[] args) {
     6         WriteIntoFile();
     7         System.out.println("Hello World!");
     8     }
     9     private static void WriteIntoFile(){
    10 
    11         UserInfo userInfo = new UserInfo();
    12         userInfo.setUserId(12);
    13         userInfo.setUserName("Jerry");
    14 
    15         // 把对象写到文件中
    16         try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("12jerry"));){
    17             objectOutputStream.writeObject(userInfo);
    18         } catch (IOException e) {
    19             e.printStackTrace();
    20         }
    21 
    22         // 从文件中读出对象
    23         try (ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("12jerry")));){
    24             UserInfo userInfo1 = (UserInfo) objectInputStream.readObject();
    25             System.out.println(userInfo1);
    26         } catch (IOException | ClassNotFoundException e) {
    27             e.printStackTrace();
    28         }
    29     }
    30 }

      不过,由于 UserInfo没有实现 Serializbale 接口,所以在运行测试类的时候会抛出异常,堆栈信息如下:

    "C:Program FilesJavajdk1.8.0_211injava.exe" "-javaagent:D:Program FilesJetBrainsIntelliJ IDEA 2019.1.3libidea_rt.jar=34706:D:Program FilesJetBrainsIntelliJ IDEA 2019.1.3in" -Dfile.encoding=UTF-8 -classpath "C:Program FilesJavajdk1.8.0_211jrelibcharsets.jar;C:Program FilesJavajdk1.8.0_211jrelibdeploy.jar;C:Program FilesJavajdk1.8.0_211jrelibextaccess-bridge-64.jar;C:Program FilesJavajdk1.8.0_211jrelibextcldrdata.jar;C:Program FilesJavajdk1.8.0_211jrelibextdnsns.jar;C:Program FilesJavajdk1.8.0_211jrelibextjaccess.jar;C:Program FilesJavajdk1.8.0_211jrelibextjfxrt.jar;C:Program FilesJavajdk1.8.0_211jrelibextlocaledata.jar;C:Program FilesJavajdk1.8.0_211jrelibext
    ashorn.jar;C:Program FilesJavajdk1.8.0_211jrelibextsunec.jar;C:Program FilesJavajdk1.8.0_211jrelibextsunjce_provider.jar;C:Program FilesJavajdk1.8.0_211jrelibextsunmscapi.jar;C:Program FilesJavajdk1.8.0_211jrelibextsunpkcs11.jar;C:Program FilesJavajdk1.8.0_211jrelibextzipfs.jar;C:Program FilesJavajdk1.8.0_211jrelibjavaws.jar;C:Program FilesJavajdk1.8.0_211jrelibjce.jar;C:Program FilesJavajdk1.8.0_211jrelibjfr.jar;C:Program FilesJavajdk1.8.0_211jrelibjfxswt.jar;C:Program FilesJavajdk1.8.0_211jrelibjsse.jar;C:Program FilesJavajdk1.8.0_211jrelibmanagement-agent.jar;C:Program FilesJavajdk1.8.0_211jrelibplugin.jar;C:Program FilesJavajdk1.8.0_211jrelib
    esources.jar;C:Program FilesJavajdk1.8.0_211jrelib
    t.jar;E:IdeaProjectsTestSeroutproductionTestSer" Main
    java.io.NotSerializableException: UserInfo
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
        at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
        at Main.WriteIntoFile(Main.java:20)
        at Main.main(Main.java:6)
    java.io.WriteAbortedException: writing aborted; java.io.NotSerializableException: UserInfo
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1577)
        at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
        at Main.WriteIntoFile(Main.java:27)
        at Main.main(Main.java:6)
    Caused by: java.io.NotSerializableException: UserInfo
        at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
        at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
        at Main.WriteIntoFile(Main.java:20)
        ... 1 more
    Hello World!
    
    Process finished with exit code 0

      顺着堆栈信息,我们来看一下 ObjectOutputStream 的 writeObject0() 方法。其部分源码如下:

     1 if (obj instanceof String) {
     2     writeString((String) obj, unshared);
     3 } else if (cl.isArray()) {
     4     writeArray(obj, desc, unshared);
     5 } else if (obj instanceof Enum) {
     6     writeEnum((Enum<?>) obj, desc, unshared);
     7 } else if (obj instanceof Serializable) {
     8     writeOrdinaryObject(obj, desc, unshared);
     9 } else {
    10     if (extendedDebugInfo) {
    11         throw new NotSerializableException(
    12             cl.getName() + "
    " + debugInfoStack.toString());
    13     } else {
    14         throw new NotSerializableException(cl.getName());
    15     }
    16 }

    也就是说,ObjectOutputStream 在序列化的时候,会判断被序列化的对象是哪一种类型,字符串?数组?枚举?还是 Serializable,如果全都不是的话,抛出 NotSerializableException

    假如 UserInfo 实现了 Serializable 接口,就可以序列化和反序列化了。  
     1 import java.io.Serializable;
     2 
     3 public class UserInfo implements Serializable {
     4     private int UserId;
     5     private String UserName;
     6     public int getUserId() {
     7         return UserId;
     8     }
     9     public void setUserId(int userId) {
    10         UserId = userId;
    11     }
    12     public String getUserName() {
    13         return UserName;
    14     }
    15     public void setUserName(String userName) {
    16         UserName = userName;
    17     }
    18     
    19 }

    具体序列化的过程如下:

    ObjectOutputStream 为例,它在序列化的时候会依次调用 writeObject()writeObject0()writeOrdinaryObject()writeSerialData()invokeWriteObject()defaultWriteFields()

    private void defaultWriteFields(Object obj, ObjectStreamClass desc)
            throws IOException
        {
            Class<?> cl = desc.forClass();
            desc.checkDefaultSerialize();
    
            int primDataSize = desc.getPrimDataSize();
            desc.getPrimFieldValues(obj, primVals);
            bout.write(primVals, 0, primDataSize, false);
    
            ObjectStreamField[] fields = desc.getFields(false);
            Object[] objVals = new Object[desc.getNumObjFields()];
            int numPrimFields = fields.length - objVals.length;
            desc.getObjFieldValues(obj, objVals);
            for (int i = 0; i < objVals.length; i++) {
    
                try {
                    writeObject0(objVals[i],
                                 fields[numPrimFields + i].isUnshared());
                }
            }
        }

    那反序列化呢?反序列化的过程如下:

    以 ObjectInputStream 为例,它在反序列化的时候会依次调用 readObject()readObject0()readOrdinaryObject()readSerialData()defaultReadFields()

     1 private void defaultWriteFields(Object obj, ObjectStreamClass desc)
     2         throws IOException
     3     {
     4         Class<?> cl = desc.forClass();
     5         desc.checkDefaultSerialize();
     6 
     7         int primDataSize = desc.getPrimDataSize();
     8         desc.getPrimFieldValues(obj, primVals);
     9         bout.write(primVals, 0, primDataSize, false);
    10 
    11         ObjectStreamField[] fields = desc.getFields(false);
    12         Object[] objVals = new Object[desc.getNumObjFields()];
    13         int numPrimFields = fields.length - objVals.length;
    14         desc.getObjFieldValues(obj, objVals);
    15         for (int i = 0; i < objVals.length; i++) {
    16 
    17             try {
    18                 writeObject0(objVals[i],
    19                              fields[numPrimFields + i].isUnshared());
    20             }
    21         }
    22     }

    Serializable 接口之所以定义为空,是因为它只起到了一个标识的作用,告诉程序实现了它的对象是可以被序列化的,但真正序列化和反序列化的操作并不需要它来完成。

    注意事项:
    1. static 和 transient 修饰的字段是不会被序列化的。
      证明:在UserInfo类里面加入一个字段:  private static int Age; 
      然后跑一边程序,发现输出的文件中是没有 Age的内容的;
      为什么出现这种现象呢?
      因为,序列化保存的是对象的状态,而 static 修饰的属性是属于类的状态,所以序列化是不会序列 static 修饰的属性;
     
    除了 Serializable 之外,Java 还提供了一个序列化接口 Externalizable
    有什么不同呢?
    首先,把 UserInfo 类实现的接口 Serializable 替换为 Externalizable
    可以看到语法提示需要实现两个方法:
     1 package Model;
     2 
     3 import java.io.*;
     4 
     5 public class UserInfo implements Externalizable {
     6 
     7     private int UserId;
     8     private String UserName;
     9     private static int Age=12;
    10 
    11     public static int getAge() {
    12         return Age;
    13     }
    14     public static void setAge(int age) {
    15         Age = age;
    16     }
    17     public int getUserId() {
    18         return UserId;
    19     }
    20     public void setUserId(int userId) {
    21         UserId = userId;
    22     }
    23     public String getUserName() {
    24         return UserName;
    25     }
    26     public void setUserName(String userName) {
    27         UserName = userName;
    28     }
    29 
    30     public void writeExternal(ObjectOutput out) throws IOException {
    31 
    32     }
    33     public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
    34 
    35     }
    36 }
     
      
  • 相关阅读:
    人月神话阅读笔记01
    个人每日总结14
    个人每日总结13
    个人每日总结12
    个人每日总结11
    个人每日总结10
    个人每日总结9
    第五周学习进度表
    梦断代码阅读笔记
    05构建之法阅读笔记之五
  • 原文地址:https://www.cnblogs.com/luoshengjie/p/11131401.html
Copyright © 2011-2022 走看看