序列化
一. 认识序列化
1. 序列化的定义
- 序列化
- 狭义的概念:即将对象转换为字节序列的过程
- 广义的概念:即将数据结构或者对象转换为我们可以存储或者传输的数据格式的一个过程
- 反序列化
- 狭义的概念:即将字节序列转换成对象的过程
- 广义的概念:即将生成的数据还原成数据结构或者对象的过程
2. 序列化的用途
- 由于在系统底层,数据的传输形式是简单的字节序列形式传递的,即在底层,系统并不认识对象,只认识字节序列,而为了达到进程间通讯的目的,就需要将对象或者说数据转换为字节序列的形式,而这个过程本质上就是序列化。
- 简单的概括:
- 序列化:主要用于网络传输,数据持久化;而一般序列化也被称为编码(Encode)
- 反序列化:主要用于从网络,磁盘上读取字节数组还原成原始对象;一般反序列化也被称为解码(Decode)
- 展开具体的举例:
- 永久保存对象数据(将对象数据保存在文件当中,或者是磁盘中)
- 通过序列化操作将对象数据在网络上进行传输(由于网络传输是以字节流的方式对数据进行传输的,因此序列化的目的是将对象数据转换成字节流的形式)
- 将对象数据在进程间进行传递(Activity之间传递对象数据时,需要在当前的Activity中对对象数据进行序列化操作,在另一个Activity中需要进行反序列化操作将数据取出)
- Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长(即每个对象都在JVM中),但在现实应用中,就可能要停止JVM运行,但有要保存某些指定的对象,并在将来重新读取被保存的对象。这时将Java对象序列化就能够实现该功能(可选择存入数据库、或以文件的形式保存)
3.实现序列化的方式
- 狭义上的序列化:
- Java中的
Serializable
/Externalizable
接口 - Android设计的
Parcelable
接口
- 广义上的序列化:
JSON
SQLite
二. 几种常见的序列化和反序列化协议
1. XML & SOAP
- XML 是一种常用的序列化和反序列化协议,具有跨机器,跨语言等优点,SOAP(Simple Object Access protocol) 是一种被广泛应用的,基于 XML 为序列化和反序列化协议的结构化消息传递协议
2. JSON(JavaScript Object Notation)
- JSON 起源于弱类型语言 JavaScript, 它的产生来自于一种称之为Associative array的概念,其本质是就是采用Attribute-value的方式来描述对象。实际上在 JavaScript和 PHP 等弱类型语言中,类的描述方式就是 Associative array;JSON 的这些优点,使得它快速成为最广泛使用的序列化协议之一。
3. Protobuf
- Protobuf是谷歌提供的序列化协议,由于其内部采用不是字节全对齐的方式,所以其序列化后的数据十分的简洁、紧凑,与XML相比,其序列化后的数据量约为前者的1/3到1/10
三. Android中两种最常用的序列化方案
1. Serializable/Externalizable接口
Serializable
接口是由Java提供的序列化接口,它是一个空接口:
public interface Serializable {
}
Serializable
接口可以看作是一种标识,用来标识当前类可以:
- 被
ObjectOutPutStream
序列化 - 被
ObjectInputStream
反序列化
Externalizable
接口是继承了Serializable
接口的,声明了两个方法:
public interface Externalizable extends Serializable {
void writeExternal(ObjectOutput var1) throws IOException;
void readExternal(ObjectInput var1) throws IOException, ClassNotFoundException;
}
Externalizable
被继承之后,必须实现writeExternal
和readExternal
方法
1) 基本使用
Serializable
接口
class UserSerializable implements Serializable {
//自动生成UID,File--Setting--Editor--Inspections--Java--Serialization issues--勾选Serializable class without "serialVersionUID"即可
private static final long serialVersionUID = 4896305340906213530L;
public static final String path = "E:\\Android_learn_project\\serializeLearn\\";
public int m_numId;
public String m_name;
public UserSerializable(int num, String name) {
m_name = name;
m_numId = num;
}
public UserSerializable() {}
public String toString() {
return "name = " + m_name + ", numID = " + m_numId;
}
public static void main(String[] args) {
UserSerializable user = new UserSerializable(1, "xxx");
UserSerializable user1 = new UserSerializable();
System.out.println("原始对象:" + user);
try {
//采用FileOutputStream,其实是将二进制数据保存到文件中了,实现了持久化
ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream(path + "a.out"));
oss.writeObject(user);
} catch (IOException e) {
e.printStackTrace();
}
try {
//采用FileInputStream,将存储了二进制数据的文件转换为ObjectInputStream数据
ObjectInputStream iss = new ObjectInputStream(new FileInputStream(path + "a.out"));
user1 = (UserSerializable) iss.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println("------------------------------------------------");
System.out.println("序列化后读取的对象:" + user1);
}
}
- 见以上代码,
serializable
接口实现序列化的基本方法,其实上述代码实现的是持久化的存储,将数据存到了对应文件中;查看ObjectOutputStream
类,存在两个构造函数,带参数的构造函数接收OutputStream
查看OutputStream
类的继承关系,我们可以使用如下类进行数据存储
serialVersionUID
-
serialVersionUID
参数的作用:serialVersionUID
用来表明类的不同版本间的兼容性。如果你修改了此类, 要修改此值。否则以前用老版本的类序列化的类恢复时会报错:InvalidClassException
-
serialVersionUID
兼容性问题:为了在反序列化时,确保类版本的兼容性,最好在每个要序列化的类中加入private static final long serialVersionUID
这个属性,具体数值自己定义。这样,即使某个类在与之对应的对象已经序列化出去后做了修改,该对象依然可以被正确反序列化。否则,如果不显式定义该属性,这个属性值将由JVM根据类的相关信息计算,而修改后的类的计算 结果与修改前的类的计算结果往往不同,从而造成对象的反序列化因为类版本不兼容而失败。不显式定义这个属性值的另一个坏处是,不利于程序在不同的JVM之间的移植。因为不同的编译器实现该属性值的计算策略可能不同,从而造成虽然类没有改变,但是因为JVM不同,出现因类版本不兼容而无法正确反序列化的现象出现
Externalizable
接口
class UserExternalizable implements Externalizable {
private static final long serialVersionUID = 967548983806058267L;
public int m_numID;
public String m_name;
public UserExternalizable() {}
public UserExternalizable(int numID, String name) {
m_name = name;
m_numID = numID;
}
public String toString() {
return "numID = " + m_numID + ", name = " + m_name;
}
@Override
public void writeExternal(ObjectOutput objectOutput) throws IOException {
System.out.println("enter UserExternalizable writeExternal");
//必须保证写入数据的顺序和读出数据的顺序是一致的,否则会报错
objectOutput.writeInt(m_numID);
objectOutput.writeObject(m_name);
}
@Override
public void readExternal(ObjectInput objectInput) throws IOException, ClassNotFoundException {
System.out.println("enter UserExternalizable readExternal");
//必须保证写入数据的顺序和读出数据的顺序是一致的,否则会报错
m_numID = objectInput.readInt();
m_name = (String) objectInput.readObject();
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
UserExternalizable user = new UserExternalizable(10, "ooooooo");
UserExternalizable user1 = null;
byte[] userData = null;
System.out.println("序列化前:" + user);
//采用数组的形式进行序列化数据的存储
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oss = new ObjectOutputStream(out);
oss.writeObject(user);
//将序列化后的数据存储到byte数组中
userData = out.toByteArray();
//通过读取byte数组进行二进制数据的获取
ObjectInputStream iss = new ObjectInputStream(new ByteArrayInputStream(userData));
user1 = (UserExternalizable) iss.readObject();
System.out.println("----------------------------------");
System.out.println("反序列化后:" + user1);
}
}
- 通过
Serializable
和Externalizable
接口的简单使用会产生一个疑问:序列化和反序列化究竟是如果使用writeObject
和readObject
以及Externalizable
接口中writeExternal
和readExternal
是怎样被调用到的?就让我们从源码解析中获得答案
2)源码分析
writeObject
- 让我们从
writeObject
开始看
ObjectOutputStream
构造函数
public ObjectOutputStream(OutputStream out) throws IOException {
verifySubclass();
bout = new BlockDataOutputStream(out);
handles = new HandleTable(10, (float) 3.00);
subs = new ReplaceTable(10, (float) 3.00);
//调用有参构造函数创建的ObjectOutputStream类,enableOverride = false
enableOverride = false;
writeStreamHeader();
bout.setBlockDataMode(true);
if (extendedDebugInfo) {
debugInfoStack = new DebugTraceInfoStack();
} else {
debugInfoStack = null;
}
}
writeObject
方法
public final void writeObject(Object obj) throws IOException {
//构造函数中,enableOverride = false
if (enableOverride) {
writeObjectOverride(obj);
return;
}
try {
//正常都会调用writeObject0方法
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
{
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;
// BEGIN Android-changed: Make Class and ObjectStreamClass replaceable.
/*
} else if (obj instanceof Class) {
writeClass((Class) obj, unshared);
return;
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
return;
*/
// END Android-changed: Make Class and ObjectStreamClass replaceable.
}
// check for replacement object
Object orig = obj;
Class<?> cl = obj.getClass();
ObjectStreamClass desc;
// BEGIN Android-changed: Make only one call to writeReplace.
/*
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;
desc = ObjectStreamClass.lookup(cl, true);
break;
}
*/
// Do only one replace pass
Class repCl;
desc = ObjectStreamClass.lookup(cl, true);
//在这里会判断是否存在ReplaceMethod
if (desc.hasWriteReplaceMethod() &&
//如果存在,则会通过反射进行调用,所以writeReplace是先于writeObject被调用的
(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.
//enableReplace是通过enableReplaceObject()方法去赋值的
if (enableReplace) {
Object rep = replaceObject(obj);
if (rep != obj && rep != null) {
cl = rep.getClass();
desc = ObjectStreamClass.lookup(cl, true);
}
obj = rep;
}
//不调用replaceObject,不会走到这里面
// 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;
// BEGIN Android-changed: Make Class and ObjectStreamClass replaceable.
/*
} else if (obj instanceof Class) {
writeClass((Class) obj, unshared);
return;
} else if (obj instanceof ObjectStreamClass) {
writeClassDesc((ObjectStreamClass) obj, unshared);
return;
*/
// END Android-changed: Make Class and ObjectStreamClass replaceable.
}
}
// 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) {
//正常来说,继承Serializable调用到writeOrdinaryObject
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);
}
}
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();
//写入的标志位是TC_OBJECT,读的时候对应的值就是这个
bout.writeByte(TC_OBJECT);
writeClassDesc(desc, false);
handles.assign(unshared ? null : obj);
//在这里做了判断,如果当前的类继承的是Externalizable接口
if (desc.isExternalizable() && !desc.isProxy()) {
//那么就会去调用writeExternalData()方法
writeExternalData((Externalizable) obj);
} else {
//否则则调用writeSerialData()方法
writeSerialData(obj, desc);
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
writeExternalData
/writeSerialData
方法
/**
* Writes externalizable data of given object by invoking its
* writeExternal() method.
*/
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;
}
/**
* Writes instance data for each serializable class of given object, from
* superclass to subclass.
*/
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()方法
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);
//如果有重写,则通过反射去调用我们自己写的WriteObject()方法
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);
}
}
}
- 总结:通过对
WriteObject
方法的源码分析,我们可以知道
- 如果在继承类中声明实现了
WriteObject()
方法,最终调用的就是自己声明的函数,并非是方法的重写,而是使用反射的方式实现的 - 如果我们声明实现了
writeReplace()
方法,那么这个方法会在WriteObject()
方法之前调用
readObject
- 再看一下
readObject()
方法
ObjectInputStream
构造函数
public c(InputStream in) throws IOException {
verifySubclass();
bin = new BlockDataInputStream(in);
handles = new HandleTable(10);
vlist = new ValidationList();
// Android-removed: ObjectInputFilter logic, to be reconsidered. http://b/110252929
// serialFilter = ObjectInputFilter.Config.getSerialFilter();
enableOverride = false;
readStreamHeader();
bin.setBlockDataMode(true);
}
readObject()
方法
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();
}
}
}
readObject0
方法
/**
* Underlying readObject implementation.
*/
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++;
// Android-removed: ObjectInputFilter logic, to be reconsidered. http://b/110252929
// totalObjectRefs++;
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);
}
}
readOrdinaryObject
方法
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 {
//此处会判断是否完成初始化,如果完成,则会调用newInstance去构建对应的类,而这个newInstance使用的是无参的构造函数
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);
}
//同样判断是不是Externalizable
if (desc.isExternalizable()) {
readExternalData((Externalizable) obj, desc);
} else {
readSerialData(obj, desc);
}
handles.finish(passHandle);
if (obj != null &&
handles.lookupException(passHandle) == null &&
//判断是否存在ResolveMethod()函数的声明定义,这个是在readObject之后
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}
3)分析、总结
Java的序列化步骤
- 序列化算法一般会按步骤做如下事情:
- 将对象实例相关的类元数据输出
- 递归地输出类的超类描述直到不再有超类
- 类元数据完了以后,开始从最顶层的超类开始输出对象实例的实际数据值
- 从上至下递归输出实例的数据
- 所以由此,我们就引出了两个需要注意的点:
- 由于序列化是将类数据按照一定的规则转换成字节序列进行输出,那么我们在反序列化的时候同样要按照对应的规则进行获取,即:重写writeObject()/readObject()或者writeExternal()/readExternal()必须保证写入数据和读取数据的顺序是一致的!
- 多引用问题,该问题通过一个例子说明:
class UserExternalizable implements Externalizable {
private static final long serialVersionUID = 967548983806058267L;
public int m_numID;
public String m_name;
public UserExternalizable() {}
public UserExternalizable(int numID, String name) {
m_name = name;
m_numID = numID;
}
public void setNumID(int numID) {
m_numID = numID;
}
public String toString() {
return "numID = " + m_numID + ", name = " + m_name;
}
@Override
public void writeExternal(ObjectOutput objectOutput) throws IOException {
System.out.println("enter UserExternalizable writeExternal");
//必须保证写入数据的顺序和读出数据的顺序是一致的,否则会报错
objectOutput.writeInt(m_numID);
objectOutput.writeObject(m_name);
}
@Override
public void readExternal(ObjectInput objectInput) throws IOException, ClassNotFoundException {
System.out.println("enter UserExternalizable readExternal");
//必须保证写入数据的顺序和读出数据的顺序是一致的,否则会报错
m_numID = objectInput.readInt();
m_name = (String) objectInput.readObject();
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
UserExternalizable user = new UserExternalizable(10, "ooooooo");
UserExternalizable user1 = null;
UserExternalizable user2 = null;
byte[] userData = null;
System.out.println("序列化前:" + user);
//采用数组的形式进行序列化数据的存储
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oss = new ObjectOutputStream(out);
oss.writeObject(user);
//改变类中的数据,再序列化一次
user.setNumID(20);
oss.writeObject(user);
//将序列化后的数据存储到byte数组中
userData = out.toByteArray();
//通过读取byte数组进行二进制数据的获取
ObjectInputStream iss = new ObjectInputStream(new ByteArrayInputStream(userData));
user1 = (UserExternalizable) iss.readObject();
user2 = (UserExternalizable) iss.readObject();
System.out.println("----------------------------------");
//发现结果并没有被改变,这是因为在默认的情况下,对于同一个实例的多个引用,为了节省空间,他只会被写入一次
System.out.println("反序列化后:" + user1);
System.out.println("反序列化后:" + user2);
}
}
那么对于这种问题该如何解决呢?有两种方式,见代码
public static void main(String[] args) throws IOException, ClassNotFoundException {
UserExternalizable user = new UserExternalizable(10, "ooooooo");
UserExternalizable user1 = null;
UserExternalizable user2 = null;
byte[] userData = null;
System.out.println("序列化前:" + user);
//采用数组的形式进行序列化数据的存储
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream oss = new ObjectOutputStream(out);
oss.writeObject(user);
//改变类中的数据,再序列化一次
user.setNumID(20);
/*1. 在写入之前reset一下*/
oss.reset();
oss.writeObject(user);
/*2. 采用非共享的方式,进行写入*/
oss.writeUnshared(user);
userData = out.toByteArray();
//通过读取byte数组进行二进制数据的获取
ObjectInputStream iss = new ObjectInputStream(new ByteArrayInputStream(userData));
user1 = (UserExternalizable) iss.readObject();
user2 = (UserExternalizable) iss.readObject();
System.out.println("----------------------------------");
System.out.println("反序列化后:" + user1);
System.out.println("反序列化后:" + user2);
}
子类实现序列化,父类不实现序列化的场景
- 如果执行序列化的类,它的父类不支持序列化且父类未声明无参的构造函数,那么就会抛出警告,那么为什么只要声明了无参构造就不会有警告呢,还得从源码中查看:
我们知道反序列化是调用readObject()
实现的,而在一路调用的过程中,在readOrdinaryObject()
方法中
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
.........
Object obj;
try {
//此处会判断是否完成初始化,如果完成,则会调用newInstance去构建对应的类,而这个newInstance使用的是无参的构造函数
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
........
}
Object newInstance()
throws InstantiationException, InvocationTargetException,
UnsupportedOperationException
{
requireInitialized();
if (cons != null) {
try {
//调用的是cons的newInstance()
//接下来找一下cons是在哪里获取的
return cons.newInstance();
} catch (IllegalAccessException ex) {
// should not occur, as access checks have been suppressed
throw new InternalError(ex);
}
} else {
throw new UnsupportedOperationException();
}
}
//初始化时,获取了cons构造函数
private ObjectStreamClass(final Class<?> cl) {
.........
if (externalizable) {
//如果是Externalizable接口则调用这个
cons = getExternalizableConstructor(cl);
} else {
//如果是Serializable接口则调用这个
cons = getSerializableConstructor(cl);
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class<?>[] { ObjectOutputStream.class },
Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",
new Class<?>[] { ObjectInputStream.class },
Void.TYPE);
.....
}
//如果是继承了Externalizable的类,调用这个方法获取cons构造函数
private static Constructor<?> getExternalizableConstructor(Class<?> cl) {
try {
//此处是反射获取无参构造函数
Constructor<?> cons = cl.getDeclaredConstructor((Class<?>[]) null);
cons.setAccessible(true);
return ((cons.getModifiers() & Modifier.PUBLIC) != 0) ?
cons : null;
} catch (NoSuchMethodException ex) {
return null;
}
}
//如果是继承了Serializable接口的,则调用这个方法进行构造函数cons的获取
private static Constructor<?> getSerializableConstructor(Class<?> cl) {
Class<?> initCl = cl;
//这里会循环获取超类,判断是否继承了Serializable接口,如果继承了则直接就返回null
while (Serializable.class.isAssignableFrom(initCl)) {
if ((initCl = initCl.getSuperclass()) == null) {
return null;
}
}
try {
//走到此处代表有超类未实现Serializable接口,那么则反射获取无参构造函数去操作
Constructor<?> cons = initCl.getDeclaredConstructor((Class<?>[]) null);
int mods = cons.getModifiers();
if ((mods & Modifier.PRIVATE) != 0 ||
((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) == 0 &&
!packageEquals(cl, initCl)))
{
return null;
}
// BEGIN Android-changed: Serialization constructor obtained differently
// cons = reflFactory.newConstructorForSerialization(cl, cons);
if (cons.getDeclaringClass() != cl) {
cons = cons.serializationCopy(cons.getDeclaringClass(), cl);
}
// END Android-changed: Serialization constructor obtained differently
cons.setAccessible(true);
return cons;
} catch (NoSuchMethodException ex) {
return null;
}
}
父类继承序列化,子类不想让它具备序列化功能
- 由类的继承关系可知,父类继承序列化接口,子类必然可以进行序列化,那么如何让其无法进行序列化呢?有两种方式:
- 将子类中所有的成员变量都用关键字:
transient
进行声明,这样序列化的时候就不会保存这些变量 - 在子类中实现
writeObject()
和readObject()
方法,并在其中抛异常或者返回空,具体原因见源码分析。
类的演化问题
- 如果序列化的类是枚举对象时,那么序列化后并不会保存元素的值,只会保存元素的name;这样即便我们改变了原来的枚举,且不再次存储一遍,再次读取的时候,我们反序列化后读取的数值也会是改变后的数值。
enum Num1{
ONE, TWO, THREE;
public void printValues(){
System.out.println("ONE: "+ ONE.ordinal() + ", TWO: " + TWO.ordinal() + ", THREE: " + THREE.ordinal());
}
}
/**
* Java的序列化机制针对枚举类型是特殊处理的。简单来讲,在序列化枚举类型时,只会存储枚举类的引用和枚举常量的名称。随后的反序列化的过程中,
* 这些信息被用来在运行时环境中查找存在的枚举类型对象。
*/
public class EnumSerializableTest {
public static void main(String[] args) throws Exception {
File file = new File("p.out");
ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream(file));
oss.writeObject(Num1.ONE);
oss.close();
Num1.THREE.printValues();
System.out.println("hashCode: " + Num1.ONE.hashCode());
System.out.println("反序列化后");
ObjectInputStream iss = new ObjectInputStream(new FileInputStream(file));
Num1 s1 = (Num1) iss.readObject();
s1.printValues();
System.out.println("hashCode: " + s1.hashCode());
System.out.println("== " + (Num1.ONE == s1));
}
}
/* 第一次的结果
* ONE: 0, TWO: 1, THREE: 2
* hashCode: 705927765
* 反序列化后
* ONE: 0, TWO: 1, THREE: 2
* hashCode: 705927765
* == true
*/
//改动一下代码
enum Num1{
//调换一下顺序
TWO, ONE, THREE;
public void printValues(){
System.out.println("ONE: "+ ONE.ordinal() + ", TWO: " + TWO.ordinal() + ", THREE: " + THREE.ordinal());
}
}
public class EnumSerializableTest {
public static void main(String[] args) throws Exception {
File file = new File("p.out");
//去除保存的步骤
//ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream(file));
//oss.writeObject(Num1.ONE);
//oss.close();
Num1.THREE.printValues();
System.out.println("hashCode: " + Num1.ONE.hashCode());
System.out.println("反序列化后");
ObjectInputStream iss = new ObjectInputStream(new FileInputStream(file));
Num1 s1 = (Num1) iss.readObject();
s1.printValues();
System.out.println("hashCode: " + s1.hashCode());
System.out.println("== " + (Num1.ONE == s1));
}
}
/* 第二次的结果
* ONE: 1, TWO: 0, THREE: 2
* hashCode: 705927765
* 反序列化后
* ONE: 1, TWO: 0, THREE: 2
* hashCode: 705927765
* == true
*/
Serializable
中方法的执行顺序
-
writeReplace
先于writeObject
,readResolve
后于readObject
;原因见源码解析
序列化带来的单例模式失效问题
- 经过上面的源码分析,我们可以获知,在序列化的过程中,会进行反射调用无参构造,而这就会破坏单例的唯一性!规避方式可以实现
readResolve()
,在其中返回单例
2. Parcelable
接口
-
Parcelable
是Android为我们提供的序列化的接口,Parcelable
相对于Serializable
的使用相对复杂一些,但Parcelable
的效率相对Serializable
也高很多;Parcelable
是Android SDK提供的,它是基于内存的,由于内存读写速度高于硬盘,因此Android中的跨进程对象的传递一般使用Parcelable
1)基本使用
Parcelable
的使用比起Serializable
复杂一些,但是SDK有例子可以参考:
/**
* Interface for classes whose instances can be written to
* and restored from a {@link Parcel}. Classes implementing the Parcelable
* interface must also have a non-null static field called <code>CREATOR</code>
* of a type that implements the {@link Parcelable.Creator} interface.
*
* <p>A typical implementation of Parcelable is:</p>
**/
<pre>
public class MyParcelable implements Parcelable {
private int mData;
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel out, int flags) {
out.writeInt(mData);
}
public static final Parcelable.Creator<MyParcelable> CREATOR
= new Parcelable.Creator<MyParcelable>() {
public MyParcelable createFromParcel(Parcel in) {
return new MyParcelable(in);
}
public MyParcelable[] newArray(int size) {
return new MyParcelable[size];
}
};
private MyParcelable(Parcel in) {
mData = in.readInt();
}
}</pre>
public int describeContents()
- 返回的是内容的描述信息,只针对一些特殊的需要描述信息的对象,需要返回1,其他情况返回0就可以
public void writeToParcel(Parcel out, int flags)
- 我们通过
writeToParcel
方法实现序列化,writeToParcel
返回了Parcel
,所以我们可以直接调用Parcel
中的write
方法,基本的write
方法都有,对象和集合比较特殊,基本的数据类型除了boolean其他都有,Boolean可以使用int或byte存储
CREATOR
变量
-
CREATOR
变量是用来反序列化使用的,内部类将parcel
对象传出来,使得我们可以通过parcel
对象读取数据
通过源码中的介绍 可以知道,Parcelable接口的实现类是可以通过Parcel写入和恢复数据的,并且必须要有一个非空的静态变量 CREATOR,而且还给了一个例子,这样我们写起来就比较简单了,但是简单的使用并不是我们的最终目的,通过查看Android源码中Parcelable可以看出,Parcelable实现过程主要分为序列化,反序列化,描述三个过程,下面分别介绍下这三个过程。
Parcel的简介
- 在介绍之前我们需要先了解Parcel是什么?Parcel翻译过来是打包的意思,其实就是包装了我们需要传输的数据,然后在Binder中传输,也就是用于跨进程传输数据;简单来说,Parcel提供了一套机制,可以将序列化之后的数据写入到一个共享内存中,其他进程通过Parcel可以从这块共享内存中读出字节流,并反序列化成对象,下图是这个过程的模型。
- Parcel可以包含原始数据类型(用各种对应的方法写入,比如
writeInt()
,writeFloat()
等),可以包含Parcelable对象,它还包含了一个活动的IBinder对象的引用,这个引用导致另一端接收到一个指向这个IBinder的代理IBinder。Parcelable通过Parcel实现了read和write的方法,从而实现序列化和反序列化
2)代码示例
- 我们展示在第一个activity中通过按键去启动第二个activity,同时传递我们自己定义的类结构,然后在第二个activity中输出
/**
* 构建一个parcelable数据传输类
*/
class ParcelableUtil implements Parcelable {
public int m_num;
public String m_name;
public ParcelableUtil(int num, String name) {
m_num = num;
m_name = name;
}
//内容描述信息,因为没有需要描述信息的对象,返回0即可
@Override
public int describeContents() {
return 0;
}
//通过writeToParcel将数据写入parcel,实现序列化
@Override
public void writeToParcel(Parcel dest, int flags) {
//和Serializable一样,必须保证数据的写入和读出顺序一致!!
dest.writeInt(m_num);
dest.writeString(m_name);
}
//将数据从parcel中读出,实现反序列化
protected ParcelableUtil(Parcel in) {
//和Serializable一样,必须保证数据的写入和读出顺序一致!!
m_num = in.readInt();
m_name = in.readString();
}
//通过CREATOR变量,将parcel对象传给外部类,提供反序列化数据来源
public static final Creator<ParcelableUtil> CREATOR = new Creator<ParcelableUtil>() {
@Override
public ParcelableUtil createFromParcel(Parcel in) {
//内部构建parelableUtil类,并将parcel传出去
return new ParcelableUtil(in);
}
@Override
public ParcelableUtil[] newArray(int size) {
return new ParcelableUtil[size];
}
};
@NonNull
@Override
public String toString() {
return "name: " + m_name + ", num = " + m_num;
}
}
/**
* First Acivity,用来将数据序列化传输给Second Activity
*/
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = findViewById(R.id.button_1);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ParcelableUtil parcelableUtil = new ParcelableUtil(10, "xxxxxxxxx");
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
//传输时,通过putExtra将数据传入即可
intent.putExtra("KEY", parcelableUtil);
System.out.println("first parcelableUtil = " + parcelableUtil);
startActivity(intent);
}
});
}
}
/**
* Second Activity 将从First Activity传输过来数据反序列化解析出来
*/
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
//从Intent中将数据读取出来
// ParcelableUtil parcelableUtil = getIntent().getExtras().getParcelable("KEY");
ParcelableUtil parcelableUtil1 = getIntent().getParcelableExtra("KEY");
System.out.println("second parcelableUtil = " + parcelableUtil1);
}
}
3. Parcelable与Serializable的性能比较
- Serializable性能分析
- Serializable是Java中的序列化接口,其使用起来简单但开销较大(因为Serializable在序列化过程中使用了反射机制,故而会产生大量的临时变量,从而导致频繁的GC),并且在读写数据过程中,它是通过IO流的形式将数据写入到硬盘或者传输到网络上。
- Parcelable性能分析
- Parcelable则是以IBinder作为信息载体,在内存上开销比较小,因此在内存之间进行数据传递时,推荐使用Parcelable,而Parcelable对数据进行持久化或者网络传输时操作复杂,一般这个时候推荐使用Serializable。
- 性能比较总结
- 在内存的使用中,Parcelable在性能方面要强于Serializable
- Serializable在序列化操作的时候会产生大量的临时变量(原因是使用了反射机制),从而导致GC的频繁调用,因此在性能上会稍微逊色
- Parcelable是以IBinder作为信息载体的.在内存上的开销比较小,因此在内存之间进行数据传递的时候,Android推荐使用Parcelable
- 在读写数据的时候,Parcelable是在内存中直接进行读写,而Serializable是通过使用IO流的形式将数据读写入在硬盘上。
- 虽然Parcelable的性能要强于Serializable,但是仍然有特殊的情况需要使用Serializable,而不去使用Parcelable,因为Parcelable无法将数据进行持久化,因此在将数据保存在磁盘的时候,仍然需要使用后者,因为前者无法很好的将数据进行持久化.(原因是在不同的Android版本当中,Parcelable可能会不同,因此数据的持久化方面仍然是使用Serializable)