JSON序列化

Json序列化

Jackson序列化与反序列化

导包

1
2
3
4
5
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version>
</dependency>

对象转json

使用:

1
2
ObjectMapper mapper=new ObjectMapper();
final String s = mapper.writeValueAsString(对象);

例子:

1
2
ObjectMapper mapper=new ObjectMapper();
final String s = mapper.writeValueAsString(PersonList);

json转对象

使用:

1
2
  ObjectMapper mapper = new ObjectMapper();
mapper.readValue(json字符串数据,类.Class)

例子:

1
2
ObjectMapper mapper = new ObjectMapper();
final List<Person> people = Arrays.asList(mapper.readValue(json, Person[].class));

常用注解:

  • @JsonIgnore 此注解用于属性上,作用是进行JSON操作时忽略该属性。

  • @JsonFormat 此注解用于属性上,作用是把Date类型直接转化为想要的格式,如@JsonFormat(pattern = “yyyy-MM-dd HH-mm-ss”)。

  • @JsonProperty 此注解用于属性上,作用是把该属性的名称序列化为另外一个名称

Gson序列化与反序列化

导包

1
2
3
4
5
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.9.0</version>
</dependency>

对象转json

使用:

1
2
Gson gson = new Gson();
String s1 = gson.toJson(对象);

例子:

1
2
Gson gson = new Gson();
final String s = gson.toJson(PersonList);

json转对象

使用:

1
2
Gson gson = new Gson();
final Person people = gson.fromJson(json,对象.class);

例子:

1
2
Gson gson = new Gson();
final List<Person> people = Arrays.asList(gson.fromJson(json, Person[].class));//转为对象list

json转为List和Map

1
2
List<Test> o = gson.fromJson(json, new TypeToken<List<转换为的对象>>() { }.getType());
HashMap<String,Test> o1 = gson.fromJson(jsonMap, new TypeToken<HashMap<String,Test>>(){}.getType());

常用注解:

  • @SerializedName("name")此注解用于属性上,作用是把该属性的名称序列化为另外一个名称
  • @Expose(serialize = false,deserialize = false)serialize:是否参与序列化,deserialize是否参与反序列化

FastJson序列化与反序列化

导包

1
2
3
4
5
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.79</version>
</dependency>

对象转json

使用:

1
final String s = JSON.toJSONString(对象);

例子:

1
final String s = JSON.toJSONString(PersonList);

json转对象

使用:

1
final List<Object> people = Arrays.asList(JSON.parse(json));

例子:

1
final List<Object> people = Arrays.asList(JSON.parse(json));

常用注解:

  • @JSONField(name = "personName")此注解用于属性上,作用是把该属性的名称序列化为另外一个名称,还能够日期匹配 @JSONField(format="yyyy-MM-dd HH:mm:ss")等,基本都在此注解基础上进行的配置。

经典案例

将map中的某个值转为指定类型的对象:

1
2
3
4
5
6
public <T> T getData(TypeReference<T> typeReference){
Object data = get("data");
final String s = JSON.toJSONString(data);
final T t = JSON.parseObject(s, typeReference);
return t;
}

例子:

1
2
3
R skuHasStock = wareFeignService.getSkuHasStock(skuIdList);
TypeReference<List<SkuHasStockVo>> data = new TypeReference<List<SkuHasStockVo>>(){};
stockMap = skuHasStock.getData(data).stream().collect(Collectors.toMap(SkuHasStockVo::getHasStock, SkuHasStockVo::getHasStock));

Kryo序列化与反序列化

导包

1
2
3
4
5
<dependency>
<groupId>com.esotericsoftware</groupId>
<artifactId>kryo</artifactId>
<version>5.3.0</version>
</dependency>

作为一个灵活的序列化框架,Kryo 并不关心读写的数据,作为开发者,你可以随意使用 Kryo 提供的那些开箱即用的序列化器。

Kryo 的注册

