使用ObjectMapper解析json不用一直new了
前言
自從國(guó)產(chǎn)之光fastjson頻頻暴雷,jackson json的使用是越來(lái)越廣泛了。尤其是spring家族把它搞成了默認(rèn)的JSON處理包,jackson的使用數(shù)量更是呈爆炸式發(fā)展。
很多同學(xué)發(fā)現(xiàn),jackson并沒(méi)有類(lèi)似fastjson的JSON.parseObjec這樣的,確實(shí)看起來(lái)很快的方法。要想解析json,你不得不new一個(gè)ObjectMapper,來(lái)處理真正的解析動(dòng)作。
就像下面這樣。
public String getCarString(Car car){ ObjectMapper objectMapper = new ObjectMapper(); String str = objectMapper.writeValueAsString(car); return str; }
這種代碼就在CV工程師手中遍地開(kāi)了花。
神奇。
這代碼有問(wèn)題么?
你要說(shuō)它有問(wèn)題,它確實(shí)能正確的執(zhí)行。你要說(shuō)它沒(méi)問(wèn)題,在追求性能的同學(xué)眼里,這肯定是一段十惡不赦的代碼。
一般的工具類(lèi),都是單例的,同時(shí)是線程安全的。ObjectMapper也不例外,它也是線程安全的,你可以并發(fā)的執(zhí)行它,不會(huì)產(chǎn)生任何問(wèn)題。
這段代碼,ObjectMapper在每次方法調(diào)用的時(shí)候,都會(huì)生成一個(gè)。那它除了造成一定的年輕代內(nèi)存浪費(fèi)之外,在執(zhí)行時(shí)間上有沒(méi)有什么硬傷呢?
new和不new,真的區(qū)別有那么大么?
有一次,xjjdog隱晦的指出某段被頻繁調(diào)用的代碼問(wèn)題,被小伙伴怒吼著拿出證據(jù)。
證據(jù)?這得搬出Java中的基準(zhǔn)測(cè)試工具JMH,才能一探究竟。
JMH(the Java Microbenchmark Harness) 就是這樣一個(gè)能夠做基準(zhǔn)測(cè)試的工具。如果你通過(guò)我們一系列的工具,定位到了熱點(diǎn)代碼,要測(cè)試它的性能數(shù)據(jù),評(píng)估改善情況,就可以交給JMH。它的測(cè)量精度非常高,最高可達(dá)到納秒的級(jí)別。
JMH是一個(gè)jar包,它和單元測(cè)試框架JUnit非常的像,可以通過(guò)注解進(jìn)行一些基礎(chǔ)配置。這部分配置有很多是可以通過(guò)main方法的OptionsBuilder進(jìn)行設(shè)置的。
上圖是一個(gè)典型的JMH程序執(zhí)行的內(nèi)容。通過(guò)開(kāi)啟多個(gè)進(jìn)程,多個(gè)線程,首先執(zhí)行預(yù)熱,然后執(zhí)行迭代,最后匯總所有的測(cè)試數(shù)據(jù)進(jìn)行分析。在執(zhí)行前后,還可以根據(jù)粒度處理一些前置和后置操作。
JMH測(cè)試結(jié)果
為了測(cè)試上面的場(chǎng)景,我們創(chuàng)造了下面的基準(zhǔn)測(cè)試類(lèi)。分為三個(gè)測(cè)試場(chǎng)景:
- 直接在方法里new ObjectMapper
- 在全局共享一個(gè)ObjectMapper
使用ThreadLocal,每個(gè)線程一個(gè)ObjectMapper
這樣的測(cè)試屬于cpu密集型的。我的cpu有10核,直接就分配了10個(gè)線程的并發(fā),cpu在測(cè)試期間跑的滿滿的。
@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(); } }
測(cè)試結(jié)果如下。
Benchmark Mode Cnt Score Error Units
ObjectMapperTest.globalTest thrpt 5 25125094.559 ± 1754308.010 ops/s
ObjectMapperTest.globalTestThreadLocal thrpt 5 31780573.549 ± 7779240.155 ops/s
ObjectMapperTest.localTest thrpt 5 2131394.345 ± 216974.682 ops/s
從測(cè)試結(jié)果可以看出,如果我們每次調(diào)用都new一個(gè)ObjectMapper,每秒可以執(zhí)行200萬(wàn)次JSON解析;如果全局使用一個(gè)ObjectMapper,則每秒可以執(zhí)行2000多萬(wàn)次,速度足足快了10倍。
如果使用ThreadLocal的方式,每個(gè)線程給它分配一個(gè)解析器,則性能會(huì)有少許上升,但也沒(méi)有達(dá)到非??鋸埖牡夭?。
所以在項(xiàng)目中寫(xiě)代碼的時(shí)候,我們只需要保證有一個(gè)全局的ObjectMapper就可以了。
當(dāng)然,由于ObjectMapper有很多的特性需要配置,你可能會(huì)為不同的應(yīng)用場(chǎng)景分配一個(gè)單獨(dú)使用的ObjectMapper。總之,它的數(shù)量不需要太多,因?yàn)樗蔷€程安全的。
End
所以結(jié)論就比較清晰了,我們只需要在整個(gè)項(xiàng)目里使用一個(gè)ObjectMapper就可以了,沒(méi)必要傻不拉幾的每次都new一個(gè),畢竟性能差了10倍。如果你的JSON有很多自定義的配置,使用全局的變量更能凸顯它的優(yōu)勢(shì)。
不要覺(jué)得這樣做沒(méi)有必要,保持良好的編碼習(xí)慣永遠(yuǎn)是好的。高性能的代碼都是點(diǎn)點(diǎn)滴滴積累起來(lái)的。不積跬步,無(wú)以至千里。不積小流,無(wú)以成江海,說(shuō)的就是這個(gè)道理。
以上就是使用ObjectMapper解析json不用一直new了的詳細(xì)內(nèi)容,更多關(guān)于ObjectMapper解析json的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java?精煉解讀時(shí)間復(fù)雜度與空間復(fù)雜度
對(duì)于一個(gè)算法,其時(shí)間復(fù)雜度和空間復(fù)雜度往往是相互影響的,當(dāng)追求一個(gè)較好的時(shí)間復(fù)雜度時(shí),可能會(huì)使空間復(fù)雜度的性能變差,即可能導(dǎo)致占用較多的存儲(chǔ)空間,這篇文章主要給大家介紹了關(guān)于Java時(shí)間復(fù)雜度、空間復(fù)雜度的相關(guān)資料,需要的朋友可以參考下2022-03-03Spring Cloud Feign 自定義配置(重試、攔截與錯(cuò)誤碼處理) 代碼實(shí)踐
這篇文章主要介紹了Spring Cloud Feign 自定義配置(重試、攔截與錯(cuò)誤碼處理) 實(shí)踐,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08@Valid 無(wú)法校驗(yàn)List<E>的問(wèn)題
這篇文章主要介紹了@Valid 無(wú)法校驗(yàn)List<E>的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10Java從數(shù)據(jù)庫(kù)中讀取Blob對(duì)象圖片并顯示的方法
這篇文章主要介紹了Java從數(shù)據(jù)庫(kù)中讀取Blob對(duì)象圖片并顯示的方法,實(shí)例分析了Java讀取數(shù)據(jù)庫(kù)中Blob對(duì)象圖片的技巧與操作方法,需要的朋友可以參考下2015-02-02Spring實(shí)現(xiàn)處理跨域請(qǐng)求代碼詳解
這篇文章主要介紹了Spring實(shí)現(xiàn)處理跨域請(qǐng)求代碼詳解,具有一定借鑒價(jià)值,需要的朋友可以了解下。2017-12-12Java SSM框架(Spring+SpringMVC+MyBatis)搭建過(guò)程
最近一段時(shí)間搭建了ssm環(huán)境,并測(cè)試了幾個(gè)小項(xiàng)目,下面小編通過(guò)圖文并茂的形式給大家分享Java SSM框架(Spring+SpringMVC+MyBatis)搭建過(guò)程,需要的朋友參考下吧2017-11-11idea2020安裝MybatisCodeHelper插件的圖文教程
這篇文章主要介紹了idea2020安裝MybatisCodeHelper插件的方法,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09JAVA實(shí)現(xiàn)微信APPV3支付保姆級(jí)教程
微信實(shí)現(xiàn)支付功能與支付寶實(shí)現(xiàn)支付功能是相似的,這篇文章主要介紹了JAVA實(shí)現(xiàn)微信APPV3支付的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-01-01