Deep understanding of —————— serialization and deserialization


Recently, I am doing a supermarket management project, storing data in Redis database through serialization. When implementing the function of modifying user information, in order to facilitate the modification of Date data type to String type, an error was reported. After opening the console, the following error occurred.

java.io.InvalidClassException: com.li.pojo.SmbmsUser; 
local class incompatible: stream classdesc serialVersionUID = 2416888619525883151, 
local class serialVersionUID = 516098953370879925

Cause of error: Inconsistent ID before and after serialization and deserialization resulted in an error.
Since an error has occurred, it must be resolved. The previous understanding of serialization and deserialization just stayed on the basis of
knowing how to use it. Let’s learn more today.

1. First know what serialization and deserialization are

When learning new knowledge points, we must always have questions: what, why, how to do it, and three aspects to accomplish this. Only in this way will it be clear and clear, and the memory will be more profound.

  • What is serialization and deserialization?

    Serialization: is to convert the object into a character sequence
    Deserialization: is to restore the character sequence to the original object

  • Why serialization and deserialization?
    As mentioned earlier, when doing ssm projects, serialization was used to convert objects into character sequences and stored in the Redis database
    . What should we do if serialization is not used, use map(key -value) storage object, it can be used when there are not too many data in a single table, and the amount of data is very troublesome. Therefore, it is recommended to store serialized objects in Redis when the amount of data is particularly large.
    Generally, serialization is used to be written to databases, files, and used for network transmission.

  • What can I do to serialize it?

Implement Serializable interface

public interface Serializable {
    
    
}

Serializable is clearly an empty interface. In fact, the function of this interface is to tell the JVM that I want to serialize and deserialize.
The specific operation is as follows:
Prepare the entity class

public class Student {
    
    
    private String name;
    private int age;

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }

    public int getAge() {
    
    
        return age;
    }

    public void setAge(int age) {
    
    
        this.age = age;
    }

    @Override
    public String toString() {
    
    
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

test:

 //初始化
        Student student = new Student();
        student.setName("张三");
        student.setAge(12);
        System.out.println(student.toString());
        //序列化
        try {
    
    
            //把对象写入文件中
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\imag\\b.txt"));
            oos.writeObject(student);
            System.out.println("序列化成功!");
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }

        //反序列化
        try {
    
    
            //从文件中读出对象
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\imag\\b.txt"));
            Student stu = (Student) ois.readObject();
            System.out.println("反序列化成功!");
            System.out.println(stu.toString());

        } catch (Exception e) {
    
    
            e.printStackTrace();
        }

The Serializable interface is not implemented and an error is reported.

java.io.NotSerializableException: com.li.pojo.Student
	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at com.li.test.SerializableTest.serializable(SerializableTest.java:25)
	at com.li.test.SerializableTest.main(SerializableTest.java:14)

Implement Serializable interface

public class Student implements Serializable{
    
    
	//省略属性
	//省略setter、getter方法
	//省略toString方法
}

Print the object before serialization:

Student{name='Zhang San', age=12}

Serialization is successful! And b.txt has some content

Insert picture description here
Then call deserialization, and the output object
deserialization is successful!

Student{name='Zhang San', age=12}

After deserialization, it was found to be consistent with the object before serialization!

2. The specific operation process of serialization and deserialization

How are specific serialization and deserialization performed?

  • The specific serialization execution process is as follows (the parameter list is omitted):

ObjectOutputStream->writeObject() -> writeObject0() -> writeOrdinaryObject() -> writeSerialData() -> defaultWriteFields()
The writeObject0 method judges
whether it is a String type, an enumeration type, a serialized object, if it is not, it will throw Is the exception
NotSerializableException very familiar? The exception that we did not implement the Serializable interface thrown is from here.

			 // remaining cases
            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);
            } 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());
                }
            }
  • The specific deserialization execution process is as follows (the parameter list is omitted):
    ObjectInputStream -> readObject() -> readObject0() -> readOrdinaryObject() -> readSerialData()-> defaultReadFields()

3. Why can't the properties modified by transient and static be serialized?

Test two keywords:

  • static
  • transient
	//新增加两个字段
    public static String addRess="宁夏";
    private transient String carName="捷豹";
     @Override
    public String toString() {
    
    
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", addRess='" + addRess + '\'' +
                ", carName='" + carName + '\''+
                '}';
    }
 		//初始化
        Student student = new Student();
        student.setName("张三");
        student.setAge(12);
        System.out.println(student.toString());
        //序列化
        try {
    
    
            //把对象写入文件中
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\imag\\b.txt"));
            oos.writeObject(student);
            System.out.println("序列化成功!");
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }

        student.setAddRess("陕西");

        //反序列化
        try {
    
    
            //从文件中读出对象
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\imag\\b.txt"));
            Student stu = (Student) ois.readObject();
            System.out.println("反序列化成功!");
            System.out.println(stu.toString());

        } catch (Exception e) {
    
    
            e.printStackTrace();
        }

