一、序列化简述
序列化是指将结构化的数据( 例如对象 )转化成一个字符串 / 字节数组。转化过程中,不会造成数据信息部分丢失。
反序列化则为反向操作,将已经进行过序列化之后的数据还原回原本的结构化数据。正是序列化中需要保证转化之后的数据信息不会丢失,才能在反序列化之后成功转化为原来的数据。
常见的序列化方式有:Java 标准库中提供的序列化方式、JSON、Hessia、protobuffer、thrift……
序列化的目的是为了方便存储和传输。存储一般是指存储在文件中,而文件一般只能存储字符串 / 二进制数据,无法直接存储对象等结构化数据。而传输一般指的是网络传输,网络传输通常都是使用 socket 进行传输,socket 本质也是文件,因此也是只能传输字符串 / 二进制数据。
二、Java 中实现序列化的常用方式
1. 文本数据
针对于文本数据,首选便是使用 JSON 格式来进行序列化操作。
我们只需在 maven 中央仓库( Maven Repository: Search/Browse/Explore (mvnrepository.com )中搜索 Jackson 的相关依赖,找到任意一个版本然后引入即可使用。使用方式如下:
我们需要先创建一个 ObjectMapper 类的对象,然后使用其中的两个方法即可完成序列化和反序列化的操作:
序列化:writeValueAsString 方法就可以将一个 Java 对象转化成为一个 JSON 字符串,下述代码就是将一个 Map 类型的数据转化成 JSON 字符串。
注意:JSON 提供的序列化和反序列化方法都会抛出 JsonProcessingException,因此需要进行处理。
@RequestMapping("/demo")
@RestController
public class DemoController {
@RequestMapping("/m1")
public String method1() {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> arguments = new HashMap<>();
arguments.put("aaa", 1);
arguments.put("bbb", 2);
try {
return objectMapper.writeValueAsString(arguments);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return "{}";
}
}
反序列化:readValue 可以将 JSON 字符串转化成一个 Java 对象,该方法需要两个参数。第一个参数便是需要转化的数据,第二个参数是指定需要转化为 Java 对象的类型。
其中第二个参数如果是一个简单的对象,也即是没有泛型参数也不是诸如 Map 类的对象,则直接传入该类的类对象即可。如下所示:
@Data
public class User {
private String userName;
private String password;
}
@RequestMapping("/demo")
@RestController
public class DemoController {
@RequestMapping("/m2")
public User method2(@RequestBody String data) {
ObjectMapper objectMapper = new ObjectMapper();
try {
User user = objectMapper.readValue(data, User.class);
System.out.println(user);
return user;
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
}
如果第二个参数是一个带泛型参数或者集合类的复杂类型,则需要使用 TypeReference 来构造一个匿名内部类,传入泛型参数来进行描述:
@RequestMapping("/demo")
@RestController
public class DemoController {
@RequestMapping("/m3")
public void method3(@RequestBody String data) {
ObjectMapper objectMapper = new ObjectMapper();
try {
Map<String, Object> ret = objectMapper.readValue(data, new TypeReference<Map<String, Object>>() {});
System.out.println(ret);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
}
2. 二进制数据
可以看到 JSON 的方式使用起来还是十分方便的,但是由于 JSON 序列后的结果是文本数据,因此无法存储二进制数据。
这是由于 JSON 中有很多特殊符号,例如:{ }," ",[ ]……如果数据是文本,转化为 JSON 之后的键值对中就不会包含这些特殊符号,那么就不会出现歧义,序列化之后可以正常进行反序列化来解析。
但是如果是二进制数据,保不齐数据其中一小段二进制就和 JSON 中某一个特殊符号的 ascii 值相同,如果出现相同就会出现歧义,例如转化为 JSON 之后的键值对中的值是一个字符串,其中一小段二进制的值和 " 的 ascii 值相同,此时 JSON 进行反序列化解析的时候就会发现三个 " ( 本应该只有两个 " ),此时就会发生解析错误,误以为前两个 " 就是值的所有内容。
因此我们就不能对二进制数据使用 JSON 来进行序列化处理,而 Java 标准库中提供的序列化方法就可以对二进制数据使用,这种方式是 Java 中最直接最方便的使用,因为不需要再引入第三方库就可以直接是使用。
首先,我们需要对需要进行序列化的 Java 对象实现 Serializable 接口,来表示该类是可序列化的。实现该接口无需重写任何方法。但是需要注意的是:如果该对象其中有属性也是类,那么这个属性也需要实现该接口,才能达到完全序列化。
例如:
public class School implements Serializable {
private Student student;
private int count;
}
如果 School 对象想要使用 Java 标准库提供的序列化方法,School 类需要实现 Serializable 接口,且由于其中的 student 属性也是一个类,因此 Student 类也需要实现 Serializable 接口。
序列化:创建一个 ObjectOutputStream 流对象,调用其中的 writeObject 方法,传入需要序列化的数据即可。
反序列化:创建一个 ObjectInputStream 流对象,调用其中的 readObject 方法,该方法无需传入参数,返回值是一个 Object 对象。