Detail Java object serialization and deserialization

  • 2020-06-15 09:04:15
  • OfStack

We covered the use of byte stream character streams in a previous article, when we used the DataOutputStream stream to output each property value from an object to the stream one by one, and read it backwards. This behavior seems tedious to us, especially if there are many attribute values in the object. Based on this, the object serialization mechanism in Java is a good solution to this operation. This article briefly introduces Java object serialization, and the main contents are as follows:

Clean code implementation The basic algorithm of serialization implementation Two special cases Custom serialization mechanism Serialized versioning

1. Simple code implementation

Before we look at how object serialization works, let's look at how we previously stored data of an object type.


// Simple definition 1 a Student class 
public class Student {

 private String name;
 private int age;

 public Student(){}
 public Student(String name,int age){
 this.name = name;
 this.age=age;
 }

 public void setName(String name){
 this.name = name;
 }
 public void setAge(int age){
 this.age = age;
 }
 public String getName(){
 return this.name;
 }
 public int getAge(){
 return this.age;
 }
 // rewrite toString
 @Override
 public String toString(){
 return ("my name is:"+this.name+" age is:"+this.age);
 }
}

//main Method writes the object to a file and reads it out 
public static void main(String[] args) throws IOException{

 DataOutputStream dot = new DataOutputStream(new FileOutputStream("hello.txt"));
 Student stuW = new Student("walker",21);
 // Writes this object to a file 
 dot.writeUTF(stuW.getName());
 dot.writeInt(stuW.getAge());
 dot.close();

 // Read the object from the file 
 DataInputStream din = new DataInputStream(new FileInputStream("hello.txt"));
 Student stuR = new Student();
 stuR.setName(din.readUTF());
 stuR.setAge(din.readInt());
 din.close();

 System.out.println(stuR);
 }

my name is:walker age is:21

Obviously this kind of code writing is tedious, so let's look at how to use serialization to complete the task of saving the object's information.


public static void main(String[] args) throws IOException, ClassNotFoundException {

 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt"));
 Student stuW = new Student("walker",21);
 oos.writeObject(stuW);
 oos.close();

 // Returns the object read from a file 
 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt"));
 Student stuR = (Student)ois.readObject();
 System.out.println(stuR);
 }

When the file is written, it is writeObject, and when it is read, it is readObject. And the set, get methods that are in Student are not available. Isn't that neat? The implementation details follow.

2. Realize the basic algorithm of serialization

In this mechanism, each object corresponds to a sequence number of only 1, and each object is saved according to this sequence number to correspond to each different object. Object serialization refers to the use of the sequence number of each object for saving and reading. First, take writing an object to a stream as an example. For each object, the first time it is encountered, the basic information of the object will be saved to the stream. If the object currently encountered has been saved, the information will not be saved again, and the serial number of the object will be recorded instead (because the data need not be saved repeatedly). For the read case, each object encountered from the stream, if encountered the first time, directly output, if read is an object's serial number, will find the associated object, output.

A few points: for an object to be serializable, it must implement the interface java.io.Serializable; , which is a tag interface and does not implement any methods. Our ObjectOutputStream stream is a stream that converts object information into bytes. The constructor is as follows:


public ObjectOutputStream(OutputStream out)

That is, all byte streams can be passed in as parameters, compatible with 1-byte operations. In this flow, the writeObject and readObject methods are defined to implement the serialization and deserialization objects. Of course, we can also customize the serialization mechanism by implementing these two methods in the class, as described later. Here we only need to understand the whole serialization mechanism, all object data will only be saved 1 copy, as for the same object again, only the corresponding sequence number is saved. Now, let's get an intuition of his basic algorithm through two special cases.

3. Two special examples

Let's start with example 1:


public class Student implements Serializable {

 String name;
 int age;
 Teacher t; // In addition 1 Object type 

 public Student(){}
 public Student(String name,int age,Teacher t){
 this.name = name;
 this.age=age;
 this.t = t;
 }

 public void setName(String name){this.name = name;}
 public void setAge(int age){this.age = age;}
 public void setT(Teacher t){this.t = t;}
 public String getName(){return this.name;}
 public int getAge(){return this.age;}
 public Teacher getT(){return this.t;}
}

public class Teacher implements Serializable {
 String name;

 public Teacher(String name){
 this.name = name;
 }
}

public static void main(String[] args) throws IOException, ClassNotFoundException {

 Teacher t = new Teacher("li");
 Student stu1 = new Student("walker",21,t);
 Student stu2 = new Student("yam",22,t);
 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt"));
 oos.writeObject(stu1);
 oos.writeObject(stu2);

 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt"));
 Student stuR1 = (Student)ois.readObject();
 Student stuR2 = (Student)ois.readObject();

 if (stuR1.getT() == stuR2.getT())
  System.out.println(" The same object ");
 }

