从 Jackson 的使用和源码看程序设计
每隔一段时间,我会梳理一些平时经常用的类库源码。这次和大多数时候一样,我又选了一个简单的来看点源码。这次选的Jackson的ObjectMapper类源码。
为什么我要选简单的来看呢?因为提高技术知识只是问题的表面,根本是要提高自己认知问题、分析问题、解决问题的思想高度。研究的问题越复杂,越容易将精力放在解决问题的本身,而阻碍了思想高度的提升。
同样,这次我们还是套用思考框架来完成这个工作。这次使用学习技术的三步曲what、why、how来思考。
What
Jackson是Java生态圈中处理JSON和XML的类库,是spring-boot默认的JSON解析框架。这是因为spring mvc会自动注册MappingJackson2HttpMessageConverter,从而支持json 输出。
Jackson提供了三种数据处理方式。分别是数据绑定、树模型、流式API。其中, 数据绑定用于JSON转化,可以将JSON与POJO对象进行转化。
是最符合面向对象的一种使用方式。流式API是江湖传闻效率最高的一种使用方式。
Why
Jackson相对Gson、JSON-lib更为高效,使用灵活方便,所以应用广泛。
注意fastjson的效率一般情况下是要超过Jackson的。但是像spring-boot这样的开源平台,最好的方法还是使用更成熟的技术。因为 像这种JSON解析框架是很容易遭受攻击的对象。自己动手实践一下,可发现,Jackson的性能瓶颈主要在ObjectMapper实例化上。而这个类在实际项目使用时都是被封装好的,可以启动时实例化一次,所以在运行时,效率上谁高谁低还是有待商榷的。
How
和大多数人一样,我也是一看源码就头大。所以我的思路是先从简单的demo入手。代码我上传到了github上。地址:
https://github.com/xiexiaojing/yuna
值得一提的是:这虽然是一个经典学习场景的学习工具工程,但是完全可以 作为生产环境的基础代码来用。我们线上跑的代码架子和这个差不了多少。
数据绑定方式源码分析
我们直
接从转换类 ObjectMapper的源码开始看。
1,采用数组做缓存,一开始就分配好空间,提高效率。
public String writeStringAsString(String toWrite) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(toWrite);
}
写个测试类,传入null。这时候将看到结果
跟踪源码可看到真正产生这个结果的是WriterBasedJsonGenerator这个类。它就是打印了个null。
从源码看到打印时采用了Buffer缓存,从一开始就分配好空间。底层采用数据结构读取速度快,效率高。
2,字符转义处理,做好安全工作。
再给上面的程序,写个测试类,传入任意字符串。这时候将看到结果
字符串被原样打出来,只是两边多了两个引号,代表是字符串,不是数字。
看一下处理的源码,还是WriterBasedJsonGenerator这个类。
对于文本,首先判断文本是否太长,长的话就跳到另一个方法,这个方法用将字符串分段分而治之。看到
Escape这个单词,就要想到为了信息安全而将字符转义了。在公司或者github不时会收到jackson、fastjson漏洞,要求升级到XXX版本以上。对,所谓的升级补漏洞最重要的就是给需要 Escape的列表加了
些字符。
在文章What部分提到:「流式API是江湖传闻效率最高的一种使用方式」。我们先来验证一下这个传闻是不是真的。
首先构造一个Pojo:
@Data public class Pojo { private int id; private String value; }
使用数据绑定方式
public String writePojoAsString(Pojo toWrite) throws Exception { return objectMapper.writeValueAsString(toWrite); }
使用流式API
public String writeJsonGenerator(Pojo toWrite) throws Exception { JsonFactory factory = new JsonFactory(); ByteArrayOutputStream os = new ByteArrayOutputStream(); //不能加上缓冲 有新增的方法 JsonGenerator jsonGenerator = factory.createGenerator(os, JsonEncoding.UTF8); //对象开始 jsonGenerator.writeStartObject(); jsonGenerator.writeNumberField("id", toWrite.getId()); jsonGenerator.writeStringField("value", toWrite.getValue()); jsonGenerator.writeEndObject(); jsonGenerator.flush(); jsonGenerator.close(); return os.toString(); }
因为初始化时要先实例化ObjectMapper,有时间消耗。为了不让流式API转这个便宜。将两个要测试的方法写到一个类里,初始化时先实例化ObjectMapper。并且先运行流式API。
测试结果来看流式API要比数据绑定快近十倍。如果交换执行顺序会发现差距更为明显。
跟进ObjectMapper里就会发现,底层其中数据绑定调用的也是同样的API。只是它在进行数据转换的同时还进行了其他的工作。这些工作比如转义、分段等。就看这些是不是我们需要的。
总结
说自己掌握了一门语言至少要掌握基本的语法和数据结构、核心库、常用第三方框架、流行的开发框架和部署方法。而这些知识底层有很强的关联性,不断增加自己的知识的深度,更好的触类旁通。
推荐阅读