Java I / O system learning series five: Java serialization mechanism

  In the Java world, created after the object, as long as necessary, the object can be resident in memory, but when the program is terminated, all objects will still be destroyed. This is actually very reasonable, but even if not necessarily meet all reasonable scenarios, there are still some cases, need to be able to keep the object in case the program does not run, so the serialization mechanism came into being.

1. Why should there be serialized

  Briefly, the sequence of the action is to save memory objects together, when needed reconstruction of the object, and the object information have reconstructed before the storage objects owned by the same. In practice, often the target sequence used in the following scenarios:

  • RPC data transmission frame;
  • Object persistence saved the state;

  You might think, persistence to achieve this effect, we directly write information to a file or database can be achieved ah, why serialization? This is a good question, imagine if we use the method described earlier, when the sequence object and deserialization to restore the object, we must consider how to complete the save and restore information object, there will involve many tedious details, do not pay attention a little can lead to loss of information. If there is a mechanism, as long as an object is declared as "persistent", and will be able to dispose of all the details for us, this not very convenient, this is the sequence of things to do. Java serialization has been added to the concept of the language, all the examples in this article about serialization is Java-based.

  Native Java serialization mechanism provides powerful functions, has its own characteristics:

  • Process sequence is as easy to implement Serializable object;
  • It can automatically compensate for differences between different operating systems, which can create an object on a computer running Windows system, serialize it and then send it to a computer running Unix system over the network, where it is then accurately re assembled, without worrying about data representation will be different on the new machine;
  • Object Serialization not only saves the object "Panorama", and in the ability to track objects contained in all the references, and save those objects; but then such a reference to the object contained within each track, and so on;
  • Object serialization to save a "state" object, that is, its member variables, it does not handle object serialization and class static variable;

2. Use serialization mechanism

  Java object serialization mechanism is to convert those achieved Serializable interface object into a sequence of bytes, and can later be completely restored in the sequence of bytes as the original object.

  To serialize an object, first create a OutputStream object, and then packaged in a ObjectOutputStream objects, then simply call writeObject () method to the target sequence, and transmits the serialized byte sequence to OutputStream . To restore a sequence as an object, you need an InputStream encapsulated within the ObjectInputStream, then calls the readObject (), which returns a reference that points to a transition Object upward, downward transition to be used directly.

  Let's look at an example of how serialization and de-serialization of objects.

public class Worm implements Serializable{
    private static Random rand = new Random(47);
    private Data[] d = {
        new Data(rand.nextInt(10)),
        new Data(rand.nextInt(10)),
        new Data(rand.nextInt(10))
    };
    private Worm next;
    private char c;
    public Worm(int i, char x){
        System.out.println("Worm constructor: " + i);
        c = x;
        if(--i > 0){
            next = new Worm(i,(char)(x + 1));
        }
    }
    public Worm(){
        System.out.println("Default constructor");
    }
    public String toString(){
        StringBuilder result = new StringBuilder(":");
        result.append(c);
        result.append("(");
        for(Data dat : d){
            result.append(dat);
        }
        result.append(")");
        if(next != null){
            result.append(next);
        }
        return result.toString();
    }
    public static void main(String[] args) throws ClassNotFoundException, IOException{
        Worm w = new Worm(6,'a');
        System.out.println("w = " + w);
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("worm.out"));
        out.writeObject("Worm storage\n");
        out.writeObject(w);
        out.close();
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("worm.out"));
        String s = (String)in.readObject();
        Worm w2 = (Worm)in.readObject();
        System.out.println(s + "w2 = " + w2);
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream out2 = new ObjectOutputStream(bout);
        out2.writeObject("Worm storage\n");
        out2.writeObject(w);
        out2.flush();
        ObjectInputStream in2 = new ObjectInputStream(new ByteArrayInputStream(bout.toByteArray()));
        s = (String)in2.readObject();
        Worm w3 = (Worm)in2.readObject();
        System.out.println(s + "w3 = " + w3);
    }
}

class Data implements Serializable{
    private int n;
    public Data(int n){this.n = n;}
    public String toString(){return Integer.toString(n);}
}

  Output:

Worm constructor: 6
Worm constructor: 5
Worm constructor: 4
Worm constructor: 3
Worm constructor: 2
Worm constructor: 1
w = :a(853):b(119):c(802):d(788):e(199):f(881)
Worm storage
w2 = :a(853):b(119):c(802):d(788):e(199):f(881)
Worm storage
w3 = :a(853):b(119):c(802):d(788):e(199):f(881)

  By linking the object code to generate a worm (worm) to test serialization mechanisms, with each object in the lower piece of the link worm, while objects belonging to different classes (Data) of the reference array chaining.

  Object Serialization not only save the "Panorama" object, and can keep track of all references contained within the object, and save those objects; also for such references within each object contains the track; and so on.

  But can also be seen from a Serializable object above output reduction process, it does not call any construction comprising a default constructor. The entire object is achieved by data recovery comes from the InputStream.

