Java 序列化和反序列化相关整理

1. 概述

  • Java 提供了一种对象序列化的机制,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。把对象转换为字节序列的过程称为对象的 序列化
  • 将序列化对象写入文件后,可以从文件中读取出来,并且对它进行反序列化,对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象。把字节序列恢复为对象的过程称为对象的 反序列化
  • 整个过程都是 Java 虚拟机独立的,在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象。

对象序列化的主要用途

  • 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中。
  • 在网络上传送对象的字节序列。
  • 通过序列化在进程间传递对象。

2. 相关接口和类

  • Java 为了方便开发人员将 Java 对象进行序列化及反序列化提供了一套方便的 API 支持。

java.io.Serializable 接口

  • 类通过实现 Serializable 接口启用其序列化功能。
    • 未实现此接口的类将无法使其任何状态序列化或反序列化。
    • 可序列化类的所有子类型本身都是可序列化的。
    • 序列化接口没有方法或字段,仅用于标识可序列化的语义
import java.io.Serializable;
import java.util.Date;

public class User implements Serializable {

    private String name;
    private int age;
    private Date birthday;
    private transient String gender;

    private static final long serialVersionUID = -6849794470754667710L;

    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;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

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

import java.io.*;
import java.util.Date;

public class SerializableDemo {

    public static void main(String[] args) {
        //Initializes The Object
        User user = new User();
        user.setName("test");
        user.setGender("male");
        user.setAge(23);
        user.setBirthday(new Date());
        System.out.println(user);

        //Write Obj to File
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
            oos.writeObject(user);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        //Read Obj from File
        File file = new File("tempFile");
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream(file));
            User newUser = (User) ois.readObject();
            System.out.println(newUser);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                ois.close();
                file.delete();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}
/**
print 
User{name='test', age=23, gender=male, birthday=Sun Jun 02 18:15:17 CST 2019}
User{name='test', age=23, gender=null, birthday=Sun Jun 02 18:15:17 CST 2019}
**/
  • 当试图对一个未实现 Serializable 接口的对象进行序列化时,将抛出 NotSerializableException 异常。
User{name='test', age=23, gender=male, birthday=Sun Jun 02 18:18:49 CST 2019}
java.io.NotSerializableException: User
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at SerializableDemo.main(SerializableDemo.java:19)

java.io.Externalizable 接口

  • Externalizable 继承 Serializable,该接口中定义了两个抽象方法 writeExternal()readExternal()
    • 当使用 Externalizable 接口进行序列化与反序列化时,需要重写 writeExternal()readExternal() 方法。
    • 使用 Externalizable 进行序列化读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,再将被保存对象的字段的值分别填充到新对象中。所以,实现 Externalizable 接口的类必须 提供一个 public 的无参构造器

ObjectOutput 和 ObjectInput 接口

  • ObjectInput 接口扩展自 DataInput 接口以包含对象的读操作。

    • DataInput 接口用于从二进制流中读取字节,并根据所有 Java 基本类型数据进行重构。同时还提供根据 UTF-8 修改版格式的数据重构 String 的工具。
    • 对于此接口中的所有数据读取例程来说,如果在读取所需字节数之前已经到达文件末尾 (end of file),则将抛出 EOFException(IOException 的一种)。
    • 如果因为到达文件末尾以外的其他原因无法读取字节,则将抛出 IOException 而不是 EOFException。尤其是,在输入流已关闭的情况下,将抛出 IOException。
  • ObjectOutput 扩展 DataOutput 接口以包含对象的写入操作。

    • DataOutput 接口用于将数据从任意 Java 基本类型转换为一系列字节,并将这些字节写入二进制流。
    • 同时还提供了一个将 String 转换成 UTF-8 修改版格式并写入所得到的系列字节的工具。
    • 对于此接口中写入字节的所有方法,如果由于某种原因无法写入某个字节,则抛出 IOException。

ObjectOutputStream 和 ObjectInputStream 类

