Serialization definition

In Java, serialization is making an object into a sequence of bytes for transfer or storage; Instead, turn a sequence of bytes into an object, called deserialization.

Serializable implements serialization

  1. A class that implements the Serializable interface and writes the serialVersionUID can serialize normally. As follows:
public class Source implements Serializable { private static final long serialVersionUID = 6067841943119850745L; private String name; private String type; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getType() { return type; } public void setType(String type) { this.type = type; }}Copy the code
  1. Call the writeObject method of ObjectOutputStream for serialization
// The object to serialize Source Source = new Source(); source.setName("gallery"); source.setType("png"); File File = new File(getFilesDir().getPath() + file.separator + "source"); if (! file.exists()) { file.mkdir(); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file)); // Call writeObject(writexxx) oos. WriteObject (source); / / close the oos. The close ();Copy the code
  1. Call ObjectInputStream’s readObject method interface to deserialize
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); // Call readObject Source source1 = (Source) ois.readObject(); / / close the ois. Close ();Copy the code

The process of serialization and deserialization is relatively simple. The function of Serializable is only a token. The writeObject method determines whether instanceof Serializable is true

This is a flowchart, you can take a look at the first, after the source analysis and then look back

Source code analysis

writeObject(Object obj) -> writeObject0(Object obj, boolean unshared)

Private void writeObject0(Object obj, Boolean unshared) throws IOException {...... } else if (obj instanceof Serializable) { writeOrdinaryObject(obj, desc, unshared); } else {... } } f }Copy the code

WriteOrdinaryObject starts writing

