• Java 序列化


      对象序列化的目标是将对象保存在磁盘中或者在网络中进行传输。实现的机制是允许将对象转为与平台无关的二进制流。java中对象的序列化机制是将允许对象转为字节序列。这些字节序列可以使Java对象脱离程序存在,从而可以保存在磁盘上,也可以在网络间传输。对象的序列化是将一个Java对象写入IO流;与此对应的,反序列化则是从IO流中恢复一个Java对象。要将一个java对象序列化,那么对象的类需要实现Serializable或者Externalizable接口。

     

    使用Serializable序列化

    • 如果一个类能被序列化,那么它的子类也能够被序列化。
    • 保证序列化对象的引用对象的类也是可序列化的,如不可序列化,可以使用transient关键字进行修饰,否则会序列化失败;
    • statictransient声明的成员变量是不能被序列化的。在变量声明前加上transient关键字,可以阻止该变量被序列化到文件中。transient只能修饰属性,不能修饰类或方法。
    public class Person implements Serializable {
        private String name;
        private transient int salary;
        public static String country;
      
        public String toString(){
            return "name:" + name + ", salary:" + salary + ", country:" + country;
        }
        // getter & setter ...
    }
    
    public class Test {
        public static final String path = "C:\object.txt";
    
        public static void main(String[] args) {
            Person person = new Person();
            person.setName("cindy");
            person.setSalary(10000000);               // 使用Serializable序列化的transient字段不能被序列化
            person.country = "China";                 // 使用Serializable序列化的static字段不能被序列化
            System.out.println(person);
            serialize(person, path);                  // 序列化时country为China
            Person.country = "UK";                    // 反序列化前设置country为UK
            System.out.println(deSerialize(path));    // 反序列化后country为UK
        }
    
        public static void serialize(Person person, String path) {
            try {
                ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
                oos.writeObject(person);
                oos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public static Person deSerialize(String path) {
            Person person = null;
            try {
                ObjectInputStream oos = new ObjectInputStream(new FileInputStream(path));
                person = (Person) oos.readObject();
                oos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return person;
        }
    }

    执行结果:

    name:cindy, salary:10000000, country:China
    name:cindy, salary:0, country:UK

    反序列化后country为UK,不是序列化时的值,说明使用Serializable序列化的static变量不能被序列化,反序列化读取出来的JVM中对应static变量的值

    使用Externalizable序列化

      外部序列化与序列化的主要区别在于序列化是内置的API,只需实现Serializable接口,不需要编写任何代码就可以实现对象的序列化,而使用外部序列化时,Externalizable接口中的方法必须由开发人员实现。因此与实现Serializable接口的方法相比,使用Externalizable编写程序的难度更大但编程时可灵活控制需要持久化的属性。当执行外部序列化时,static静态变量也被序列化了。

      若使用Externalizable序列化,则没有任何东西可以自动序列化,需要在writeExternal方法中进行手工指定所要序列化的变量,与字段是否被transient修饰无关。

    public class Person implements Externalizable {
        private String name;
        private transient int salary;
        private static String country;
        
        @Override
        public void readExternal(ObjectInput in) throws IOException,ClassNotFoundException {
            name = (String)in.readObject();
            salary = (Integer)in.readObject();
            country = (String)in.readObject();
        }
    
        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeObject(name);
            out.writeObject(country);
        }
        // getter & setter ...
    }
    
    public class Test {
        public static final String path = "D:\object.txt";
    
        public static void main(String[] args) {
            Person person = new Person();
            serialize(person, path);
            deSerialize(path);
        }
    
        public static void serialize(Person person, String path) {
            try {
                ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
                oos.writeObject(person);
                oos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public static Person deSerialize(String path) {
            Person person = null;
            try {
                ObjectInputStream oos = new ObjectInputStream(new FileInputStream(path));
                person = (Person) oos.readObject();
                oos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return person;
        }
    }
    View Code

    版本问题

      每个可序列化的类都有一个都有一个唯一的标识号与其关联,也就是我们通常说的 serialVersionUID。如果可序列化的类没有显示的声明该常量,Java 虚拟机会自动根据这个类调用一个复杂的运算过程,从而产生运行时的 serialVersionUID。这个自动生成的值将会受到类名、实现的接口名称、以及所有的公有方法和受保护的成员名称所影响。如果你通过任何方式改变了这些信息,兼容性都会遭到破坏,在运行时会导致 InvalidClassException 异常。

      执行序列化和反序列化时有可能会遇到JRE版本问题。尤其是在网络的两端进行通信时,这种情况更为多见。如果你没有显示的声明 serialVersionUID,就算类的信息没有发生改变,不同 Java 虚拟机序列化的实现方式上可能有所不同,这些细微的差别都有可能导致运行时生成的 serialVersionUID 不一致从而导致反序列化失败。

      为了解决这种问题,Java允许为序列化的类提供一个serialVersionUID的常量标识该类的版本。只要serialVersionUID的值不变,Java就会把它们当作相同的序列化版本。自定义serialVersionUID主要由如下3个优点。

    1. 提高程序运行效率。如果在类中未显示声明serialVersionUID,那么在序列化时会通过计算得到一个serialVersionUID。通过显示声明serialVersionUID的方式省去了计算的过程,提高了程序效率。
    2. 提高程序不同平台上的兼容性。由于各个平台计算serialVersionUID的方式可能不同,通过显示的方式可以完全避免该问题。
    3. 增强程序各个版本的可兼容性。在默认情况下每个类都有唯一的一个serialVersionUID,因此当后期对类进行修改时,类的serialVersionUID值将会发生变化,这将会导致类在修改前对象的文件在修改后无法进行反序列化操作。同样通过显示声明serialVersionUID也会解决该问题。

     

    序列化注意事项

    关于对象的序列化,总结下注意事项:

    • 反序列化时必须要有序列化对象的类的class文件;
    • 当通过文件网络读取序列化对象的时候,必需按写入的顺序来读取。
    • 反序列化无需通过构造器初始化对象;
    • 如果使用序列化机制向文件中写入了多个对象,那么取出和写入的顺序必须一致;
    • Java对类的对象进行序列化时,若类中存在对象引用(且值不为null),也会对类的引用对象进行序列化。

    参考文章:
    http://www.cnblogs.com/amunote/p/4171799.html
    http://www.cnblogs.com/zhanglei93/p/5890212.html

  • 相关阅读:
    网络编程(四)
    网络编程(三)
    网络编程(二)
    网络编程(一)
    异常处理
    Python 的名称空间和作用域
    如何在Java 8中愉快地处理日期和时间
    线段树入门整理、
    最小生成树prim、
    <climits>头文件
  • 原文地址:https://www.cnblogs.com/anxiao/p/6700681.html
Copyright © 2020-2023  润新知