  • 使用 ObjectOutputStream 的 writeObject() 方法把一个对象进行持久化。
  • 使用 ObjectInputStream 的 readObject() 方法从持久化存储中把对象读取出来。
  • 在序列化过程中,如果被序列化的类中定义了 writeObject()readObject() 方法,虚拟机会试图调用对象类里的 writeObject()readObject() 方法,进行用户自定义的序列化和反序列化。
  • 默认调用的是 ObjectOutputStream 的 defaultWriteObject() 方法以及 ObjectInputStream 的 defaultReadObject() 方法。
  • 用户自定义的 writeObject()readObject() 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。

2.1 Transient 关键字

  • Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中。
    • 在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。

Transient 与 Static

  • 静态变量不是对象状态的一部分,因此它不参与序列化。
  • 将静态变量声明为 transient 变量没有用处。

Final 与 Transient

  • final 变量将直接通过值参与序列化,将 final 变量声明为 transient 变量不会产生任何影响。

2.2 serialVersionUID(序列化 ID)

  • 虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致。
  • SerialVersionUID 是一个标识符,它通常使用对象的哈希码,序列化时会标记在对象上。
    • 可以通过 Java 中 serialver 工具找到该对象的 serialVersionUID,命令格式:serialver classname
  • Java 序列化机制通过判断类的 serialVersionUID 验证版本的一致性。在进行反序列化时,Java 虚拟机会把传来的字节流中的 serialVersionUID 与本地相应实体类的 serialVersionUID 进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是 InvalidCastException
  • serialVersionUID 有两种显示的生成方式。
    • 默认为 1L,例如 private static final long serialVersionUID = 1L;
    • 根据类名、接口名、成员方法及属性等来生成一个 64 位的哈希字段,随机生成的序列化 ID,通过改变序列化 ID 可以用来限制某些用户的使用。
  • 当实现 java.io.Serializable 接口的类没有显式地定义一个 serialVersionUID 变量时候,Java 序列化机制会根据编译的 Class 自动生成一个 serialVersionUID 作为序列化版本比较使用。这种情况下,如果 Class 文件(类名,方法明等)没有发生变化(增加空格,换行,增加注释等等),就算再编译多次,serialVersionUID 也不会变化。

3. 不同情境

父类的序列化

  • 一个子类实现了 Serializable 接口,它的父类没有实现 Serializable 接口,序列化该子类对象,然后反序列化后输出父类定义的某变量的数值,会发现该变量数值与序列化时的数值不同。
    • 要想将父类对象也序列化,就需要让父类也实现 Serializable 接口。
    • 如果父类不实现的话,就需要有默认的无参的构造函数。
    • 在父类没有实现 Serializable 接口时,虚拟机是不会序列化父对象的,而一个 Java 对象的构造必须先有父对象,才有子对象,反序列化也不例外。
    • 反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。
    • 因此当获取父对象的变量值时,是调用父类无参构造函数后的值。如果考虑到这种序列化的情况,在父类无参构造函数中对变量进行初始化,否则,父类变量值都是默认声明的值,如 int 型的默认是 0,string 型的默认是 null。

对敏感字段加密

  • 对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。
    • 用户可以自定义的 writeObject()readObject() 方法可以允许用户控制序列化的过程(定义序列化策略)。

序列化存储规则

  • Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用,该存储规则极大的节省了存储空间。

ArrayList 的序列化

  • 其中 elementData 变量为 transient 关键字修饰,因此认为这个成员变量不会被序列化保留下来。
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;
    transient Object[] elementData; // non-private to simplify nested class access
    private int size;
}
  • ArrayList 底层通过数组实现,数组 elementData 用来保存列表中的元素,该属性的声明方式标明无法通过序列化进行持久化,通过下例查看实际上仍然进行了持久化。
import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class SerializableDemo {

    public static void main(String[] args) {
        //Initializes The list
        List<String> list = new ArrayList<String>();
        list.add("hello");
        list.add("world");
        list.add("test");
        System.out.println("init list" + list);

        //Write list to File
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
            oos.writeObject(list);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        //Read list from File
        File file = new File("tempFile");
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream(file));
            List<String> newlist = (List<String>) ois.readObject();
            System.out.println("newlist" + newlist);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                ois.close();
                file.delete();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}
/**
print
init list[hello, world, test]
newlist[hello, world, test]
**/
  • 分析 ArrayList 的 writeObject()readObject() 方法。
