读完这篇文章你将会收获到
序列化就是将对象的状态信息转为可以存储或者传输的形式的过程
比如说将对象序列化之后存储在硬盘上
比如说将对象序列化之后返回给调用方
反序列化则是序列化的反过程
Serializable
我们在 ``Java
` 中经常借助
`Serializable
` 和
`ObjectOutputStream
` 和
`ObjectInputStream
`` 进行序列化和反序列化操作
public class Person implements Serializable {
private String name;
private String wish;
.............
.............
}
private void serialize() throws Exception {
Person person = new Person();
person.setName("coderLi");
person.setWish("被关注");
FileOutputStream fileOutputStream = new FileOutputStream("coderLi.per");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(person);
objectOutputStream.close();
fileOutputStream.close();
}
private void deserialize() throws Exception {
FileInputStream fileInputStream = new FileInputStream("coderLi.per");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
Person person = (Person) objectInputStream.readObject();
System.out.println(person);
}
在 ``Java
`` 中一个对象想要序列化成功、必须满足两个条件
在 ``Serializable
` 接口中其实并无任何的方法、只是单纯的一个空接口。它的作用仅仅是作为一个标记。我们可以在
`java.io.ObjectOutputStream#writeObject0
`` 中找到其判断
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 {
.......
.......
throw new NotSerializableException(cl.getName());
}
可以看到如果你既不是 ``String
` ,
`Array
` ,
`Enum
` , 也不是
`Serializable
`` , 那么你就等着吃异常吧 !
Externalizable
``Externalizable
` 继承
`Serializable
`` 接口并添加了两个方法,通过实现这两个类来序列化或反序列化对象
public interface Externalizable extends java.io.Serializable {
void writeExternal(ObjectOutput out) throws IOException;
void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
}
public class Animal implements Externalizable {
private String name;
private int age;
@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println(out.getClass().getSimpleName());
out.writeInt(age);
out.writeObject(name);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
System.out.println(in.getClass().getSimpleName());
age = in.readInt();
name = (String) in.readObject();
}
}
但是使用 ``Externalizable
`` 要注意
调试发现、序列化的时候 ``ObjectOutput
` 的参数对象的类型是
`ObjectOutputStream
` 、反序列化的时候
`ObjectInput
` 的参数对象的类型是
`ObjectInputStream
``
序列化 ID
在上面的例子中、我们都没有加上 ``serialVersionUID
` , 我们现在在
`Animal
`` 中加上并随意赋值
private static final long serialVersionUID = 1L;
然后我将其序列化到 ``animal.ani
` 文件中、然后修改
`serialVersionUID
` 的值变为
`2L
``、然后看看会发生什么
java.io.InvalidClassException: com.demo.Animal;
local class incompatible: stream classdesc serialVersionUID = 1,
local class serialVersionUID = 2
我们可以从异常信息中知道、序列化的时候是有保存其 ``serialVersionUI
``D 的,如果反序列化的时候两个值不一致、则会反序列化失败
那假如我们不指定这个静态常量的值,它是根据什么生成的?
其实这个值的生成是根据这个类的信息去生成的,我尝试了一下、不指定 ``serialVersionUID
` 序列化之后、然后为这个类增加一个非
`final
` 的属性,反序列化就报上面的异常了。当然,你也可以为这个类增加方法、同样也会导致
`serialVersionUID
`` 不同继而反序列化失败
所以没有什么特殊要求的时候、我们可以将 ``serialVersionUID
` 设置为
`1
`` (当然也可以是其他值,只要指定值就行) , 那么就不会说因为服务端升级改东西了,客户端暂时没有升级而导致反序列化失败
静态变量序列化
默认情况下、静态变量不参与序列化。对象的序列化、当然只是序列化对象的属性啦
例子就不贴了
父类的序列化
要将父类的属性也序列化、那就让父类实现 ``Serializable
` 接口吧。如果父类不实现呢?那爸爸你给我一个无参的构造方法吧,那么这个时候我就不序列化你了,但是因为又这个无参的构造函数、那么我在反序列化的时候就可以调用这个构造函数、因为
`Java
`` 里面、是先有父对象才有子对象嘛,当然反序列完之后的父类属性、如果没有在在无参构造方法中赋值的话、那么就是其类型的默认的值了
ArrayList 序列化
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
transient Object[] elementData;
private int size;
............
}
我们在上面的已经分析了
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 {
.......
.......
throw new NotSerializableException(cl.getName());
}
当一个对象是 ``Serializable
` 的实例、那么就会进入到
`writeOrdinaryObject
`` 中,那么我们看看它这个方法做了什么
private void writeOrdinaryObject(Object obj,
ObjectStreamClass desc,
boolean unshared)
throws IOException
{
try {
desc.checkSerialize();
bout.writeByte(TC_OBJECT);
writeClassDesc(desc, false);
handles.assign(unshared ? null : obj);
if (desc.isExternalizable() && !desc.isProxy()) {
writeExternalData((Externalizable) obj);
} else {
writeSerialData(obj, desc);
}
} finally {
}
}
``ArrayList
` 并没有实现
`Externalizable
` 接口,所以直接进入到
`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;
if (slotDesc.hasWriteObjectMethod()) {
...........
slotDesc.invokeWriteObject(obj, this);
bout.setBlockDataMode(false);
bout.writeByte(TC_ENDBLOCKDATA);
} finally {
curContext.setUsed();
curContext = oldContext;
}
curPut = oldPut;
} else {
defaultWriteFields(obj, slotDesc);
}
}
}
我们 能看到这里有一个分支,如果你有一个叫做 ``writeObject
` 的方法,那么我就调用你这个方法进行序列化,如果你没有则调用
`defaultWriteFields
` 方法。我们看看
`slotDesc.invokeWriteObject
`` 方法吧
void invokeWriteObject(Object obj, ObjectOutputStream out)
throws IOException, UnsupportedOperationException
{
requireInitialized();
if (writeObjectMethod != null) {
try {
writeObjectMethod.invoke(obj, new Object[]{ out });
} catch (InvocationTargetException ex) {
......
throw (IOException) th;
} catch (IllegalAccessException ex) {
throw new InternalError(ex);
}
} else {
throw new UnsupportedOperationException();
}
}
private Method writeObjectMethod;
private Method readObjectMethod;
我们看到这两个属性的定义、一个是 ``writeObject
`、一个是
`readObject
` ,这里便去
`invoke
``
我们再回到 ``ArrayList
` 这个方法,巧了、在
`ArrayList
`` 中还真有这么两个方法
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
int expectedModCount = modCount;
s.defaultWriteObject();
s.writeInt(size);
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
* Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
* deserialize it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
s.defaultReadObject();
s.readInt();
if (size > 0) {
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
其实为啥 ``ArrayList
` 需要自定义一个序列化和反序列化?其实大概看看其序列化的代码、就能大概估摸出其意图,
`ArrayList
`` 中的数组大小本身是比实际存储的元素个数要多的,我们序列化的时候没必要将没有用到的数组空间也序列化下来、显然是浪费性能的
其实代码中可以看到 ``size
` 被序列化了两次,而在反序列化的时候却直接丢弃第二次序列化的
`size
` ?
`why
``
其实这么做是为了兼容问题、在旧版本的 ``JDK
` 中、
`ArrayList
` 的实现有所不同、会对
`length
`` 字段进行序列化
而在现在的版本中、不再序列化 ``length
` 了。为了能是新版本的序列化的对象能在旧版中能顺利的反序列化、所以就将
`size
`` 序列化两次了
序列化对单例的破坏
我们常见的单例模式 比如说 ``DCL
``
public class Singleton implements Serializable{
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
我们将其序列化、然后反序列化、那么得到的是一个新的对象,而不是原来的对象、也就是说 JVM 中现在同时存在两个 ``Singleton
`` 对象了
其实挺扯淡的、你想单例还实现 ``Serializable
` 接口? 哈哈哈、好吧,那我们把
`Serializable
` 接口去掉,那是不是还可以通过反射创建一个新的实例,那行我们在构造方法中判断一下静态变量
`singleton
` 是否为空、不为
`null
`` 就直接抛异常。貌似这样子还行,但是这个判断放在构造函数里面是否会太迟了。
我们回到上面的代码中、假设就是有这么扯淡的代码、要单例还实现了 ``Serializable
` 接口、那么我们可以保障其在
`JVM
`` 中的唯一性呢
我们查看 ``ObjectInputStream
` 的
`readObject
`` 方法开始追踪
readObject->readObject0->readOrdinaryObject
在这个方法里面看到了一个比较跟上面 ``invokeWriteObject
` 类似的方法:
`invokeReadResolve
``
点进去看了下、哦吼、我们可以在 ``Singleton
` 中实现一个
`readResolve
`` 的方法、它会在反序列化的时候被调用到、然后就最终返回给反序列化调用方
public Object readResolve(){
return singleton;
}
但是这里还是存在这么一个问题、在某个时刻,``JVM
`` 确实存在过两个这个单例类的对象、即使它没有被返回给反序列化的调用方,但却是真实存在
那么有没有一个好的单例模式可以用呢? 有、那就是枚举。因为枚举的反序列化最终调用的是 ``Enum.valueOf
`` 的方法
其实这就是为啥推荐使用枚举作为单例的原因。对于反射调用构造方法、枚举也是做了限制直接抛异常的
评论