今天我们来一起聊一下序列化和反序列化,其实这两个“活宝”对大家来说都不陌生,而且也会觉得这有什么好聊的,其实笔者之前也是这样想的,可就是这么简单的两个东西,反而往往会忽略。
鸡汤分享:
“年轻时就要多吃苦” 这句话是让大家“趁着年轻学习效率高,集中精神努力去提升自己”,而不是把打好的时光花费在哪些简单重复、能力得不到提升,同时又很辛苦的工作上。
那些不能让你学到东西的简单工作,可以作为起点,但要尽早离开。千万别以为:工作经验的增长==长时间的重复同样的事情。在自己精力旺盛、效率极高的时候,一定要尽可能的把时间花在提升自己上,不然再怎么努力,最终也会被淘汰。
这也是对“少壮不努力老大徒伤悲” 的通俗易懂的诠释吧。
1、什么是序列化和反序列化呢?
序列化(Encode): 就是将数据结构或对象转换成二进制串的过程。
反序列化(Decode): 就是将序列化过程所生成的二进制串转化成数据结构或对象的过程。
强调:
序列化只针对非静态变量,不针对方法
2、序列化和反序列化有什么用呢?
1)永久的保存对象数据(将对象数据保存在文件或磁盘中);
2)通过序列化操作将对象数据再网络上进行传输
3)将对象数据在进程之间进行传递
小朋友是否有很多的问号?????
由于在操作系统底层,数据的传输形式是简单额字节序列形式传递的,也就是说在操作系统底层,系统是不认识我们自己定义的对象这些的,他只认识01010101这一类的字节序列,那么为了我们的应用可以和操作系统交流,我们就需要将这些数据进行序列化;当我们的应用程序运行到某个阶段,需要通过反序列化将操作系统认识的字节序列再转化成我们所熟悉的对象等,这样就可以完成交互了。
而网络传输也是以字节流的方式对数据进行传输的;
3、序列化协议的特性,我们选择序列化方案的参考点
我们先来了解一下序列化协议的特性,以便我们可以在实际工作中选择更有的方案,那有哪些参考点可供参考呢?
通用性
从技术层面上是否支持跨平台、跨语言;从流行度层面上,流行度低的这样意味着比较少的人使用,会造成昂贵的学习成本,往往也会缺乏稳定而成熟的跨平台跨语言的公共包
** 强健性、鲁棒性**
成熟度是否不够会造成语言/平台的不公平性
可调试性、可读性
如果支持不到位会造成访问限制
** 性能**
主要从时间复杂度和空间复杂度两个方面考虑。
时间开销是指在复杂的序列化协议会导致比较长的解析时间,这样会导致在使用序列化和反序列化阶段耗费较长的时间,成为系统的瓶颈。
空间开销,因为序列化过程需要在原有的数据上加上描述字段,以便用于在反序列化解析时用,如果这个过程中增加过多的描述,会给网络传输和磁盘等都造成巨大的压力。
可扩展性、兼容性
当今移动互联时代,业务需求的更新周期非常的快,新的需求也在源源不断的出现,而老的系统又需要维护,如果序列化方案不具备良好的扩展性和兼容性,那这样会大大的降低了系统的灵活度。
** 安全性、访问限制**
目前的网络安全几乎都是基于HTTP/HTTPS的80和443端口,如果使用的序列号协议没有兼容和成熟的HTTP传输层框架支持,可能会导致以下三种情况的发生:
因为访问限制而降低服务的可用性;被迫重新实现安全协议而导致实施成本的大大提高;开发更多的防火墙端口和协议访问,而牺牲安全性
4、几种常见的序列化方案协议
4.1 XML & SOAP
XML是一种常用的序列化和反序列化协议,具有跨及其,库语言等优点,SOAP(Simple Object Access Protocal)是一种被广泛使用的基于XML未序列化和反序列化的结构化消息床底协议。
4.2 JSON(JavaScript Object Notation)
JSON起源于JavaScript.它的产生来自一种称之为“Associative Array”的概念,本质就是采用“Attribute-value"的方式来描述对象。JSON最为最广泛使用的协议之一,得益于他有如下的优点:
1.这种key-value的形式非常容易让开发的工程师理解;
2.他保持了非常容易读懂的方式;
3.他相对于XML序列化之后的数据更加简洁。更加某种研究表明: 序列化之后的文件的大小约等于XML序列化后的文件大小的百分之五十(%50);
4.他有JavaScript先天性的支持,是Ajax的实际标准协议
5.解析速度更快
6.类似于散文一样的“行散而神不散”的精神,具有比较好的扩展性和兼容性
4.3 ProtoBuf
这玩意虽然笔者在曾经的工作经历中使用几乎为0 ,但是笔者也开始针对不同的项目开始使用,是因为他有不少让笔者心动的特性:
1.标准的IDL和IDL的编译器,这对我们这些码农来说是很友好的;
2.序列化后的书籍很简洁,紧凑,他序列化后的文件是XML的百分之三十以下(30%);
3.解析速度之快,快到比XML约20-100倍;
4.使用简单,提供了很友好的库;
Java 为我们提供了一种序列化方案:Seriablizable ,Android 为我们极力推荐的序列化方案为:Parcelable ,解析来我们就从源码角度来深入学习一下。
5、序列化、反序列化方案---Serializable分析
需要实现序列化的Class需要实现Serializable接口,而这个接口没有方法,他仅仅是作为一个标记,标记该类的对象是序列化或反序列化 ,而真正实现序列化/反序列化的是在ObjectOutputStream.java 中的writeObject()和readObject().
说明:
1.serialVersionUID 他是可序列化类的版本标识,如果你修改了类,这个值也要修改,否则在反序列化的时候回报错:InvalidClassException. 建议在序列化的类中,自己定义serialVersionUID的值,这样可以确保版本的兼容性,也不利于在不同的JVM之间移植
2.如果序列化类中存在List,set,Map等集合时,集合元素的类型也需要实现Serializable接口
3.用tranisient 关键字可以标记哪一个成员变量不参与序列化,在被反序列化的时候,tranisient修饰的变量会被设置为初始默认值
4.静态成员变量不参与序列化(因为他不属于对象,而序列化保存的是对象的状态)
5.反序列化过程中,如果有不可序列化的变量,那么会调用这些不可序列化变量的无参数的构造方法进行创建和初始化,所以这些不可序列化变量的无参构造方法必须可以访问,否则会报错
6.序列化会随继承关系,传承下去(即:一个序列化的类,那么他的子类也是可以序列化的)
Externalizable 是继承于Serializable的接口,他提供了两个方法供用户来实现自定义序列化和反序列化: writeExternal(ObjectOutput out) 、readExternal(ObjectInput in)。所以我们想自己实现序列化和反序列化的时候,可以直接继承Externalizable。
Externalizable中两个接口的参数ObjectOutput类型,是提供了对应的接口,所以我们需要找到ObjectOutput的实现类就可以了,下面就来看看ObjectOutputStream (因为他实现了ObjectOutput接口)的源码,其他的实现类读者可以自行查看。
我们先通过一张图来了解一下序列化的过程:
熟悉了上面的流程后,我们再来一步一步在源码中遨游。。。。。。。
ObjectOutputStream.java:
第一步:writeObject方法的源码
public final void writeObject(Object obj) throws IOException {
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()方法
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;
}
// check for replacement object
//此处如果我们的Serializable 的子类重写了write方法,就会通过反射调用子类的实现,而不调用父类的
Object orig = obj;
Class> cl = obj.getClass();
ObjectStreamClass desc;
Class repCl;
desc = ObjectStreamClass.lookup(cl, true);
if (desc.hasWriteReplaceMethod() &&
(obj = desc.invokeWriteReplace(obj)) != null &&
(repCl = obj.getClass()) != cl)
{
cl = repCl;
desc = ObjectStreamClass.lookup(cl, true);
}
// END Android-changed: Make only one call to writeReplace.
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;
}
}
// 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());
}
}
} finally {
depth--;
bout.setBlockDataMode(oldMode);
}
}
第三步,先来看看序列化数组 writeArray()
private void writeArray(Object array,
ObjectStreamClass desc,
boolean unshared)
throws IOException
{
bout.writeByte(TC_ARRAY);
writeClassDesc(desc, false);
handles.assign(unshared ? null : array);
Class> ccl = desc.forClass().getComponentType();
if (ccl.isPrimitive()) {
if (ccl == Integer.TYPE) {
int[] ia = (int[]) array;
bout.writeInt(ia.length);
bout.writeInts(ia, 0, ia.length);
} else if (ccl == Byte.TYPE) {
byte[] ba = (byte[]) array;
bout.writeInt(ba.length);
bout.write(ba, 0, ba.length, true);
} else if (ccl == Long.TYPE) {
long[] ja = (long[]) array;
bout.writeInt(ja.length);
bout.writeLongs(ja, 0, ja.length);
} else if (ccl == Float.TYPE) {
float[] fa = (float[]) array;
bout.writeInt(fa.length);
bout.writeFloats(fa, 0, fa.length);
} else if (ccl == Double.TYPE) {
double[] da = (double[]) array;
bout.writeInt(da.length);
bout.writeDoubles(da, 0, da.length);
} else if (ccl == Short.TYPE) {
short[] sa = (short[]) array;
bout.writeInt(sa.length);
bout.writeShorts(sa, 0, sa.length);
} else if (ccl == Character.TYPE) {
char[] ca = (char[]) array;
bout.writeInt(ca.length);
bout.writeChars(ca, 0, ca.length);
} else if (ccl == Boolean.TYPE) {
boolean[] za = (boolean[]) array;
bout.writeInt(za.length);
bout.writeBooleans(za, 0, za.length);
} else {
throw new InternalError();
}
} else {
Object[] objs = (Object[]) array;
int len = objs.length;
bout.writeInt(len);
if (extendedDebugInfo) {
debugInfoStack.push(
"array (class \"" + array.getClass().getName() +
"\", size: " + len + ")");
}
try {
for (int i = 0; i < len; i++) {
if (extendedDebugInfo) {
debugInfoStack.push(
"element of array (index: " + i + ")");
}
try {
//此处回调写入数组元素类型的对象序列化
writeObject0(objs[i], false);
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
}
第四步:枚举类型的序列化 writeEnum()
/**
* Writes given enum constant to stream.
*/
private void writeEnum(Enum> en,
ObjectStreamClass desc,
boolean unshared)
throws IOException
{
bout.writeByte(TC_ENUM);
ObjectStreamClass sdesc = desc.getSuperDesc();
writeClassDesc((sdesc.forClass() == Enum.class) ? desc : sdesc, false);
handles.assign(unshared ? null : en);
writeString(en.name(), false);
}
第五步,序列化对象 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()) {
//此处就是处理继承自Externalizable接口,自己实现了序列化方法
writeExternalData((Externalizable) obj);
} else {
//序列化写入
writeSerialData(obj, desc);
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
第六步 调用自定义实现的序列化、反序列化的方法 writeExternalData(Externalizable obj) throws IOException
private void writeExternalData(Externalizable obj) throws IOException {
PutFieldImpl oldPut = curPut;
curPut = null;
if (extendedDebugInfo) {
debugInfoStack.push("writeExternal data");
}
SerialCallbackContext oldContext = curContext;
try {
curContext = null;
if (protocol == PROTOCOL_VERSION_1) {
obj.writeExternal(this);
} else {
bout.setBlockDataMode(true);
//此处调用自定义实现的接口方法
obj.writeExternal(this);
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
}
} finally {
curContext = oldContext;
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
curPut = oldPut;
}
第七步:调用自定义或父类的序列化方法 writeSerialData(Object obj, ObjectStreamClass desc)
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()) {
//子类重写了父类的WriteObject()方法,则调用子类的
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);
//反射调用 子类实现的writeObject()
slotDesc.invokeWriteObject(obj, this);
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
} finally {
curContext.setUsed();
curContext = oldContext;
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
curPut = oldPut;
} else {
//没有重写,则调用默认的写入Field方法
defaultWriteFields(obj, slotDesc);
}
}
}
小结:
1、序列化的过程其实主要是结合递归反射将序列化类中的成员变量逐步都调用到基础类型的写入的过程。
2、因为他大量的使用了反射,会频繁的导致GC ,效率要低一些
6、序列化方案-Parcelable分析
Parcelable 是Android 平台为我们提供的序列号的接口,虽然使用起来要比Serializable要复杂一些,但是他的效率要比Serializable高很多,那是因为Serializable是基于磁盘的读写,而Parcelable是基于内存的,这也是Google工程师引以为傲的地方。
先来看看Parcelable的源码:
public interface Parcelable {
/** @hide */
@IntDef(flag = true, prefix = { "PARCELABLE_" }, value = {
PARCELABLE_WRITE_RETURN_VALUE,
PARCELABLE_ELIDE_DUPLICATES,
})
@Retention(RetentionPolicy.SOURCE)
public @interface WriteFlags {}
/**
* Flag for use with {@link #writeToParcel}: the object being written
* is a return value, that is the result of a function such as
* "Parcelable someFunction()
",
* "void someFunction(out Parcelable)
", or
* "void someFunction(inout Parcelable)
". Some implementations
* may want to release resources at this point.
*/
public static final int PARCELABLE_WRITE_RETURN_VALUE = 0x0001;
/**
* Flag for use with {@link #writeToParcel}: a parent object will take
* care of managing duplicate state/data that is nominally replicated
* across its inner data members. This flag instructs the inner data
* types to omit that data during marshaling. Exact behavior may vary
* on a case by case basis.
* @hide
*/
public static final int PARCELABLE_ELIDE_DUPLICATES = 0x0002;
/*
* Bit masks for use with {@link #describeContents}: each bit represents a
* kind of object considered to have potential special significance when
* marshalled.
*/
/** @hide */
@IntDef(flag = true, prefix = { "CONTENTS_" }, value = {
CONTENTS_FILE_DESCRIPTOR,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ContentsFlags {}
/**
* Descriptor bit used with {@link #describeContents()}: indicates that
* the Parcelable object's flattened representation includes a file descriptor.
*
* @see #describeContents()
*/
public static final int CONTENTS_FILE_DESCRIPTOR = 0x0001;
/**
* Describe the kinds of special objects contained in this Parcelable
* instance's marshaled representation. For example, if the object will
* include a file descriptor in the output of {@link #writeToParcel(Parcel, int)},
* the return value of this method must include the
* {@link #CONTENTS_FILE_DESCRIPTOR} bit.
*
* @return a bitmask indicating the set of special object types marshaled
* by this Parcelable object instance.
*/
public @ContentsFlags int describeContents();
/**
* Flatten this object in to a Parcel.
*
* @param dest The Parcel in which the object should be written.
* @param flags Additional flags about how the object should be written.
* May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
*/
public void writeToParcel(Parcel dest, @WriteFlags int flags);
/**
* Interface that must be implemented and provided as a public CREATOR
* field that generates instances of your Parcelable class from a Parcel.
*/
public interface Creator {
/**
* Create a new instance of the Parcelable class, instantiating it
* from the given Parcel whose data had previously been written by
* {@link Parcelable#writeToParcel Parcelable.writeToParcel()}.
*
* @param source The Parcel to read the object's data from.
* @return Returns a new instance of the Parcelable class.
*/
public T createFromParcel(Parcel source);
/**
* Create a new array of the Parcelable class.
*
* @param size Size of the array.
* @return Returns an array of the Parcelable class, with every entry
* initialized to null.
*/
public T[] newArray(int size);
}
/**
* Specialization of {@link Creator} that allows you to receive the
* ClassLoader the object is being created in.
*/
public interface ClassLoaderCreator extends Creator {
/**
* Create a new instance of the Parcelable class, instantiating it
* from the given Parcel whose data had previously been written by
* {@link Parcelable#writeToParcel Parcelable.writeToParcel()} and
* using the given ClassLoader.
*
* @param source The Parcel to read the object's data from.
* @param loader The ClassLoader that this object is being created in.
* @return Returns a new instance of the Parcelable class.
*/
public T createFromParcel(Parcel source, ClassLoader loader);
}
}
从上面的源码中的注释,相信各位同学已经都了解了,那我们接着看看他的使用,其实也很简单:
class GoodsBean implements Parcelable {
private String mGoodsName;
private BigDecimal mGoodsPrice=BigDecimal.ZERO;
/*
* TODO 构造方法
*/
protected GoodsBean(Parcel in) {
mGoodsName = in.readString();
mGoodsPrice=new BigDecimal(in.readString());
}
/*
* TODO 该静态的CREATOR是实现Parcelable必须要有这个CREATOR实例,用于反序列化,如createFromParcel()方法
*/
public static final Creator CREATOR = new Creator() {
/*
* TODO 反序列化的方法,从Parcel对象反序列化为该类的实例,实际是在该类的构造方法中实现
*/
@Override
public GoodsBean createFromParcel(Parcel in) {
return new GoodsBean(in);
}
/*
* TODO
*/
@Override
public GoodsBean[] newArray(int size) {
return new GoodsBean[size];
}
};
/*
* TODO 描述当前的Parcelable实例的对象类型,如果该对象文件有描述,这个方法就会返回描述内存,否则返回一个位掩码
*/
@Override
public int describeContents() {
return 0;
}
/*
* TODO 将该类的对象转化成Parcel对象
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mGoodsName);
dest.writeString(mGoodsPrice.toString());
}
}
从上面的代码中,可以看到,我们在序列化和反序列化的过程中,其实出现的是Parcel ,那么他又是什么东西呢?中文翻译过来就是包装的意思,那这样我们是不是可以理解成是:Parcelable是通过Parcel来将我们的Class对象实例包装成序列化数据,在我们需要使用的时候,再从其中反序列化中解析出来?????
那我们就接着进入到Parcel中去找一下答案。
/**
Container for a message (data and object references) that can
be sent through an IBinder. A Parcel can contain both flattened data
that will be unflattened on the other side of the IPC(using the various methods here for writing specific types, or the general interface), and references to live objects that will result in the other side receiving a proxy IBinder connected with the original IBinder in the Parcel.
Parcel is a general-purpose serialization mechanism. This class (and the corresponding API for placing arbitrary objects into a Parcel) is designed as a high-performance IPC transport. As such, it is not appropriate to place any Parcel data in to persistent storage: changes in the underlying implementation of any of the data in the Parcel can render older data unreadable.
**/
public final class Parcel {
//......
private static native void nativeWriteByteArray(long nativePtr, byte[] b, int offset, int len);
private static native void nativeWriteBlob(long nativePtr, byte[] b, int offset, int len);
@FastNative
private static native void nativeWriteInt(long nativePtr, int val);
@FastNative
private static native void nativeWriteLong(long nativePtr, long val);
@FastNative
private static native void nativeWriteFloat(long nativePtr, float val);
@FastNative
private static native void nativeWriteDouble(long nativePtr, double val);
@FastNative
private static native void nativeWriteString8(long nativePtr, String val);
@FastNative
private static native void nativeWriteString16(long nativePtr, String val);
@FastNative
private static native void nativeWriteStrongBinder(long nativePtr, IBinder val);
@FastNative
private static native long nativeWriteFileDescriptor(long nativePtr, FileDescriptor val);
//...
}
笔者截取了Parcel源码中的一部分,可以看到Parcel是一个最终类(不能被继承),而他里面有大量的像上面截取的源码中native方法。 我们从上述官方源码注释中可以得知,他是将数据存放在一个公共的内存中,这样不管读写都是直接在这个公共内存中操作,但是他不适合将数据持久化。
小结:
1、Parcelable原理是在一个共享的内存中进行数据的序列化和反序列化,效率要高很多
2、不适合持久性的数据存储
3、是以IBinder作为信息载体,内存的开销也比较小,所以Android在数据传递时推荐使用Parcelable
4、在序列化和反序列化的过程中,如果遇到同类型的(如:String),则一定要顺序一致操作
以上就是分享给各位的内容,当然还有ProtoBuf的序列化方案,笔者仅仅了解过,没有深入学习和使用过,所以就不在此啰嗦了,后续再来。。。