Java开发中JSON库的选型比较-jacksonnewObjectMapper性能测试-《Java笔记》

admin 2025-10-19 03:14:40 编程 来源:ZONE.CI 全球网 0 阅读模式

Java Jackson自从国产之光fastjson频频暴雷,jackson json的使用是越来越广泛了。尤其是Spring家族把它搞成了默认的JSON处理包,jackson的使用数量更是呈爆炸式发展。很多同学发现,jackson并没有类似fastjson的JSON.parseObject这样的,确实看起来很快的方法。要想解析json,不得不new一个ObjectMapper,来处理真正的解析动作。就像下面这样。

  1. public String getCarString(Car car){
  2. ObjectMapper objectMapper = new ObjectMapper();
  3. String str = objectMapper.writeValueAsString(car);
  4. return str;
  5. }

这种代码就在CV工程师手中遍地开了花。神奇。

这代码有问题么?

要说它有问题,它确实能正确的执行。要说它没问题,在追求性能的同学眼里,这肯定是一段十恶不赦的代码。一般的工具类,都是单例的,同时是线程安全的。ObjectMapper也不例外,它也是线程安全的,可以并发的执行它,不会产生任何问题。这段代码,ObjectMapper在每次方法调用的时候,都会生成一个。那它除了造成一定的年轻代内存浪费之外,在执行时间上有没有什么硬伤呢?new和不new,真的区别有那么大么?这得搬出Java中的基准测试工具JMH,才能一探究竟。JMH(the Java Microbenchmark Harness) 就是这样一个能够做基准测试的工具。如果通过一系列的工具,定位到了热点代码,要测试它的性能数据,评估改善情况,就可以交给JMH。它的测量精度非常高,最高可达到纳秒的级别。JMH是一个jar包,它和单元测试框架JUnit非常的像,可以通过注解进行一些基础配置。这部分配置有很多是可以通过main方法的OptionsBuilder进行设置的。jackson new ObjectMapper性能测试 - 图1上图是一个典型的JMH程序执行的内容。通过开启多个进程,多个线程,首先执行预热,然后执行迭代,最后汇总所有的测试数据进行分析。在执行前后,还可以根据粒度处理一些前置和后置操作。

JMH测试结果

为了测试上面的场景,创造了下面的基准测试类。分为三个测试场景:

  1. 直接在方法里new ObjectMapper
  2. 在全局共享一个ObjectMapper
  3. 使用ThreadLocal,每个线程一个ObjectMapper

这样的测试属于cpu密集型的。cpu有10核,直接就分配了10个线程的并发,cpu在测试期间跑的满满的。

  1. @BenchmarkMode({Mode.Throughput})
  2. @OutputTimeUnit(TimeUnit.SECONDS)
  3. @State(Scope.Thread)
  4. @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
  5. @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
  6. @Fork(1)
  7. @Threads(10)
  8. public class ObjectMapperTest {
  9. String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }";
  10. @State(Scope.Benchmark)
  11. public static class BenchmarkState {
  12. ObjectMapper GLOBAL_MAP = new ObjectMapper();
  13. ThreadLocal<ObjectMapper> GLOBAL_MAP_THREAD = new ThreadLocal<>();
  14. }
  15. @Benchmark
  16. public Map globalTest(BenchmarkState state) throws Exception{
  17. Map map = state.GLOBAL_MAP.readValue(json, Map.class);
  18. return map;
  19. }
  20. @Benchmark
  21. public Map globalTestThreadLocal(BenchmarkState state) throws Exception{
  22. if(null == state.GLOBAL_MAP_THREAD.get()){
  23. state.GLOBAL_MAP_THREAD.set(new ObjectMapper());
  24. }
  25. Map map = state.GLOBAL_MAP_THREAD.get().readValue(json, Map.class);
  26. return map;
  27. }
  28. @Benchmark
  29. public Map localTest() throws Exception{
  30. ObjectMapper objectMapper = new ObjectMapper();
  31. Map map = objectMapper.readValue(json, Map.class);
  32. return map;
  33. }
  34. public static void main(String[] args) throws Exception {
  35. Options opts = new OptionsBuilder()
  36. .include(ObjectMapperTest.class.getSimpleName())
  37. .resultFormat(ResultFormatType.CSV)
  38. .build();
  39. new Runner(opts).run();
  40. }
  41. }

测试结果如下。

  1. Benchmark Mode Cnt Score Error Units
  2. ObjectMapperTest.globalTest thrpt 5 25125094.559 ± 1754308.010 ops/s
  3. ObjectMapperTest.globalTestThreadLocal thrpt 5 31780573.549 ± 7779240.155 ops/s
  4. ObjectMapperTest.localTest thrpt 5 2131394.345 ± 216974.682 ops/s

从测试结果可以看出,如果每次调用都new一个ObjectMapper,每秒可以执行200万次JSON解析;如果全局使用一个ObjectMapper,则每秒可以执行2000多万次,速度足足快了10倍。如果使用ThreadLocal的方式,每个线程给它分配一个解析器,则性能会有少许上升,但也没有达到非常夸张的地步。所以在项目中写代码的时候,只需要保证有一个全局的ObjectMapper就可以了。当然,由于ObjectMapper有很多的特性需要配置,可能会为不同的应用场景分配一个单独使用的ObjectMapper。总之,它的数量不需要太多,因为它是线程安全的。

End

所以结论就比较清晰了,只需要在整个项目里使用一个ObjectMapper就可以了,没必要每次都new一个,毕竟性能差了10倍。如果JSON有很多自定义的配置,使用全局的变量更能凸显它的优势。不要觉得这样做没有必要,保持良好的编码习惯永远是好的。高性能的代码都是点点滴滴积累起来的。不积跬步,无以至千里。不积小流,无以成江海,说的就是这个道理。

以太坊cppgolang区别 编程

以太坊cppgolang区别

以太坊是一种去中心化的开源平台,它采用智能合约技术,旨在构建和运行不受干扰的分布式应用程序。作为目前最受欢迎的区块链平台之一,以太坊提供了多种编程语言的支持,其
progolang 编程

progolang

Go语言(Golang)是由Google开发的一门静态类型编程语言。作为一名专业的Golang开发者,我深知这门语言的优势和特点。在本文中,我将介绍Golang
golangn个发送者 编程

golangn个发送者

Golang是一种开源的编程语言,由Google团队开发,旨在提高程序的并发性和简化软件开发过程。在Go语言中,有时需要向多个接收者发送信息。本文将介绍如何在G
golang技能图谱 编程

golang技能图谱

从互联网行业的快速发展到人工智能技术的日益成熟,各种编程语言也应运而生。而在这众多的编程语言中,Golang(即Go)作为一门强大且高效的开发语言备受关注。Go
评论:0   参与:  7