Interview essential: serialized comprehensive analysis

Foreword

I believe that in daily development, you often see the Java object "implements Serializable". So, what is it useful for? This article analyzes this piece of knowledge from the following perspectives ~

  • What is Java serialization?
  • Why do you need serialization?
  • Serialization purpose
  • Java serialization commonly used API
  • Use of serialization
  • Serialize the bottom layer
  • Notes on serialization of daily development
  • Serializing common interview questions

1. What is Java serialization?

  • Serialization: The process of converting Java objects to byte sequences
  • Reverse sequence: the process of restoring a byte sequence to a Java object

Second, why serialization is required?

Java objects are running in the heap memory of the JVM. If the JVM is stopped, its life will suddenly stop.

What if you want to save these objects to disk or transfer them to another remote machine via the network after the JVM stops? Disk hardware does not recognize Java objects, they only recognize binary machine languages, so we have to convert these objects into byte arrays. This process is serialization ~

To make an analogy, as a farmer who drifts in a big city, moving is the norm. When we move the desk, the table is too big to pass through the smaller door, so we need to disassemble it and move it in. The process of disassembling the table is serialization. The process of restoring (installing) the desk is deserialization.

Three, serialization purposes

Serialization allows objects to exist independently of program execution. It has two main purposes:

  • 1) The serialization mechanism allows objects to be saved to the hard disk, reducing memory pressure, but also playing a persistent role;

For example, the Session object in the Web server, when there are 100,000 users concurrently accessed, there may be 100,000 Session objects, the memory may be indigestible, so the Web container will serialize some seesion to the hard disk first, etc. Used, and then restore the objects saved in the hard disk to the memory.

  • 2) The serialization mechanism makes Java object transmission on the network no longer a fantasy.

When we use Dubbo to call the service framework remotely, we need to implement the Serializable interface of the transmitted Java object, that is, to serialize the Java object, because this allows the object to be transmitted on the network.

Four, Java serialization commonly used API

java.io.ObjectOutputStream
java.io.ObjectInputStream
java.io.Serializable
java.io.Externalizable
复制代码

Serializable interface

The Serializable interface is a markup interface with no methods or fields. Once this interface is implemented, it indicates that objects of this class are serializable.

public interface Serializable {
}
复制代码

Externalizable interface

Externalizable inherits the Serializable interface and also defines two abstract methods: writeExternal () and readExternal (). If developers use Externalizable to implement serialization and deserialization, you need to rewrite the writeExternal () and readExternal () methods

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

java.io.ObjectOutputStream类

Represents the object output stream, its writeObject (Object obj) method can serialize the specified obj object parameters, and then write the resulting byte sequence to a target output stream.

java.io.ObjectInputStream

Represents an object input stream. Its readObject () method reads a sequence of bytes from the input stream, deserializes it into an object, and finally returns it.

Five, the use of serialization

How to use serialization? Take a look at a few key points about the use of serialization:

  • Declare an entity class and implement the Serializable interface
  • Use the writeObject method of the ObjectOutputStream class to achieve serialization
  • Use the readObject method of the ObjectInputStream class to achieve deserialization

Declare a Student class, implement Serializable

public class Student implements Serializable {