3. What needs to serialize

  We have one of the objects in front of the sequence comes to support data transmission frame rpc, such an object will be serialized and transmitted over the network to another computer, another computer to restore this by deserializing objects that need only be able to restore the sequence of file objects? We use the following code to test.

public class Serialize implements Serializable{}
}

public class FreezeSerialize {
    public static void main(String[] args) throws Exception{
        ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("X.file"));
        Serialize serialize = new Serialize();
        os.writeObject(alien);
    }
}

public class ThawSerialize {
    public static void main(String[] args) throws Exception{
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("X.file"));
        Object mystery = in.readObject();
        System.out.println(mystery.getClass());
    }
}

  FreezeSerialize class used to serialize an object to a file, ThawSerialize class to deserialize the object on a computer if you perform the test at the same time these two categories is no problem, but if we will change my position Serialize class (or directly the FreezeSerialize and serialize deleted), the execution error will be reported when ThawSerialize --ClassNotFoundException deserialization, so you can know when deserialization is the need Class object of the original object.

  Since the need for corresponding Class object deserialization, and that the corresponding deserialization Class is not the same version of what will happen (this situation exists) If serialization? To simulate this situation, let's execute the main method FreezeSerialize class, give Serialize add a class attribute, then pitted at ThawSerialize class main method can be found in newspaper java.io.InvalidClassException abnormal, obviously compiled by but being given, this case is there any way to solve it? Yes, we can give class that implements the Serializable interface to add members of a long type: serialVersionUID, modifier is private static final, and you can assign a random number.

  In fact, this is called serialVersionUID serialized version number, if not specified, the compiler will add a default in the compiled class file, whose value is generated based on the current class structure. But that has a problem, if the class structure has changed, the corresponding build numbers after that change will happen, and whether to allow the virtual machine deserialization, depends not only on the class path and function codes match, as well as a very important point is whether the two classes of serialized ID is consistent reason, and if not then do not allow serialization and throws InvalidClassException an exception, this is the time to change the class structure in front of the serial number will not be added again when the error deserializing . It is proposed to implement the Serializable interface class to add a serialized version serialVersionUID, and specify the value.

  There is also a need to mind about the point serialized version number, the version number in the case of the same, if the object to be deserialized is inconsistent with the current class conventional configuration, compatibility mode is used, namely: properties of the object has an existing class of the reduction, not ignored.

4. The control sequence

  Above us that we are using the default Java serialization mechanism provided by all the members of the object is about to be serialized. However, if you have special needs? For example, only a desired portion of the object to be serialized. In this particular case, the desired effect can be achieved by several methods, introduced one by one below.

4.1 实现Externalizable接口

  通过实现Externalizable接口(代替实现Serializable),可以对序列化过程进行控制。这个Externalizable接口继承了Serializable接口,同时增加了两个方法:writeExternal()和readExternal()。这两个方法会分别在序列化和反序列化还原的过程中被自动调用,这样就可以在这两个方法种指定执行一些特殊操作。下面来看一个简单例子:

public class Blip implements Externalizable{
    private int i;
    private String s;
    public Blip(){
        System.out.println("Blip Constructor");
    }
    public Blip(String x, int a){
        System.out.println("Blip(String x, int a)");
        s = x;
        i = a;
    }
    public String toString(){
        return s + i;
    }
    public void writeExternal(ObjectOutput out) throws IOException{
        System.out.println("Blip.writeExternal");
        out.writeObject(s);
        out.writeInt(i);
    }
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException{
        System.out.println("Blip.readExternal");
        s = (String)in.readObject();
        i = in.readInt();
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException{
        System.out.println("Constructing objects:");
        Blip b = new Blip("A String ",47);
        System.out.println(b);
        // 序列化对象
        ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("Blip.out"));
        System.out.println("Saving object:");
        o.writeObject(b);
        o.close();
        // 还原对象
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("Blip.out"));
        System.out.println("Recovering b:");
        b = (Blip)in.readObject();
        System.out.println(b);
    } 
}

  在这个例子中,对象继承了Externalizable接口,成员s和i只在第二个构造器中初始化(而不是默认构造器),我们在writeExternal()方法中对要序列化保存的成员执行写入操作,在readExternal()方法中将其恢复。输出结果如下:

