简述
开始前先搞清楚一个问题什么是序列化?
就是一个目的:将 JAVA 对象转换成二进制
的数据进行各种操作,如传输、保存、增删等。
是的,你没看错,就是要转成二进制
的数据。
主要聊三个问题: Java序列化与反序列化是什么? 为什么需要序列化与反序列化? 如何实现Java序列化与反序列化?
还是一样,先说怎么用,再说为什么。
使用
这次的示例是准备了几种场景:
- 序列化后文件存储
- 序列化后内存中使用
java当中提供了原生序列化方式,也就是把内存中的数据,转换成二进制,或者把二进制数据,转换成内存数据的API。 在java的世界中,二进制数据称为流,通过流,也就是抽象成一个个流对象进行处理。
序列化并持久化
首先第一步,需要实现序列化接口,才能被序列化。
代码语言:javascript复制public class TestObj2 implements Serializable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
然后再去操作这个初序列化的对象。 这里要做一个示例,就是序列化之后,把这个对象存储起来。 对象还能存储到磁盘当中? 那是必然的,对象也是创建出来的数据,数据能放到内存当中,当然也可以放到磁盘当中。
代码语言:javascript复制import java.io.*;
public class TestSerializable {
public static void main(String[] args) {
try {
// 创建一个测试对象
TestObj obj = new TestObj();
// 输出流,持久化对象
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("/tmp/testObject.obj"));
out.writeObject(obj);
// 输入流,反序列化对象
ObjectInputStream in = new ObjectInputStream(new FileInputStream("/tmp/testObject.obj"));
TestObj2 newObj = (TestObj2) in.readObject();
System.out.println(newObj.getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
这样就完成了一次序列化操作,可以看看存储位置的磁盘保存形式。
以上就是java提供的序列化操作,通过两个Stream
实现,分别是:
ObjectOutputStream
ObjectInputStream
实现序列化过程
实现接口,IDE 会自动给一个 serializableId,没有也得自己给加上去,不加项目中出现过问题,踩坑。
如果变量声明为 transient
,则不会被序列化。
JDK java.io 提供 API 操作:
- ObjectOutputStream.writeObject 对象输入流
- ObjectInputStream.readObject 对象输入流
实现步骤
- ObjectOutputStream.writeObject 将对象进行序列化,把得到的字节序列写到一个目标的输出流中。
- ObjectInputStream.readObject 从源输入流中读取字节序列,再把它们反序列化成一个对象,并将其返回。
两个接口的不同序列方式 大部分情况下只要实现 Serializable 接口就够用了,具体的还有几种情况,需要注意。 ObjectOutputStream/ObjectInputStream 下面简称 流处理类。
- 直接实现 Serializable 接口,流处理类调用默认方法;
- 实现 Serializable 接口,并实现接口方法 writeObject/readObject,流处理类调用实现类方法;
- 实现 Externalizable 接口,必须实现接口方法 readExternal/writeExternal,流处理类调用实现类方法。
这两个API相对简单,就不赘述了。
序列化后内存
实际上就是序列化成二进制数据,然后操作这个数据。 直接给测试类,感兴趣的同学可以自己复制代码验证一下。
代码语言:javascript复制package test1;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class Test {
/**
* 序列化
*
* @param object
* @return
*/
public static byte[] serialize(Object object) {
byte[] bytes = null;
ObjectOutputStream oos = null;
ByteArrayOutputStream baos = null;
try {
// 序列化
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(object);
bytes = baos.toByteArray();
} catch (Exception e) {
System.out.println(e.getMessage());
}
return bytes;
}
/**
* 反序列化
*
* @param bytes
* @return
*/
public static Object unserialize(byte[] bytes) {
Object value = null;
ByteArrayInputStream bais = null;
try {
// 反序列化
bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
value = ois.readObject();
} catch (Exception e) {
System.out.println(e.getMessage());
}
return value;
}
public static void main(String[] args) {
TestObj2 testObj2 = new TestObj2();
testObj2.setName("test123");
byte[] serialize = serialize(testObj2);
TestObj2 unserialize = (TestObj2) unserialize(serialize);
System.out.println(unserialize.getName());
}
}
class TestObj2 implements Serializable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
结果
test123
解惑
对开头说的几个问题一一说明
为什么需要序列化与反序列化
上面说了,序列化可以把对象转成二进制数据,其本质就是二进制数据可以进行各项操作,如保存、传输、复制。 举个例子: Http调用的对象为什么要序列化?因为转成二进制数据后,可以传输。 leveldb存储二进制数据。
序列化后,数据是二进制的。 机计算时只能使用二进制,因为只有高电平和低电平,所以用二进制,用十进制表示只是为了方便些。
序列化应用场景
1.网络传输
主要应用例如:RPC,将对象进行序列化后,运行远程主机上的服务,就像在本地机上运行对象时一样。 RMI也是一样。
2.持久化
序列化可以将内存中的类写入文件或数据库中。 文件已经演示过了,存数据库,最典型的就是leveldb,就是存二进制数据。
3.java对象状态保存
这个有点意思。 java对象序列化不仅保留一个对象的数据,而且递归保存对象引用的每个对象的数据。
可以将整个对象层次写入字节流中,可以保存在文件中或在网络连接上传递。利用对象序列化可以进行对象的"深复制",即复制对象本身及引用的对象本身。 序列化一个对象可能得到整个对象序列。 比如:将某个类序列化后存为文件,下次读取时只需将文件中的数据反序列化就可以将原先的类还原到内存中。 也可以将类序列化为流数据进行传输。
总的来说就是将一个已经实例化的类转成文件存储,下次需要实例化的时候只要反序列化即可将类实例化到内存中并保留序列化时类中的所有变量和状态。
4.统一编码
可以将二进制数据认为是一种编码。 对象、文件、数据,有许多不同的格式,很难统一传输和保存。
序列化以后就都是字节流了,无论原来是什么东西,都能变成一样的东西,就可以进行通用的格式传输或保存,传输结束以后,要再次使用,就进行反序列化还原,这样对象还是对象,文件还是文件。
总结
序列化在日常开发中,如果涉及到I/O的场景,就不可避免的会使用到序列化,序列化不止有java自带的API,还有别的一些序列化框架如hessian
、protobuf
等也是序列化框架也可以实现,本质上干的事是一样的。
了解序列化的原因和操作之后,在工作中可以少踩坑。