Kryo 使用指南

2023-10-29

1Kryo 的简介

Kryo 是一个快速序列化/反序列化工具,其使用了字节码生成机制(底层依赖了 ASM 库),因此具有比较好的运行速度。

Kryo 序列化出来的结果,是其自定义的、独有的一种格式,不再是 JSON 或者其他现有的通用格式;而且,其序列化出来的结果是二进制的(即 byte[];而 JSON 本质上是字符串 String);二进制数据显然体积更小,序列化、反序列化时的速度也更快。

Kryo 一般只用来进行序列化(然后作为缓存,或者落地到存储设备之中)、反序列化,而不用于在多个系统、甚至多种语言间进行数据交换 —— 目前 kryo 也只有 java 实现。

像 Redis 这样的存储工具,是可以安全地存储二进制数据的,所以可以直接把 Kryo 序列化出来的数据存进去。

当然,如果你希望用 String 的形式存储、传输 Kryo 序列化之后的数据,也可以通过 Base64 等编码方式来实现。但这会降低程序的运行速度,一定程度上违背了使用 kryo 的初衷。

Kryo 在使用时,需要根据使用场景进行一定的设置;如果设置不当,会导致一些严重的错误。(这些问题的原因参见第 2 节)

附件中提供了我们部门封装的 KryoUtil ,其根据分布式 Web 应用的一般场景,进行了配置及封装;可以在自己的项目里安全地使用此工具类。

2Kryo 的特点和配置的选择

2.1 支持的范围

除了常见的 JDK 类型、以及这些类型组合而来的普通 POJO,Kryo 还支持以下特殊情况:

  • 枚举;
  • 任意 Collention、数组;
  • 子类/多态(详见 2.2 节);
  • 循环引用(详见 2.5 节);
  • 内部类(详见 2.6 节);
  • 泛型对象(详见 3.2 节);
  • Builder 模式;

其中部分特性的支持,需要使用者手动设定 Kryo 的某些配置(KryoUtil 已经进行了这些配置)。

Kryo 不支持以下情况:

  • 增加或删除 Bean 中的字段;

举例来说,某一个 Bean 使用 Kryo 序列化后,结果被放到 Redis 里做了缓存,如果某次上线增加/删除了这个 Bean 中的一个字段,则缓存中的数据进行反序列化时会报错;作为缓存功能的开发者,此时应该 catch 住异常,清除这条缓存,然后返回 “缓存未命中” 信息给上层调用者。

字段顺序的变化不会导致反序列化失败。

2.2 记录类型/对多态的支持

Kryo 的一大特点是,支持把对象的类型信息,也放进序列化的结果里。

举例来说:假设我们有一个自己定义的接口 WeightList<T>,有两个实现:ArrayWeightList<T> 和 LinkedWeightList<T>;一般的 JSON 序列化工具,在默认情况下无法记录我们使用的是哪一个实现类;如果不进行特殊的配置,JSON 序列化工具在进行反序列化时会报错。

而 Kryo 将原始对象的类型信息,记录到了序列化的结果里;所以反序列的时候可以精确地找到原始的类型,不会报错。

同时,在反序列化任意对象时,也不再需要再提供 Class 信息或者 Type 信息了,代码也更为简洁、通用。(可以参考第 5 节中的例子)

如果选择记录类型信息,则使用 kryo 中的 writeClassAndObject/readClassAndObject 方法,如果选择不记录类型信息(反序列化时由调用方提供类型信息),则使用 writeObject/readObject 方法。

2.3 线程安全

Kryo 对象不是线程安全的,所以需要借用 ThreadLocal 来保证线程安全性。具体实现可以参考附件中的 KryoUtil。

如果对性能有更高要求,也可以使用 KryoPool:https://github.com/EsotericSoftware/kryo#threading

2.4 注册行为

Kryo 支持对类进行注册。注册行为会给每一个 Class 编一个号码,从 0 开始;但是,Kryo 并不保证同一个 Class 每一次的注册的号码都相同(比如重启 JVM 后,用户访问资源的顺序不同,就会导致类注册的先后顺序不同)。

也就是说,同样的代码、同一个 Class ,在两台机器上的注册编号可能不一致;那么,一台机器序列化之后的结果,可能就无法在另一台机器上反序列化。