Constructing objects:
Blip(String x, int a)
A String 47
Saving object:
Blip.writeExternal
Recovering b:
Blip Constructor
Blip.readExternal
A String 47

  这里需要注意的几个点:

  1. 对象实现了Externalizable之后,没有任何成员可以自动序列化,需要在writeExternal()内部只对所需部分进行显式的序列化,并且在readExternal()方法中将其恢复。
  2. 在将实现了Externalizable接口的对象进行反序列化操作时,会调用其默认构造函数,如果没有,则会报错java.io.InvalidClassException。所以如果对象实现了Externalizable接口,则还需要检查其是否有默认构造函数。

4.2 transient(瞬时)关键字

  在我们对序列化进行控制时,可能会碰到某个特定子对象不想让Java的序列化机制自动保存与恢复。比如一些敏感信息(密码),即使对象中的这些成员是由private修饰,一经序列化处理,通过读取文件或者网络抓包的方式还是能访问到它。

  前面说的通过实现Externalizable接口可以解决这个问题,但是假如对象有很多的成员,而我们只希望其中少量成员不被序列化,那通过实现Externalizable接口的方式就不合适了(因为需要在writeExternal()方法中做大量工作),这种情况下,transient关键就可以大显身手了。在实现了Serializable接口的类中,被transient关键字修饰的成员是不会被序列化的。而且,由于Externalizable对象在默认情况下不会序列化对象的任何字段,transient关键字只能和Serializable对象一起使用。

4.3 Externalizable的替代方法

  除了上面两种方法,还有一种相对不那么“正规”的办法--我们可以实现Serializable接口,并添加名为writeObject()和readObject()的方法。当对象被序列化或者被反序列化还原时,就会自动地分别调用这两个方法(只要我们提供了这两个方法,就会使用它们而不是默认的序列化机制)。但是需要注意的是这两个方法必须有准确的方法特征签名:

private void writeObject(ObjectOutputStream stream) throws IOException;

private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException;

  在调用ObjectOutputStream.writeObject()时,会检查所传递的Serializable对象,看看是否实现了它自己的writeObject()。如果是,就跳过正常的序列化过程并调用对象自己的writeObject()方法。readObject()的情况是类似的。这就是这种方式的原理。

  还有一个技巧,在我们提供的writeObject()内部,可以调用defaultWriteObject()来选择执行默认的writeObject()。类似,在readObject()内部,我们可以调用defaultReadObject()。下面看一个例子,如何对一个Serializable对象的序列化与恢复进行控制:

public class SerialCtl implements Serializable{
    
    private String noTran;
    private transient String tran;
    public SerialCtl(String noTran, String tran){
        this.noTran = "Not Transient: " + noTran;
        this.tran = "Transient: " + tran;
    }
    public String toString(){ return noTran + "\n" + tran; }
    private void writeObject(ObjectOutputStream stream) throws IOException {
        stream.defaultWriteObject();
        stream.writeObject(tran);
    }
    private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException{
        stream.defaultReadObject();
        tran = (String)stream.readObject();
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException{
        SerialCtl sc = new SerialCtl("papaya","mango");
        System.out.println("Before:\n" + sc);
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        ObjectOutputStream o = new ObjectOutputStream(buf);
        o.writeObject(sc);
        // 还原
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray()));
        SerialCtl sc2 = (SerialCtl)in.readObject();
        System.out.println("After:\n" + sc2);
    }

}

  输出结果:

Before:
Not Transient: papaya
Transient: mango
After:
Not Transient: papaya
Transient: mango

  在这个例子中,有一个String字段是普通字段,而另一个是transient字段,对比证明非transient字段由defaultWriteObject()方法保存,而transient字段必须在程序中明确保存和恢复。

  在writeObject()内部第一行调用defaultWriteObject()方法是为了利用默认序列化机制序列化对象的非transient成员,同样,在readObject()内部第一行调用defaultReadObject()方法是为了利用默认机制恢复非transient成员。注意,必须是第一行调用。

5. 再深入一点

  使用序列化的一个主要目的是存储程序的一些状态,以便我们后面可以容易地将程序恢复到当前状态。在这样做之前,我们先考虑几种情况。如果我们将两个对象(它们都包含有有指向第三个对象的引用成员)进行序列化,会发生什么情况?当我们从它们的序列化文件中恢复这两个对象时,第三个对象会只出现一次吗?如果将这两个对象序列化成独立的文件,然后在代码的不同部分对它们进行反序列化还原,又会怎样呢?先看例子:

public class MyWorld {

