JAVA中的序列化和反序列化主要用于:
(1)将对象或者异常等写入文件,通过文件交互传输信息;
(2)将对象或者异常等通过网络进行传输。
那么为什么需要序列化和反序列化呢?简单来说,如果你只是自己同一台机器的同一个环境下使用同一个JVM来操作,序列化和反序列化是没必要的,当需要进行数据传输的时候就显得十分必要。比如你的数据写到文件里要被其他人的电脑的程序使用,或者你电脑上的数据需要通过网络传输给其他人的程序使用,像服务器客户端的这种模型就是一种应用,这个时候,大家想想,每个人的电脑配置可能不同,运行环境可能也不同,字节序可能也不同,总之很多地方都不能保证一致,所以为了统一起见,我们传输的数据或者经过文件保存的数据需要经过序列化和编码等操作,相当于交互双方有一个公共的标准,按照这种标准来做,不管各自的环境是否有差异,各自都可以根据这种标准来翻译出自己能理解的正确的数据。
总之,大家要记住以下几点:
(1)序列化和反序列化的实现方法和应用场合;
(2)静态成员是不能被序列化的,因为静态成员是随着类的加载而加载的,与类共存亡;
(3)要明白错误的那个测试程序的原因,搞明白JVM的一些基本机制;
(4)要想直接通过打印对象而输出对象的一些属性信息,要重写toString方法。
注意:序列化只是一种拆装组装对象的规则,那么这种规则肯定也可能有多种多样,比如现在常见的序列化方式有:JDK(不支持跨语言)、JSON、XML、Hessian、Kryo(不支持跨语言)、Thrift、Protostuff、FST(不支持跨语言)
1、仅仅实现Serializeable是不够的,要想实现持久化,必须自己用ObjectOutputStream进行存储才可以。反序列化,使用ObjectInputStream读取出来。就是通过对象的IO实现对象的序列化与反序列化。
2、Externalizable需要注意,使用时应该成双成对;注意:(1)读写顺序要求一致;(2)读写成员变量的个数也要求一致。你可以不写,但是读的时候不能多读;(3)如果自己修改了构造函数,就必须要自己写一个默认的构造方法(空实现,Serializeable没有这个需求)。
// 这个空实现的构造方法必须要有,否则会报错
public Person() {
}
具体原因在这里:
// java.io.ObjectInputStream#readOrdinaryObject
private Object readOrdinaryObject(boolean unshared) throws IOException {
if (bin.readByte() != TC_OBJECT) {
throw new InternalError();
}
// 这里去获取构造方法。对于externalizable还是serializable,他们的构造方法的参数是不同的
ObjectStreamClass desc = readClassDesc(false);
desc.checkDeserialize();
.......
}
return obj;
}
// java.io.ObjectStreamClass#ObjectStreamClass(java.lang.Class<?>)
private ObjectStreamClass(final Class<?> cl) {
......
// 区分用externalizable还是serializable
if (externalizable) {
cons = getExternalizableConstructor(cl);
} else {
cons = getSerializableConstructor(cl);
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class<?>[] { ObjectOutputStream.class },
Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",
new Class<?>[] { ObjectInputStream.class },
Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(
cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);
}
......
}
// 这里对于Externalizable用了一个无参的构造方法去创建
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最终会走到这里,返回一个object().
private final Constructor<?> generateConstructor(Class<?> cl,
Constructor<?> constructorToCall) {
ConstructorAccessor acc = new MethodAccessorGenerator().
generateSerializationConstructor(cl,
constructorToCall.getParameterTypes(),
constructorToCall.getExceptionTypes(),
constructorToCall.getModifiers(),
constructorToCall.getDeclaringClass());
Constructor<?> c = newConstructor(constructorToCall.getDeclaringClass(),
constructorToCall.getParameterTypes(),
constructorToCall.getExceptionTypes(),
constructorToCall.getModifiers(),
langReflectAccess().
getConstructorSlot(constructorToCall),
langReflectAccess().
getConstructorSignature(constructorToCall),
langReflectAccess().
getConstructorAnnotations(constructorToCall),
langReflectAccess().
getConstructorParameterAnnotations(constructorToCall));
setConstructorAccessor(c, acc);
c.setAccessible(true);
return c;
}
使用场景:UID一旦发生变化,服务端传递的数据,客户端反序列化就会报错。这样可以保证客户端用最新的数据;因为若不显式定义 serialVersionUID 的值,Java 会根据类细节自动生成 serialVersionUID 的值,如果对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,也有可能会导致不同的serialVersionUID。所以 ide 才会提示声明 serialVersionUID 的值。
4、静态变量与trasient变量不会被序列化。这是因为:序列化保存的是对象的状态,静态变量数以类的状态,因此序列化并不保存静态变量。
5、如果类的一个成员未实现序列化的接口,那么也会报错。NotSerializableException。如果子类是可序列化的,但是超类不是。可以通过下边这种方式来让父类的数据也被序列化(这里父类必须要有一个无参构造方法,方法名不能写错,如果不写这段代码,就会读出来的值是空的。):
private void writeObject(ObjectOutputStream out) throws IOException {//不是重写父类的方案
out.defaultWriteObject();
out.writeObject(getSex());
out.writeInt(getId());
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
setSex((String)in.readObject());
setId(in.readInt());
}
6、是否可以通过自定义的序列化流程来覆盖Java默认的序列化流程?可以,给对象增加5中的两个方法:这两个方法何时调用的呢(见后边的流程分析)?
7、如果父类实现了序列化,那么默认子类本身就会被序列化,可以通过在子类中编写这几个方法让子类不可以序列化:一旦序列化子类,就让它抛出异常。
private void writeObject(ObjectOutputStream out) throws IOException {
throw new NotSerializableException("Can not serialize this class");
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
throw new NotSerializableException("Can not serialize this class");
}
private void readObjectNoData() throws ObjectStreamException {
throw new NotSerializableException("Can not serialize this class");
}
8、序列化的整体流程:
(1)java.io.ObjectOutputStream#writeObject
public final void writeObject(Object obj) throws IOException {
if (enableOverride) {
writeObjectOverride(obj);
return;
}
try {
writeObject0(obj, false);
} catch (IOException ex) {
if (depth == 0) {
writeFatalException(ex);
}
throw ex;
}
}
(2)java.io.ObjectOutputStream#writeObject0
private void writeObject0(Object obj, boolean unshared)
throws IOException
{
......
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);
}
......
// 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) {// 如果某个引用实现了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);
}
}
// java.io.ObjectStreamClass#lookup(java.lang.Class<?>, boolean)
static ObjectStreamClass lookup(Class<?> cl, boolean all) {
......
if (entry == null) {
try {
entry = new ObjectStreamClass(cl);
} catch (Throwable th) {
entry = th;
}
if (future.set(entry)) {
Caches.localDescs.put(key, new SoftReference<Object>(entry));
} else {
// nested lookup call already set future
entry = future.get();
}
}
......
}
// java.io.ObjectStreamClass#ObjectStreamClass(java.lang.Class<?>)
private ObjectStreamClass(final Class<?> cl) {
......
// 下边代码完成我们自定义方法的保存。
if (externalizable) {
cons = getExternalizableConstructor(cl);
} else {
cons = getSerializableConstructor(cl);
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class<?>[] { ObjectOutputStream.class },
Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",
new Class<?>[] { ObjectInputStream.class },
Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(
cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);
}
writeReplaceMethod = getInheritableMethod(
cl, "writeReplace", null, Object.class);
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
return null;
......
}
(3)java.io.ObjectOutputStream#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);
// 区分是用的Externalizable还是serializable
if (desc.isExternalizable() && !desc.isProxy()) {
writeExternalData((Externalizable) obj);
} else {
writeSerialData(obj, desc);
}
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
(4)java.io.ObjectOutputStream#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或者readObject,会走这里。原因在ObjectStreamClass的构造方法里边,我们写的这两个方法都会被存起来。
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);
}
}
}
// java.io.ObjectStreamClass#ObjectStreamClass(java.lang.Class<?>)
private ObjectStreamClass(final Class<?> cl) {
......
cons = getSerializableConstructor(cl);
// 我们自定义的writeObject,readObject都会被存起来。
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class<?>[] { ObjectOutputStream.class },
Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",
new Class<?>[] { ObjectInputStream.class },
Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(
cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);
}
domains = getProtectionDomains(cons, cl);
......
}
(5)java.io.ObjectOutputStream#defaultWriteFields
private void defaultWriteFields(Object obj, ObjectStreamClass desc)
throws IOException
{
......
try {
// 递归解析所有的成员变量,特别是带有引用的那种
writeObject0(objVals[i],
fields[numPrimFields + i].isUnshared());
} finally {
if (extendedDebugInfo) {
debugInfoStack.pop();
}
}
}
}
}
其实,这里边才是真正真正实现序列化的地方:
private void writeString(String str, boolean unshared) throws IOException {}
private void writeArray(Object array, ObjectStreamClass desc, boolean unshared)
throws IOException
{}
需要特别注意的是枚举类型,它只是存了Name。
Java的序列化机制针对枚举类型是特殊处理的。简单来讲,在序列化枚举类型时,只会存储枚举类的引用和枚举常量的名称。随后的反序列化的过程中,这些信息被用来在运行时环境中查找存在的枚举类型对象。利用枚举使用的单例可以避免在序列化之后失效。枚举存储的时候只是存了一个name,查找的时候通过字典,利用名字查找;
// java.io.ObjectStreamClass#ObjectStreamClass(java.lang.Class<?>)
private ObjectStreamClass(final Class<?> cl) {
......
cons = getSerializableConstructor(cl);
// 我们自定义的writeObject,readObject都会被存起来。
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class<?>[] { ObjectOutputStream.class },
Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",
new Class<?>[] { ObjectInputStream.class },
Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(
cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);
}
domains = getProtectionDomains(cons, cl);
......
}
9、Parcelable与Serializable的区别:
Parcelable修改涉及到跨进程通信,而Binder是对跨进程通信的数据量有限制。同步是1M-8K,异步是(1M-8K)/ 2;
10、面试题:
(1)反序列化后的对象,需要调用构造方法吗?
直接从二进制解析出来进行强转,不会调用到构造方法。碎片的产生原因,反序列化的时候,他的成员变量需要使用无参构造方法。这个时候会产生大量的临时碎片。
(2)序列化之前的对象与序列化后边的对象是什么关系?
深拷贝(浅拷贝: 对于仅仅是复制了引用(地址),换句话说,复制了之后,原来的变量和新的变量指向同一个东西,彼此之间的操作会互相影响,为浅拷贝。 深拷贝:而如果是在堆中重新分配内存,拥有不同的地址,但是值是一样的,复制后的对象与原来的对象是完全隔离,互不影响,为深拷贝。),完全不同的对象,但是枚举除外。
(3)Android里边为什么要设计出Bundle而不是使用HashMap
Bundle内部使用了ArrayMap,它的存储空间效率会比HashMap好一些。
(4)SerialVersionID的作用?
是用来做版本控制。
(5)Android中Intent/Bundle的通信原理与大小限制?
Binder的通信原理;
(6)为什么Intent不能直接在组件之间传递对象,而是要通过序列化机制?
需要与AMS交互,这是两个不同的进程,需要序列化的操作。
(7)序列化与持久化的区别与关系?
序列化是为了进程间数据交互而设计的,持久化是为了把数据存储下来。