• Java序列化(Serialization)


      关于Java的序列化的文章在网上已经够多了,在这里写关于Java序列化的文章是对自己关于这方面的的一种总结,结合以前的开发经验与网上的资料,写了这篇文章,对自己是有着巩固记忆的作用,也希望能够对大家有一定帮助。

    一、什么是序列化(Serialization)?

      序列化是Java提供的一种机制,将对象转化成字节序列,在字节序列中保存了对象的数据、对象的类型的信息与存储在对象中的数据的类型。序列化实际上就是将保存对象的"状态",可以方便以后的程序使用或者通过网络传输到另一台主机使用。一般来说,对象的生成周期取决于程序是否在执行,而序列化能够将对象保存在磁盘或网络中,这样对象就能生存在程序的调用之间。

    二、序列化的目的

      用序列化来保存对象的状态,主要是为了支持两大特性:

    1. Java的远程方法调用(RMI),它能够存活于其他机器上的对象使用起来就像存活于本地一样。当向远程对象发送消息时,需要通过对象序列化来传输参数和返回值。
    2. 在Java Beans中,必须使用对象序列化。

    三、序列化基本实例

      Java中实现序列化最基本的方法是实现Serializable接口,Serializable接口是标记接口,不包含任何方法。要序列化一个对象,必须先创建某些OutoutStream对象,然后封装到ObjectOutputStream对象内,其中ObjectOutputStream提供writeObject()方法,调用该方法即可将对象序列化。

      反序列化是指将对象序列化生成的字节序列还原成对象,需要将InputStream对象封装在ObjectInputStream对象中,然后调用readObject即可读出对象。

      下面是对象序列化的基本实例:

    class Pet implements Serializable {
        private String name;
    
        public Pet(String name) {
            this.name = name;
        }
    
        public String toString() {
            return name;
        }
    }
    
    public class Person implements Serializable {
        private static final long serialVersionUID = 1L;
        private int age;
        private String name;
        private Pet ownPet;
    
        public Person(String name, int age, Pet ownPet) {
            this.name = name;
            this.age = age;
            this.ownPet = ownPet;
        }
    
        @Override
        public String toString() {
            return "Person [age=" + age + ", name=" + name + ", ownPet=" + ownPet
                    + "]";
        }
    
        public static void main(String[] args) throws FileNotFoundException,
                IOException, ClassNotFoundException {
            Person p1 = new Person("person1 ", 47, new Pet("dog"));
            Person p2 = new Person("person2 ", 34, new Pet("cat"));
            // 对象序列化
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(
                    "person.out"));
            System.out.println("Save objects:");
            out.writeObject(p1);
            out.writeObject(p2);
            out.close();
    
            // 对象反序列化
            ObjectInputStream in = new ObjectInputStream(new FileInputStream(
                    "person.out"));
            System.out.println("Recovering objects:");
            p1 = (Person) in.readObject();
            p2 = (Person) in.readObject();
            System.out.println(p1);
            System.out.println(p2);
        }
    }

      上述程序结果是:

    Save objects:
    Recovering objects:
    Person [age=47, name=person1 , ownPet=dog]
    Person [age=34, name=person2 , ownPet=cat]

      从上面的结果看出,对象序列化不仅将调用writeObject()的对象序列化,而且通过追踪调用writeObject()的对象的引用,并保存那些引用的对象,所以对象序列化能够自动保存对象的相关引用对象,能够保证对象信息的完整性,因此能够使用对象序列化进行对象的深度复制。

      注意事项:实现Serializable接口对象反序列化并没有调用构造器,是直接将字节序列还原成对象。在序列化过程中必须保证classpath中必须有该类,在本实例即Person.class,否则抛出ClassNotFoundException。

    三、实现Externalizable接口

      序列化的另一种方法是实现Externalizable接口,Externalizable接口提供以下默认方法:

        void writeExternal(ObjectOutput out) throws IOException;
        void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;

      用户必须使用自己实现这两个方法,在方法中可以控制序列化,如可以让一些成员属性不进行序列化,实例如下:

    public class Test implements Externalizable {
    
        private int i;
        private String s;
    
        public Test() {
            System.out.println("Test default constructor");
        }
    
        public Test(String x, int a) {
            System.out.println("Test(String x, int a)");
            s = x;
            i = a;
        }
    
        public String toString() {
            return s + i;
        }
    
        public void writeExternal(ObjectOutput out) throws IOException {
            System.out.println("Blip3.writeExternal");
            // You must do this:
            out.writeObject(s);
            out.writeInt(i);
        }
    
        public void readExternal(ObjectInput in) throws IOException,
                ClassNotFoundException {
            System.out.println("Blip3.readExternal");
            // You must do this:
            s = (String) in.readObject();
            i = in.readInt();
        }
    
        public static void main(String[] args) throws FileNotFoundException,
                IOException, ClassNotFoundException {
            System.out.println("Constructorint objects:");
            Test test = new Test("A String ", 47);
            System.out.println(b3);
            // 对象序列化
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(
                    "test.out"));
            System.out.println("Save objects:");
            out.writeObject(b3);
            out.close();
    
            // 对象反序列化
            ObjectInputStream in = new ObjectInputStream(new FileInputStream(
                    "test.out"));
            System.out.println("Recovering test:");
            b3 = (Test) in.readObject();
        }
    }

      上述程序运行结果为:

    Constructorint objects:
    Test(String x, int a)
    A String 47
    Save objects:
    Test.writeExternal
    Recovering test:
    Test default constructor
    Test.readExternal

      从结果中能够看出为了能实现对象的序列化,不仅必须在writeExternal()方法中写入对象成员变量,而且要在readExternal()方法中读取成员变量。在对象的反序列化过程中调用了默认的构造方法。

    四、transient关键词

      transient能够对成员变量进行控制,防止敏感信息泄漏,当然上面的实现Externalizable的方法也可以完成该功能,但是必须自己控制所有的属性进行序列化,比较复杂。而采用transient(瞬时)关键词就能关闭该字段的序列化,如下实例所示:

    public class Logon implements Serializable {
        private Date date = new Date();
        private String username;
        private transient String password;
    
        public Logon(String name, String password) {
            this.username = name;
            this.password = password;
        }
    
        public String toString() {
            return "Logon info: 
     username: " + username + "
     date: " + date
                    + "
     password: " + password;
        }
    
        public static void main(String[] args) throws FileNotFoundException,
                IOException, ClassNotFoundException {
            Logon a = new Logon("Hulk", "myLittlePony");
            System.out.println("logon a =" + a);
            ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(
                    "out/Logon.out"));
            o.writeObject(a);
            o.close();
    
            // 获取序列化的数据
            ObjectInputStream in = new ObjectInputStream(new FileInputStream(
                    "out/Logon.out"));
            System.out.println("Recovering object at " + new Date());
            a = (Logon) in.readObject();
            System.out.println("logon a =" + a);
        }
    }

      上述程序的结果为:

    logon a = Logon info: 
     username: Hulk
     date: Wed Jan 20 21:55:16 CST 2016
     password: myLittlePony
    Recovering object at Wed Jan 20 21:55:16 CST 2016
    logon a = Logon info: 
     username: Hulk
     date: Wed Jan 20 21:55:16 CST 2016
     password: null

      从结果中,可知对password字段进行了隐藏,没有进行序列化。

    五、代替实现Externalizable

      在对象序列化过程中对序列化控制,如果采用实现Externalizable接口的方式,必须实现每一个属性的序列化控制,太过复杂,那如何替代它呢?通过实现Serializable接口并添加两个方法,只要提供这两个方法,就会使用它们而不是默认的序列化机制。这两个方法签名如下所示:

      private void writeObject(ObjectOutputStream stream) throws IOException

      private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException

      在上面两个方法中能够调用默认的处理方法,也可以自己控制属性的序列化,实例代码如下:

    public class SerialCtl implements Serializable {
        private String a;
        private transient String b;
    
        public SerialCtl(String aa, String bb) {
            this.a = "Not trnsient: " + aa;
            this.b = "Transient: " + bb;
        }
    
        public String toString() {
            return a + "
    " + b;
        }
    
        private void writeObject(ObjectOutputStream stream) throws IOException {
            stream.defaultWriteObject();
            stream.writeObject(b);
        }
    
        private void readObject(ObjectInputStream stream) throws IOException,
                ClassNotFoundException {
            stream.defaultReadObject();
            b = (String) stream.readObject();
        }
    
        public static void main(String[] args) throws FileNotFoundException,
                IOException, ClassNotFoundException {
            SerialCtl sc = new SerialCtl("Test1", "Test2");
            System.out.println("Before:
     " + sc);
            ByteArrayOutputStream buf = new ByteArrayOutputStream();
            ObjectOutputStream o = new ObjectOutputStream(buf);
            o.writeObject(sc);
            // 获取序列化的数据
            ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
                    buf.toByteArray()));
            sc = (SerialCtl) in.readObject();
            System.out.println("After:
     " + sc);
        }
    }

      上述程序的结果:

    Before:
     Not trnsient: Test1
    Transient: Test2
    After:
     Not trnsient: Test1
    Transient: Test2

    六、总结

      综上所述,最简单的序列化就是实现Serializable接口,如果为了敏感信息的安全,可以使用transient关键字。如果想近一步控制序列化过程,建议实现Serializable接口并添加两个序列化方法,并自定义实现方法。

  • 相关阅读:
    详解SQL Server的两个存储过程:sp_MSforeachtable/sp_MSforeachdb
    使用WorkService创建定时任务
    Mahout下个性化推荐引擎Taste介绍
    Apache Mahout中的机器学习算法集
    内网信息收集笔记 楼下的小可怜
    关于linux的suid提权 楼下的小可怜
    Cobalt Strike初探 楼下的小可怜
    Google hacking 楼下的小可怜
    Git和Repo扫盲——如何取得Android源代码 zt
    Howto find native code memory leak in Android
  • 原文地址:https://www.cnblogs.com/codingexperience/p/5146800.html
Copyright © 2020-2023  润新知