因此,对于多机器部署的情况,建议关闭注册,让 Kryo 记录每个类型的真实的名称。

而且,注册行为需要用户对每一个类进行手动注册:即便使用者注册了 A 类型,而 A 类型内部使用了 B 类型,使用者也必须手动注册 B 类型;(甚至,即便某一个类型是 JDK 内部的类型,比如 ArrayList ,也是需要手动注册的)一个普通的业务对象,往往需要注册十几个 Class,这是十分麻烦、甚至是寸步难行的。

关闭注册行为,需要保证没有进行过这样的设置:

kryo.setRegistrationRequired(true);

并且要保证没有显式地注册任何一个类,例如:

kryo.register(ArrayList.class);

同时保证以上二者,才真正地关闭了注册行为。

2.5 对循环引用的支持

举例而言,“循环引用” 是指,假设有一个 “账单” 的 Bean(比如:BillDomain),这个账单下面有很多明细(比如:private List<ItemDomain> items;),而明细类中又有一个字段引用了所属的账单(比如:private BillDomain superior;),那么这就构成了“循环引用”。

Kryo 是支持循环引用的,只需要保证没有进行过这样的设置就可以了:

kryo.setReferences(false);

配置成 false 的话,序列化速度更快,但是遇到循环引用,就会报 “栈内存溢出” 错误。这有很大的风险:等你不得不支持循环引用的那一天你就会发现,你必须在代码上线的同时,清除 Redis 里已有的大量缓存(详见 2.8 节)。

2.6 内部类

Kryo 支持静态内部类,既可以是私有/包级私有的,也可以是 public 的;但是对非静态内部类的支持不够好(一般不会报错,但在有些情况下会产生错误的数据),这和不同的编译器对内部类的处理有关(可参阅 Java 内部类的语法糖机制)。同样地,Kryo 支持 Builder 模式。

Kryo 不支持匿名类,反序列化时往往会产生错误的数据(这比报错更加危险),请尽量不要使用匿名类传递数据。

2.7 序列化格式

Kryo 实际上支持任意的序列化格式,并不一定使用 Kryo 自己定义的那种特殊的格式(甚至可以为不同的 class 指定不同的序列化格式),比如使用 Java 语言自己的序列化格式(在 Kryo 中注册 JavaSerializer 即可) —— 但我们强烈建议不要这么使用,Java 语言本身的序列化方式有很多限制,比如必须要保证每一个 Bean 都实现 Serializable 接口;而系统中可能有很多 Bean 都忘了实现这个接口;这些类在编译时并不会报错,只有在运行期间、进行序列化时才会报错,这是危险的。

Kryo 默认的序列化格式没有任何限制,显然方便的多。

2.8 配置的修改

Kryo 可以通过修改配置来达到更快的速度,或者支持更多的特殊形式;但是必须注意的是,一旦改变某一个个配置,序列化出来的格式和之前的格式是完全不一样的; 也就是说,你必须在上线代码的同时,清除 Redis 里所有已有的缓存,否则那些缓存里的数据再回来进行反序列化的时候,就会报错。

3、常见问题

3.1 使用的时候报 asm 相关类的错误

Kryo 底层用了 asm 库(一个字节码生成库),Spring 底层也用了这个库 ;但是,Kryo 使用的版本比较高;而 Spring 用的版本较低; 如果 pom 里的 Kryo 和 Spring 的顺序不对的话,Kryo 就会读到低版本的 asm,就会出错。

请检查对 Kryo 的 Maven 依赖,如果 artifactId 是这样的:

<artifactId>kryo</artifactId>:

就改为:

<artifactId>kryo-shaded</artifactId>

加了个 shaded 就能解决了。在这个 shaded 的版本里,Kryo 的作者复制了一份高版本的 asm,集成到了 Kryo 内部(作者修改了 asm 类的包名,所以和原来的 asm 就不会再冲突了)。

3.2 泛型对象的反序列化

在使用常见的 JSON 库时,泛型对象不能使用 *.class 进行反序列化;比如 Gson 在反序列化 List<SomeDomain> 的时候,除了传入 JSON 字符串,还需要传入第二个参数:

