使用ObjectMapper解析json不用一直new了

作者:小姐姐味道 时间:2023-11-25 05:20:10 

前言

自从国产之光fastjson频频暴雷,jackson json的使用是越来越广泛了。尤其是spring家族把它搞成了默认的JSON处理包,jackson的使用数量更是呈 * 式发展。

很多同学发现,jackson并没有类似fastjson的JSON.parseObjec这样的,确实看起来很快的方法。要想解析json,你不得不new一个ObjectMapper,来处理真正的解析动作。

就像下面这样。

public String getCarString(Car car){
   ObjectMapper objectMapper = new ObjectMapper();
   String str = objectMapper.writeValueAsString(car);
   return str;
}

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

神奇。

这代码有问题么?

你要说它有问题,它确实能正确的执行。你要说它没问题,在追求性能的同学眼里,这肯定是一段十恶不赦的代码。

一般的工具类,都是单例的,同时是线程安全的。ObjectMapper也不例外,它也是线程安全的,你可以并发的执行它,不会产生任何问题。

这段代码,ObjectMapper在每次方法调用的时候,都会生成一个。那它除了造成一定的年轻代内存浪费之外,在执行时间上有没有什么硬伤呢?

new和不new,真的区别有那么大么?

有一次,xjjdog隐晦的指出某段被频繁调用的代码问题,被小伙伴怒吼着拿出证据。

证据?这得搬出Java中的基准测试工具JMH,才能一探究竟。

JMH(the Java Microbenchmark Harness) 就是这样一个能够做基准测试的工具。如果你通过我们一系列的工具,定位到了热点代码,要测试它的性能数据,评估改善情况,就可以交给JMH。它的测量精度非常高,最高可达到纳秒的级别。

JMH是一个jar包,它和单元测试框架JUnit非常的像,可以通过注解进行一些基础配置。这部分配置有很多是可以通过main方法的OptionsBuilder进行设置的。

使用ObjectMapper解析json不用一直new了

上图是一个典型的JMH程序执行的内容。通过开启多个进程,多个线程,首先执行预热,然后执行迭代,最后汇总所有的测试数据进行分析。在执行前后,还可以根据粒度处理一些前置和后置操作。

JMH测试结果

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

  • 直接在方法里new ObjectMapper

  • 在全局共享一个ObjectMapper

  • 使用ThreadLocal,每个线程一个ObjectMapper

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

@BenchmarkMode({Mode.Throughput})
@OutputTimeUnit(TimeUnit.SECONDS)
@State(Scope.Thread)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(1)
@Threads(10)
public class ObjectMapperTest {
   String json = "{ \"color\" : \"Black\", \"type\" : \"BMW\" }";
   @State(Scope.Benchmark)
   public static class BenchmarkState {
       ObjectMapper GLOBAL_MAP = new ObjectMapper();
       ThreadLocal<ObjectMapper> GLOBAL_MAP_THREAD = new ThreadLocal<>();
   }
   @Benchmark
   public Map globalTest(BenchmarkState state) throws Exception{
       Map map = state.GLOBAL_MAP.readValue(json, Map.class);
       return map;
   }
   @Benchmark
   public Map globalTestThreadLocal(BenchmarkState state) throws Exception{
       if(null == state.GLOBAL_MAP_THREAD.get()){
           state.GLOBAL_MAP_THREAD.set(new ObjectMapper());
       }
       Map map = state.GLOBAL_MAP_THREAD.get().readValue(json, Map.class);
       return map;
   }
   @Benchmark
   public Map localTest() throws Exception{
       ObjectMapper objectMapper = new ObjectMapper();
       Map map = objectMapper.readValue(json, Map.class);
       return map;
   }
   public static void main(String[] args) throws Exception {
       Options opts = new OptionsBuilder()
               .include(ObjectMapperTest.class.getSimpleName())
               .resultFormat(ResultFormatType.CSV)
               .build();
       new Runner(opts).run();
   }
}

测试结果如下。

Benchmark                                Mode  Cnt         Score         Error  Units
ObjectMapperTest.globalTest             thrpt    5  25125094.559 &plusmn; 1754308.010  ops/s
ObjectMapperTest.globalTestThreadLocal  thrpt    5  31780573.549 &plusmn; 7779240.155  ops/s
ObjectMapperTest.localTest              thrpt    5   2131394.345 &plusmn;  216974.682  ops/s

从测试结果可以看出,如果我们每次调用都new一个ObjectMapper,每秒可以执行200万次JSON解析;如果全局使用一个ObjectMapper,则每秒可以执行2000多万次,速度足足快了10倍。

如果使用ThreadLocal的方式,每个线程给它分配一个解析器,则性能会有少许上升,但也没有达到非常夸张的地步。

所以在项目中写代码的时候,我们只需要保证有一个全局的ObjectMapper就可以了。

当然,由于ObjectMapper有很多的特性需要配置,你可能会为不同的应用场景分配一个单独使用的ObjectMapper。总之,它的数量不需要太多,因为它是线程安全的。

End

所以结论就比较清晰了,我们只需要在整个项目里使用一个ObjectMapper就可以了,没必要傻不拉几的每次都new一个,毕竟性能差了10倍。如果你的JSON有很多自定义的配置,使用全局的变量更能凸显它的优势。

不要觉得这样做没有必要,保持良好的编码习惯永远是好的。高性能的代码都是点点滴滴积累起来的。不积跬步,无以至千里。不积小流,无以成江海,说的就是这个道理。

来源:https://juejin.cn/post/7086443387636678670

标签:ObjectMapper,解析,json
0
投稿

猜你喜欢

  • SpringBoot如何实现word文档转pdf

    2023-04-19 09:33:55
  • java简单实现复制 粘贴 剪切功能代码分享

    2023-10-14 05:08:23
  • Jmeter自定义函数base64加密实现过程解析

    2021-10-21 14:04:00
  • java中的GC收集器详情

    2021-11-22 16:59:34
  • Java 抽象类与接口的对比

    2022-05-31 08:28:30
  • 利用Jetpack Compose绘制可爱的天气动画

    2022-04-06 18:50:35
  • Linux下java环境配置图文方法

    2021-10-24 06:22:52
  • Android sdcard实现图片存储 、联网下载

    2023-03-17 22:36:35
  • springBoot详解集成Swagger流程

    2023-02-26 22:23:33
  • Springboot-Shiro基本使用详情介绍

    2022-10-13 03:02:42
  • C#面向对象设计原则之里氏替换原则

    2022-08-27 23:59:22
  • Android 用 camera2 API 自定义相机

    2023-04-30 11:29:37
  • Android线程实现图片轮播

    2021-06-02 17:49:23
  • SpringBoot定时任务设计之时间轮案例原理详解

    2023-04-24 00:48:36
  • 蓝牙原理Android代码实现

    2023-07-10 20:25:31
  • Java的异常体系以及File类构造方法详解

    2021-09-05 20:06:41
  • 详解Mybatis的缓存

    2022-10-23 22:49:13
  • idea中的Maven导包失败问题解决方案汇总

    2023-07-12 12:34:22
  • android开发教程之自定义属性用法详解

    2022-09-22 20:26:17
  • OpenCV计算图像的水平和垂直积分投影

    2021-10-09 08:40:45
  • asp之家 软件编程 m.aspxhome.com