【java学习】序列化与反序列化:Json框架、xml转换、msgpack;java序列化
1,概念
1)Json(JavaScript Object Notation)
- json结构:大而占内存。
不能有注释。只支持JSONObject、JSONArray两种结构。
JSONObject(json对象)
k-v对象,不能为null。
JSONArray(数组)
举例:[value1,value2,value3]
- 解析耗时;
1>JSON对比JSONP
JSONP(JSON with Padding).
json返回的是一串数据;
jsonp返回的是脚本代码(包含一个函数调用);
json是理想的数据交换格式,但没办法跨域直接获取,于是就将json包裹(padding)在一个合法的js语句中作为js文件传过去,来实现跨域获取数据。
2>json对比jsonb
json和jsonb都作为数据库的一种格式,被很多数据库使用:Postgresql 和 MongoDB 等。
主要区别:
| 区别 | json | jsonb | 备注 |
|---|---|---|---|
| 存储方式 | 保存为文本格式 | 保存为二进制格式的 | |
| 磁盘空间 | 占用更多 | ||
| 读写 | 更耗时 | ||
| 解析 | 更快,操作更方便,很多函数支持。 |
- 如果你的应用只用json表示,PostgreSQL只用于保存与获取时,你应该使用json.
- 如果你需要在PostgreSQL中做比较多的json值的操作,或者在一些json字段上使用索引时,你应该使用jsonb
2)MessagePack
It’s like JSON.
but fast and small.
- 高效的二进制序列化格式,兼容跨语言;
- 比json快、压缩率高;
- 兼容性差,必须按照顺序保存字段。
3)xml
- 比json描述性更高。
- 编码解码性能差。
4)ProtocolBuffer(protobuf)
- google出品,一般用于传输数据比较大的API。
- 强大,灵活,但是对内存的消耗会比较大。
- Netty的高性能就默认提供了对GoogleProtobuf的支持。
- 是否支持跨语言:c++、java、Python.
5)FlatBuffer
是google的一个跨平台串行化库,开发这个最初是用在游戏项目中,是二进制存储和二进制读取,不需要解析,通过fb的二进制文件生成对象的时间可以忽略。需要通过工具把普通model文件生成flatBuffer格式的model文件。flatBuffer解析library就四个文件,代码很少。本地cache比较大的可以考虑通过这种方式存储和读取。
Json是当今全服务平台的轻量级数据传输格式,Json量级轻,并且可读性强,使用友好却不过时,Json是语言独立的数据格式,但Json转换的时候却耗费较多的时间和内存,Facebook尝试过将所有的App中Json传输都换成使用Flatbuffers,而最终的效果遵循Android快速响应界面的16ms原则。
6)Nano-Proto-Buffers
基于Protocal,为移动终端做了特殊的优化,代码执行效率更高,内存使用效率更佳。
7)protostuff
8)thrift
9)hession
10)kryo
11)avro
12)fst
2,json框架使用
1)json-lib库(net.sf.json-lib)
json-lib是一个纯Java的开源库。
- 使用率低、性能低。
- 不支持类包含Map属性或者Map属性中包含List属性;
1>String转换为JSONObject
JSONObject jsonObject = JSONObject.fromObject(jsonStr);
2>JSONObject转换为java对象
Person person = (Person)JSONObject.toBean(obj,Person.class);
3>java对象转换为String
//将java对象转换为json对象
//将json对象转换为字符串
String str = json.toString();
4>json数组转java数组
//将JSON数组转换为Java数组
String[] strArray = (String[]) JSONArray.toArray(jsonArray, String.class);
2)FastJson框架(com.alibaba.fastjson)
特点:
- 速度快;
- 严格按照JavaBean的规范;
实体类有一点不对就无法解析。而Gson就无此要求;
JavaBean规范
在fastJson中,解析用到的实体类必须严格按照JavaBean规范来写。
特点:
- public类
- 有一个无参数的构造方法。
- private属性、有getXxx()和setXxx方法(方法的第四个字母为大写)。
- 必须实现序列号接口。
- fastjson会把数值类型的null(如Integer)转换为0;
1>序列化&反序列化
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
//Java Bean序列化
String str = JSONObject.toJSONString(obj);
//Java Bean反序列化1
People people = (People)JSONObject.parseObject(str, People.class);
//Java Bean反序列化2 支持泛型
Result<People> obj = (Result<People>) JSON.parseObject(js, new TypeReference<Result<People>>(){});
//集合 序列化
String str = JSONArray.toJSONString(list);
//集合 反序列化1
List<People> list = JSONArray.parseArray(str, People.class);
//集合 反序列化2 支持泛型
List<People> list = JSON.parseObject(json, new TypeReference<List<People>>(){});
//Map 序列化 同上
//Map 反序列化
Map map= (Map)JSONObject.parseObject(str);
2>重复数据$ref
循环引用:当一个对象包含另一个对象时,fastjson就会把该对象解析成引用。引用是通过$ref标示的,下面介绍一些引用的描述
"$ref":".." 上一级
"$ref":"@" 当前对象,也就是自引用
"$ref":"$" 根对象
"$ref":"$.children.0" 基于路径的引用,相当于 root.getChildren().get(0)
3>日期处理
User user = new User();
user.setDate(new Date);
// 序列化 {"date":1516884789616}
System.out.println(JSON.toJSONString(user));
// 序列化处理时间,方式一 {"date":"2018-01-25 20:53:09.616"}
System.out.println(JSON.toJSONStringWithDateFormat(user, "yyyy-MM-dd HH:mm:ss.SSS"));
// 序列化处理时间,方式二:ISO-8601日期格式 {"date":"2018-01-25T20:53:09.616+08:00"}
System.out.println(JSON.toJSONString(user, SerializerFeature.UseISO8601DateFormat));
// 序列化处理时间,方式三:全局修改日期格式 {"date":"2018-01-25"}
JSON.DEFFAULT_DATE_FORMAT = "yyyy-MM-dd";
System.out.println(JSON.toJSONString(user, SerializerFeature.WriteDateUseDateFormat));
4>枚举处理
5>自省处理
6>反序列化漏洞
v1.2.69中已修复;v1.2.68可通过safeMode规避。
fastjson序列化一个子类时,会抹去子类只保留父类或接口,此时反序列化无法拿到原始类型。为解决这个问题,fastjson引入@type,试图把json内容反序列化成这个对象,并调用setter方法。==》攻击者使用@type,指定一个想要攻击的类库json串,通过set方法可以调用攻击命令。
3)Gson
- Google API,适用于小量的数据解析
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
1>基本数据类型的解析:
Gson gson = new Gson();
int i = gson.fromJson("100", int.class); //100
2>基本数据类型的生成:
Gson gson = new Gson();
String jsonNumber = gson.toJson(100); // 100
3>封装通用类
package util;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
import entity.DraftStorgeProblemEntity;
import entity.UploadEntity;
import java.lang.reflect.Type;
import java.util.List;
/**
* Java对象和JSON字符串相互转化工具类
* @author luo
* @date 2018-06-21
*/
public final class JsonUtil {
private JsonUtil(){}
/**
* 对象转换成json字符串
* @param obj
* @return
*/
public static String toJson(Object obj) {
Gson gson = new Gson();
return gson.toJson(obj);
}
/**
* json字符串转成对象
* @param str
* @param type
* @return
*
* 调用方法:
UploadEntity<ProblemEntity> uploadEntity1 = JsonUtil.fromJson(jsonStr, new TypeToken<UploadEntity<ProblemEntity>>() {
}.getType());
*/
public static <T> T fromJson(String str, Type type) {
Gson gson = new Gson();
return gson.fromJson(str, type);
}
/**
* json字符串转成对象
* @param str
* @param type
* @return
*
* 调用方法:
UploadEntity<ProblemEntity> uploadEntity1 = JsonUtil.fromJson(jsonStr, UploadEntity.class);
但这样无法解析内层的ProblemEntity。建议采用TypeToken来调用
*/
public static <T> T fromJson(String str, Class<T> type) {
Gson gson = new Gson();
return gson.fromJson(str, type);
}
/**
* 列表转换为JsonArray
* @param list
* @param <T>
* @return
*/
public static <T> JsonArray list2JsonArray(List<T> list){
return new Gson().toJsonTree(list, new TypeToken<List<T>>() {
}.getType()).getAsJsonArray();
}
/**
* jsonArray转List
* @param jsonArray
* @param clazz
* @param <T>
* @return
*
* 使用:List<UploadEntity> list1 = (List<UploadEntity>) JsonUtil.jsonArray2List(jsonArray, UploadEntity.class);
*/
public static <T> List<T> jsonArray2List(JsonArray jsonArray, Class<T> clazz) {
return new Gson().fromJson(jsonArray,new TypeToken<List<T>>() {
}.getType());
}
/**
* Object转JsonObject
* @param obj
* @return
*/
public static JsonObject object2JsonObject(Object obj){
return new Gson().toJsonTree(obj).getAsJsonObject();
}
/**
* JsonString转JsonObject
* @param str
* @return
*/
public static JsonObject jsonString2JsonObject(String str){
return new JsonParser().parse(str).getAsJsonObject();
}
}
4)Jackson框架
- 实现json、xml与java对象的互相转换;
- 适用于大量的数据解析。
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.4</version>
</dependency>
<!--引入jackson-databind会关联引入jackson-annotations和jackson-core-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.4</version>
</dependency>
<!--xml转换-->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
1>常用注解
| json注解 | 场景说明 | 备注 |
|---|---|---|
| @JsonProperty | 把该属性的名称序列化为另外一个名称。 | |
| @JsonIgnore | 序列化的时忽略,不序列此字段。 | 不适用于fastjson |
| @JSONField(serialize = false) | fastjson 序列化时字段及是否序列化 | |
| @JsonIgnoreProperties | 类注解,作用是json序列化时将java bean中的一些属性忽略掉,序列化和反序列化都受影响。 | |
| @JsonIgnoreType | 标注在类上,当其他类有该类作为属性时,该属性将被忽略。 | |
| @JsonComponent | 为指定类设置序列化规则,无需其他任何额外配置,即可生效。 | |
| @JsonFormat | 为指定字段设置序列化规则,优先级高于JsonComponent。 | |
| @JsonInclude | 如果该属性为NULL则不参与序列化 | 如果放在类上边,那对这个类的全部属性起作用。 |
| @JsonSerialize | 此注解用于属性或者getter方法上,用于在序列化时嵌入我们自定义的代码,比如序列化一个double时在其后面限制两位小数点。 | |
| @JsonDeserialize | 此注解用于属性或者setter方法上,用于在反序列化时可以嵌入我们自定义的代码,类似于上面的@JsonSerialize | |
| @Transient | 如果一个属性并非数据库表的字段映射,就务必将其标示为@Transient,否则ORM框架默认其注解为@Basic; | |
| @JsonPropertyOrder | 可以指定属性在 json 字符串中的顺序。 | |
| @JsonSetter | 标注于 setter 方法上,类似 @JsonProperty ,也可以解决 json 键名称和 java pojo 字段名称不匹配的问题。 |
| xml注解 | 作用域 | 说明 | 实现时机 |
|---|---|---|---|
| @JacksonXmlRootElement | 类上 | 指定 XML 根标签的名字 | 序列化时 |
| @JacksonXmlProperty | 属性或getter/setter方法上 | 指定属性对应 XML 映射的名称 | 序列化和反序列化时 |
| @JacksonXmlText | 属性或getter/setter方法上 | 将属性直接作为未被标签包裹的普通文本 | 序列化和反序列化时 |
| @JacksonXmlCData | 属性或getter/setter方法上 | 将属性值包裹在 CDATA 标签中 | 序列化时 |
| @JacksonXmlElementWrapper | 属性或getter/setter方法上 | 类中有集合的属性时,是否生成包裹的标签 | 序列化时 |
- @JsonProperty
public class Apple {
//序列化后键值为fake_name而不是fakeName
@JsonProperty(value = "fake_name", required = true)
private String fakeName;
}
- @JsonIgnoreProperties
//生成json时将name和age属性过滤
@JsonIgnoreProperties({"name"},{"age"})
public class user {
private String name;
private int age;
}
- @JsonFormat
日期格式化输出。
public class User{
@JsonFormat(pattern="yyyy-MM-dd",timezone = "GMT+8")
private Date date;
}
关于日期的格式化,也可以在yaml中配置:
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
# 如果打开 write-dates-as-timestamps,会自动将日期转换为时间戳。
serialization:
write-dates-as-timestamps: false
- @JsonInclude
@JsonInclude(Include.NON_NULL)
//Include.Include.ALWAYS 默认
//Include.NON_DEFAULT 属性为默认值不序列化
//Include.NON_EMPTY 属性为 空(“”) 或者为 NULL 都不序列化
//Include.NON_NULL 属性为NULL 不序列化
2>转换
Apple apple = new Apple();
apple.setFakeName("a");
apple.setFakeOld(1);
List<Apple> apples = new ArrayList<>();
apples.add(apple);
ObjectMapper mapper = new ObjectMapper();
//Object转json字符串
String str = mapper.writeValueAsString(apple);
String listStr = mapper.writeValueAsString(apples);
System.out.println("str is " + str + ", apples ListStr is " + listStr);
//json字符串转换为对象
Apple app = mapper.readValue(str, Apple.class);
System.out.println(app);
//json字符串转换为list对象
JavaType javaType = mapper.getTypeFactory().constructParametricType(List.class, Apple.class);
List<Apple> appleList = mapper.readValue(listStr, javaType);
System.out.println(appleList);
3,MessagePack序列化框架
<dependency>
<groupId>org.msgpack</groupId>
<artifactId>msgpack-core</artifactId>
<version>0.8.16</version>
</dependency>
<dependency>
<groupId>org.msgpack</groupId>
<artifactId>jackson-dataformat-msgpack</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>org.msgpack</groupId>
<artifactId>msgpack</artifactId>
<version>0.6.12</version>
</dependency>
序列化与反序列化:
//注:使用MessagePack序列化时,该对象必须在类上加@Message注解
User user = new User();
user.setId(1);
user.setName("张三");
MessagePack msgpk = new MessagePack();
byte[] msgBtyes = msgpk.write(user);
System.out.println("MessagePack序列化后流的大小:"+msgBtyes.length);
//输出结果:MessagePack序列化后流的大小:9
//反序列化对象
User userNew =msgpk.read(msgBtyes,User.class);
4,protobuf序列化框架
1)依赖和环境准备
<dependency>
<groupId>com. Google .protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.9.0-rc-1</version>
</dependency>
- protobuf是用C ++编写的。对于非C ++用户,需要从github上下载预构建的二进制文件: ,
找到对应自己系统版本, 找到C++最新版本的 :protoc-3.9.0-rc-1-win64.zip,下载好后解压,后续会通过cmd命令生产java文件。
注: maven中protobuf的version要与你下载的生成器版本保持一致,不然会出现很多问题。
2)创建文件
- 手动新建User.proto文件
syntax = "proto2";//固定
package protobuf; //当前文件路径
option java_package = "com.protobuf";//生成Java类后存放路径,生成java文件时会自动创建
option java_outer_classname = "UserProto";//类名,UserProto.java
//内部类,用来保存实例对象
message UserProtoBuf {
required int64 id = 1; // 类型Long 指定唯一序号, 这个主要用于列对应的值
required string name = 2;
}
- cmd命令进入到我们下载的工具bin路径下
protoc -I=D:toolssrcprotobuf --java_out=D:toolssrcmainjavacomprotobuf User.proto
-I=D:toolssrcprotobuf 表示你User.proto的物理路径
–java_out=D:toolssrcmainjava 表示生成java类保存的路径
User.proto 表示你对哪一个proto文件生成java类
- 检查指定路径下是否生成的UserProto.java
UserProto.java类中会有部分方法java.lang.Override报错,这是因为JDK版本原因,删掉即可。
如果出现部分方法错误,请检查自己的maven版本号和生成器的版本号是否一致。
3)java代码
//对内部类赋值
UserProto.UserProtoBuf userProBuf = UserProto.UserProtoBuf.newBuilder().setId(1L).setName("张三").build();
byte[] bytes = user.toByteArray();
//输出结果protobuf流长度:10
System.out.println("protobuf流长度:" + bytes.length);
//反序列化调用
UserProto.UserProtoBuf user = UserProto.UserProtoBuf.parseFrom(byte[] data);
5,java序列化
序列化:
将对象转换未可传输格式的过程,是一种数据持久化手段。
1)java如何支持序列化?
支持序列化的类型:
- String、enum、Array、基本数据类型的封装类型;
- 实现了Serializable接口的类。
缺点:字节形式,可读性差;占用内存大。
2)修改字节码后是否还可以反序列化成功?
——会报错。
jvm是否支持反序列化,取决于以下2点:
- 类路径和功能代码一致;
- 两个类的序列化ID(serialVersionUID)一致。
避免文件被篡改。
如果没有定义serialVersionUID,那么java序列化机制会自动生成一个serialVersionUID变量,无论编译多次都不会变化,除非发生变更。
自动生成的serialVersionUID反序列化时偶尔会报错,建议显式定义,用private修饰。
更多推荐
所有评论(0)