new TypeToken<List<SomeDomain>>(){}.getType()

直接使用 List.class 是不行的(而 List<SomeDomain>.class 则是语法错误),这是 Java 泛型的 “擦除” 机制导致的。

而 Kryo 的 readObject 方法则没有这个问题。在上例中,向 Kryo 的 readObject 方法传入 List.class 即可;Kryo 实际上在序列化结果里记录了泛型参数的实际类型的信息,反序列化时会根据这些信息来实例化对象。

直觉上我们会觉得,不在序列化结果中包含类型信息,能减小空间的占用、提高速度;但实际上,我们发现,所谓的 “不包含类型信息”,在 Kryo 内部的实现里,仅仅是 “不包含最外层对象的类型信息” ,对象内部的子对象的类型信息依然是包含的(可能是为了支持多态问题);也就是说,“不包含类型信息” 能带来的空间节省非常有限。

如果对速度、序列化之后的数据大小没有特别极端的要求,推荐在序列化结果中包含类型信息,这样的话,反序列化时能少些一个参数,也更为通用。

4、使用 Kryo 需要添加的 Maven 依赖

<!-- Kryo -->

<dependency>

    <groupId>com.esotericsoftware</groupId>

    <artifactId>kryo-shaded</artifactId>

    <version>4.0.0</version>

</dependency>

如果使用 KryoUtil 的话,还需要以下依赖:

<!-- commons-codec -->

<dependency>

    <groupId>commons-codec</groupId>

    <artifactId>commons-codec</artifactId>

    <version>1.10</version>

</dependency>

5KryoUtil 使用示例

KryoUtil 是我们部门编写的工具类,其对 Kryo 进行了一定的封装,能够满足分布式系统的一般需求,而无需进行任何额外的配置。

除了用于获得当前线程的 kryo 实例的 getInstance() 方法之外,KryoUtil 内共有 8 个 public 方法,分为两组:

<T> byte[] writeToByteArray(T obj);

<T> String writeToString(T obj);

<T> T readFromByteArray(byte[] byteArray);

<T> T readFromString(String str);

及:

<T> byte[] writeObjectToByteArray(T obj)

<T> String writeObjectToString(T obj)

<T> T readObjectFromByteArray(byte[] byteArray, Class<T> clazz)

<T> T readObjectFromString(String str, Class<T> clazz)

其中第一组序列化的结果里包含了类型信息,第二组不包含 —— 因此,可以看到,在使用第二组方法进行反序列化的时候,需要提供原始对象的 Class 。但我们建议使用第一组方法,原因见第 3.2 节。

另外,必须注意,第一组方法和第二组方法不能混用,第一组序列化出来的结果,只能由第一组的方法进行反序列化;第二组亦然。

每组方法内,序列化的结果格式都可以选择二进制格式,或者字符串格式。具体的使用示例代码如下:

将任意对象序列化成 byte[]:

byte[] tempByteArray = KryoUtil.writeToByteArray(domainA);

//tempByteArray 就是序列化的结果,直接放到 Redis 里面即可

 

DomainA domainA1 = KryoUtil.readFromByteArray(tempByteArray);

//domainA1 就是反序列化之后的对象

如果你们的存储服务不支持二进制数据(或者说不是 “二进制安全” 的),那么也可以序列化成 String:

String tempStr = KryoUtil.writeToString(domainA);

//tempStr 就是序列化的结果

 

DomainA domainA1 = KryoUtil.readFromString(tempStr);

//domainA1 就是反序列化之后的对象

附 KryoUtil.java 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
package  com.jd.personal.hanwenyang5.util;
 
import  com.esotericsoftware.kryo.Kryo;
import  com.esotericsoftware.kryo.io.Input;
import  com.esotericsoftware.kryo.io.Output;
import  org.apache.commons.codec.binary.Base64;
import  org.objenesis.strategy.StdInstantiatorStrategy;
 
import  java.io.*;
 
/**
  * Kryo Utils
  * <p/>
  */
public  class  KryoUtil {
 
     private  static  final  String DEFAULT_ENCODING =  "UTF-8" ;
 
