1. 什么是序列化与反序列化?
- 序列化就是将代码中的对象的某一个状态转化成字节数组的过程,也就是转化成二进制文件的过程。反序列化与之相反。
2. 为什么要进行序列化?
- 在将对象存储在文件中或者通过网络进行传输的时候,对象是不能直接存储和传输的,所以要将它序列化为对应的二进制代码。
3. 实现序列化的常用方式有哪些?
- 使用Java的序列化协议(实现Serializable接口)
- 使用Google的序列化协议(Protocol buff)
4.两种序列化方法的区别有哪些?
- 首先来看Java序列化协议的序列化过程
/**
* @Description 对象实体类实现Serializable接口
*/
public class User implements Serializable {
private int id;
private String name;
private String password;
constructor and getters、setters...
}
/**
* 对象反序列化过程
* @param bytes
* @throws IOException
* @throws ClassNotFoundException
*/
public static void toUser(byte[] bytes) throws IOException, ClassNotFoundException {
ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(bytes));
User user = (User) inputStream.readObject();
System.out.println("id: " + user.getId());
System.out.println("name: " + user.getName());
System.out.println("password: " + user.getPassword());
}
/**
* 对象序列化过程
* @return
* @throws IOException
*/
public static byte[] toBytes() throws IOException {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(stream);
User user = new User(1,"admin", "123");
outputStream.writeObject(user);
byte[] bytes = stream.toByteArray();
System.out.println(Arrays.toString(bytes));
return bytes;
}
### 结果
[-84, -19, 0, 5, 115, 114, 0, 13, 99, 111, 109, 46, 106, 97, 118, 97, 46, 85, 115, 101, 114, 98, 0, -44, -50, -126, 35, -58, 126, 2, 0, 3, 73, 0, 2, 105, 100, 76, 0, 4, 110, 97, 109, 101, 116, 0, 18, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 76, 0, 8, 112, 97, 115, 115, 119, 111, 114, 100, 113, 0, 126, 0, 1, 120, 112, 0, 0, 0, 1, 116, 0, 5, 97, 100, 109, 105, 110, 116, 0, 3, 49, 50, 51]
id: 1
name: admin
password: 123
- Protocol buff序列化对象的过程(文件配置省略了,读者自行百度)
//user.proto 文件
option java_package = "com.proto";
option java_outer_classname = "UserModule";
message User {
required int32 id = 1;
required string name = 2;
required string password = 3;
}
/**
* 反序列化
* @param bytes
* @throws InvalidProtocolBufferException
*/
public static void toUser(byte[] bytes) throws InvalidProtocolBufferException {
//反序列化为对象
UserModule.User user = UserModule.User.parseFrom(bytes);
System.out.println("id: " + user.getId());
System.out.println("username: " + user.getName());
System.out.println("password: " + user.getPassword());
}
/**
* 序列化
* @return
*/
public static byte[] toBytes() {
//获取一个PBUser构造器
UserModule.User.Builder builder = UserModule.User.newBuilder();
//设置数据
builder.setId(1).setName("admin").setPassword("123");
//构造对象
UserModule.User user = builder.build();
//序列化
byte[] bytes = user.toByteArray();
System.out.println(Arrays.toString(bytes));
return bytes;
}
### 结果
[8, 1, 18, 5, 97, 100, 109, 105, 110, 26, 3, 49, 50, 51]
id: 1
username: admin
password: 123
所以从结果来看,java序列化要比protobuf序列化数组长很多,也就意味着进行网络传输时protobuf所需带宽更少,传输效率更高。
5. 产生这种区别的原因是什么?
- Java的序列化包含了类名,字段名,字段属性,字段值等信息,而Protocolbuf只包含了字段值,其余信息是通过配置文件表示的。
- Protocolbuf存取字节数组的方式采用的可伸缩性的方法。
例如对int字段的存取,Java序列化是固定长度的,比如int就是四个字节
/**
* Java存储方式
* @param i
* @return
*/
public static byte[] int2bytes(int i){
byte[] bytes = new byte[4];
bytes[0] = (byte)(i >> 3*8);
bytes[1] = (byte)(i >> 2*8);
bytes[2] = (byte)(i >> 1*8);
bytes[3] = (byte)(i >> 0*8);
return bytes;
}
而Procobuf采用伸缩方式,对于int存取1-5个字节
/**
* Encode and write a varint. {@code value} is treated as
* unsigned, so it won't be sign-extended if negative.
*/
public void writeRawVarint32(int value) throws IOException {
while (true) {
/*0x7f 0111 1111
~0x7f 1000 0000
value&~0x7f 就是除去value(32位)的后7位后判断是否为0
即判断value是否小于 0x7f*/
if ((value & ~0x7F) == 0) {
writeRawByte(value);
return;
} else { //如果不是,把value的后7位加上第八位1写入,即写入1xxx xxxx,然后value右移7位
writeRawByte((value & 0x7F) | 0x80);
value >>>= 7;
}
}
Protocolbuff将每一个字节的最高位定义为这个字节是否有数据的标记位,没有数据的字节不存,所以int总共有32-4=28位可用,所以如果大于28位的int值将会多用一个字节进行存,所以int占用1-5个字节。