Output print result:

//序列化前的对象
Student{
    
    name='张三', age=12, addRess='宁夏', carName='捷豹'}
序列化成功!
反序列化成功!
//反序列化后的对象
Student{
    
    name='张三', age=12, addRess='陕西', carName='null'}

From the results, we can compare and see:
Before serialization: addRessthe value of the '宁夏'field '陕西', modify the field value after serialization, and the field value after deserialization '陕西', not the state before deserialization.
Why? The original static modified field belongs to the class state, and the serialization is for the object state, so the keyword modified by the static keyword does not save the state
before serialization. carNameThe value of the field before ''捷豹serialization and the field value after serialization are null. Why?
The original transient (temporary) keyword-modified field can prevent the field from being serialized to the file. After being deserialized, the value of the transient field is set to the initial value, for example, the initial value of the int type is 0, and the object type The initial value of is null.

If you want to explore, it is also reflected in the source code

/**
     * Returns array of ObjectStreamFields corresponding to all non-static
     * non-transient fields declared by given class.  Each ObjectStreamField
     * contains a Field object for the field it represents.  If no default
     * serializable fields exist, NO_FIELDS is returned.
     */
 private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {
    
    
        Field[] clFields = cl.getDeclaredFields();
        ArrayList<ObjectStreamField> list = new ArrayList<>();
        int mask = Modifier.STATIC | Modifier.TRANSIENT;

        for (int i = 0; i < clFields.length; i++) {
    
    
            if ((clFields[i].getModifiers() & mask) == 0) {
    
    
                list.add(new ObjectStreamField(clFields[i], false, true));
            }
        }
        int size = list.size();
        return (size == 0) ? NO_FIELDS :
            list.toArray(new ObjectStreamField[size]);
    }

It is explained in the comments: Modifier.STATIC | Modifier.TRANSIENTstatic and temporary fields will not be serialized

4. What is the role of serialized ID

I don’t know if the friends have discovered that all classes that implement the Serializable interface will have the following line of code
private static final long serialVersionUID = 5791892247841471821L

serialVersionUID: the meaning of serialization ID
So what is the role of this serialization ID?
In fact, it is a factor to ensure the success of deserialization.
If you do not set the serialization ID after the serialization interface is implemented, java will generate a serialization ID for you (generated according to the number of field attribute types)
but if you are serializing After conversion, adding a certain field will report an error.
Let's verify it:

	//准备两个字段
     private String name;
     private int age;
     //提供setter、getter方法 

Test:
serialization

    //初始化
        Student student = new Student();
        student.setName("张三");
        student.setAge(12);
        System.out.println(student.toString());
        //序列化
        try {
    
    
            //把对象写入文件中
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\imag\\b.txt"));
            oos.writeObject(student);
            System.out.println("序列化成功!");
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
        //反序列化
        try {
    
    
            //从文件中读出对象
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\imag\\b.txt"));
            Student stu = (Student) ois.readObject();
            System.out.println("反序列化成功!");
            System.out.println(stu.toString());

        } catch (Exception e) {
    
    
            e.printStackTrace();
        }

Print result:

Student{name='张三', age=12}
Serialized successfully!
Deserialized successfully!
Student{name='张三', age=12}

Modify agethe data type of the field to String type. For
this test, let’s serialize it first

//初始化
		Student student = new Student();
        student.setName("张三");
        student.setAge(12);
        System.out.println(student.toString());
        //序列化
        try {
    
    
            //把对象写入文件中
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\imag\\b.txt"));
            oos.writeObject(student);
            System.out.println("序列化成功!");
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }

Then modify the field of the entity class to String type, comment the serialization code, perform deserialization,
and report an error:

java.io.InvalidClassException: com.li.pojo.Student; local class incompatible: stream classdesc serialVersionUID = -5791892247841471821, local class serialVersionUID = -3882745368779827342
	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:699)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1885)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1751)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2042)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1573)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:431)

This error is the same as the error I said at the beginning, caused by the inconsistency of serialization ID and deserialization ID.
So generally we only need to implement the serialization interface and use the default serialization ID (1L) to be
private static final long serialVersionUID = 1L

Guess you like

Origin blog.csdn.net/lirui1212/article/details/109514723