private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
}
......
private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;

        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in capacity
        s.readInt(); // ignored

        if (size > 0) {
            // be like clone(), allocate array based upon size not capacity
            int capacity = calculateCapacity(elementData, size);
            SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
            ensureCapacityInternal(size);

            Object[] a = elementData;
            // Read in all elements in the proper order.
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();
            }
        }
}
  • ArrayList 实际上是动态数组,每次在放满之后自动增长设定的长度值,如果数组自动增长长度设为 100,而实际只放入一个元素,就会序列化 99 个 null 元素。
    • 为了保证在序列化的时候不会将这么多 null 同时进行序列化,ArrayList 把元素数组设置为 transient。
    • 然后通过重写 writeObject()readObject() 方法的方式把其中的元素保留下来。
      • writeObject() 方法把 elementData 数组中的元素遍历的保存到输出流(ObjectOutputStream)中。
      • readObject() 方法从输入流(ObjectInputStream)中读出对象并保存赋值到 elementData 数组中。

序列化对单例的破坏

import java.io.Serializable;

public class Singleton implements Serializable {

    private volatile static Singleton singleton;

    private Singleton() {
    }

    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}
......
import java.io.*;

public class SerializableDemo {

    public static void main(String[] args) {

        //Write Singleton to File
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
            oos.writeObject(Singleton.getSingleton());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        //Read Singleton from File
        File file = new File("tempFile");
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream(file));
            Singleton newSingleton = (Singleton) ois.readObject();
            System.out.println(newSingleton == Singleton.getSingleton());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                ois.close();
                file.delete();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}
// print false
  • 通过对 Singleton 的序列化与反序列化得到的对象是一个新的对象,破坏了 Singleton 的单例性。

ObjectInputStream 类 readObject() 方法的调用栈

 
readObject 方法的调用栈
private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
        if (bin.readByte() != TC_OBJECT) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);
        desc.checkDeserialize();

        Class<?> cl = desc.forClass();
        if (cl == String.class || cl == Class.class
                || cl == ObjectStreamClass.class) {
            throw new InvalidClassException("invalid class descriptor");
        }

        Object obj;
        try {
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }

        passHandle = handles.assign(unshared ? unsharedMarker : obj);
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(passHandle, resolveEx);
        }

        if (desc.isExternalizable()) {
            readExternalData((Externalizable) obj, desc);
        } else {
            readSerialData(obj, desc);
        }

        handles.finish(passHandle);

        if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                // Filter the replacement object
                if (rep != null) {
                    if (rep.getClass().isArray()) {
                        filterCheck(rep.getClass(), Array.getLength(rep));
                    } else {
                        filterCheck(rep.getClass(), -1);
                    }
                }
                handles.setObject(passHandle, obj = rep);
            }
        }

        return obj;
}
  • 其中 desc.newInstance() 该方法通过反射的方式新建了一个对象。

防止序列化破坏单例模式

  • 在 Singleton 类中定义一个 readResolve() 方法。
import java.io.Serializable;

public class Singleton implements Serializable {

    private volatile static Singleton singleton;

    private Singleton() {
    }

    public static Singleton getSingleton() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

    private Object readResolve() {
        return getSingleton();
    }
}
// print true
  • hasReadResolveMethod() 方法判断如果实现了 Serializable 或者 Externalizable 接口的类中包含 readResolve() 方法。
  • invokeReadResolve() 方法就通过反射的方式调用要被反序列化类的 readResolve() 方法。
  • Singleton 中定义 readResolve() 方法,并在该方法中指定返回对象的生成策略,防止了单例被破坏。
if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
{
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                // Filter the replacement object
                if (rep != null) {
                    if (rep.getClass().isArray()) {
                        filterCheck(rep.getClass(), Array.getLength(rep));
                    } else {
                        filterCheck(rep.getClass(), -1);
                    }
                }
                handles.setObject(passHandle, obj = rep);
            }
}

4. 实现原理

ObjectOutputStream 类 writeObject 方法的调用过程

public final void writeObject(Object obj) throws IOException {
        if (enableOverride) {
            writeObjectOverride(obj);
            return;
        }
        try {
            writeObject0(obj, false);
        } catch (IOException ex) {
            if (depth == 0) {
                writeFatalException(ex);
            }
            throw ex;
        }
}
  • Serializable 一个空的接口,确能保证只有实现了该接口的方法才能进行序列化与反序列化。
    • 因为 writeObject0() 方法中 obj instanceof Serializable 条件判断。
  • 在进行序列化操作时,会判断要被序列化的类是否是 Enum、Array 和 Serializable 类型,如果不是则直接抛出 NotSerializableException 异常。
