• JavaSE-序列化和反序列化


    什么是序列化,什么时候要进行序列化?

      序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化,将数据分解成字节流,以便存储在文件中或在网络上传输。

      我们在对java对象进行IO流操作或者进行网络传输的时候就要进行序列化。

    Java对象序列化的方式

      一、实现Serializable接口

    package org.burning.sport.javase.serializable.demo1;
    
    import java.io.Serializable;
    import java.util.Date;
    
    public class User implements Serializable {
    
        private static final long serialVersionUID = -6330597348767194997L;
    
        private String name;
    
        private int age;
    
        private Date birthday;
    
        private transient String gender;
    
        setters();getters();toString();
    }
    package org.burning.sport.javase.serializable.demo1;
    
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.util.Date;
    
    public class SerializableDemo {
        public static void main(String[] args) {
            User user = new User();
            user.setName("hollis");
            user.setGender("male");
            user.setAge(23);
            user.setBirthday(new Date());
            System.out.println("serializable before:" + user);
    
            /**
             * 将对象持久化到文件中
             */
            ObjectOutputStream oos = null;
            try {
                oos = new ObjectOutputStream(new FileOutputStream("temp"));
                oos.writeObject(user);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if(oos != null) {
                    try {
                        oos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    
            /**
             * 从文件中读取对象
             */
            ObjectInputStream ois = null;
            try {
                ois = new ObjectInputStream(new FileInputStream("temp"));
                User readUser = (User)ois.readObject();
                System.out.println("serializable after:" + readUser);
            } catch (Exception e) {
                e.printStackTrace();
            }  finally {
                if (ois != null) {
                    try {
                        ois.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
    // output
    //serializable before:User{name='hollis', age=23, birthday=Sat May 26 11:35:06 CST 2018, gender='male'}
    //serializable after:User{name='hollis', age=23, birthday=Sat May 26 11:35:06 CST 2018, gender='null'}

    小结:

      1、在Java中,只要一个类实现了 java.io.Serializable 接口,那么它就可以被序列化。

      2、通过 ObjectOutputStream 和 ObjectInputStream 对对象进行序列化及反序列化。

      3、虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID )

      4、序列化并不保存静态变量。

      Demo:

    package org.burning.sport.javase.serializable.demo2;
    
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.io.Serializable;
    
    public class StaticVariableTest implements Serializable {
    
        private static final long serialVersionUID = 1L;
        /**初始化静态变量为5*/
        public static int staticVar = 5;
    
        public static void main(String[] args) throws Exception{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("result.obj"));
            outputStream.writeObject(new StaticVariableTest());
            outputStream.close();
    
            // 将静态变量值改为10,再读入
            StaticVariableTest.staticVar = 10;
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("result.obj"));
            StaticVariableTest st = (StaticVariableTest)objectInputStream.readObject();
            objectInputStream.close();
    
            System.out.println(st.staticVar);
        }
    }
    // output 10

      5、要想将父类对象也序列化,就需要让父类也实现 Serializable 接口。

      6、Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。

      7、服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。

    ArrayList序列化有什么不一样?

      我们先看看ArrayList的一段源码:

    public class ArrayList<E> extends AbstractList<E>
            implements List<E>, RandomAccess, Cloneable, java.io.Serializable
    {
        private static final long serialVersionUID = 8683452581122892189L;
        transient Object[] elementData; // non-private to simplify nested class access
        private int size;
    }

      我们看到elementData是transient的,所以我们认为elementData是不会被序列化保留下来的,下面做个Demo:

    package org.burning.sport.javase.serializable.demo1;
    
    import org.apache.commons.io.IOUtils;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.util.ArrayList;
    import java.util.List;
    
    public class ArrayListSerializableDemo {
        public static void main(String[] args) throws Exception{
            List<String> stringList = new ArrayList<String>();
            stringList.add("hello");
            stringList.add("world");
            stringList.add("hollis");
            stringList.add("chuang");
            System.out.println("init StringList" + stringList);
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("stringlist"));
            objectOutputStream.writeObject(stringList);
    
            IOUtils.closeQuietly(objectOutputStream);
            File file = new File("stringlist");
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
            List<String> newStringList = (List<String>)objectInputStream.readObject();
            IOUtils.closeQuietly(objectInputStream);
            if(file.exists()){
                file.delete();
            }
            System.out.println("new StringList" + newStringList);
        }
    }
    
    // output~
    //init StringList[hello, world, hollis, chuang]
    //new StringList[hello, world, hollis, chuang]

      了解ArrayList的人都知道,ArrayList底层是通过数组实现的。那么数组 elementData 其实就是用来保存列表中的元素的。通过该属性的声明方式我们知道,他是无法通过序列化持久化下来的。那么为什么code 4的结果却通过序列化和反序列化把List中的元素保留下来了呢?

      答案就在ArrayList中的writeObject和readObject两个方法中:

    private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            elementData = EMPTY_ELEMENTDATA;
    
            // Read in size, and any hidden stuff
            s.defaultReadObject();
    
            // Read in capacity
            s.readInt(); // ignored
    
            if (size > 0) {
                // be like clone(), allocate array based upon size not capacity
                ensureCapacityInternal(size);
    
                Object[] a = elementData;
                // Read in all elements in the proper order.
                for (int i=0; i<size; i++) {
                    a[i] = s.readObject();
                }
            }
        }
    private void writeObject(java.io.ObjectOutputStream s)
            throws java.io.IOException{
            // Write out element count, and any hidden stuff
            int expectedModCount = modCount;
            s.defaultWriteObject();
    
            // Write out size as capacity for behavioural compatibility with clone()
            s.writeInt(size);
    
            // Write out all elements in the proper order.
            for (int i=0; i<size; i++) {
                s.writeObject(elementData[i]);
            }
    
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
        }

    小结:

      在序列化过程中,如果被序列化的类中定义了writeObject 和 readObject 方法,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化。如果没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。

      那有个问题,为什么AarrayList要把elementDate给transient呢?

      ArrayList实际上是动态数组,每次在放满以后自动增长设定的长度值,如果数组自动增长长度设为100,而实际只放了一个元素,那就会序列化99个null元素。为了保证在序列化的时候不会将这么多null同时进行序列化,ArrayList把元素数组设置为transient。但是,作为一个集合,在序列化过程中还必须保证其中的元素可以被持久化下来,所以,通过重写 writeObject 和 readObject 方法的方式把其中的元素保留下来。

      writeObject 方法把 elementData 数组中的元素遍历的保存到输出流(ObjectOutputStream)中。

      readObject 方法从输入流(ObjectInputStream)中读出对象并保存赋值到 elementData 数组中。

      所以如果想自定义序列化和反序列化策略就可以模仿ArrayList的做法。  

    ObjectOuptStream&ObjectInputStream

      在上个面讲到的ArrayList的例子中,有个问题就是,我们没有看到writeObject和readObject的调用啊,这两个方法是怎么被调用的呢?原因就是ObjectOuptStream&ObjectInputStream

      看一下ObjectOuptStream的调用栈是怎样的?writeObject —> writeObject0 —> writeOrdinaryObject —> writeSerialData —> invokeWriteObject  。这里看一下invokeWriteObject:

    void invokeWriteObject(Object obj, ObjectOutputStream out)
            throws IOException, UnsupportedOperationException
        {
            if (writeObjectMethod != null) {
                try {
                    writeObjectMethod.invoke(obj, new Object[]{ out });
                } catch (InvocationTargetException ex) {
                    Throwable th = ex.getTargetException();
                    if (th instanceof IOException) {
                        throw (IOException) th;
                    } else {
                        throwMiscException(th);
                    }
                } catch (IllegalAccessException ex) {
                    // should not occur, as access checks have been suppressed
                    throw new InternalError(ex);
                }
            } else {
                throw new UnsupportedOperationException();
            }
        }

      其中 writeObjectMethod.invoke(obj, new Object[]{ out }); 是关键,通过反射的方式调用writeObjectMethod方法。官方是这么解释这个writeObjectMethod的:

      class-defined writeObject method, or null if none

      在我们的例子中,这个方法就是我们在ArrayList中定义的writeObject方法。通过反射的方式被调用了。

      至此,我们先试着来回答刚刚提出的问题:

      如果一个类中包含writeObject 和 readObject 方法,那么这两个方法是怎么被调用的?

      答:在使用ObjectOutputStream的writeObject方法和ObjectInputStream的readObject方法时,会通过反射的方式调用。

      至此,我们已经介绍完了ArrayList的序列化方式。

    总结:

       1、如果一个类想被序列化,需要实现Serializable接口。否则将抛出 NotSerializableException 异常,这是因为,在序列化操作过程中会对类型进行检查,要求被序列化的类必须属于Enum、Array和Serializable类型其中的任何一种。

       2、在变量声明前加上该关键字,可以阻止该变量被序列化到文件中。

       3、在类中增加writeObject 和 readObject 方法可以实现自定义序列化策略

     

     https://gitee.com/play-happy/base-project

    参考:

    【1】博客,https://www.cnblogs.com/Qian123/articles/5665671.html

    【2】博客,https://blog.csdn.net/tlycherry/article/details/8986720

    【3】博客,https://blog.csdn.net/jiangwei0910410003/article/details/18989711/

  • 相关阅读:
    jsp中的contentType与pageEncoding的区别和作用
    HashMap实现原理及源码分析
    JAVA使用urlrewrite实现伪静态化
    java动态代理(JDK和cglib)
    Codeforces 1201D. Treasure Hunting
    Codeforces 1201C. Maximum Median
    Codeforces 1229C. Konrad and Company Evaluation
    Codeforces 1229B. Kamil and Making a Stream
    Codeforces 1229A. Marcin and Training Camp
    P1315 观光公交
  • 原文地址:https://www.cnblogs.com/happyflyingpig/p/9092417.html
Copyright © 2020-2023  润新知