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));
|
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(); 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<Kryo> kryoPool = new Pool<Kryo>(true, false, 8) { protected Kryo create () { Kryo kryo = new Kryo(); return kryo; } };
Kryo kryo = kryoPool.obtain();
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(); try (Output opt = new Output(1024, -1)) { kryo.writeClassAndObject(opt, obj); opt.flush(); return opt.getBuffer(); }finally { kryoPool.free(kryo); } }
|