     //每个线程的 Kryo 实例
     private  static  final  ThreadLocal<Kryo> kryoLocal =  new  ThreadLocal<Kryo>() {
         @Override
         protected  Kryo initialValue() {
             Kryo kryo =  new  Kryo();
 
             /**
              * 不要轻易改变这里的配置!更改之后,序列化的格式就会发生变化,
              * 上线的同时就必须清除 Redis 里的所有缓存,
              * 否则那些缓存再回来反序列化的时候,就会报错
              */
             //支持对象循环引用(否则会栈溢出)
             kryo.setReferences( true );  //默认值就是 true,添加此行的目的是为了提醒维护者,不要改变这个配置
 
             //不强制要求注册类(注册行为无法保证多个 JVM 内同一个类的注册编号相同;而且业务系统中大量的 Class 也难以一一注册)
             kryo.setRegistrationRequired( false );  //默认值就是 false,添加此行的目的是为了提醒维护者,不要改变这个配置
 
             //Fix the NPE bug when deserializing Collections.
             ((Kryo.DefaultInstantiatorStrategy) kryo.getInstantiatorStrategy())
                     .setFallbackInstantiatorStrategy( new  StdInstantiatorStrategy());
 
             return  kryo;
         }
     };
 
     /**
      * 获得当前线程的 Kryo 实例
      *
      * @return 当前线程的 Kryo 实例
      */
     public  static  Kryo getInstance() {
         return  kryoLocal.get();
     }
 
     //-----------------------------------------------
     //          序列化/反序列化对象,及类型信息
     //          序列化的结果里,包含类型的信息
     //          反序列化时不再需要提供类型
     //-----------------------------------------------
 
     /**
      * 将对象【及类型】序列化为字节数组
      *
      * @param obj 任意对象
      * @param <T> 对象的类型
      * @return 序列化后的字节数组
      */
     public  static  <T>  byte [] writeToByteArray(T obj) {
         ByteArrayOutputStream byteArrayOutputStream =  new  ByteArrayOutputStream();
         Output output =  new  Output(byteArrayOutputStream);
 
         Kryo kryo = getInstance();
         kryo.writeClassAndObject(output, obj);
         output.flush();
 
         return  byteArrayOutputStream.toByteArray();
     }
 
