最近遇到了一位同事咨询:Redis在存储数据的时候直接写入json字符串和序列化byte之后写入,产生的aof文件有没有大小差距?
这个问题咨询导师获得的答案是:两者产生的aof文件大小是一样的。
虽然知道了是因为在数据传输的时候字符会被转为byte数组再进行传输,但对这个过程是什么时候发生的,为什么要转换为byte进行传输还是存在疑问。周末对序列化进行了学习和总结。
1. 序列化是什么?
序列化就是将java对象转换为字节序列,反序列化当然就是将字节序列转换为java对象。
序列化的基本思路就是将对象转换为一组字节,然后将字节写入流中,可以使用ObjectOutputStream来序列化一个对象:
public static void serialize(Object obj, OutputStream out) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(obj);
oos.close();
}
2. 为什么要有序列化
先来看一个熟悉的生活场景: 在一般家庭里面通常会有一张可以坐下10-20个人的大圆桌,这张大圆桌通常只在人多的时候会被拿出来用,平时都是以桌板和桌腿的形式存放在杂物间。
这个场景中的大圆桌就和Java中的对象是非常相似的,它会在客厅(堆)中占据一个大空间,在不用的时候我们会把他拆卸((序列化))成桌面和桌腿等零件(字节序列)收到杂物间(磁盘)保存,在需要的时候再按照说明书(编码)拼装起来(反序列化)。我们还可能会搬家,在搬家的时候这张桌子也是以零件的形式运输的。
通过这个简单的例子,我们就能知道序列化的目的是为了更方便地存储和传输对象。
3. 如何序列化和解序列化
如果要让类可以被序列化,类需要实现Serializable接口。
Serializable接口时一个tag类的标记接口,没有需要实现的方法,唯一作用是告诉虚拟机和使用他的人:实现它的类是可以被序列化的。
3.1 将序列化对象存储到文件
public static void main(String[] args) {
Person person1 = new Person("Ellie", 12);
Person person2 = new Person("Pokia", 14);
try (
// 创建出FileOutputStream
FileOutputStream fileOutputStream = new FileOutputStream("serialize_test.txt");
//创建出ObjectOutStream
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream)
) {
//写入对象
objectOutputStream.writeObject(person1);
objectOutputStream.writeObject(person2);
//记得关闭ObjectOutPutStream和fileOutputStream,我把他放在try()里面,try块退出时,会自动调用资源的close()方法,关闭资源
} catch (IOException ioException) {
System.out.println("文件写入异常:" + ioException.getMessage());
}
}
3.2 将文件中序列化对象还原
public static void main(String[] args) {
try (
//创建FileInputStream
FileInputStream fileInputStream = new FileInputStream("serialize_test.txt");
//创建ObjectInputStream
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream)
) {
//读取对象
Person person1 = (Person) objectInputStream.readObject();
Person person2 = (Person) objectInputStream.readObject();
//打印对象
System.out.println(person1.toString());
System.out.println(person2.toString());
} catch (IOException exception) {
System.out.println(exception.getMessage());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
3.3 将序列化对象保存到字节数组中和从字节数组中还原对象
public static void main(String[] args) {
Person person = new Person("Wigfrid", 16);
byte[] bytes = person.serializeToByteArray();
person.deSerializeFromByteArray(bytes);
}
/**
* 序列化对象保存到字节数组中
*
* @return
*/
private byte[] serializeToByteArray() {
byte[] bytes = new byte[0];
try (
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
) {
objectOutputStream.writeObject(this);
bytes = outputStream.toByteArray();
} catch (Exception e) {
System.out.println(e.getMessage());
}
return bytes;
}
/**
* 将字节数组还原成对象
*
* @param bytes
*/
private void deSerializeFromByteArray(byte[] bytes) {
try (
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream)
) {
Person person = (Person) objectInputStream.readObject();
System.out.println(person.toString());
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
4. 序列化机制
Java 中的序列化机制是基于对象流的,即通过将对象写入到流中来实现序列化。Java 中的对象流包括 ObjectOutputStream 和 ObjectInputStream 两个类,分别用于将对象写入到流中和将对象从流中读取出来。
Java 中的序列化机制支持以下特性:
- 对象引用:序列化时会将对象的引用序列化为一个标识符,反序列化时会根据这个标识符来恢复对象的引用关系。被引用的对象也必须是一个实现了序列化接口的类,否则会序列化失败
- 继承关系:序列化时会将对象的类信息一并序列化,反序列化时会根据这个类信息来恢复对象的类关系
- 版本控制:序列化时会将对象的版本号一并序列化,反序列化时会根据这个版本号来判断是否可以反序列化。如果对象的版本号与当前类版本不匹配,则抛出 InvalidClassException 异常
- 同一对象序列化一次:所有保存到磁盘的对象都有一个序列化编号,当程序试图序列化一个对象时会先检查对象是否已经被序列化过,只有对象未被序列化过,才会将此对象序列化为字节序列输出,否则,直接输出编号即可。
5. 序列化的坑
- 如果一些变量不能或者不应该被序列化,应该把他标记为transient(瞬时的)。在反序列化的时候,transient标记的标量他们的值不是null就是基本数据类型(primitive)的默认值
//不想被序列化的变量currentId
transient String currentId;
//需要被序列化的变量
String userName;
- 静态对象不会被序列化
静态对象static代表每个类一个,当对象被还原的时候,静态变量会维持类中原本的样子,而不是存储时的样子 - 由于java序利化算法不会重复序列化同一个对象,只会记录已序列化对象的编号。如果序列化一个可变对象(对象内的内容可更改)后,更改了对象内容,再次序列化,并不会再次将此对象转换为字节序列,而只是保存序列化编号。
学习完《Head First Java》序列化部分,我明白了使用Jedis存储数据的时候直接写入json字符串和byte[]在传输的时候都是以byte序列形式传输到redis服务器的,既然传输的内容一致,持久化产生的aof文件大小也就没有差异,也找到了Jedis将字符串转为为byte[]发生的位置。