和很多其他的序列化框架一样,Kryo 为了提供性能和减小序列化结果体积,提供注册的序列化对象类的方式。在注册时,会为该序列化类生成 int ID,后续在序列化时使用 int ID 唯一标识该类型。注册的方式如下:

1
kryo.register(SomeClass.class);

或者

1
kryo.register(SomeClass.class, 1);

可以明确指定注册类的 int ID,但是该 ID 必须大于等于 0。如果不提供,内部将会使用 int++的方式维护一个有序的 int ID 生成。

线程不安全

Kryo 不是线程安全的。每个线程都应该有自己的 Kryo 对象、输入和输出实例。

因此在多线程环境中,可以考虑使用 ThreadLocal 或者对象池来保证线程安全性。

ThreadLocal + Kryo 解决线程不安全

ThreadLocal 是一种典型的牺牲空间来换取并发安全的方式,它会为每个线程都单独创建本线程专用的 kryo 对象。对于每条线程的每个 kryo 对象来说,都是顺序执行的,因此天然避免了并发安全问题。创建方法如下:

1
2
3
4
5
6
7
8
9
static private final ThreadLocal<Kryo> kryos = new ThreadLocal<Kryo>() {
protected Kryo initialValue() {
Kryo kryo = new Kryo();
// 在此处配置kryo对象的使用示例,如循环引用等
return kryo;
};
};

Kryo kryo = kryos.get();

之后,仅需要通过 kryos.get() 方法从线程上下文中取出对象即可使用。

对象池 + Kryo 解决线程不安全

「池」是一种非常重要的编程思想,连接池、线程池、对象池等都是「复用」思想的体现,通过将创建的“对象”保存在某一个“容器”中,以便后续反复使用,避免创建、销毁的产生的性能损耗,以此达到提升整体性能的作用。

Kryo 对象池原理也是如此。Kryo 框架自带了对象池的实现,整个使用过程不外乎创建池、从池中获取对象、归还对象三步,以下为代码实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
// Pool constructor arguments: thread safe, soft references, maximum capacity
Pool<Kryo> kryoPool = new Pool<Kryo>(true, false, 8) {
protected Kryo create () {
Kryo kryo = new Kryo();
// Kryo 配置
return kryo;
}
};

// 获取池中的Kryo对象
Kryo kryo = kryoPool.obtain();
// 将kryo对象归还到池中
kryoPool.free(kryo);

创建 Kryo 池时需要传入三个参数,其中第一个参数用于指定是否在 Pool 内部使用同步,如果指定为 true,则允许被多个线程并发访问。第三个参数适用于指定对象池的大小的,这两个参数较容易理解,因此重点来说一下第二个参数。

如果将第二个参数设置为 true,Kryo 池将会使用 java.lang.ref.SoftReference 来存储对象。这允许池中的对象在 JVM 的内存压力大时被垃圾回收。Pool clean 会删除所有对象已经被垃圾回收的软引用。当没有设置最大容量时,这可以减少池的大小。当池子有最大容量时,没有必要调用 clean,因为如果达到了最大容量,Pool free 会尝试删除一个空引用。

创建玩 Kryo 池后,使用 kryo 就变得异常简单了,只需调用 kryoPool.obtain() 方法即可,使用完毕后再调用 kryoPool.free(kryo) 归还对象,就完成了一次完整的租赁使用。

理论上,只要对象池大小评估得当,就能在占用极小内存空间的情况下完美解决并发安全问题。如果想要封装一个 Kryo 的序列化方法,可以参考如下的代码

1
2
3
4
5
6
7
8
9
10
11
public static byte[] serialize(Object obj) {
Kryo kryo = kryoPool.obtain();
// 使用 Output 对象池会导致序列化重复的错误(getBuffer返回了Output对象的buffer引用)
try (Output opt = new Output(1024, -1)) {
kryo.writeClassAndObject(opt, obj);
opt.flush();
return opt.getBuffer();
}finally {
kryoPool.free(kryo);
}
}

JSON序列化
http://example.com/2022/09/04/Json序列化/
作者
liziyuan
发布于
2022年9月4日
许可协议