深度解析JAVA序列化

1、序列化

java序列化提供了一个框架,用来将对象编码成字节流,并从字节流编码中从新构建的对象。将对象编码为字节流称之为序列化,反之将字节流重建成对象称之为反序列化。java序列为对象的可持久化及远程共享提供了一种简单机制。它实现起来很是方便,只须要实现serializble接口便可。但每每表面的上简单,隐藏了背后巨大的风险,若是你不了解serializable请慎用,由于其中有太多坑,且当你遇到时可能会不知道所措。effective java在序列化一章第一条就提出“谨慎地实现serializable接口”,可见serializable接口背后实现可能隐藏着“坑人的秘密”。
本文参考了网上大量的技术文章和effective java,将从序列化的原理、注意事项及实际应用几个方面,经过实例来揭开java序列化的面纱。
在这里补充研究序列化的背景:有一个Object持久化于缓存中,常常须要变动字段(添加或删除),每次作变动就要更改缓存表(担忧不兼容带来问题,一直不肯定哪些变动会来问题或引发什么样的问题),我但愿实现一种序列化,当变动或删除字段时不须要变动缓存表,这须要达到两个目的:一、新的类访问旧的缓存时没问题;2.旧的类访问新的缓存时也没问题。这个问题虽然在个人需求背景之下获得了快速解决,但仍是但愿将序列化给出一个充分研究,以备后续信手拈来。 javascript

2、序列化实现方式

2.1 简单示例

一个Person类,具备两个属性:name和age; java

public class Person implements Serializable {
    private String name;
    private int age;

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

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

    public void setAge(int age) {
        this.age = age;
    }
 }复制代码

生成一个Person的实例p,将期经过ObjectOutputStream写入文件,并经过ObjectInputStream读出来。这是一个完整的序列化/反序列化过程:ObjectOutputStream将p转化成字节流写入文件,ObjectInputStream将从文件中读出的字节流从新建立newPerson实例。缓存

@Test
public void testSerializable() throws Exception {
    File file = new File("p.dat");
    Person p = new Person("xiaoming", 10);
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
    oos.writeObject(p);
    oos.close();

    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
    Object newPerson  = ois.readObject();
    ois.close();
    System.out.println(newPerson);
}复制代码

经过上面的过程,咱们能够看出默认的序列化机制对使用者而言是很是简单的。序列化具体的实现是由ObjectOutputStream完成的;反序列化的具体实现是由ObjectInputStream完成的。那接下来咱们就看一下它们具体作了什么事app

2.2 Serializable

在介绍具体实现以前,咱们先来看一下Serializable接口,这毕竟是默认状况下的,使用者看到的惟一的东西。 框架