    public static void main(String[] args) throws IOException, ClassNotFoundException{
        House house = new House();
        List<Animal> animals = new ArrayList<Animal>();
        animals.add(new Animal("Bosco the dog", house));
        animals.add(new Animal("Ralph the hamster", house));
        animals.add(new Animal("Molly the cat",house));
        System.out.println("animals: " + animals);
        ByteArrayOutputStream buf1 = new ByteArrayOutputStream();
        ObjectOutputStream o1 = new ObjectOutputStream(buf1);
        o1.writeObject(animals);
        o1.writeObject(animals);
        // 写入到另一个流中:
        ByteArrayOutputStream buf2 = new ByteArrayOutputStream();
        ObjectOutputStream o2 = new ObjectOutputStream(buf2);
        o2.writeObject(animals);
        // 反序列化:
        ObjectInputStream in1 = new ObjectInputStream(new ByteArrayInputStream(buf1.toByteArray()));
        ObjectInputStream in2 = new ObjectInputStream(new ByteArrayInputStream(buf2.toByteArray()));
        List animals1 = (List)in1.readObject(),animals2 = (List)in1.readObject(),animals3 = (List)in2.readObject();
        System.out.println("animals1: " + animals1);
        System.out.println("animals2: " + animals2);
        System.out.println("animals3: " + animals3);
    }
    
}

class House implements Serializable{}

class Animal implements Serializable{
    private String name;
    private House preferredHouse;
    Animal(String nm, House h){
        name = nm;
        preferredHouse = h;
    }
    public String toString(){
        return name + "[" + super.toString() + "]. " + preferredHouse + "\n";
    }
}

  输出结果:

animals: [Bosco the dog[testDemos.Animal@7852e922]. testDemos.House@4e25154f
, Ralph the hamster[testDemos.Animal@70dea4e]. testDemos.House@4e25154f
, Molly the cat[testDemos.Animal@5c647e05]. testDemos.House@4e25154f
]
animals1: [Bosco the dog[testDemos.Animal@2d98a335]. testDemos.House@16b98e56
, Ralph the hamster[testDemos.Animal@7ef20235]. testDemos.House@16b98e56
, Molly the cat[testDemos.Animal@27d6c5e0]. testDemos.House@16b98e56
]
animals2: [Bosco the dog[testDemos.Animal@2d98a335]. testDemos.House@16b98e56
, Ralph the hamster[testDemos.Animal@7ef20235]. testDemos.House@16b98e56
, Molly the cat[testDemos.Animal@27d6c5e0]. testDemos.House@16b98e56
]
animals3: [Bosco the dog[testDemos.Animal@4f3f5b24]. testDemos.House@15aeb7ab
, Ralph the hamster[testDemos.Animal@7b23ec81]. testDemos.House@15aeb7ab
, Molly the cat[testDemos.Animal@6acbcfc0]. testDemos.House@15aeb7ab
]

  这里我们通过一个字节数组来使用对象序列化,这样可以实现对任何可Serializable对象的“深度复制”(deep copy)--深度复制意味着复制的是整个对象网,而不仅仅是基本对象及其引用。

  在这个例子中,我们从打印的结果可以看出,只要将任何对象序列化到单一流中,就可以恢复出与我们写入时一样的对象网,并且不会有任何意外重复复制出的对象,对比animals1和animals2中的House。

  另一方面,在恢复animals3时,输出的House与animals1和animals2是不同的,这说明了如果将对象序列化到不同的文件中,然后在代码的不同部分对它们进行反序列化还原,这时会产生出两个对象。

6. 总结

  序列化的出现给保存程序运行状态提供了一种新的途径,实际主要使用在RPC框架的数据传输以及对象状态的持久化保存等场景。

  • 要将对象进行序列化处理,只需要实现Serializable接口,然后通过ObjectOutputStream的writeObject()方法即可完成对象的序列化;
  • 在某个类实现了Serializable接口之后,为了保证能够成功反序列化,通常建议再添加一个序列化版本号serialVersionUID,并指定值;
  • 实现Serializable接口只能使用Java提供的默认序列化机制(即将对象所有部分序列化),若想自定义序列化过程,有如下三种方式:
  1. 实现Externalizable接口,并实现writeExternal()和readExternal()方法;
  2. 用transient修饰不希望被序列化的成员;
  3. 在类中添加名为writeObject()和readObject()的方法,在其中指定自己的逻辑;
  • 实现Externalizable接口之后,没有任何成员可以自动序列化,需要在writeExternal()内部只对所需部分进行显式的序列化,并且在readExternal()方法中将其恢复;
  • 在对实现了Serializable接口的类进行反序列化的过程中不会调用任何构造函数,而对实现了Externalizable接口的类进行反序列化时会调用其默认构造函数,如果没有默认构造函数,则会报java.io.InvalidClassException错误;

Guess you like

Origin www.cnblogs.com/volcano-liu/p/11615306.html