    private Integer age;
    private String name;

    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
复制代码

Use the writeObject method of the ObjectOutputStream class to serialize the Student object

After setting the value of the Student object, write to a file, that is, serialization, haha ​​~

ObjectOutputStream objectOutputStream = new ObjectOutputStream( new FileOutputStream("D:\\text.out"));
Student student = new Student();
student.setAge(25);
student.setName("jayWei");
objectOutputStream.writeObject(student);

objectOutputStream.flush();
objectOutputStream.close();
复制代码

Take a look at the cute look of serialization. The contents of the test.out file are as follows (opened with UltraEdit):

Use the readObject method of the ObjectInputStream class to achieve deserialization and regenerate the student object

Then read out the test.out file and deserialize it into a Student object

ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\text.out"));
Student student = (Student) objectInputStream.readObject();
System.out.println("name="+student.getName());
复制代码

Six, serialize the bottom

Serializable

The Serializable interface is just an empty interface with no methods or fields. Why is it so magical that you can serialize objects when you implement it?

public interface Serializable {
}
复制代码

In order to verify the function of Serializable, remove the Student object of the above demo and implement the Serializable interface, see how the serialization process ~

An exception was thrown during the serialization process, the stack information is as follows:

Exception in thread "main" java.io.NotSerializableException: com.example.demo.Student
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.example.demo.Test.main(Test.java:13)
复制代码

Looking at the stack information, there are major discoveries, as follows ~

The original bottom layer is this: When ObjectOutputStream is serialized, it will determine which type of Object is serialized, String? array? enum? Still Serializable, if not, throw NotSerializableException. So, Serializable is really just a sign, a serialized sign ~

writeObject(Object)

The serialization method is writeObject. Based on the above demo, let's analyze a wave of its core method invocation chain ~

writeObject directly calls the writeObject0 () method,

public final void writeObject(Object obj) throws IOException {
    ......
    writeObject0(obj, false);
    ......
}
复制代码

writeObject0 The main implementation is different types of objects, call different methods to write serialized data, if the object implements the Serializable interface, call writeOrdinaryObject () method ~

private void writeObject0(Object obj, boolean unshared)
        throws IOException
    {
    ......
   //String类型
    if (obj instanceof String) {
        writeString((String) obj, unshared);
   //数组类型
    } else if (cl.isArray()) {
        writeArray(obj, desc, unshared);
   //枚举类型
    } else if (obj instanceof Enum) {
        writeEnum((Enum<?>) obj, desc, unshared);
   //Serializable实现序列化接口
    } else if (obj instanceof Serializable) {
        writeOrdinaryObject(obj, desc, unshared);
    } else{
        //其他情况会抛异常~
        if (extendedDebugInfo) {
            throw new NotSerializableException(
                cl.getName() + "\n" + debugInfoStack.toString());
        } else {
            throw new NotSerializableException(cl.getName());
        }
    }
    ......
复制代码

writeOrdinaryObject () will first call writeClassDesc (desc), write the generated information of this class, and then call the writeSerialData method to write serialized data

    private void writeOrdinaryObject(Object obj,
                                     ObjectStreamClass desc,
                                     boolean unshared)
        throws IOException
    {
            ......
            //调用ObjectStreamClass的写入方法
            writeClassDesc(desc, false);
            // 判断是否实现了Externalizable接口
            if (desc.isExternalizable() && !desc.isProxy()) {
                writeExternalData((Externalizable) obj);
            } else {
                //写入序列化数据
                writeSerialData(obj, desc);
            }
            .....
    }
复制代码

writeSerialData () is to write the field data of the serialized object

  private void writeSerialData(Object obj, ObjectStreamClass desc)
        throws IOException
    {
        for (int i = 0; i < slots.length; i++) {
            if (slotDesc.hasWriteObjectMethod()) {
                   //如果被序列化的对象自定义实现了writeObject()方法,则执行这个代码块
                    slotDesc.invokeWriteObject(obj, this);
            } else {
                // 调用默认的方法写入实例数据
                defaultWriteFields(obj, slotDesc);
            }
        }
    }
复制代码

The defaultWriteFields () method, to obtain the basic data type data of the class, directly write to the underlying byte container; to obtain the obj type data of the class, recursively call the writeObject0 () method, write data ~

   private void defaultWriteFields(Object obj, ObjectStreamClass desc)
        throws IOException
    {   
        // 获取类的基本数据类型数据,保存到primVals字节数组
        desc.getPrimFieldValues(obj, primVals);
        //primVals的基本类型数据写到底层字节容器
        bout.write(primVals, 0, primDataSize, false);

        // 获取对应类的所有字段对象
        ObjectStreamField[] fields = desc.getFields(false);
        Object[] objVals = new Object[desc.getNumObjFields()];
        int numPrimFields = fields.length - objVals.length;
        // 获取类的obj类型数据,保存到objVals字节数组
        desc.getObjFieldValues(obj, objVals);
        //对所有Object类型的字段,循环
        for (int i = 0; i < objVals.length; i++) {
            ......
              //递归调用writeObject0()方法,写入对应的数据
            writeObject0(objVals[i],
                             fields[numPrimFields + i].isUnshared());
            ......
        }
    }
复制代码

7. Some points for daily development serialization

  • Static static variables and transient modified fields will not be serialized
  • serialVersionUID problem
  • If the member variable of a serialized class is an object type, the class of that object type must implement serialization
  • The child class is serialized, the parent class is not serialized, and the fields in the parent class are missing

Static static variables and transient modified fields will not be serialized

Static static variables and transient-modified fields will not be serialized. Let's look at an example to analyze a wave ~ The Student class adds a class variable gender and a transient-modified field specialty

public class Student implements Serializable {

    private Integer age;
    private String name;

    public static String gender = "男";
    transient  String specialty = "计算机专业";

    public String getSpecialty() {
        return specialty;
    }

    public void setSpecialty(String specialty) {
        this.specialty = specialty;
    }

    @Override
    public String toString() {
        return "Student{" +"age=" + age + ", name='" + name + '\'' + ", gender='" + gender + '\'' + ", specialty='" + specialty + '\'' +
                '}';
    }
    ......
复制代码

Print the student object, serialize it to a file, then modify the value of the static variable, then deserialize, and output the deserialized object ~

operation result:

序列化前Student{age=25, name='jayWei', gender='男', specialty='计算机专业'}
序列化后Student{age=25, name='jayWei', gender='女', specialty='null'}
复制代码

Comparison results can be found:

  • 1) The static variable gender before serialization is obviously 'male'. After serialization, it is modified in the program, but after deserialization, it becomes 'female'. What ? Obviously this static property is not serialized. In fact, static (static) member variables belong to the class level, and serialization is for objects ~ so it cannot be serialized .
  • 2) After the serialization and deserialization process, the value of the specialty field variable is changed from 'computer major' to empty, why? In fact, because of the transient keyword, it can prevent the modified field from being serialized into the file . After being deserialized, the value of the transient field is set to the initial value, such as the value of int type will be set to 0, the object type The initial value will be set to null.

serialVersionUID problem

serialVersionUID means serialized version number ID . In fact, every class that implements the Serializable interface has a static variable that represents the serialized version identifier, or it is equal to 1L by default, or is equal to the hash code of the object.

private static final long serialVersionUID = -6384871967268653799L;
复制代码

What is the use of serialVersionUID?

The mechanism of JAVA serialization is to verify whether the version is consistent by judging the serialVersionUID of the class. During deserialization, the JVM compares the serialVersionUID in the incoming byte stream with the serialVersionUID of the corresponding local entity class. If they are the same, the deserialization succeeds. If they are not the same, an InvalidClassException is thrown.

Next, let's verify it, modify the Student class, and then deserialize the operation

Exception in thread "main" java.io.InvalidClassException: com.example.demo.Student;
local class incompatible: stream classdesc serialVersionUID = 3096644667492403394,
local class serialVersionUID = 4429793331949928814
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:687)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1876)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1745)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2033)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1567)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:427)
	at com.example.demo.Test.main(Test.java:20)
