0%

Type接口

序列化

​ 序列化是将数据对象转换为可传输格式的过程。是一种数据持久化的手段,一般广泛应用于网络传输、RPC、RMI等场景中。

常见的序列化框架:

java jdk自带,使用方便,可序列化所有类,速度慢,占空间
protobuf 速度快,但需要先进行静态编译
kryo 速度快,序列化后体积小;跨语言支持较复杂

java的序列化机制中,需要进行序列化的对象需要实现Serializable接口,这个接口没有方法或者字段,单纯是为了表示可序列化语义。自定义的类通过实现Serializable接口,进而在IO过程中实现序列化和反序列化。

在序列化过程中,会对将要序列化/方序列化的类进行检查,要求操作对象必须是字符串、枚举类、数组类型或者是实现了Serializable的类。

创建一个继承于Serializable接口的类,然后通过流处理将这个类写入到1.txt中,注意,这里写入之后,如果直接打开文件,会发现这是个乱码文件,这是因为序列化写入的是二进制文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Friend implements Serializable{

private String name;


public static void main(String[] args) {
Friend friend = new Friend();
friend.setName("tom");

ObjectOutputStream objectOutputStream = null;
try{
objectOutputStream = new ObjectOutputStream(new FileOutputStream("1.txt"));
objectOutputStream.writeObject(friend);
}catch (Exception e){

}
}
private void writeObject(ObjectOutputStream out) throws IOException {
// 自定义序列化逻辑
System.out.println("1");
}
}

这里的序列化调用链:objectOutputStream.writeObject()writeObject0(obj, false); → writeOrdinaryObject(obj, desc, unshared);(区分是Serializable还是Externalizable)→如果没有实现自定义的序列化策略则调用defaultWriteFields(obj, slotDesc);,如果实现了自定义序列化策略,则通过反射调用到自定义序列化策略slotDesc.invokeWriteObject(obj, this);

但是需要注意,就算是自定义了序列化接口writeObject(),且不进行写入动作,最终的输出文件依然不会是空文件,为了反序列化,包含了一些格式信息,不单纯只有数据,比如有字段类型等信息。

Externalizable的作用是什么?

它相比于Serializable更为灵活,比如一个类有一个父类,在序列化时,如果也想序列化父类中的属性,则这个父类也需要实现Serializable接口;另一种做法是是子类实现Externalizable接口,但是实现该接口需要重写writeExternal()readExternal()方法,这种情况下就可以指定需要序列化的属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Friend2 implements Externalizable {

private String name;

private int age;

@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(name);
out.writeInt(age);
}

@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
name = in.readUTF();
age = in.readInt();
}
}

serialVersionUID的作用?

一般一个类在继承Serializable接口后,都会定义一个字段private static final long serialVersionUID = 1L;,那么它的作用是什么?

它主要是用来校验是否允许进行反序列化的,当将某个类进行序列化后,比如是存储到文件中,会将这个值也序列化到文件中,当进行反序列化时,将这个serialVerionUID进行比对,如果不一致,则抛出InvalidCastException异常。

如果类发生了改变(比如新增字段、删除字段、改变字段类型等),这时如果进行反序列化,可能出现不匹配的情况,如果没有显示声明这个字段值,java会生成一个默认的serialVersionUID,这个值是基于类的结构生成的,当类的结构发生变化的时候,再次序列化时这个值也会发生变化,就会导致使用变更之前的数据进行反序列化会抛出异常。但是我们在使用时,通常为了确保即使类发生了变化,也要序列化成功,会显示的声明这个值。

数组的序列化

在序列化的过程中,如果被序列化对象自定义实现了writeObject()readObject()方法,则会尝试使用自定义的方法进行序列化和方序列化。

ArrayList中,它的存储是transient Object[] elementData,但是这个对象数组又被声明为transient,所以在默认的序列化策略中,并不会对它进行序列化,这是因为ArrayList是动态数组,每次在放满之后会进行动态的扩容,那么就会出现一个数组中有多个null元素,为了保证null元素不被序列化,所以将数组设置为transient后,通过自定义序列化策略进行序列化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
private void writeObject(java.io.ObjectOutputStream s)throws java.io.IOException{
// 写入固定格式
int expectedModCount = modCount;
s.defaultWriteObject();

// 写入容器大小
s.writeInt(size);

// 写入元素值,只循环size次
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}

if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}

private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;

// Read in size, and any hidden stuff
s.defaultReadObject();

// Read in capacity
s.readInt(); // ignored

if (size > 0) {
// be like clone(), allocate array based upon size not capacity
int capacity = calculateCapacity(elementData, size);
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
ensureCapacityInternal(size);

Object[] a = elementData;
// 也只读出size值的元素
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
-------------本文结束感谢您的阅读-------------