zoukankan      html  css  js  c++  java
  • Java序列化

    简要解释:
      
    序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。

      序列化是为了解决在对对象流进行读写操作时所引发的问题。序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,
    然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

    详细解释:

          当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。

      只能将支持 java.io.Serializable 接口的对象写入流中。每个 serializable 对象的类都被编码,编码内容包括类名和类签名、对象的字段值和数组值,以及从初始对象中引用的其他所有对象的闭包

    1.概念

      序列化:把Java对象转换为字节序列的过程。
      反序列化:把字节序列恢复为Java对象的过程。

    2.用途

      对象的序列化主要有两种用途:
      1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
      2) 在网络上传送对象的字节序列。

     

    3.对象序列化

    序列化API

      java.io.ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。只有实现了Serializable和Externalizable接口的类的对象才能被序列化。

      java.io.ObjectInputStream代表对象输入流,它的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。

    代码示例

    复制代码
     1 import java.io.*;
     2 import java.util.Date;
     3 
     4 public class ObjectSaver {
     5     public static void main(String[] args) throws Exception {
     6         /*其中的  D:\objectFile.obj 表示存放序列化对象的文件*/
     7 
     8         
     9         //序列化对象
    10         ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("D:\objectFile.obj"));
    11         Customer customer = new Customer("王麻子", 24);    
    12         out.writeObject("你好!");    //写入字面值常量
    13         out.writeObject(new Date());    //写入匿名Date对象
    14         out.writeObject(customer);    //写入customer对象
    15         out.close();
    16 
    17         
    18         //反序列化对象
    19         ObjectInputStream in = new ObjectInputStream(new FileInputStream("D:\objectFile.obj"));
    20         System.out.println("obj1 " + (String) in.readObject());    //读取字面值常量
    21         System.out.println("obj2 " + (Date) in.readObject());    //读取匿名Date对象
    22         Customer obj3 = (Customer) in.readObject();    //读取customer对象
    23         System.out.println("obj3 " + obj3);
    24         in.close();
    25     }
    26 }
    27 
    28 class Customer implements Serializable {
    29     private String name;
    30     private int age;
    31     public Customer(String name, int age) {
    32         this.name = name;
    33         this.age = age;
    34     }
    35 
    36     public String toString() {
    37         return "name=" + name + ", age=" + age;
    38     }
    39 }
    复制代码

    执行结果

     

    4.说明

      读取对象的顺序与写入时的顺序要一致

      对象的默认序列化机制写入的内容是:对象的类,类签名,以及非瞬态非静态字段的值。

    =========================================================================================================

    一、Java序列化和反序列化是什么?

      通俗的来讲,序列化过程就是将对象转成二进制流存入内存或者文件,反序列化从内存或文件中读取二进制流转换成对象。

      

    二、为什么需要序列化与反序列化?

      其实这个问题也就是它们的应用场景有哪些?这样就容易回答多了。比如文件(文本、图片等)进行传输,这些文件都是通过二进制序列的形式进行传输的(序列化过程),而接收方则要读取这些二进制数据进行相对应的转换(反序列化过程)。除了这个它主要用于网络传输(进程之间的通信等)。

     

    三、怎么实现Java序列化和反序列化?

      要想实现序列化有个必要条件就是要实现Serializable接口或Externalizable接口。大部分可能只知道有Serializable接口没有关注Externalizable接口,那么你看了本文之后就应该知道了,后面再介绍它们的区别。

      有了上面个必要条件后还需要借助jdk中有两个类:java.io.ObjectOutputStream和java.io.ObjectInputStream,它们分别负责序列化和反序列化。我们可以看下这两个类的说明就知道是这两个类负责相对应的功能。

    复制代码
    /**
     * An ObjectOutputStream writes primitive data types and graphs of Java objects
     * to an OutputStream.  The objects can be read (reconstituted) using an
     * ObjectInputStream.  Persistent storage of objects can be accomplished by
     * using a file for the stream.  If the stream is a network socket stream, the
     * objects can be reconstituted on another host or in another process.
     *
     * <p>Only objects that support the java.io.Serializable interface can be
     * written to streams.  The class of each serializable object is encoded
     * including the class name and signature of the class, the values of the
     * object's fields and arrays, and the closure of any other objects referenced
     * from the initial objects.
     * ...
     */
    public class ObjectOutputStream
        extends OutputStream implements ObjectOutput, ObjectStreamConstants{}
    复制代码
    复制代码
    /**
     * An ObjectInputStream deserializes primitive data and objects previously
     * written using an ObjectOutputStream.
     *
     * <p>ObjectOutputStream and ObjectInputStream can provide an application with
     * persistent storage for graphs of objects when used with a FileOutputStream
     * and FileInputStream respectively.  ObjectInputStream is used to recover
     * those objects previously serialized. Other uses include passing objects
     * between hosts using a socket stream or for marshaling and unmarshaling
     * arguments and parameters in a remote communication system.
     * ...
     */
    public class ObjectInputStream
        extends InputStream implements ObjectInput, ObjectStreamConstants{
    }    
    复制代码

      下面我们以学生对象为例,将对象序列化保存至文件中,再从文件中反序列化转换成对象。

    复制代码
    import java.io.Serializable;
    
    /**
     * @Description: 学生类 已实现序列化接口
     * @author yuanfy
     * @date 2018年1月11日 上午11:36:37 
     * @version 1.0
     */
    public class Student implements Serializable{
    
        private static final long serialVersionUID = 6415983562512521049L;
    
        private String name;
        
        private int age;
        
        private String sex;
        
        public Student(String name, int age, String sex) {
            this.name = name;
            this.age = age;
            this.sex = sex;
        }
        
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
        public String getSex() {
            return sex;
        }
        public void setSex(String sex) {
            this.sex = sex;
        }
        @Override
        public String toString() {
            return "Student [name=" + name + ", age=" + age + ", sex=" + sex + "]";
        }
    }
    复制代码

      上面已经定义Java对象, 下面进行序列化测试

    复制代码
        @Test
        public void testSerializable() throws FileNotFoundException, IOException{
            Student s = new Student("james", 31, "man");
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("E:\Student.txt")));
            oos.writeObject(s);
            oos.close();
        }
    复制代码

       如果电脑E盘存在的话运行肯定通过(windows系统),单元测试通过后(说明序列化过程已完成)查看Student.txt文件中的内容,如下:

      

      上面是正常的情况, 如果Student类没有实现Serializable接口呢?那么序列化时会存在什么样的问题。我们把Student类去掉Serializable接口的实现,然后再进行序列化测试。这时你会发现单元测试后不通过,先看看报错原因:下面截图中的错误信息提示Student类没有被序列化。

      

      那么为什么java.io.ObjectOutputStream类会抛出这样的异常呢,它是怎么识别有没有实现Serializable接口的。接下来,我们来看看他的源码:writeObject方法中调用了writeObject()方法,所以主要逻辑在它里面。

    复制代码
        /**
         * Underlying writeObject/writeUnshared implementation.
         */
        private void writeObject0(Object obj, boolean unshared)
            throws IOException
        {
            boolean oldMode = bout.setBlockDataMode(false);
            depth++;
            try {
                //前面部分代码省略
                // remaining cases
                if (obj instanceof String) {
                    writeString((String) obj, unshared);
                } else if (cl.isArray()) {
                    writeArray(obj, desc, unshared);
                } else if (obj instanceof Enum) {
                    writeEnum((Enum<?>) obj, desc, unshared);
                } else if (obj instanceof Serializable) {//判断是否是Serializable的子类。
                    writeOrdinaryObject(obj, desc, unshared);
                } else {
                    //除了String类型、数组类型和枚举类型,其他对象如果没有实现Serializable接口,都会抛出NotSerializableException异常
                    if (extendedDebugInfo) {
                        throw new NotSerializableException(
                            cl.getName() + "
    " + debugInfoStack.toString());
                    } else {
                        throw new NotSerializableException(cl.getName());
                    }
                }
            } finally {
                depth--;
                bout.setBlockDataMode(oldMode);
            }
        }
    复制代码

      从中可以看出,除了String类型、数组类型和枚举类型,其他对象如果没有实现Serializable接口,都会抛出NotSerializableException异常。

      接下来进行反序列化测试:

    复制代码
        @Test
        public void testDeserialize() throws FileNotFoundException, IOException, ClassNotFoundException{
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:\Student.txt")));
            Student s = (Student)ois.readObject();
            System.out.println(s);
            ois.close();
        }
    复制代码

     

      在进行正常的序列化测试后接着测试反序列化测试,正常来说是不会报错的。看看测试结果就明了了。

      同样上面是正常的情况。接下来异常的情况就能体现出Student类中serialVersionUID变量的作用了:它就是验证序列化与反序列化的唯一性。在序列化后修改Student类中的serialVersionUID= 61234L,然后再测试反序列化,测试结果如下:

     

       从错误提示中可以看出,会从解析出来的serialVersionUID和要转换的class中serialVersionUID做对比,判断是否相等。java.io.ObjectStreamClass关键源码如下:

    复制代码
        /**
         * Initializes class descriptor representing a non-proxy class.
         */
        void initNonProxy(ObjectStreamClass model,
                          Class<?> cl,
                          ClassNotFoundException resolveEx,
                          ObjectStreamClass superDesc)
            throws InvalidClassException
        {
            long suid = Long.valueOf(model.getSerialVersionUID());
            ObjectStreamClass osc = null;
            if (cl != null) {
                osc = lookup(cl, true);
                if (osc.isProxy) {
                    throw new InvalidClassException(
                            "cannot bind non-proxy descriptor to a proxy class");
                }
                if (model.isEnum != osc.isEnum) {
                    throw new InvalidClassException(model.isEnum ?
                            "cannot bind enum descriptor to a non-enum class" :
                            "cannot bind non-enum descriptor to an enum class");
                }
                //这里会判断serialVersionUID是否一致
                if (model.serializable == osc.serializable &&
                        !cl.isArray() &&
                        suid != osc.getSerialVersionUID()) {
                    throw new InvalidClassException(osc.name,
                            "local class incompatible: " +
                                    "stream classdesc serialVersionUID = " + suid +
                                    ", local class serialVersionUID = " +
                                    osc.getSerialVersionUID());
                }
                //后面代码略
            }
        }
    复制代码

     四、几个序列化注意事项

      1、对继承父类的子类序列化

    复制代码
    public class Person {
        public int age;
        
        public int weight;
        
        public Person(int age, int weight) {
            this.age = age;
            this.weight = weight;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public int getWeight() {
            return weight;
        }
    
        public void setWeight(int weight) {
            this.weight = weight;
        }
        
    }
    复制代码
    复制代码
    import java.io.Serializable;
    
    public class Women extends Person implements Serializable{
        
        private static final long serialVersionUID = -1259423203325949704L;
        
        private String name;
      
        public Women(String name, int age, int weight) {
            super(age, weight);
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "Student [name=" + name + ", age=" + age + ", weight=" + weight + "]";
        }
    }
    复制代码

      单元测试代码如下,先猜错下会不会运行通过,结果又是啥?

    复制代码
        @Test
        public void test2() throws FileNotFoundException, IOException, ClassNotFoundException{
            Women w = new Women("Scarlett Johansson", 34, 50);
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("E:\Women.txt")));
            oos.writeObject(w);
            oos.close();
            
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:\Women.txt")));
            Women s = (Women)ois.readObject();
            System.out.println(s);
            ois.close();
        }
    复制代码

      测试结果如下,居然报错。该错误提示需要提供无参构造函数(During deserialization, the fields of non-serializable classes will be initialized using the public or protected no-arg constructor of the class. A no-arg constructor must be accessible to the subclass that is serializable. The fields of serializable subclasses will be restored from the stream.),具体详情解释见这here.

      

      当我们提供无参构造函数后再进行单元测试得到结果:Student [name=Scarlett Johansson, age=0, weight=0]。所以可以得出结论:父类中的字段不参与序列化,只是将其初始化而已。

      2、transient 关键字

       使用transient关键字来修饰变量,然后进行序列化(在实现serializable接口情况下)效果其实和父类的序列化一样,它所修饰的变量不参与序列化。这里就不举例说明了,自己可以写个案例测试下。

      另外transient关键字只能修饰变量, 不能修饰类和方法。

      3、static关键字

      使用static关键字来修饰变量,不管有没有transient修饰,同样不参与序列化(在实现serializable接口情况下)

      4、Externalizable接口

       Externalizable接口extends Serializable接口,而且在其基础上增加了两个方法:writeExternal()和readExternal()。这两个方法会在序列化和反序列化还原的过程中被自动调用,以便执行一些特殊的操作。

      下面看案例:

    复制代码
    import java.io.Externalizable;
    import java.io.IOException;
    import java.io.ObjectInput;
    import java.io.ObjectOutput;
    
    /**
     * @Description: 学生类 已实现序列化接口
     * @author yuanfy
     * @date 2018年1月11日 上午11:36:37 
     * @version 1.0
     */
    public class Student implements Externalizable{
    
        private static final long serialVersionUID = 61234L;
    
        private String name;
        
        private transient int age;
        
        private static String sex;
        
        //如果覆盖了无参构造函数就一定要显示声明无参构造函数,跟父类序列化的案例一样。
        public Student(){}
        
        public Student(String name, int age, String sex) {
            this.name = name;
            this.age = age;
            this.sex = sex;
        }
        
        public  String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
        public String getSex() {
            return sex;
        }
        public void setSex(String sex) {
            this.sex = sex;
        }
        @Override
        public String toString() {
            return "Student [name=" + name + ", age=" + age + ", sex=" + sex + "]";
        }
    
        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            // TODO Auto-generated method stub
            out.writeUTF(name);
            out.writeInt(age);
            out.writeObject(sex);
        }
    
        @Override
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            this.name = in.readUTF();
            this.age = in.readInt();
            this.sex = (String)in.readObject();
        }
    }
    复制代码
    复制代码
        @Test
        public void test3() throws FileNotFoundException, IOException, ClassNotFoundException{
            Student s1 = new Student("james", 31, "man");
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("E:\Student.txt")));
            oos.writeObject(s1);
            oos.close();
            
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("E:\Student.txt")));
            Student s2 = (Student)ois.readObject();
            System.out.println(s2);
            ois.close();
        }
    复制代码

     

      上面是正常完整案例,其中要注意的是,要序列化的类要提供无参构造函数,否则反序列化会报错(When an Externalizable object is reconstructed, an instance is created using the public no-arg constructor, then the readExternal method called. Serializable objects are restored by reading them from an ObjectInputStream.)详解here。得出结论如下:

      与Serizable对象不同,使用Externalizabled,就意味着没有任何东西可以自动序列化, 为了正常的运行,我们需要在writeExtenal()方法中将自对象的重要信息写入,从而手动的完成序列化。对于一个Externalizabled对象,对象的默认构造函数都会被调用(包括哪些在定义时已经初始化的字段),然后调用readExternal(),在此方法中必须手动的恢复数据。这就说明在Externalizable接口下不管是transient或static修饰的变量,如果没有指定写入,就不会序列化。

  • 相关阅读:
    什么是 bean 的自动装配?
    什么是 Spring 的内部 bean?
    什么是 Spring 的 MVC 框架?
    Spring AOP and AspectJ AOP 有什么区别?
    解释 JDBC 抽象和 DAO 模块?
    volatile 类型变量提供什么保证?
    一个 Spring Bean 定义 包含什么?
    什么是 Spring MVC 框架的控制器?
    使用 Spring 访问 Hibernate 的方法有哪些?
    什么是 Callable 和 Future?
  • 原文地址:https://www.cnblogs.com/cn-chy-com/p/11146093.html
Copyright © 2011-2022 走看看