private void writeObject0(Object obj, boolean unshared)
        throws IOException
    {
        boolean oldMode = bout.setBlockDataMode(false);
        depth++;
        try {
            // handle previously written and non-replaceable objects
            int h;
            if ((obj = subs.lookup(obj)) == null) {
                writeNull();
                return;
            } else if (!unshared && (h = handles.lookup(obj)) != -1) {
                writeHandle(h);
                return;
            } else if (obj instanceof Class) {
                writeClass((Class) obj, unshared);
                return;
            } else if (obj instanceof ObjectStreamClass) {
                writeClassDesc((ObjectStreamClass) obj, unshared);
                return;
            }

            // check for replacement object
            Object orig = obj;
            Class<?> cl = obj.getClass();
            ObjectStreamClass desc;
            for (;;) {
                // REMIND: skip this check for strings/arrays?
                Class<?> repCl;
                desc = ObjectStreamClass.lookup(cl, true);
                if (!desc.hasWriteReplaceMethod() ||
                    (obj = desc.invokeWriteReplace(obj)) == null ||
                    (repCl = obj.getClass()) == cl)
                {
                    break;
                }
                cl = repCl;
            }
            if (enableReplace) {
                Object rep = replaceObject(obj);
                if (rep != obj && rep != null) {
                    cl = rep.getClass();
                    desc = ObjectStreamClass.lookup(cl, true);
                }
                obj = rep;
            }

            // if object replaced, run through original checks a second time
            if (obj != orig) {
                subs.assign(orig, obj);
                if (obj == null) {
                    writeNull();
                    return;
                } else if (!unshared && (h = handles.lookup(obj)) != -1) {
                    writeHandle(h);
                    return;
                } else if (obj instanceof Class) {
                    writeClass((Class) obj, unshared);
                    return;
                } else if (obj instanceof ObjectStreamClass) {
                    writeClassDesc((ObjectStreamClass) obj, unshared);
                    return;
                }
            }

            // 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());
                }
            }
        } finally {
            depth--;
            bout.setBlockDataMode(oldMode);
        }
}
  • 判断为 Serializable 调用 writeOrdinaryObject() 方法。
private void writeOrdinaryObject(Object obj,
                                     ObjectStreamClass desc,
                                     boolean unshared)
        throws IOException
    {
        if (extendedDebugInfo) {
            debugInfoStack.push(
                (depth == 1 ? "root " : "") + "object (class \"" +
                obj.getClass().getName() + "\", " + obj.toString() + ")");
        }
        try {
            desc.checkSerialize();

            bout.writeByte(TC_OBJECT);
            writeClassDesc(desc, false);
            handles.assign(unshared ? null : obj);
            if (desc.isExternalizable() && !desc.isProxy()) {
                writeExternalData((Externalizable) obj);
            } else {
                writeSerialData(obj, desc);
            }
        } finally {
            if (extendedDebugInfo) {
                debugInfoStack.pop();
            }
        }
}
......
private void writeSerialData(Object obj, ObjectStreamClass desc)
        throws IOException
    {
        ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
        for (int i = 0; i < slots.length; i++) {
            ObjectStreamClass slotDesc = slots[i].desc;
            if (slotDesc.hasWriteObjectMethod()) {
                PutFieldImpl oldPut = curPut;
                curPut = null;
                SerialCallbackContext oldContext = curContext;

                if (extendedDebugInfo) {
                    debugInfoStack.push(
                        "custom writeObject data (class \"" +
                        slotDesc.getName() + "\")");
                }
                try {
                    curContext = new SerialCallbackContext(obj, slotDesc);
                    bout.setBlockDataMode(true);
                    slotDesc.invokeWriteObject(obj, this);
                    bout.setBlockDataMode(false);
                    bout.writeByte(TC_ENDBLOCKDATA);
                } finally {
                    curContext.setUsed();
                    curContext = oldContext;
                    if (extendedDebugInfo) {
                        debugInfoStack.pop();
                    }
                }

                curPut = oldPut;
            } else {
                defaultWriteFields(obj, slotDesc);
            }
        }
}
......
void invokeWriteObject(Object obj, ObjectOutputStream out)
        throws IOException, UnsupportedOperationException
    {
        requireInitialized();
        if (writeObjectMethod != null) {
            try {
                writeObjectMethod.invoke(obj, new Object[]{ out });
            } catch (InvocationTargetException ex) {
                Throwable th = ex.getTargetException();
                if (th instanceof IOException) {
                    throw (IOException) th;
                } else {
                    throwMiscException(th);
                }
            } catch (IllegalAccessException ex) {
                // should not occur, as access checks have been suppressed
                throw new InternalError(ex);
            }
        } else {
            throw new UnsupportedOperationException();
        }
}
  • 最终通过 反射 的方式调用 writeObjectMethod() 方法。