/** * Serializability of a class is enabled by the class implementing the * java.io.Serializable interface. Classes that do not implement this * interface will not have any of their state serialized or * deserialized. All subtypes of a serializable class are themselves * serializable. The serialization interface has no methods or fields * and serves only to identify the semantics of being serializable. <p> * * To allow subtypes of non-serializable classes to be serialized, the * subtype may assume responsibility for saving and restoring the * state of the supertype's public, protected, and (if accessible) * package fields. The subtype may assume this responsibility only if * the class it extends has an accessible no-arg constructor to * initialize the class's state. It is an error to declare a class * Serializable if this is not the case. The error will be detected at * runtime. <p> * * During deserialization, the fields of non-serializable classes will * be initialized using the public or protected no-arg constructor of * the class. A no-arg constructor must be accessible to the subclass * that is serializable. The fields of serializable subclasses will * be restored from the stream. <p> * * When traversing a graph, an object may be encountered that does not * support the Serializable interface. In this case the * NotSerializableException will be thrown and will identify the class * of the non-serializable object. <p> * * Classes that require special handling during the serialization and * deserialization process must implement special methods with these exact * signatures: * * <PRE> * private void writeObject(java.io.ObjectOutputStream out) * throws IOException * private void readObject(java.io.ObjectInputStream in) * throws IOException, ClassNotFoundException; * private void readObjectNoData() * throws ObjectStreamException; * </PRE> * * <p>The writeObject method is responsible for writing the state of the * object for its particular class so that the corresponding * readObject method can restore it. The default mechanism for saving * the Object's fields can be invoked by calling * out.defaultWriteObject. The method does not need to concern * itself with the state belonging to its superclasses or subclasses. * State is saved by writing the individual fields to the * ObjectOutputStream using the writeObject method or by using the * methods for primitive data types supported by DataOutput. * * <p>The readObject method is responsible for reading from the stream and * restoring the classes fields. It may call in.defaultReadObject to invoke * the default mechanism for restoring the object's non-static and * non-transient fields. The defaultReadObject method uses information in * the stream to assign the fields of the object saved in the stream with the * correspondingly named fields in the current object. This handles the case * when the class has evolved to add new fields. The method does not need to * concern itself with the state belonging to its superclasses or subclasses. * State is saved by writing the individual fields to the * ObjectOutputStream using the writeObject method or by using the * methods for primitive data types supported by DataOutput. * * <p>The readObjectNoData method is responsible for initializing the state of * the object for its particular class in the event that the serialization * stream does not list the given class as a superclass of the object being * deserialized. This may occur in cases where the receiving party uses a * different version of the deserialized instance's class than the sending * party, and the receiver's version extends classes that are not extended by * the sender's version. This may also occur if the serialization stream has * been tampered; hence, readObjectNoData is useful for initializing * deserialized objects properly despite a "hostile" or incomplete source * stream. * * <p>Serializable classes that need to designate an alternative object to be * used when writing an object to the stream should implement this * special method with the exact signature: * * <PRE> * ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException; * </PRE><p> * * This writeReplace method is invoked by serialization if the method * exists and it would be accessible from a method defined within the * class of the object being serialized. Thus, the method can have private, * protected and package-private access. Subclass access to this method * follows java accessibility rules. <p> * * Classes that need to designate a replacement when an instance of it * is read from the stream should implement this special method with the * exact signature. * * <PRE> * ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException; * </PRE><p> * * This readResolve method follows the same invocation rules and * accessibility rules as writeReplace.<p> * * The serialization runtime associates with each serializable class a version * number, called a serialVersionUID, which is used during deserialization to * verify that the sender and receiver of a serialized object have loaded * classes for that object that are compatible with respect to serialization. * If the receiver has loaded a class for the object that has a different * serialVersionUID than that of the corresponding sender's class, then * deserialization will result in an {@link InvalidClassException}. A * serializable class can declare its own serialVersionUID explicitly by * declaring a field named <code>"serialVersionUID"</code> that must be static, * final, and of type <code>long</code>: * * <PRE> * ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L; * </PRE> * * If a serializable class does not explicitly declare a serialVersionUID, then * the serialization runtime will calculate a default serialVersionUID value * for that class based on various aspects of the class, as described in the * Java(TM) Object Serialization Specification. However, it is <em>strongly * recommended</em> that all serializable classes explicitly declare * serialVersionUID values, since the default serialVersionUID computation is * highly sensitive to class details that may vary depending on compiler * implementations, and can thus result in unexpected * <code>InvalidClassException</code>s during deserialization. Therefore, to * guarantee a consistent serialVersionUID value across different java compiler * implementations, a serializable class must declare an explicit * serialVersionUID value. It is also strongly advised that explicit * serialVersionUID declarations use the <code>private</code> modifier where * possible, since such declarations apply only to the immediately declaring * class--serialVersionUID fields are not useful as inherited members. Array * classes cannot declare an explicit serialVersionUID, so they always have * the default computed value, but the requirement for matching * serialVersionUID values is waived for array classes. * * @author unascribed * @see java.io.ObjectOutputStream * @see java.io.ObjectInputStream * @see java.io.ObjectOutput * @see java.io.ObjectInput * @see java.io.Externalizable * @since JDK1.1 */
public interface Serializable {
}复制代码

