Java 杂谈

Java中的序列化

2018-08-12  本文已影响0人  zhang5788

序列化

什么是序列化?

当你创建对象时,只有你需要,它就会一直存在,但是在程序终止时,无论如何他都不会在内存中存活。尽管这是有意义的,但是如果能将一个对象剥离到硬盘中,进行持久化存储,这也是很有意义的。Java提供了这么一个功能,就是序列化。

如何序列化?

在Java中,提供了两个接口来进行序列化操作。

1. Serializable 接口

这个接口是java中的一个标记接口,实现这个接口的类会被java识别为可以被序列化的类。

public class A implements Serializable {
    private String a;
    private String b;
    private int c;

    A(String a, String b, int c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }
    @Override
    public String toString()  {
        return a + ", "+ b + ", " + c;
    }
}

public static void main(String[] args) throws Exception {
        A a = new A("1", "2", 3);
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("/Users/zhangt/Desktop/A.out"));
        out.writeObject(a);
        out.close();

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("/Users/zhangt/Desktop/A.out"));
        A a1 = (A)in.readObject();
        System.out.println(a1);
}

输出结果为:
1, 2, 3
这代表我们从文件中读取的类就是我们实例化的那个类。

2. Externalizable 接口

Externalizable 接口是Seriazable 接口的一个子接口。这个接口中定义了两个方法:

public interface Externalizable extends java.io.Serializable {
   void writeExternal(ObjectOutput out) throws IOException;
   void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}

这个接口虽然是Serializable的子接口,但是并不是一个标记性质的接口。实现该接口的类必须强制实现这两个方法:

public class A implements Externalizable {
    private String a;
    private String b;
    private int c;

    public A(String a, String b, int c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    @Override
    public String toString()  {
        return a + ", "+ b + ", " + c;
    }


    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(a);
        out.writeObject(b);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this.a = (String)in.readObject();
        this.b = (String)in.readObject();
    }
}

public static void main(String[] args) throws Exception {
        A a = new A("1", "2", 3);
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("/Users/zhangt/Desktop/A.out"));
        out.writeObject(a);
        out.close();

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("/Users/zhangt/Desktop/A.out"));
        A a1 = (A)in.readObject();
        System.out.println(a1);
}

输出结果为:

Exception in thread "main" java.io.InvalidClassException: ioPackage.A; no valid constructor
    at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:157)
    at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:862)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2041)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1571)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)
    at ioPackage.Client.main(Client.java:14)

在相关资料中,查到:

Externalizable 接口标识的类,在进行反序列化时,会调用类的默认构造器。

由于我们自定义了A的构造器,导致默认的构造器并没有被生成,于是我们在A中添加一个默认的构造器。运行, 结果为:
1, 2, 0
因为,我们并没有对int c 进行序列化,反序列化的操作,导致c被初始化为默认值0。

是不是想要对序列化的类进行字段限制,就只能实现Externalizable 接口,实现方法呢? 并不是的,其实Serializable 接口也可以控制字段的序列化。

3.Serializable的序列化控制

transient 关键字就是用来控制Serializable的字段的.
我们修改类A:

 private String a;
    transient private String b;
    private int c;


    public A(String a, String b, int c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }

    @Override
    public String toString()  {
        return a + ", "+ b + ", " + c;
    }

运行结果为:
1, null, 3
字段String b 就被默认为null

这里需要提及的一点是:

String 类型的默认值是null , 而不是字符串"null", 但是,在反序列化的过程中,被限制的String 类型的字段值是字符串"null".

序列化可能会遇到的问题

1. 如果我反序列化相同的类多次,它们的在java中会是同一个对象么?

由于反序列的过程就是将一个类读入内存的过程,如果多次反序列化,肯定是开辟多个内存地址.这就表示在Java中,它们是不同的两个对象.
但是,也存在例外.如果你的对象是由同一个流读进来的对象,那么它们就是相同的.

ObjectInputStream in = new ObjectInputStream(new FileInputStream("/Users/zhangt/Desktop/A.out"));
        A a1 = (A)in.readObject();
        in.close();
        ObjectInputStream in2 = new ObjectInputStream(new FileInputStream("/Users/zhangt/Desktop/A.out"));
        A a2 = (A)in2.readObject();
        in2.close();

a1a2的地址是不同的.

A a = new A("1", "2", 3);
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("/Users/zhangt/Desktop/A.out"));
        out.writeObject(a);
        out.writeObject(a); // 输出两次
        out.close();

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("/Users/zhangt/Desktop/A.out"));
        A a1 = (A)in.readObject();
        A a2 = (A)in.readObject();
        System.out.println(a1);
        System.out.println(a2);

a1a2 的地址是相同的.

2. 如果序列化的类中包含其他类的对象,多次反序列化后,第三方对象会在内存中存在一份还是多份?

如果在序列化前,多个序列化类指向的对象是同一个对象,那么反序列化后指向的还是同一个对象.反序列化的结果的对象网和序列化之前是相同的.

上一篇 下一篇

猜你喜欢

热点阅读