对象的序列化格式

import java.io.Serializable;

public class Test implements Serializable {

    public byte version = 100;

    public byte count = 0;

    @Override
    public String toString() {
        return "version" + version + ",count " + count;
    }
}
......
import java.io.*;

public class SerializableDemo {

    public static void main(String[] args) {

        Test test = new Test();
        //Write Test to File
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("d://tempFile"));
            oos.writeObject(test);
            System.out.println(test);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        //Read Test from File
        File file = new File("d://tempFile");
        ObjectInputStream ois = null;
        try {
            ois = new ObjectInputStream(new FileInputStream(file));
            Test newTest = (Test) ois.readObject();
            System.out.println(newTest);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            try {
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}
/**
print
version100,count 0
version100,count 0
**/
  • 用十六进制方式打开对象序列化输出的 tempFile 文件。
 
tempFile 文件内容(共 45 byte)
  • 除了本身的数据(占用 2 byte)以外,该文件还包括了对序列化对象的其他描述信息(占用 43 byte)。
内容说明
AC ED STREAM_MAGIC,声明使用了序列化协议。
00 05 STREAM_VERSION,定义了序列化协议的版本。
0x73 TC_OBJECT,声明这是一个新的对象。
0x72 TC_CLASSDESC,声明这里开始一个新的 Class。
00 04 Class 名字的长度。
54 65 73 74 Class 类名(这里为 Test)。
A9 6C B8 D7 81 BC 7A 50 SerialVersionUID,序列化 ID,如果没有指定,则会由算法随机生成一个 8 byte 的 ID。
0x02 标记号,声明该对象支持序列化。
00 02 该类所包含的域(Field)个数。
0x42 域类型,0x42 代表 " B ",是 Byte 的意思。
00 05 域名称长度。(count 名称为 5 位)
63 6F 75 6E 74 域描述。(对应 count 的十六进制)
0x42 同样是域类型,0x42 代表 " B ",是 Byte 的意思。
00 07 同样是域名称长度。(version 名称为 7 位)
76 65 72 73 69 6F 6E 同样是域描述。(对应 version 的十六进制)
0x78 TC_ENDBLOCKDATA,对象块结束标志。
0x70 TC_NULL,说明没有其他超类的标志。
0x00 count 值为 0。
0x64 version 值为 100。(0x64 的十进制为 100)
ASCII 码十六进制符号ASCII 码十六进制符号
41 A 61 a
42 B 62 b
43 C 63 c
44 D 64 d
45 E 65 e
46 F 66 f
47 G 67 g
48 H 68 h
49 I 69 i
4A J 6A j
4B K 6B k
4C L 6C l
4D M 6D m
4E N 6E n
4F O 6F o
50 P 70 p
51 Q 71 q
52 R 72 r
53 S 73 s
54 T 74 t
55 U 75 u
56 V 76 v
57 W 77 w
58 X 78 x
59 Y 79 y
5A Z 7A z

 

参考资料

https://blog.csdn.net/qq_31457665/article/details/82587942
https://www.runoob.com/java/java-serialization.html
https://baijiahao.baidu.com/s?id=1622011683975285944&wfr=spider&for=pc
https://www.cnblogs.com/wxgblogs/p/5849951.html
https://www.cnblogs.com/xdp-gacl/p/3777987.html
http://www.importnew.com/17964.html



posted @ 2020-03-26 19:01  windy杨树  阅读(317)  评论(0编辑  收藏  举报