Private void writeOrdinaryObject(Object obj, ObjectStreamClass Desc, Boolean unshared) throws IOException {... desc.checkSerialize(); // The tag to start writing TC_OBJECT flout. writeByte(TC_OBJECT); // Write the class descriptor writeClassDesc(desc, false); ... WriteSerialData (obj, desc); ... }}Copy the code

WriteClassDesc normally calls to (non-dynamic proxy) writeNonProxyDesc

private void writeClassDesc(ObjectStreamClass desc, boolean unshared) throws IOException { int handle; if (desc == null) { writeNull(); }... else { writeNonProxyDesc(desc, unshared); }}Copy the code
private void writeNonProxyDesc(ObjectStreamClass desc, Boolean unshared) throws IOException {// Start writing class description bout. WriteByte (TC_CLASSDESC); ... // Actually write the class description desc.writenonProxy (this); ... // Write class description bout. WriteByte (TC_ENDBLOCKDATA); ... Null writeClassDesc(desc.getSuperDesc(), false); }Copy the code

The class name, serialVersionUID, tag, field type, and field name are written in the writeNonProxy. SerialVersionUID is a unique identifier. If the changes are sent before and after serialization, deserialization will fail. The calculation process is complicated and will be affected by field methods, etc. For example, when the field mode of the class changes before and after serialization, the suID automatically calculated will be different, resulting in serialization failure. Therefore, serialVersionUID needs to be written.

void writeNonProxy(ObjectOutputStream out) throws IOException { out.writeUTF(name); out.writeLong(getSerialVersionUID()); byte flags = 0; ... flags |= ObjectStreamConstants.SC_SERIALIZABLE; ... out.writeByte(flags); out.writeShort(fields.length); for (int i = 0; i < fields.length; i++) { ObjectStreamField f = fields[i]; out.writeByte(f.getTypeCode()); out.writeUTF(f.getName()); if (! f.isPrimitive()) { out.writeTypeString(f.getTypeString()); }}}Copy the code

After the description of the writeClassDesc class is complete, writeSerialData follows

Private void writeSerialData(Object obj, ObjectStreamClass desc) throws IOException {// Array of class description, Parent row in front of ObjectStreamClass. ClassDataSlot [] slots = desc. GetClassDataLayout (); for (int i = 0; i < slots.length; i++) { ObjectStreamClass slotDesc = slots[i].desc; If (slotDesc hasWriteObjectMethod ()) {/ / if the class wrote writeObject method, go here... } else {// This class does not write the writeObject method. Call the default defaultWriteFields(obj, slotDesc); }}}Copy the code

DefaultWriteFields distinguishes between basic data types and non-basic data types. Basic data types are written directly, and non-basic types are called writeObject0. Repeat the above steps.

In addition, static and transient modified fields are not serialized

Private void defaultWriteFields(Object obj, ObjectStreamClass Desc) throws IOException {...... // Get the serializable basic type of object obj (byte, short, int, long, float, double...) Field value and marshals it into a byte array buf starting at offset 0. desc.getPrimFieldValues(obj, primVals); bout.write(primVals, 0, primDataSize, false); / / get all the serializable field, which transferred to getDefaultSerialFields, filtering in the static and transient modified fields (Modifier. The static | Modifier. The transient), ObjectStreamField[] fields = desc.getFields(false); // Returns an array of serializable fields of non-basic data types for the class represented. (writeObject0); (writeObject0); (writeObject0); Object[] objVals = new Object[descal.getNuMobjFields ()]; // Get the serializable object field values for object obj and store them in an array vals starting at offset 0. desc.getObjFieldValues(obj, objVals); for (int i = 0; i < objVals.length; I++) {... try { writeObject0(objVals[i], fields[numPrimFields + i].isUnshared()); } the finally {... }}}Copy the code

Customize the serialization process

The procedure analyzed above uses the default serialization, but there are some custom methods to implement the serialization of the interface

Looking at the fields in ObjectStreamClass, there are five methods that can be defined automatically

    /** class-defined writeObject method, or null if none */
    private Method writeObjectMethod;
    /** class-defined readObject method, or null if none */
    private Method readObjectMethod;
    /** class-defined readObjectNoData method, or null if none */
    private Method readObjectNoDataMethod;
    /** class-defined writeReplace method, or null if none */
    private Method writeReplaceMethod;
    /** class-defined readResolve method, or null if none */
    private Method readResolveMethod;
Copy the code

By looking at its reflection fetch, you can define

WriteObjectMethod = getPrivateMethod(cl, "writeObject", new Class<? >[] { ObjectOutputStream.class },Void.TYPE); readObjectMethod = getPrivateMethod(cl, "readObject", new Class<? >[] { ObjectInputStream.class },Void.TYPE); readObjectNoDataMethod = getPrivateMethod( cl, "readObjectNoData", null, Void.TYPE); //getInheritableMethod gets non-static, non-abstract, common and protected class methods that can be looked up from the parent class, WriteReplaceMethod = getInheritableMethod(CL, "writeReplace", null, object.class); readResolveMethod = getInheritableMethod(cl, "readResolve", null, Object.class);Copy the code

When deserialization: first call readObject, when empty call readObjectNoData, and finally call readResolve

  • If a serialized class contains the writeReplace() method, then the actual serialized object will be the one returned as the writeReplace method value
  • If a serialized class contains the writeObject() method, then the actual serialization process uses the one in writeObject().
  • If a serialized class contains a readObject() method, then the actual deserialization process uses the one in readObject().
  • If a serialized class contains the readResolve() method, the end result of the actual deserialization is the value of the readResolve return value.
  • If a serialized class contains the readObjectNoData() method, then the value returned by the method is used when the serialized result obtained from the field is null, usually because the class has been modified.

ReadObjectNoData: if Source inherits from Source2 when serialization begins, Source2 has name and type fields. When deserializing, Source changes from inheriting Source2 to inheriting Source3, which has the name and type and age fields. Source3 writes the readObjectNoData method, which will be triggered when it is deserialized. The usual way to do this is to set a default value for age.

    private Object writeReplace() {

    }

   private void writeObject(ObjectOutputStream oos) throws IOException {
        oos.writeObject(getName());
        oos.writeObject(getType());
    }

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
        setName((String) ois.readObject());
        setType((String) ois.readObject());
    }

    private void readObjectNoData() {

    }

    private Object readResolve() {

    }
Copy the code

Custom serialization in HashMap

HashMap uses the writeObject and readObject methods to define serialization, and uses the transient to decorate the table. We know that HashMap is an “array” + “linked list/red-black tree” form, and the table is an array of data. The HashMap computes the hash value and then computes an index value to store and retrieve data from the table,

HashMap computes the hash and eventually calls the hashCode method on Object. But the hashCode method in Object is native. Different JVMS may have different implementations, and the hash generated may be different.

Therefore, deserialization in different virtual machines, using the GET method, because the index value may be different, so may not get the data.

In addition, the table in most cases is not full, serialize unused parts, waste space.

Externalizable interface

Public Interface Externalizable extends java.io.Serializable {// Serialize void writeExternal(ObjectOutput out) throws IOException; Void readExternal(ObjectInput in) throws IOException, ClassNotFoundException; void readExternal(ObjectInput in) throws IOException, ClassNotFoundException; }Copy the code

Externalizable inherits Serializable. Using Externalizable does not require generating the serialization ID, but requires overwriting the two serialization and deserialization interfaces and a constructor with no parameters. Externalizable is a user-defined serialization, writeExternal is similar to Serializable writeObject, and readExternal is similar to readObject.

Check that the writeObject0 method was called during the creation of DESC

ObjectStreamClass. Lookup – > new ObjectStreamClass – > getExternalizableConstructor;

private static Constructor<? > getExternalizableConstructor(Class<? > cl) { try { Constructor<? > cons = cl.getDeclaredConstructor((Class<? >[]) null); cons.setAccessible(true); return ((cons.getModifiers() & Modifier.PUBLIC) ! = 0)? cons : null; } catch (NoSuchMethodException ex) { return null; }}Copy the code

In this code, after calling writeClassDesc, we then call writeExternalData if isExternalizable and not a proxy

if (desc.isExternalizable() && ! desc.isProxy()) { writeExternalData((Externalizable) obj); } else { writeSerialData(obj, desc); }Copy the code

Externalizable writeExternal is called in writeExternalData

Private void writeExternalData(Externalizable obj) throws IOException {...... obj.writeExternal(this); ... }Copy the code

Parcelable implements serialization

Serializable is a Java serialization mode, and Parcelable is recommended in Android. It uses shared memory to realize the exchange of user space and kernel space. The performance is good, but the implementation method is more complicated.

Parcelable is compared with Serializable

Parcelable Serizlizable
Parcelable Serizlizable
Implement the Parcelable interface Implement the Serizlizable interface
For android Java bring
Memory consumption: low Memory consumption: moderate
Direct reading and writing to memory In the form of an IO stream

Read and write data to the hard disk
Persistence is not supported Persistence support
Speed is fast Speed is generally

Parcelable is more than 10 times faster than Serializable in speed

Comparison and summary:

  • Parcelable has higher performance than Serializable when using memory. It is recommended to use Parcelable.
  • Serializable uses a lot of reflection during serialization and creates a lot of temporary variables, resulting in excessive load, which Parcelable does not.
  • Parcelable can’t be used when data is to be stored on disk, because Parcelable doesn’t do a good job of ensuring the persistence of data in the event of external changes. Although Serializable is inefficient, in this case, Serializable is used.

Parcelable implementation class introduction

In this article, we introduced the rapid generation of Parcelable objects. We will not repeat it here. The following Source class is a standard class that implements Parcelable:

public class Source implements Parcelable { private String name; private String type; @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(this.name); dest.writeString(this.type); } public Source() { } protected Source(Parcel in) { this.name = in.readString(); this.type = in.readString(); } public static final Creator<Source> CREATOR = new Creator<Source>() { @Override public Source createFromParcel(Parcel source) { return new Source(source); } @Override public Source[] newArray(int size) { return new Source[size]; }}; }Copy the code

describeContents

All classes that implement Parcelable need to implement this method. The return value is usually 0, but in special cases (including file Descriptor) it needs to return 1.

Is a variable defined in Parcelable. /** * indicates that the flat representation of the Parcelable object includes the file descriptor. */ public static final int CONTENTS_FILE_DESCRIPTOR = 0x0001;

writeToParcel

The writeToParcel is the method being serialized, and the data will be written internally through the writeXXX method on the Parcel, eventually calling the nativeWritexxx method.

Constructor that takes Parcel

Deserialize the Parcel using the readXXX method in the same order as the write

CREATOR

Provides methods to create a column object and an array of objects

Parcel

Parcelable uses the read and write methods of parcels to serialize and deserialize them. Parcel calls jNI methods to write and read data.

Parce provides basic data types, write and read strings, their arrays, and Java standard collection classes. WriteValue covers all supported types.

public final void writeValue(@Nullable Object v) { if (v == null) { writeInt(VAL_NULL); } else if (v instanceof String) { writeInt(VAL_STRING); writeString((String) v); } else if (v instanceof Integer) { writeInt(VAL_INTEGER); writeInt((Integer) v); } else if (v instanceof Map) { writeInt(VAL_MAP); writeMap((Map) v); } else if (v instanceof Bundle) { // Must be before Parcelable writeInt(VAL_BUNDLE); writeBundle((Bundle) v); } else if (v instanceof PersistableBundle) { writeInt(VAL_PERSISTABLEBUNDLE); writePersistableBundle((PersistableBundle) v); } else if (v instanceof Parcelable) { // IMPOTANT: cases for classes that implement Parcelable must // come before the Parcelable case, so that their specific VAL_* // types will be written. writeInt(VAL_PARCELABLE); writeParcelable((Parcelable) v, 0); } else if (v instanceof Short) { writeInt(VAL_SHORT); writeInt(((Short) v).intValue()); } else if (v instanceof Long) { writeInt(VAL_LONG); writeLong((Long) v); } else if (v instanceof Float) { writeInt(VAL_FLOAT); writeFloat((Float) v); } else if (v instanceof Double) { writeInt(VAL_DOUBLE); writeDouble((Double) v); } else if (v instanceof Boolean) { writeInt(VAL_BOOLEAN); writeInt((Boolean) v ? 1:0); } else if (v instanceof CharSequence) { // Must be after String writeInt(VAL_CHARSEQUENCE); writeCharSequence((CharSequence) v); } else if (v instanceof List) { writeInt(VAL_LIST); writeList((List) v); } else if (v instanceof SparseArray) { writeInt(VAL_SPARSEARRAY); writeSparseArray((SparseArray) v); } else if (v instanceof boolean[]) { writeInt(VAL_BOOLEANARRAY); writeBooleanArray((boolean[]) v); } else if (v instanceof byte[]) { writeInt(VAL_BYTEARRAY); writeByteArray((byte[]) v); } else if (v instanceof String[]) { writeInt(VAL_STRINGARRAY); writeStringArray((String[]) v); } else if (v instanceof CharSequence[]) { // Must be after String[] and before Object[] writeInt(VAL_CHARSEQUENCEARRAY);  writeCharSequenceArray((CharSequence[]) v); } else if (v instanceof IBinder) { writeInt(VAL_IBINDER); writeStrongBinder((IBinder) v); } else if (v instanceof Parcelable[]) { writeInt(VAL_PARCELABLEARRAY); writeParcelableArray((Parcelable[]) v, 0); } else if (v instanceof int[]) { writeInt(VAL_INTARRAY); writeIntArray((int[]) v); } else if (v instanceof long[]) { writeInt(VAL_LONGARRAY); writeLongArray((long[]) v); } else if (v instanceof Byte) { writeInt(VAL_BYTE); writeInt((Byte) v); } else if (v instanceof Size) { writeInt(VAL_SIZE); writeSize((Size) v); } else if (v instanceof SizeF) { writeInt(VAL_SIZEF); writeSizeF((SizeF) v); } else if (v instanceof double[]) { writeInt(VAL_DOUBLEARRAY); writeDoubleArray((double[]) v); } else { Class<? > clazz = v.getClass(); if (clazz.isArray() && clazz.getComponentType() == Object.class) { // Only pure Object[] are written here, Other arrays of non-primitive types are // handled by serialization as this does not record the component type. writeInt(VAL_OBJECTARRAY); writeArray((Object[]) v); } else if (v instanceof Serializable) { // Must be last writeInt(VAL_SERIALIZABLE); writeSerializable((Serializable) v); } else { throw new RuntimeException("Parcel: unable to marshal value " + v); }}}Copy the code

A cache is maintained within the Parcel, so it is recommended to use Parcel.obtain() to obtain a Parcel.

Parce contains a number of write, read, and native methods. Native uses a pointer to read and write data.

To further analyze how the post operates, mMAP memory mapping (to be continued)

Reference:

  • www.cnblogs.com/dgwblog/p/1…
  • www.cnblogs.com/wytiger/p/1…