复制代码

As can be seen from the log stack exception information, the class in the file stream is different from the class in the current class path, and their serialVersionUIDs are not the same, so the deserialization throws InvalidClassException exception. So, what if you really need to modify the Student class and want to deserialize successfully? You can manually specify the value of serialVersionUID, generally can be set to 1L or, or let our editor IDE generate

private static final long serialVersionUID = -6564022808907262054L;
复制代码

In fact, the Ali development manual requires that the serialVersionUID field cannot be modified when it is mandatory to add new attributes to the serialization class ~

If the member variable of a serialized class is an object type, the class of that object type must implement serialization

Add a member variable of the Teacher type to the Student class, where the Teacher does not implement the serialization interface

public class Student implements Serializable {
    
    private Integer age;
    private String name;
    private Teacher teacher;
    ...
}
//Teacher 没有实现
public class Teacher  {
......
}
复制代码

When serialized, it will report NotSerializableException

Exception in thread "main" java.io.NotSerializableException: com.example.demo.Teacher
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
	at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
	at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.example.demo.Test.main(Test.java:16)
复制代码

In fact, this can be found in the underlying source code analysis of the previous section. An object serialization process will call its Object type field cyclically and recursively call serialization. That is to say, when the Student class is serialized, the Teacher class will be Serialization, but does not implement the serialization interface for Teacher, so throws NotSerializableException exception. So if the member variable of an instantiated class is an object type, the class of that object type must be serialized