接口自己未实现任何方法,但其注释值得好好看一下(只翻译部分,最好本身看原文吧): ide

  • 一个类的序列化能力是由实现Serializable接口决定的。未实现该接口的类将没法实现序列化和反序列化,实现序列化的类的子类也能够实现序列化。Serializable接口没有任何方法和属性,只是一个类能够实现序列化的标志。
  • 子类实现序列化,父类不实现序列化,此时父类要实现一个无参数构造器,不然会报错(见坑二)
    遇到不支持序列化的类会抛出NotSerializableException
    在序列化的过程当中须要特殊处理时,能够经过实现writeObject,readObject,readObjectNoData来实现
  • writeObject实现序列化将属性和值写入,默认的写入机制由defaultWriteObject来实现
    readObject实现从数据流中重建对像,默认的读出机制由defaultReadObject来实现,(This handles the case when the class has evolved to add new fields)并且能够处理类演化(添加字段)的状况,那删除一个字段呢?见坑三.)性能

  • 若是某个超类不支持序列化,但又不但愿使用默认值怎么办?实现readObjectNoData
    writeReplace() 方法可使对象被写入流之前,用一个对象来替换本身。当序列化时,可序列化的类要将对象写入流,若是咱们想要另外一个对象来替换当前对象来写入流,则能够要实现下面这个方法,方法的签名也要彻底一致:测试

  • readResolve (经常使用于单例模式)方法在对象从流中读取出来的时候调用, ObjectInputStream 会检查反序列化的对象是否已经定义了这个方法,若是定义了,则读出来的对象返回一个替代对象。同 writeReplace()方法,返回的对象也必须是与它替换的对象兼容,不然抛出 ClassCastException
    serialVersionUID 相关见下面的(兼容性)

2.3 ObjectOutputStream

/** * Write the specified object to the ObjectOutputStream. The class of the * object, the signature of the class, and the values of the non-transient * and non-static fields of the class and all of its supertypes are * written. Default serialization for a class can be overridden using the * writeObject and the readObject methods. Objects referenced by this * object are written transitively so that a complete equivalent graph of * objects can be reconstructed by an ObjectInputStream. * * <p>Exceptions are thrown for problems with the OutputStream and for * classes that should not be serialized. All exceptions are fatal to the * OutputStream, which is left in an indeterminate state, and it is up to * the caller to ignore or recover the stream state. * * @throws InvalidClassException Something is wrong with a class used by * serialization. * @throws NotSerializableException Some object to be serialized does not * implement the java.io.Serializable interface. * @throws IOException Any exception thrown by the underlying * OutputStream. */
public final void writeObject(Object obj) throws IOException {
    //是否重写了Object方法
    if (enableOverride) {
        writeObjectOverride(obj);
        return;
    }
    try {
        // 写入操做的具体实现
        writeObject0(obj, false);
    } catch (IOException ex) {
        if (depth == 0) {
            writeFatalException(ex);
        }
        throw ex;
    }
}复制代码

先来对上面的注释翻译一下:将一个具体的object写入ObjectOutputStream.类名、类的签名(能够理解为类名和UID,虽然不止这些),除non-transient和静态属性外属于和值以及其超类。能够在子类中重写writeObject 和 readObject 方法,一个实例的多个引用,采用瞬态的写入方式(坑1参考下面的介绍),所以能够构造出一个完整的类的结构图。
writeObject0具体实现一个类的写入,源码以下(只保留了关键部分): ui

折叠原码
/** * Underlying writeObject/writeUnshared implementation. */
private void writeObject0(Object obj, boolean unshared)
    throws IOException
{

       ....
        // 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());
            }
        }
  .....
}复制代码

能够看出,支持写入的有几种类型,包括String,Array,Enum,和Serializable(这就是实现Serializable的目的),固然原生类型也会以数据块的形式写入(其实最终写入的确定是原生类型)。
对于Enum类型有必要单独说一下(见坑四)。
此时咱们可能会想知道,到底写了哪些值(writeOrdinaryObject) this

/** * Writes representation of a "ordinary" (i.e., not a String, Class, * ObjectStreamClass, array, or enum constant) serializable object to the * stream. */
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);
        }
    }
}复制代码

到此为止,咱们知道写入了一个二进制数据块,其中包含类名、签名、属性名、属性类型、及属性值,固然还有开头结尾等数据。咱们将二进制转换为UTF-8后以下

¬í^@^Esr^@)com.sankuai.meituan.meishi.poi.tag.PersonÝ<9f>;<9d><8e>^B°³^B^@^BI^@^CageL^@^Dnamet^@^RLjava/lang/String;xp^@^@^@t^@^Hxiaoming复制代码

2.4 ObjectInputStream

理解ObjectOutputStream再来理解ObjectInputStream就简单不少了,大概过一下

/** * Read an object from the ObjectInputStream. The class of the object, the * signature of the class, and the values of the non-transient and * non-static fields of the class and all of its supertypes are read. * Default deserializing for a class can be overriden using the writeObject * and readObject methods. Objects referenced by this object are read * transitively so that a complete equivalent graph of objects is * reconstructed by readObject. * * <p>The root object is completely restored when all of its fields and the * objects it references are completely restored. At this point the object * validation callbacks are executed in order based on their registered * priorities. The callbacks are registered by objects (in the readObject * special methods) as they are individually restored. * * <p>Exceptions are thrown for problems with the InputStream and for * classes that should not be deserialized. All exceptions are fatal to * the InputStream and leave it in an indeterminate state; it is up to the * caller to ignore or recover the stream state. * * @throws ClassNotFoundException Class of a serialized object cannot be * found. * @throws InvalidClassException Something is wrong with a class used by * serialization. * @throws StreamCorruptedException Control information in the * stream is inconsistent. * @throws OptionalDataException Primitive data was found in the * stream instead of objects. * @throws IOException Any of the usual Input/Output related exceptions. */
public final Object readObject()
    throws IOException, ClassNotFoundException
{
    if (enableOverride) {
        return readObjectOverride();
    }

    // if nested read, passHandle contains handle of enclosing object
    int outerHandle = passHandle;
    try {
        Object obj = readObject0(false);
        handles.markDependency(outerHandle, passHandle);
        ClassNotFoundException ex = handles.lookupException(passHandle);
        if (ex != null) {
            throw ex;
        }
        if (depth == 0) {
            vlist.doCallbacks();
        }
        return obj;
    } finally {
        passHandle = outerHandle;
        if (closed && depth == 0) {
            clear();
        }
    }
}复制代码

仍是看一下注释:读关类(类名), 签名、非瞬态非静态属性值和属性名。
剩下的注解也和ObjectOutputStream基本一致
实际解析的数据是readObject0
readObject0就是按照协议进行解析数据了

private Object readObject0(boolean unshared) throws IOException {
    boolean oldMode = bin.getBlockDataMode();
    if (oldMode) {
        int remain = bin.currentBlockRemaining();
        if (remain > 0) {
            throw new OptionalDataException(remain);
        } else if (defaultDataEnd) {
            /* * Fix for 4360508: stream is currently at the end of a field * value block written via default serialization; since there * is no terminating TC_ENDBLOCKDATA tag, simulate * end-of-custom-data behavior explicitly. */
            throw new OptionalDataException(true);
        }
        bin.setBlockDataMode(false);
    }

    byte tc;
    while ((tc = bin.peekByte()) == TC_RESET) {
        bin.readByte();
        handleReset();
    }

    depth++;
    try {
        switch (tc) {
            case TC_NULL:
                return readNull();

            case TC_REFERENCE:
                return readHandle(unshared);

            case TC_CLASS:
                return readClass(unshared);

            case TC_CLASSDESC:
            case TC_PROXYCLASSDESC:
                return readClassDesc(unshared);

            case TC_STRING:
            case TC_LONGSTRING:
                return checkResolve(readString(unshared));

            case TC_ARRAY:
                return checkResolve(readArray(unshared));

            case TC_ENUM:
                return checkResolve(readEnum(unshared));

            case TC_OBJECT:
                return checkResolve(readOrdinaryObject(unshared));

            case TC_EXCEPTION:
                IOException ex = readFatalException();
                throw new WriteAbortedException("writing aborted", ex);

            case TC_BLOCKDATA:
            case TC_BLOCKDATALONG:
                if (oldMode) {
                    bin.setBlockDataMode(true);
                    bin.peek();             // force header read
                    throw new OptionalDataException(
                        bin.currentBlockRemaining());
                } else {
                    throw new StreamCorruptedException(
                        "unexpected block data");
                }

            case TC_ENDBLOCKDATA:
                if (oldMode) {
                    throw new OptionalDataException(true);
                } else {
                    throw new StreamCorruptedException(
                        "unexpected end of block data");
                }

            default:
                throw new StreamCorruptedException(
                    String.format("invalid type code: %02X", tc));
        }
    } finally {
        depth--;
        bin.setBlockDataMode(oldMode);
    }
}复制代码

3、兼容性

java序列化是经过在运行时判断serialVersionUID来验证版本的一致性。在进行反序列化时,JVM会把传过来的字节流中serialVersionUID与本地相应的实体(类)的serialVersionUID进行对比, 若是相同则是认为一致的,不然就会抛出异常InvalidClassException。
serialVersionUID有两种生成方式:默认生成和显示指定。具体实现方式以下:

/** * Adds serialVersionUID if one does not already exist. Call this before * modifying a class to maintain serialization compatability. */
public static void setSerialVersionUID(CtClass clazz)
    throws CannotCompileException, NotFoundException
{
    // check for pre-existing field.
    try {
        clazz.getDeclaredField("serialVersionUID");
        return;
    }
    catch (NotFoundException e) {}

    // check if the class is serializable.
    if (!isSerializable(clazz))
        return;

    // add field with default value.
    CtField field = new CtField(CtClass.longType, "serialVersionUID",
                                clazz);
    field.setModifiers(Modifier.PRIVATE | Modifier.STATIC |
                       Modifier.FINAL);
    clazz.addField(field, calculateDefault(clazz) + "L");
}复制代码

默认生成的UID的值计算方式参考以下源码:
能够看出UID的值来源于类的几个方面:类名(class name)、类及其属性的修饰符(class modifiers)、 接口及接口顺序(interfaces)、属性(fields)、静态初始化(static initializer), 构造器(constructors)。也就是说这其中任何一个的改变都会影响UID的值,致使不兼容性。

/** * Calculate default value. See Java Serialization Specification, Stream * Unique Identifiers. */
static long calculateDefault(CtClass clazz)
    throws CannotCompileException
{
    try {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        DataOutputStream out = new DataOutputStream(bout);
        ClassFile classFile = clazz.getClassFile();

        // class name.
        String javaName = javaName(clazz);
        out.writeUTF(javaName);

        CtMethod[] methods = clazz.getDeclaredMethods();

        // class modifiers.
        int classMods = clazz.getModifiers();
        if ((classMods & Modifier.INTERFACE) != 0)
            if (methods.length > 0)
                classMods = classMods | Modifier.ABSTRACT;
            else
                classMods = classMods & ~Modifier.ABSTRACT;

        out.writeInt(classMods);

        // interfaces.
        String[] interfaces = classFile.getInterfaces();
        for (int i = 0; i < interfaces.length; i++)
            interfaces[i] = javaName(interfaces[i]);

        Arrays.sort(interfaces);
        for (int i = 0; i < interfaces.length; i++)
            out.writeUTF(interfaces[i]);

        // fields.
        CtField[] fields = clazz.getDeclaredFields();
        Arrays.sort(fields, new Comparator() {
            public int compare(Object o1, Object o2) {
                CtField field1 = (CtField)o1;
                CtField field2 = (CtField)o2;
                return field1.getName().compareTo(field2.getName());
            }
        });

        for (int i = 0; i < fields.length; i++) {
            CtField field = (CtField) fields[i];
            int mods = field.getModifiers();
            if (((mods & Modifier.PRIVATE) == 0) ||
                ((mods & (Modifier.STATIC | Modifier.TRANSIENT)) == 0)) {
                out.writeUTF(field.getName());
                out.writeInt(mods);
                out.writeUTF(field.getFieldInfo2().getDescriptor());
            }
        }

        // static initializer.
        if (classFile.getStaticInitializer() != null) {
            out.writeUTF("<clinit>");
            out.writeInt(Modifier.STATIC);
            out.writeUTF("()V");
        }

        // constructors.
        CtConstructor[] constructors = clazz.getDeclaredConstructors();
        Arrays.sort(constructors, new Comparator() {
            public int compare(Object o1, Object o2) {
                CtConstructor c1 = (CtConstructor)o1;
                CtConstructor c2 = (CtConstructor)o2;
                return c1.getMethodInfo2().getDescriptor().compareTo(
                                    c2.getMethodInfo2().getDescriptor());
            }
        });

        for (int i = 0; i < constructors.length; i++) {
            CtConstructor constructor = constructors[i];
            int mods = constructor.getModifiers();
            if ((mods & Modifier.PRIVATE) == 0) {
                out.writeUTF("<init>");
                out.writeInt(mods);
                out.writeUTF(constructor.getMethodInfo2()
                             .getDescriptor().replace('/', '.'));
            }
        }

        // methods.
        Arrays.sort(methods, new Comparator() {
            public int compare(Object o1, Object o2) {
                CtMethod m1 = (CtMethod)o1;
                CtMethod m2 = (CtMethod)o2;
                int value = m1.getName().compareTo(m2.getName());
                if (value == 0)
                    value = m1.getMethodInfo2().getDescriptor()
                        .compareTo(m2.getMethodInfo2().getDescriptor());

                return value;
            }
        });

        for (int i = 0; i < methods.length; i++) {
            CtMethod method = methods[i];
            int mods = method.getModifiers()
                       & (Modifier.PUBLIC | Modifier.PRIVATE
                          | Modifier.PROTECTED | Modifier.STATIC
                          | Modifier.FINAL | Modifier.SYNCHRONIZED
                          | Modifier.NATIVE | Modifier.ABSTRACT | Modifier.STRICT);
            if ((mods & Modifier.PRIVATE) == 0) {
                out.writeUTF(method.getName());
                out.writeInt(mods);
                out.writeUTF(method.getMethodInfo2()
                             .getDescriptor().replace('/', '.'));
            }
        }

        // calculate hash.
        out.flush();
        MessageDigest digest = MessageDigest.getInstance("SHA");
        byte[] digested = digest.digest(bout.toByteArray());
        long hash = 0;
        for (int i = Math.min(digested.length, 8) - 1; i >= 0; i--)
            hash = (hash << 8) | (digested[i] & 0xFF);

        return hash;
    }
    catch (IOException e) {
        throw new CannotCompileException(e);
    }
    catch (NoSuchAlgorithmException e) {
        throw new CannotCompileException(e);
    }
}复制代码

显示指定:

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

那两种方式使用的情景是什么呢?我认为应该把握一个判断原则:是否容许向下兼容。
默认方式使用情景:一旦建立则不容许改变
显示方式使用情景:对类有必定的向下兼容性(稍后将具体分析哪些状况兼容),当不容许兼容时,能够经过改变UID的值在实现。
强烈建议使用显示指定的方式,以防范潜在的不兼容根源,且能够带来小小的性能提高。

4、坑

(下面的坑都是在指定显示指定UID而且一致的状况下产生的,非显示指定UID的坑更多,再也不介绍了)

4.1 坑1(多引用写入)

@Test
public void testSerializable() throws Exception {
    File file = new File("p.dat");
    Person p = new Person("xiaoming", 10);
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
    oos.writeObject(p);
    p.setAge(20);
    oos.writeObject(p);
    oos.close();


    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
    Person p1  = (Person) ois.readObject();
    Person p2  = (Person) ois.readObject();
    ois.close();
    System.out.println(p1.toString() + "name:"+p1.getName() + "age:"+p1.getAge());
    System.out.println(p2.toString() + "name:"+p2.getName() + "age:"+p2.getAge());

}复制代码

读出来的结果

com.sankuai.meituan.meishi.poi.tag.Person@b7f23d9name:xiaomingage:10
com.sankuai.meituan.meishi.poi.tag.Person@b7f23d9name:xiaomingage:10复制代码

是否是和但愿的不同?其实在默认状况下,对于一个实例的多个引用,为了节省空间,只会写入一次,后面会追加几个字节表明某个实例的引用。
咱们可能经过rest或writeUnshared方法对一个实例屡次写入,以下:

@Test
public void testSerializable() throws Exception {
    File file = new File("p.dat");
    Person p = new Person("xiaoming", 10);
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
    oos.writeObject(p);
    p.setAge(20);
    oos.reset();
    //oos.writeUnshared(p);
    oos.writeObject(p);
    oos.close();


    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
    Person p1  = (Person) ois.readObject();
    Person p2  = (Person) ois.readObject();
    ois.close();
    System.out.println(p1.toString() + "name:"+p1.getName() + "age:"+p1.getAge());
    System.out.println(p2.toString() + "name:"+p2.getName() + "age:"+p2.getAge());
    System.out.println(p2.toString() + "name:"+p2.getName() + "age:"+p2.getAge());
}复制代码

结果以下:

com.sankuai.meituan.meishi.poi.tag.Person@b7f23d9name:xiaomingage:10
com.sankuai.meituan.meishi.poi.tag.Person@61d47554name:xiaomingage:20复制代码

4.2 坑2(子父引用序列化)

子类实现序列化,父类不实现序列化

父类是Person,定义一个字类Student

public class Student extends Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private int studentId;

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

    public int getStudentId() {
        return studentId;
    }

    public void setStudentId(int studentId) {
        this.studentId = studentId;
    }
}复制代码

测试代码以下:

@Test
public void testSerializable() throws Exception {
    File file = new File("p.dat");
    Student s = new Student( "xiaoming", 10, 1 );
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
    oos.writeObject(s);

    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
    Student s1  = (Student) ois.readObject();
    ois.close();
    System.out.println(s1.toString() + "name:"+s1.getName() + "age:"+s1.getAge() + "height:"+s1.getStudentId());
    System.out.println(s1.toString() + "name:"+s1.getName() + "age:"+s1.getAge() + "height:"+s1.getStudentId());
}复制代码

在readObject时抛出java.io.NotSerializableException异常。
咱们更改一下Person,添加一个无参数构造器

public class Student extends Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private int studentId;

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

    public int getStudentId() {
        return studentId;
    }

    public void setStudentId(int studentId) {
        this.studentId = studentId;
    }
}复制代码

结果以下

com.sankuai.meituan.meishi.poi.tag.Student@12405818name:nullage:0height:1复制代码

这是由于当父类不可序列化时,须要调用默认无参构造器初始化属性的值。

对象引用

public class Student implements Serializable {
    private static final long serialVersionUID = 1L;
    private int studentId;
    private Person person;

    public Student(int studentId, Person person) {
        this.studentId = studentId;
        this.person = person;
    }

    public int getStudentId() {
        return studentId;
    }

    public void setStudentId(int studentId) {
        this.studentId = studentId;
    }

    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }
}复制代码
@Test
public void testSerializable() throws Exception {
    File file = new File("p.dat");
    Student s = new Student( 1 , new Person("xiaoming", 10));
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
    oos.writeObject(s);

    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
    Student s1  = (Student) ois.readObject();
    ois.close();
    System.out.println(s1.toString() + "name:"+s1.getPerson().getName() + "age:"+s1.getPerson().getAge() + "height:"+s1.getStudentId());

}复制代码

仍然模拟两种状况(实现无参构造器和不实现无参数构造器),
发现两种状况都会抛出java.io.NotSerializableException异常,这就须要可序列化类的每一个属性都要可序列化(固然去瞬态属性和静态属性).

4.3 坑三(类的演化)

演化类以下:

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    private int height;

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

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

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

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

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }
}复制代码

反序列化目标类多一个字段(height),序列化写入的Person 包含两个属性:name,age

@Test
public void testSerializable() throws Exception {
    File file = new File("p.dat");
    /*Person p = new Person("xiaoming", 10); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file)); oos.writeObject(p);*/

    ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
    Person p1  = (Person) ois.readObject();
    ois.close();
    System.out.println(p1.toString() + "name:"+p1.getName() + "age:"+p1.getAge() + "height:"+p1.getHeight());
}复制代码

结果以下

com.sankuai.meituan.meishi.poi.tag.Person@37574691name:xiaomingage:10height:0复制代码

能够看出反序列化以后,并无报错,只是height实赋成了默认值。相似的其它对象也会赋值为默认值。

相反,若是写入的多一个字段,读出的少一个字段

com.sankuai.meituan.meishi.poi.tag.Person@37574691name:xiaomingage:10复制代码

其它演化,好比更改类型等,这种演化自己就有问题,不必再探讨。

4.4 坑四(枚举类型)

对于枚举类型,咱们常常会调整对象的值,咱们这里使用默认值(0,1,2)进行序列化,而后调整元素顺序进行反序列化,看看会发生什么现象(是0,1,2仍是2,1,0);
枚举类

public enum Num {
    ONE,TWO,THREE;

    public void printValues() {
        System.out.println(ONE + "ONE.ordinal" + ONE.ordinal());
        System.out.println(TWO + "TWO.ordinal" + TWO.ordinal());
        System.out.println(THREE + "THREE.ordinal" + THREE.ordinal());
    }
}复制代码

序列化

@Test
public void testSerializable() throws Exception {
    File file = new File("p.dat");
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
    oos.writeObject(Num.ONE);
    oos.close();

 /* ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); Num s1 = (Num) ois.readObject(); s1.printValues(); ois.close(); */

}复制代码

咱们只写入一个ONE值,

¬í^@^E~r^@&com.sankuai.meituan.meishi.poi.tag.Num^@^@^@^@^@^@^@^@^R^@^@xr^@^Njava.lang.Enum^@^@^@^@^@^@^@^@^R^@^@xpt^@^CONE复制代码

对其调整顺序(THREE,TWO,ONE;)再读出文件中读出结果,看看会是什么现象

NEONE.ordinal2
TWOTWO.ordinal1
THREETHREE.ordinal0复制代码

能够看到ONE的值变成了2.
事实上序列化Enum对象时,并不会保存元素的值,只会保存元素的name。这样,在不依赖元素值的前提下,ENUM对象如何更改都会保持兼容性。

5、重写readObject,writeObject

怎么样重写这里就不说了,在这里引用effective java的一句话告诉你何时重写:“只有当你自行设计的自定义序列化形式与默认的序列化形式基本相同时,才能接受默认的序列化形式”.“当一个对象的物理表示方法与它的逻辑数据内容有实质性差异时,使用默认序列化形式有N种缺陷”.其实从effective java的角度来说,是强烈建议咱们重写的,这样有助于咱们更好地把控序列化过程,防范未知风险

相关文章
相关标签/搜索