Java-序列化-反序列化

作者: CokeNello | 来源:发表于2018-11-07 10:59 被阅读1次

    Thanks

    Java基础学习总结——Java对象的序列化和反序列化
    java序列化反序列化原理
    Java 序列化的高级认识
    Java中的关键字 transient

    Java中的序列化

    对象是存储在内存中,但如果我们想把对象持久化存到硬盘上该怎么做呢?在Java中,可以使用序列化:Serialization(序列化)是一种将对象以一连串的字节描述的过程;反序列化deserialization是一种将这些字节重建成一个对象的过程。

    简单例子

    Java中必须实现接口Serializable才能够被序列化,而Serializable接口没有方法,更像是个标记。 有了这个标记的Class就能被序列化机制处理。

    简单定义一个需要序列化的类:

    public class Person implements Serializable{
        public int height = 180;
        private int age = 21;
    }
    

    然后进行序列化

    public class Main {
        public static void main(String args[]) throws IOException {
            FileOutputStream fos = new FileOutputStream("temp.out");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            Person ts = new Person();
            oos.writeObject(ts);
            oos.flush();
            oos.close();
        }
    }
    

    序列化后保存到一份文件里面,文件内容如下,这些内容是有特定含义,

    aced 0005 7372 0022 736f 7572 6365 436f
    6465 2e74 6573 7453 6572 6961 6c69 7a61
    626c 652e 5065 7273 6f6e e400 6d6d fbe4
    589a 0200 0249 0003 6167 6549 0006 6865
    6967 6874 7870 0000 0015 0000 00b4
    

    一堆的16进制,保存的就是对象的属性了,如果我们再反序列话出来,就得到原来的对象。

    public static void main(String args[]) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream("temp.out");
        ObjectInputStream oin = new ObjectInputStream(fis);
        Person person = (Person) oin.readObject();
        System.out.println("height="+person.height);
    }
    

    SerialVersionUID

    对于每一个序列化的类,虚拟机会维护其一个对应的UID,相当于一个标识。如果没有显式地去声明UID,则在修改了类后,其UID会改变,会导致修改前的类反序列化失败,抛出ClassNotFoundException的错误。

    例如,上面序列化的类:

    public class Person implements Serializable{
        public int height = 180;
        private int age = 21;
    }
    

    及其对应的序列化文件:

    aced 0005 7372 0022 736f 7572 6365 436f
    6465 2e74 6573 7453 6572 6961 6c69 7a61
    626c 652e 5065 7273 6f6e e400 6d6d fbe4
    589a 0200 0249 0003 6167 6549 0006 6865
    6967 6874 7870 0000 0015 0000 00b4
    

    我们稍微修改一下类:

    public class Person implements Serializable{
        public int height = 180;
        private int age = 21;
    
        @Override
        public String toString() {
            return super.toString();
        }
    }
    

    然后反序列化,发现报错了:

    Exception in thread "main" java.io.InvalidClassException: sourceCode.testSerializable.Person; local class incompatible: stream classdesc serialVersionUID = -2017492313917073254, local class serialVersionUID = 9042608728172191849
        at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
        at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1630)
        at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
        at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)
        at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
        at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
    

    UID不一致,虚拟机认为这两个类是不一样的,抛出异常。序列化 ID 在 Eclipse 下提供了两种生成策略,一个是固定的 1L,一个是随机生成一个不重复的 long 类型数据(实际上是使用 JDK 工具生成),在这里有一个建议,如果没有特殊需求,就是用默认的 1L 就可以,这样可以确保代码一致时反序列化成功。那么随机生成的序列化 ID 有什么作用呢,有些时候,通过改变序列化 ID 可以用来限制某些用户的使用。

    静态变量序列化

    增加一个静态的成员:

    public class Person implements Serializable{
    
        private static final long serialVersionUID = 1L;
        public static int skin = 1;
        public int height = 180;
        public int age = 21;
    
        @Override
        public String toString() {
            return "Person{" +
                    "height=" + height +
                    ", age=" + age +
                    ", skin=" + skin +
                    '}';
        }
    }
    

    序列化前修改一下:

    FileOutputStream fos = new FileOutputStream("temp.out");
    ObjectOutputStream oos = new ObjectOutputStream(fos);
    Person person = new Person();
    person.height = 170;
    person.age = 25;
    person.skin = 3;
    oos.writeObject(person);
    oos.flush();
    oos.close();
    

    反序列化:

    FileInputStream fis = new FileInputStream("temp.out");
    ObjectInputStream oin = new ObjectInputStream(fis);
    Person person = (Person) oin.readObject();
    System.out.println(person.toString());
    //输出:
    Person{height=170, age=25, skin=1}
    

    可见,反序列化出来的静态成员不是序列化前的那个值。序列化时,并不保存静态变量,我们这样去理解,序列化保存的是对象的状态,静态变量属于类的状态,因此 序列化并不保存静态变量。

    Transient关键字

    Java中transient关键字的作用,简单地说,就是让某些被修饰的成员属性变量不被序列化,这一看好像很好理解,就是不被序列化,那么什么情况下,一个对象的某些字段不需要被序列化呢?如果有如下情况,可以考虑使用关键字transient修饰:

    1、类中的字段值可以根据其它字段推导出来,如一个长方形类有三个属性:长度、宽度、面积(示例而已,一般不会这样设计),那么在序列化的时候,面积这个属性就没必要被序列化了;

    2、其它,看具体业务需求吧,哪些字段不想被序列化

    序列化加密

    序列化和反序列化的时候,如果用户有重写对象类里面的 writeObjectreadObject 方法,则虚拟机直接调用它,反之默认调用默认调用是 ObjectOutputStreamdefaultWriteObject 方法以及 ObjectInputStreamdefaultReadObject 方法。

    那我们可以定义自己的规则来达到一个序列化时候的加密和反序列化时候解密。来测试一下:

    public class Person implements Serializable{
        private static final long serialVersionUID = 1L;
        private transient final String psdStr = "psdStr";
        public String name;
        public int height = 180;
        public int age = 21;
        @Override
        public String toString() {
            return "Person{" +
                    "name=" + name +
                    ", height=" + height +
                    ", age=" + age +
                    '}';
        }
        private void writeObject(ObjectOutputStream out) {
            try {
                ObjectOutputStream.PutField putFields = out.putFields();
                putFields.put("name", EncoderUtils.AESEncode(psdStr,String.valueOf(name)));
                putFields.put("height", height*1234);
                putFields.put("age", age*1234);
                out.writeFields();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        private void readObject(ObjectInputStream in) {
            try {
                ObjectInputStream.GetField readFields = in.readFields();
                name = EncoderUtils.AESDecode(psdStr, (String) readFields.get("name", ""));
                height = (readFields.get("height", 0))/1234;
                age = (readFields.get("age", 0))/1234;
            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
    

    加密:

    FileOutputStream fos = new FileOutputStream("temp.out");
    ObjectOutputStream oos = new ObjectOutputStream(fos);
    Person person = new Person();
    person.name = "王尼玛";
    person.height = 190;
    person.age = 28;
    oos.writeObject(person);
    oos.flush();
    oos.close();
    

    解密:

    FileInputStream fis = new FileInputStream("temp.out");
    ObjectInputStream oin = new ObjectInputStream(fis);
    Person person = (Person) oin.readObject();
    System.out.println(person.toString());
    //
    Person{name=王尼玛, height=190, age=28}
    

    相关文章

      网友评论

        本文标题:Java-序列化-反序列化

        本文链接:https://www.haomeiwen.com/subject/vwbmxqtx.html