If the subclass implements Serializable, if the parent class does not implement the Serializable interface, the parent class will not be serialized.

The subclass Student implements the Serializable interface, the parent class User does not implement the Serializable interface

//父类实现了Serializable接口
public class Student  extends User implements Serializable {

    private Integer age;
    private String name;
}
//父类没有实现Serializable接口
public class User {
    String userId;
}

Student student = new Student();
student.setAge(25);
student.setName("jayWei");
student.setUserId("1");

ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D:\\text.out"));
objectOutputStream.writeObject(student);

objectOutputStream.flush();
objectOutputStream.close();

//反序列化结果
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D:\\text.out"));
Student student1 = (Student) objectInputStream.readObject();
System.out.println(student1.getUserId());
//output
/** 
 * null
 */
复制代码

From the deserialization result, it can be found that the attribute value of the parent class is lost. Therefore, the subclass implements the Serializable interface. If the parent class does not implement the Serializable interface, the parent class will not be serialized.

Eight, serialized common interview questions

  • How is the underlying serialization implemented?
  • When serializing, how to prevent certain members from being serialized?
  • What is the difference between Serializable and Externalizable in Java
  • What is the use of serialVersionUID?
  • Can I customize the serialization process, or can I override the default serialization process in Java?
  • During Java serialization, which variables are not serialized?

1. How is the underlying serialization implemented?

The sixth section of this article can answer this question, such as answering the role of the Serializable keyword, the serialization flag, and its role in the source code. Also, you can answer several core methods of writeObject, such as writing directly to the basic type, obtaining the obj type Data, written recursively, haha ​​~

2. When serializing, how to prevent certain members from being serialized?

It can be modified with the transient keyword, which can prevent the modified field from being serialized into the file. After being deserialized, the value of the transient field is set to the initial value. The initial value will be set to null.

3. In Java, what is the difference between Serializable and Externalizable

Externalizable inherits Serializable and provides us with writeExternal () and readExternal () methods, allowing us to control Java's serialization mechanism without relying on Java's default serialization. Correct implementation of the Externalizable interface can significantly improve the performance of the application.

4. What is the use of serialVersionUID?

You can look back at the seventh section of this article. The JAVA serialization mechanism verifies whether the version is consistent by judging the serialVersionUID of the class. During deserialization, the JVM compares the serialVersionUID in the incoming byte stream with the serialVersionUID of the corresponding local entity class. If they are the same, the deserialization succeeds. If they are not the same, an InvalidClassException is thrown.

5. Is it possible to customize the serialization process, or can I override the default serialization process in Java?

Yes. We all know that to serialize an object, you need to call ObjectOutputStream.writeObject (saveThisObject) and use ObjectInputStream.readObject () to read the object, but there is one more thing that the Java Virtual Machine provides for you, is to define these two methods. If these two methods are defined in the class, the JVM will call these two methods instead of applying the default serialization mechanism. At the same time, you can declare these methods as private methods to avoid inheritance, rewriting, or overloading.

6. During Java serialization, which variables are not serialized?

Static static variables and transient modified fields will not be serialized. Static (static) member variables belong to the class level, and serialization is object-oriented. The transient keyword modifies the field decoration to prevent the field from being serialized into the file.

Reference and thanks

Personal public account

  • I feel like writing a good companion to give a like + attention, thank you ~
  • If there is something incorrectly written, please point out and be grateful.
  • At the same time, I very much look forward to my friends to pay attention to my public number, and later slowly introduce better dry goods ~ hee hee

Guess you like

Origin juejin.im/post/5e7f150d51882573b3309ceb