The result is obvious: the same object is output. We define two student objects in the main function, but they both refer to the same teacher object inside. Complete serialization, after deserialization out two objects, through comparing their internal teacher object is with one instance, can see that at the time of object serialization first student t is written in the inflow, but to meet a second student object teacher object instance, found that it has already been written, and no longer write inflow, preserve only the corresponding serial number as a reference. Of course, the principle is similar when it comes to deserialization. This is similar to the basic algorithm we introduced above.

Here's a second special example:


public class Student implements Serializable {

 String name;
 Teacher t;

}

public class Teacher implements Serializable {
 String name;
 Student stu;

}

public static void main(String[] args) throws IOException, ClassNotFoundException {

 Teacher t = new Teacher();
 Student s =new Student();
 t.name = "walker";
 t.stu = s;
 s.name = "yam";
 s.t = t;

 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt"));
 oos.writeObject(t);
 oos.writeObject(s);
 oos.close();

 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt"));
 Teacher tR = (Teacher)ois.readObject();
 Student sR = (Student)ois.readObject();
 if(tR == sR.t && sR == tR.stu)System.out.println("ok");

 }

The output is ok, which is what you might call a circular reference. From the results, we can see that the reference relationship between the two objects before serialization still exists after serialization. In fact judgment algorithm, introduce according to our previous first we serialize the teacher object, because he's internal reference student object, both are first meet, so will both serialization to flow, and then we go to serialize student object, found that this object as well as the internal teacher object has been serialized, so only to save the corresponding serial number. Restore the object according to the sequence number when read.

4. Custom serialization mechanism

In summary, we have covered the basics of serialization and deserialization. However, we often have some special requirements. Although the default serialization mechanism has been well developed, it still fails to meet our requirements sometimes. So let's see how to customize the serialization mechanism. In the custom serialization mechanism, we will use the one keyword that we have encountered frequently when looking at the source code, transient. Declaring the field transient tells the default serialization mechanism that you don't want to write this field to me in the stream, I'll handle it myself.


public class Student implements Serializable {

 String name;
 transient int age;

 public String toString(){
 return this.name + ":" + this.age;
 }
}

public static void main(String[] args) throws IOException, ClassNotFoundException {

 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("hello.txt"));
 Student stu = new Student();
 stu.name = "walker";stu.age = 21;
 oos.writeObject(stu);

 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("hello.txt"));
 Student stuR = (Student)ois.readObject();

 System.out.println(stuR);
 }

Output: walker:0

Didn't we assign an initial value to the age field? Why is it 0? As we mentioned above, the fields modified by transient will not be written into the stream and will have no value when read naturally. The default is 0. Let's see how we can serialize this age ourselves.


// Altered the student Class, main The method hasn't changed, so you can look up 
public class Student implements Serializable {

 String name;
 transient int age;

 private void writeObject(ObjectOutputStream oos) throws IOException {
 oos.defaultWriteObject();

 oos.writeInt(25);
 }

 private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
 ois.defaultReadObject();

 age = ois.readInt();
 }

 public String toString(){
 return this.name + ":" + this.age;
 }
}

Output: walker:25

The result is neither 21, which we initialized, nor 0, but 25, which we wrote in the writeObject method. Now let's take a look at each of these steps point by point. First, to implement custom serialization, you need to implement two methods, writeObject and readObject, in the class defined by the object. The format must be the same as the one posted above. This is because Java USES reflection to check if the object is in a class that implements either of these methods. If not, use the default ObjectOutputStream method to serialize all fields and, if so, execute your own.

Next, take a look at the implementation details of the two methods. First, look at the writeObject method. The arguments are of type ObjectOutputStream. Line 1 we call oos.defaultWriteObject (); In the second statement we explicitly call the writeInt method to write the value of age into the stream. The reading method is similar and will not be repeated here.

Version control

Finally, let's look at versioning of the serialization process. After we serialize an object into the stream, the structure of the corresponding class of that object changes. What happens if we read the previously saved object from the stream again? It depends. If a field in the original class is deleted, the corresponding field output from the stream will be ignored. If a field is added to the original class, the value of the new field is the default. If the field type has changed, an exception is thrown. In Java, each class has a variable that records the version number: static final serivalVersionUID = 115616165165L, where the value is used for demonstration purposes only and does not correspond to any particular class. This version number is calculated based on the field and other 1 attribute information in the class. Every time you read it, you compare the previous and current version Numbers to see if there is a version 1 error. If there is a version 1 error, you do the same as above.

Object serialization is finished, if there is something wrong with the content, I hope you point out!


Related articles: