本篇博客主要解决以下问题:
1、Serializable是怎么实现的?
2、Serializable中serialVersionUID的作用是什么?为什么会有这个字段?
3、transient字段有何作用?如何自己定制序列化?
一、Serializable是怎么实现的
一般我们使用序列化时,只需要将类实现Serializable接口就可以了,但内部是如何实现的呢,我从源码角度谈一下我个人人的理解。
首先,查看Serializable这个类里都有啥:
/*
* Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.io;
// Android-added: Notes about serialVersionUID, using serialization judiciously, JSON.
/**
* 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.
*
* Android implementation of serialVersionUID computation will change slightly
* for some classes if you're targeting android N. In order to preserve compatibility,
* this change is only enabled is the application target SDK version is set to
* 24 or higher. It is highly recommended to use an explicit serialVersionUID
* field to avoid compatibility issues.
*
* <h3>Implement Serializable Judiciously</h3>
* Refer to <i>Effective Java</i>'s chapter on serialization for thorough
* coverage of the serialization API. The book explains how to use this
* interface without harming your application's maintainability.
*
* <h3>Recommended Alternatives</h3>
* <strong>JSON</strong> is concise, human-readable and efficient. Android
* includes both a {@link android.util.JsonReader streaming API} and a {@link
* org.json.JSONObject tree API} to read and write JSON. Use a binding library
* like <a href="http://code.google.com/p/google-gson/">GSON</a> to read and
* write Java objects directly.
*
* @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 {
}
可以看到,这里面并没有定义任何的方法,却写了一堆注释,我挑一些重要的翻译下:
1、实现java.io.Serializable接口的类启用了类的可序列化。 未实现此接口的类将不会将其任何状态序列化或反序列化。 可序列化类的所有子类型本身都是可序列化的。 序列化接口没有方法或字段,仅用于标识可序列化的语义。
2、子类实现序列化,父类不实现序列化,此时父类要实现一个无参数构造器,否则会报错,遇到不支持序列化的类会抛出NotSerializableException,在序列化的过程中需要特殊处理时,可以通过实现writeObject,readObject,readObjectNoData方法来实现。
3、writeObject方法负责为其特定类编写对象的状态,以便相应的readObject方法可以恢复它。 可以通过调用out.defaultWriteObject来调用保存Object字段的默认机制。 该方法不需要关注属于其超类或子类的状态。 通过使用writeObject方法或使用DataOutput支持的原始数据类型的方法将各个字段写入ObjectOutputStream来保存状态。
4、readObject方法负责从流中读取并恢复类字段。 它可以调用in.defaultReadObject来调用恢复对象的非静态和非瞬态字段的默认机制。 defaultReadObject方法使用流中的信息来指定流中保存的对象的字段以及当前对象中相应命名的字段。 这处理了类在演变为添加新字段时的情况。 该方法不需要关注属于其超类或子类的状态。 通过使用writeObject方法将单个字段写入ObjectOutputStream或使用DataOutput支持的原始数据类型的方法来保存状态
5、writeObject实现序列化将属性和值写入,默认的写入机制由defaultWriteObject来实现readObject实现从数据流中重建对像,默认的读出机制由defaultReadObject来实现,而且可以处理类演化(添加字段)的情况。
6、如果某个超类不支持序列化,但又不希望使用默认值怎么办?实现readObjectNoData writeReplace() 方法可以使对象被写入流以前,用一个对象来替换自己。当序列化时,可序列化的类要将对象写入流,如果我们想要另一个对象来替换当前对象来写入流,则可以要实现下面这个方法,方法的签名也要完全一致。
可以看出,Serializable是通过ObjectOutputStream的writeObject将对象转化为某种状态,然后通过ObjectInputStream的readObject从流中读取数据并转化为原始对象。那么ObjectOutputStream是如何进行转化的呢,分析ObjectOutputStream中writeObject()方法的源码:
public final void writeObject(Object obj) throws IOException {
//是否重写了Object方法
if (enableOverride) {
writeObjectOverride(obj);
return;
}
try {
// 写入操作的具体实现
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0) {
// BEGIN Android-changed: Ignore secondary exceptions during writeObject().
// writeFatalException(ex);
try {
writeFatalException(ex);
} catch (IOException ex2) {
// If writing the exception to the output stream causes another exception there
// is no need to propagate the second exception or generate a third exception,
// both of which might obscure details of the root cause.
}
// END Android-changed: Ignore secondary exceptions during writeObject().
}
throw ex;
}
}
writeObject0具体实现一个类的写入,源码如下(只保留了关键部分):
折叠原码
/**
* Underlying writeObject/writeUnshared implementation.
*/
private void writeObject0(Object obj, boolean unshared)
throws IOException
{
....
// remaining cases
// BEGIN Android-changed: Make Class and ObjectStreamClass replaceable.
if (obj instanceof Class) {
writeClass((Class) obj, unshared);
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
// END Android-changed: Make Class and ObjectStreamClass replaceable.
} else 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的目的),当然原生类型也会以数据块的形式写入(其实最终写入的肯定是原生类型)。进一步分析各处write方法,可以发现,其实序列化的过程也是将类名、变量名、描述信息等等都与特定的标志位相对应,比如writeString()方法:
/**
* Writes given string to stream, using standard or long UTF format
* depending on string length.
*/
private void writeString(String str, boolean unshared) throws IOException {
handles.assign(unshared ? null : str);
long utflen = bout.getUTFLength(str);
if (utflen <= 0xFFFF) {
bout.writeByte(TC_STRING);
bout.writeUTF(str, utflen);
} else {
bout.writeByte(TC_LONGSTRING);
bout.writeLongUTF(str, utflen);
}
}
这里的 TC_STRING 就是 ObjectStreamConstants类中的一个固定值: 0x74。
package java.io;
public interface ObjectStreamConstants {
/**
* Magic number that is written to the stream header.
*/
final static short STREAM_MAGIC = (short)0xaced;
/**
* Version number that is written to the stream header.
*/
final static short STREAM_VERSION = 5;
/* Each item in the stream is preceded by a tag
*/
/**
* First tag value.
*/
final static byte TC_BASE = 0x70;
/**
* Null object reference.
*/
final static byte TC_NULL = (byte)0x70;
/**
* Reference to an object already written into the stream.
*/
final static byte TC_REFERENCE = (byte)0x71;
/**
* new Class Descriptor.
*/
final static byte TC_CLASSDESC = (byte)0x72;
/**
* new Object.
*/
final static byte TC_OBJECT = (byte)0x73;
/**
* new String.
*/
final static byte TC_STRING = (byte)0x74;
/**
* new Array.
*/
final static byte TC_ARRAY = (byte)0x75;
/**
* Reference to Class.
*/
final static byte TC_CLASS = (byte)0x76;
/**
* Block of optional data. Byte following tag indicates number
* of bytes in this block data.
*/
final static byte TC_BLOCKDATA = (byte)0x77;
/**
* End of optional block data blocks for an object.
*/
final static byte TC_ENDBLOCKDATA = (byte)0x78;
/**
* Reset stream context. All handles written into stream are reset.
*/
final static byte TC_RESET = (byte)0x79;
/**
* long Block data. The long following the tag indicates the
* number of bytes in this block data.
*/
final static byte TC_BLOCKDATALONG= (byte)0x7A;
/**
* Exception during write.
*/
final static byte TC_EXCEPTION = (byte)0x7B;
/**
* Long string.
*/
final static byte TC_LONGSTRING = (byte)0x7C;
/**
* new Proxy Class Descriptor.
*/
final static byte TC_PROXYCLASSDESC = (byte)0x7D;
/**
* new Enum constant.
* @since 1.5
*/
final static byte TC_ENUM = (byte)0x7E;
/**
* Last tag value.
*/
final static byte TC_MAX = (byte)0x7E;
/**
* First wire handle to be assigned.
*/
final static int baseWireHandle = 0x7e0000;
/******************************************************/
/* Bit masks for ObjectStreamClass flag.*/
/**
* Bit mask for ObjectStreamClass flag. Indicates a Serializable class
* defines its own writeObject method.
*/
final static byte SC_WRITE_METHOD = 0x01;
/**
* Bit mask for ObjectStreamClass flag. Indicates Externalizable data
* written in Block Data mode.
* Added for PROTOCOL_VERSION_2.
*
* @see #PROTOCOL_VERSION_2
* @since 1.2
*/
final static byte SC_BLOCK_DATA = 0x08;
/**
* Bit mask for ObjectStreamClass flag. Indicates class is Serializable.
*/
final static byte SC_SERIALIZABLE = 0x02;
/**
* Bit mask for ObjectStreamClass flag. Indicates class is Externalizable.
*/
final static byte SC_EXTERNALIZABLE = 0x04;
/**
* Bit mask for ObjectStreamClass flag. Indicates class is an enum type.
* @since 1.5
*/
final static byte SC_ENUM = 0x10;
/* *******************************************************************/
/* Security permissions */
/**
* Enable substitution of one object for another during
* serialization/deserialization.
*
* @see java.io.ObjectOutputStream#enableReplaceObject(boolean)
* @see java.io.ObjectInputStream#enableResolveObject(boolean)
* @since 1.2
*/
final static SerializablePermission SUBSTITUTION_PERMISSION =
new SerializablePermission("enableSubstitution");
/**
* Enable overriding of readObject and writeObject.
*
* @see java.io.ObjectOutputStream#writeObjectOverride(Object)
* @see java.io.ObjectInputStream#readObjectOverride()
* @since 1.2
*/
final static SerializablePermission SUBCLASS_IMPLEMENTATION_PERMISSION =
new SerializablePermission("enableSubclassImplementation");
/**
* A Stream Protocol Version. <p>
*
* All externalizable data is written in JDK 1.1 external data
* format after calling this method. This version is needed to write
* streams containing Externalizable data that can be read by
* pre-JDK 1.1.6 JVMs.
*
* @see java.io.ObjectOutputStream#useProtocolVersion(int)
* @since 1.2
*/
public final static int PROTOCOL_VERSION_1 = 1;
/**
* A Stream Protocol Version. <p>
*
* This protocol is written by JVM 1.2.
*
* Externalizable data is written in block data mode and is
* terminated with TC_ENDBLOCKDATA. Externalizable class descriptor
* flags has SC_BLOCK_DATA enabled. JVM 1.1.6 and greater can
* read this format change.
*
* Enables writing a nonSerializable class descriptor into the
* stream. The serialVersionUID of a nonSerializable class is
* set to 0L.
*
* @see java.io.ObjectOutputStream#useProtocolVersion(int)
* @see #SC_BLOCK_DATA
* @since 1.2
*/
public final static int PROTOCOL_VERSION_2 = 2;
}
此时我们可能会想知道,到底写了哪些值(writeOrdinaryObject)。
/**
* 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()) {
//如果类实现的是Externalizable接口,写入ExternalData
writeExternalData((Externalizable) obj);
} else {
//写入要序列化的数据
writeSerialData(obj, desc);
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
写入类的描述信息,调用writeSerialData()方法:
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;
//如果用户重写了writeObject()方法,刚写入用户的writeObject方法内数据
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 {
//用户没有重写writeObject方法时,写入默认的数据
defaultWriteFields(obj, slotDesc);
}
}
}
如果用户重写了writeObject()方法,按writeObject()方法写入数据。否则,调用defaultWriteFields()方法写入数据:
private void defaultWriteFields(Object obj, ObjectStreamClass desc)
throws IOException
{
Class<?> cl = desc.forClass();
if (cl != null && obj != null && !cl.isInstance(obj)) {
throw new ClassCastException();
}
desc.checkDefaultSerialize();
int primDataSize = desc.getPrimDataSize();
if (primVals == null || primVals.length < primDataSize) {
primVals = new byte[primDataSize];
}
desc.getPrimFieldValues(obj, primVals);
bout.write(primVals, 0, primDataSize, false);
//获取此可序列化类的字段数组
ObjectStreamField[] fields = desc.getFields(false);
//获取此可序列化类的非原始可序列化字段的数量
Object[] objVals = new Object[desc.getNumObjFields()];
int numPrimFields = fields.length - objVals.length;
desc.getObjFieldValues(obj, objVals);
for (int i = 0; i < objVals.length; i++) {
if (extendedDebugInfo) {
debugInfoStack.push(
"field (class \"" + desc.getName() + "\", name: \"" +
fields[numPrimFields + i].getName() + "\", type: \"" +
fields[numPrimFields + i].getType() + "\")");
}
try {
//递归方式将每个字段写入
writeObject0(objVals[i],
fields[numPrimFields + i].isUnshared());
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
}
可以看到,ObjectOutputStream最终采用递归方式将可序列化类中的字段都写入了一个OutputStream流中。
理解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,按照协议进行解析数据了
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);
}
}
源码看完了,我们来看一下序列化之后,最终写入的都有哪些东西:
这里我创建了一个User类,包含了name、age、password、让User序列化:
public class User implements Serializable {
private static final long serialVersionUID = 5278951443481930489L;
private String name;
private transient String password;
private int age;
public User(String name, String password, int age) {
this.name = name;
this.password = password;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "name:" + name + ",password:" + password + ",age:" + age;
}
}
同时在Main方法里,调用ObjectOutputStream把序列化的结果写到文件里,然后查看文件内容:
public class Main {
public static void main(String[] params) throws IOException,
ClassNotFoundException {
User user = new User("Merea", "123da", 22);
System.out.println(user.toString());
FileOutputStream fos = new FileOutputStream("test.out");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(user);
oos.flush();
oos.close();
fos.close();
}
}
test.out里的内容如下:
从源码里我们知道,ObjectOutputStream的各种写数据的方法都会与一个叫ObjectStreamConstants类中的标志相对应,由此我们就可以分析test.out里的内容了:
1、aced 0005:声明使用了序列化协议,版本号0005;
2、73:声明这是一个新的对象;
3、72:声明这里开始一个新Class;
4、00 22:Class名字的长度;
5、63 6f 6d 2e 65 78 61 6d 70 6c 65 2e 67 72 6f 6f 70 6c 65 2e 6a 61 76 61 64 65 6d 6f 73 2e 55 73 65 72: 代表类的名字,将这些16进制的数字转化为字符后就是:com.example.groople.javademos.User;
6、49 42 9a 5e 67 46 aa f9:SerialVersionUID, 序列化ID,如果没有指定,则会由算法随机生成一个8byte的ID;
7、03:标记号. 该值声明该对象支持序列化;
8、00 03:该类所包含的域个数;
9、49:域类型, 代表"I", 也就是Int;
10、00 03:域的长度
11、 61 67 65:域的名字,转化为字符为: age;
后面的是另外两个域的描述,省略(4c是"L",代表的是Object,也说明String其实就是Object类型)。
12、78:对象块结束的标志;
13、70:没有超类了;
14、00 00 00 16:containVersion标志,
15、70 74 00 05 4d 65 72 65 61 78:containVersion的值:ptMereax
二、关于serialVersionUID
事实上,如果一个类实现了Serializable进行序列化,我们通常会在类中声明这样一句话:
private static final long serialVersionUID = 5278951443481930489L;
从上面的源码也可以发现,serialVersionUID是用来验证序列化数据版本的一致性,在进行反序列化时,JVM会把传过来的字节流中serialVersionUID与本地相应的实体(类)的serialVersionUID进行对比, 如果相同则是认为一致的,否则就会抛出异常InvalidClassException。serialVersionUID有两种生成方式:默认生成和显示指定。当然如果我们不指定serialVersionUID的值,系统也会为我们创建一个默认值:1L,serialVersionUID的生成源码如下:
/**
* Return the serialVersionUID for this class. The serialVersionUID
* defines a set of classes all with the same name that have evolved from a
* common root class and agree to be serialized and deserialized using a
* common format. NonSerializable classes have a serialVersionUID of 0L.
*
* @return the SUID of the class described by this descriptor
*/
public long getSerialVersionUID() {
// REMIND: synchronize instead of relying on volatile?
if (suid == null) {
suid = AccessController.doPrivileged(
new PrivilegedAction<Long>() {
public Long run() {
return computeDefaultSUID(cl);
}
}
);
}
return suid.longValue();
}
查看 computeDefaultSUID()方法:
/**
* Computes the default serial version UID value for the given class.
*/
private static long computeDefaultSUID(Class<?> cl) {
if (!Serializable.class.isAssignableFrom(cl) || Proxy.isProxyClass(cl))
{
return 0L;
}
try {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
dout.writeUTF(cl.getName());
int classMods = cl.getModifiers() &
(Modifier.PUBLIC | Modifier.FINAL |
Modifier.INTERFACE | Modifier.ABSTRACT);
/*
* compensate for javac bug in which ABSTRACT bit was set for an
* interface only if the interface declared methods
*/
Method[] methods = cl.getDeclaredMethods();
if ((classMods & Modifier.INTERFACE) != 0) {
classMods = (methods.length > 0) ?
(classMods | Modifier.ABSTRACT) :
(classMods & ~Modifier.ABSTRACT);
}
dout.writeInt(classMods);
if (!cl.isArray()) {
/*
* compensate for change in 1.2FCS in which
* Class.getInterfaces() was modified to return Cloneable and
* Serializable for array classes.
*/
Class<?>[] interfaces = cl.getInterfaces();
String[] ifaceNames = new String[interfaces.length];
for (int i = 0; i < interfaces.length; i++) {
ifaceNames[i] = interfaces[i].getName();
}
Arrays.sort(ifaceNames);
for (int i = 0; i < ifaceNames.length; i++) {
dout.writeUTF(ifaceNames[i]);
}
}
Field[] fields = cl.getDeclaredFields();
MemberSignature[] fieldSigs = new MemberSignature[fields.length];
for (int i = 0; i < fields.length; i++) {
fieldSigs[i] = new MemberSignature(fields[i]);
}
Arrays.sort(fieldSigs, new Comparator<MemberSignature>() {
public int compare(MemberSignature ms1, MemberSignature ms2) {
return ms1.name.compareTo(ms2.name);
}
});
for (int i = 0; i < fieldSigs.length; i++) {
MemberSignature sig = fieldSigs[i];
int mods = sig.member.getModifiers() &
(Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED |
Modifier.STATIC | Modifier.FINAL | Modifier.VOLATILE |
Modifier.TRANSIENT);
if (((mods & Modifier.PRIVATE) == 0) ||
((mods & (Modifier.STATIC | Modifier.TRANSIENT)) == 0))
{
dout.writeUTF(sig.name);
dout.writeInt(mods);
dout.writeUTF(sig.signature);
}
}
// Android-changed: Clinit serialization workaround b/29064453
boolean checkSuperclass = !(VMRuntime.getRuntime().getTargetSdkVersion()
<= MAX_SDK_TARGET_FOR_CLINIT_UIDGEN_WORKAROUND);
if (hasStaticInitializer(cl, checkSuperclass)) {
dout.writeUTF("<clinit>");
dout.writeInt(Modifier.STATIC);
dout.writeUTF("()V");
}
Constructor<?>[] cons = cl.getDeclaredConstructors();
MemberSignature[] consSigs = new MemberSignature[cons.length];
for (int i = 0; i < cons.length; i++) {
consSigs[i] = new MemberSignature(cons[i]);
}
Arrays.sort(consSigs, new Comparator<MemberSignature>() {
public int compare(MemberSignature ms1, MemberSignature ms2) {
return ms1.signature.compareTo(ms2.signature);
}
});
for (int i = 0; i < consSigs.length; i++) {
MemberSignature sig = consSigs[i];
int mods = sig.member.getModifiers() &
(Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED |
Modifier.STATIC | Modifier.FINAL |
Modifier.SYNCHRONIZED | Modifier.NATIVE |
Modifier.ABSTRACT | Modifier.STRICT);
if ((mods & Modifier.PRIVATE) == 0) {
dout.writeUTF("<init>");
dout.writeInt(mods);
dout.writeUTF(sig.signature.replace('/', '.'));
}
}
MemberSignature[] methSigs = new MemberSignature[methods.length];
for (int i = 0; i < methods.length; i++) {
methSigs[i] = new MemberSignature(methods[i]);
}
Arrays.sort(methSigs, new Comparator<MemberSignature>() {
public int compare(MemberSignature ms1, MemberSignature ms2) {
int comp = ms1.name.compareTo(ms2.name);
if (comp == 0) {
comp = ms1.signature.compareTo(ms2.signature);
}
return comp;
}
});
for (int i = 0; i < methSigs.length; i++) {
MemberSignature sig = methSigs[i];
int mods = sig.member.getModifiers() &
(Modifier.PUBLIC | Modifier.PRIVATE | Modifier.PROTECTED |
Modifier.STATIC | Modifier.FINAL |
Modifier.SYNCHRONIZED | Modifier.NATIVE |
Modifier.ABSTRACT | Modifier.STRICT);
if ((mods & Modifier.PRIVATE) == 0) {
dout.writeUTF(sig.name);
dout.writeInt(mods);
dout.writeUTF(sig.signature.replace('/', '.'));
}
}
dout.flush();
MessageDigest md = MessageDigest.getInstance("SHA");
byte[] hashBytes = md.digest(bout.toByteArray());
long hash = 0;
for (int i = Math.min(hashBytes.length, 8) - 1; i >= 0; i--) {
hash = (hash << 8) | (hashBytes[i] & 0xFF);
}
return hash;
} catch (IOException ex) {
throw new InternalError(ex);
} catch (NoSuchAlgorithmException ex) {
throw new SecurityException(ex.getMessage());
}
}
可以看出UID的值来源于类的几个方面:类名(class name)、类及其属性的修饰符(class modifiers)、 接口及接口顺序(interfaces)、属性(fields)、静态初始化(static initializer), 构造器(constructors)。也就是说这其中任何一个的改变都会影响UID的值。
默认方式使用情景:一旦创建则不允许改变
显示方式使用情景:对类有一定的向下兼容性(稍后将具体分析哪些情况兼容),当不允许兼容时,可以通过改变UID的值在实现。
强烈建议使用显示指定的方式,以防范潜在的不兼容根源,且可以带来小小的性能提升。
三、transient
transient的使用可参考:https://blog.youkuaiyun.com/u010838555/article/details/88320771
四、关于 writeObject 和 readObject
默认情况下,Serializable会将所有非transient及static修饰的变量都序列化,如果你想自己定义要序列化的内容,可以重写下面这两个方法:
private void writeObject(ObjectOutputStream out){
try {
//默认的写入方式,声明defaultWriteObject后,会将所有非static与transient的字段序列化
out.defaultWriteObject();
//手动写入字段
out.writeUTF(name);
out.writeInt(age);
} catch (IOException e) {
e.printStackTrace();
}
}
private void readObject(ObjectInputStream in){
try {
//默认的读取方式,声明defaultReadObject后,会读取所有的序列化内容
in.defaultReadObject();
//手动控制读取内容
name= in.readUTF();
age=in.readInt();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
请注意,如果要定制序列化,请确保上面两个方法中对变量的操作是成双成对的!!!否则编译器会报不识别字符错误~~
可以看到,虽然可以定制序列化,但Serializable仍旧有很多限制,同时定制序列化的方式也可用 Externalizable,重写writeExternal/readExternal这两个方法即可。