     /**
      * 将对象【及类型】序列化为 String
      * 利用了 Base64 编码
      *
      * @param obj 任意对象
      * @param <T> 对象的类型
      * @return 序列化后的字符串
      */
     public  static  <T> String writeToString(T obj) {
         try  {
             return  new  String(Base64.encodeBase64(writeToByteArray(obj)), DEFAULT_ENCODING);
         catch  (UnsupportedEncodingException e) {
             throw  new  IllegalStateException(e);
         }
     }
 
     /**
      * 将字节数组反序列化为原对象
      *
      * @param byteArray writeToByteArray 方法序列化后的字节数组
      * @param <T>       原对象的类型
      * @return 原对象
      */
     @SuppressWarnings ( "unchecked" )
     public  static  <T> T readFromByteArray( byte [] byteArray) {
         ByteArrayInputStream byteArrayInputStream =  new  ByteArrayInputStream(byteArray);
         Input input =  new  Input(byteArrayInputStream);
 
         Kryo kryo = getInstance();
         return  (T) kryo.readClassAndObject(input);
     }
 
     /**
      * 将 String 反序列化为原对象
      * 利用了 Base64 编码
      *
      * @param str writeToString 方法序列化后的字符串
      * @param <T> 原对象的类型
      * @return 原对象
      */
     public  static  <T> T readFromString(String str) {
         try  {
             return  readFromByteArray(Base64.decodeBase64(str.getBytes(DEFAULT_ENCODING)));
         catch  (UnsupportedEncodingException e) {
             throw  new  IllegalStateException(e);
         }
     }
 
     //-----------------------------------------------
     //          只序列化/反序列化对象
     //          序列化的结果里,不包含类型的信息
     //-----------------------------------------------
 
     /**
      * 将对象序列化为字节数组
      *
      * @param obj 任意对象
      * @param <T> 对象的类型
      * @return 序列化后的字节数组
      */
     public  static  <T>  byte [] writeObjectToByteArray(T obj) {
         ByteArrayOutputStream byteArrayOutputStream =  new  ByteArrayOutputStream();
         Output output =  new  Output(byteArrayOutputStream);
 
         Kryo kryo = getInstance();
         kryo.writeObject(output, obj);
         output.flush();
 
         return  byteArrayOutputStream.toByteArray();
     }
 
     /**
      * 将对象序列化为 String
      * 利用了 Base64 编码
      *
      * @param obj 任意对象
      * @param <T> 对象的类型
      * @return 序列化后的字符串
      */
     public  static  <T> String writeObjectToString(T obj) {
         try  {
             return  new  String(Base64.encodeBase64(writeObjectToByteArray(obj)), DEFAULT_ENCODING);
         catch  (UnsupportedEncodingException e) {
             throw  new  IllegalStateException(e);
         }
     }
 
     /**
      * 将字节数组反序列化为原对象
      *
      * @param byteArray writeToByteArray 方法序列化后的字节数组
      * @param clazz     原对象的 Class
      * @param <T>       原对象的类型
      * @return 原对象
      */
     @SuppressWarnings ( "unchecked" )
     public  static  <T> T readObjectFromByteArray( byte [] byteArray, Class<T> clazz) {
         ByteArrayInputStream byteArrayInputStream =  new  ByteArrayInputStream(byteArray);
         Input input =  new  Input(byteArrayInputStream);
 
         Kryo kryo = getInstance();
         return  kryo.readObject(input, clazz);
     }
 
     /**
      * 将 String 反序列化为原对象
      * 利用了 Base64 编码
      *
      * @param str   writeToString 方法序列化后的字符串
      * @param clazz 原对象的 Class
      * @param <T>   原对象的类型
      * @return 原对象
      */
     public  static  <T> T readObjectFromString(String str, Class<T> clazz) {
         try  {
             return  readObjectFromByteArray(Base64.decodeBase64(str.getBytes(DEFAULT_ENCODING)), clazz);
         catch  (UnsupportedEncodingException e) {
             throw  new  IllegalStateException(e);
         }
     }
}
 
 
http://www.cnblogs.com/hntyzgn/p/7122709.html
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Kryo 使用指南 的相关文章

随机推荐

  • UE4 部分命令知识点梳理

    UE4 部分命令知识点梳理 1 r SSGI Enable 0 1 屏幕空间 全局光照关闭 开启 2 DFO 距离场AO 指数指的是AO强度 遮挡最大距离 指的是距离场AO影响的最大距离 3 r forcelod 1 0 1 等 设置场景模
  • CSS中clear:both的作用

    clear both意思就是清除浮动 例如我们设置了三个div如下
  • es6中let var const 的特点及区别

    首先 var是定义一个变量常用的方法 与其相似的还有let和const 以下介绍他们三个的特点及不同 一 var var的用法很多 没有什么局限 可以对变量进行声明 例如 注意 var let const 是js的关键词 需要写在scrip
  • CSDN笔记

    拉普拉斯变换的收敛域 ROC 与逆变换 ILT 1 是否可积即是否收敛 如果可收敛 面积 拉氏值即为收敛域 1 收敛的条件 e jwt 积分为振荡函数 2 常系数线性微分方程对应线性时不变系统 其分析步骤有三 3 拉氏逆变换 ILT 的方法
  • Linux僵尸进程怎么处理,Linux 僵尸进程如何处理

    Linux 允许进程查询内核以获得其父进程的 PID 或者其任何子进程的执行状态 例如 进程可以创建一个子进程来执行特定的任务 然后调用诸如 wait 这样的一些库函数检查子进程是否终止 如果子进程已经终止 那么 它的终止代号将告诉父进程这
  • js求时间差

    js求时间差 var date1 new Date 开始时间 alert aa var date2 new Date 结束时间 var date3 date2 getTime date1 getTime 时间差的毫秒数 计算出相差天数 va
  • 基于SpringBoot的购票系统的设计与实现

    博主介绍 在职Java研发工程师 专注于程序设计 源码分享 技术交流 专注于Java技术领域和毕业设计 温馨提示 文末有 CSDN 平台官方提供的老师 Wechat QQ 名片 项目名称 基于SpringBoot的购票系统的设计与实现 演示
  • 十五分钟带你学会 Electron

    文章目录 什么是 Electron 为什么要选择 Electron 安装 Electron 桌面CSDN实战 Electron 基础配置 Electron 进程 主进程 渲染进程 主进程与渲染进程的区别 主进程与渲染进程的通信 Electr
  • 孔乙己:new的五种写法

    孔乙己 new的五种写法 这个是目标类 INT 拥有一个字面常量构造函数 和一个平凡析构函数 可以从int构造 也可以隐式转换为int 也可以和int比较大小 class INT private int value public const
  • 【CNC——第6篇】PMAC上位机编程基础篇(上位机和下位机如何通信)

    拓展链接 PAMC官网 DELTA TAU 官网手册 手册大全 PMAC官网 PCOMM32PRO用户手册 PMAC 的内部变量 内部变量分为四种 I 变量为电机等常用基本控制变量 P 变量为全局用户常量 Q 变量为坐标系变量 M 变量为地
  • 华为OD机试 C++ 打卡统计

    题目 任务 你的工作是帮我们找出打卡次数最多的前五名员工 有些小细节需要注意 如果两位员工打卡次数一样多 那么先打卡的员工排名更靠前 如果他们开始打卡的时间也一样 那就按照员工id的大小排序 id小的员工排在前面 输入 第一行是员工的数量N
  • osgFBO(十)多pass-3,pass3,shader将背景从绿色变为蓝色

    pass3和pass2类似 只是再熟悉下 这个Pass设定为最后一步 可以不再输出纹理 即 1 pass3摄像机输入tex2 osg ref ptr
  • matplotlib之饼状图

    import matplotlib pyplot as plt labels A B C D fracs 35 20 45 10 plt pie x fracs labels labels plt show 圆形饼图 import matp
  • 数据结构顺序表与链表(查找,插入,删除)

    目录 顺序表 链表 顺序表 顺序表是在计算机内存中以数组的形式保存的线性表 线性表的顺序存储是指用一组地址连续的存储单元依次存储线性表中的各个元素 使得线性表中在逻辑结构上相邻的数据元素存储在相邻的物理存储单元中 即通过数据元素物理存储的相
  • JVM调优实战

    1 当项目运行一段时间以后 产生了OOM的问题 我们该如何排查问题呢 用top命令 看看是哪个进程CPU占用率高 获取它的进程ID 再根据具体的进程id 执行 top HP 进程id号 命令 看看哪个线程的CPU占用率高 如果是业务线程出现
  • WIN7打开方式列表无法添加某个程序

    win7打开方式不能添加程序 你问我答网 原因 程序移动了位置 解决 开始 运行 regedit 在 HKEY CLASSES ROOT Applications 中找到无法添加的程序 例如 ColorStorm exe 看一下它的 she
  • STM32CubeMX之RTC电子钟

    STM32CubeMX之RTC电子钟 1 简介 实时时钟是一个独立的定时器 RTC模块拥有一组连续计数的计数器 在相应软件配置下 可提供时钟日历的功能 修改计数器的值可以重新设置系统当前的时间和日期 2 特性 可编程的预分频系数 分频系数最
  • 《消息队列高手课》如何实现高性能的异步网络传输?

    我们开发的绝大多数业务系统 它都是 IO 密集型系统 跟 IO 密集型系统相对的另一种系统叫计算密集型系统 通过这两种系统的名字 估计你也能大概猜出来 IO 密集型系统是什么意思 IO 密集型系统大部分时间都在执行 IO 操作 这个 IO
  • mysql 查找表中某个字段相同的数据_怎么查看数据库表中某个字段的值有哪些重复记录...

    展开全部 下面以 sqlserver数据库为例进行说明 select from TableA where b in select b from TableA group by b having count b gt 1 这样就列举出了b字段
  • Kryo 使用指南

    1 Kryo 的简介 Kryo 是一个快速序列化 反序列化工具 其使用了字节码生成机制 底层依赖了 ASM 库 因此具有比较好的运行速度 Kryo 序列化出来的结果 是其自定义的 独有的一种格式 不再是 JSON 或者其他现有的通用格式 而