<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          如何保持json序列化的順序性?

          共 5585字,需瀏覽 12分鐘

           ·

          2021-01-17 09:20

          點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”

          優(yōu)質(zhì)文章,第一時間送達(dá)

          ? 作者?|? 等你歸去來

          來源 |? urlify.cn/7RvEni

          76套java從入門到精通實(shí)戰(zhàn)課程分享

           說到j(luò)son,相信沒有人會陌生,我們天天都在用。那么,我們來討論個問題,json有序嗎?是誰來決定的呢?如何保持?

            說到底,json是框架還是啥?實(shí)際上它只是一個數(shù)據(jù)格式,一個規(guī)范標(biāo)準(zhǔn),它永遠(yuǎn)不會限制實(shí)現(xiàn)方的任何操作,即不會自行去保證什么順序性之類的。json的格式僅由寫入數(shù)據(jù)的一方?jīng)Q定其長像如何。而數(shù)據(jù)讀取一方,則按照json的協(xié)議標(biāo)準(zhǔn)進(jìn)行解析,即可理解原數(shù)據(jù)的含義。json擁有較為豐富的數(shù)據(jù)格式,所以對當(dāng)前應(yīng)用還是比較友好的。

            那么,我們?nèi)绾翁幚韏son的順序性呢?


          1. 保持json有序的思路

            首先,我們要澄清有序性的概念:從某種程度上,我們可以把json看作是一個個的kv組成的數(shù)據(jù),從這個層面上來講,我們可以把有序性定義為json的key保持有序,先假設(shè)為字典序吧,那么就說這個json數(shù)據(jù)是有序的。

            其次,因?yàn)閖son的數(shù)據(jù)支持嵌套,所以,我們應(yīng)該需要保持每一層的數(shù)據(jù)都有序,才是完整有序的。

            ok, 理解完有序的概念,下面我們來看看如何實(shí)現(xiàn)有序?

            json本身是不可能保持有序了,所以,當(dāng)我們自行寫入json數(shù)據(jù)時,只需要按照 abcde... 這種key順序?qū)懭霐?shù)據(jù),那么得到的最終json就是有序的。

            但我們一般都是使用對象進(jìn)行程序變換的,所以,就應(yīng)該要從對象中取出有序的key, 然后序列化為json.

            這里保持有序,至少有兩個層面的有序:1. kv形式的key的有序; 2. 列表形式的數(shù)據(jù)有序; 還有其他可能非常復(fù)雜的有序性需求,比如按照某字段有序,倒序。。。

            所以,想保持json有序很簡單,保證有序?qū)懭刖涂梢粤恕#菜频扔跊]有說哦)

          ?

          2. 保持json有序的應(yīng)用場景舉例

            為什么要保持json有序呢?json相當(dāng)于kv數(shù)據(jù),一般情況下我們是不需要保證有序的,但有些特殊情況下也許有用。比如我有兩份json數(shù)據(jù),我想比較它們是否是相等的時候!

            比如第一份數(shù)據(jù)是 {"a":1, "b":2}, 第二份數(shù)據(jù)是 {"b":2, "a":1}, 那么你說這兩份數(shù)據(jù)是否是相等的呢?相等或不相等依據(jù)是啥?

            如果對于固定數(shù)據(jù)結(jié)構(gòu)的json, 那么也許我們可以直接取出每個key的值,然后進(jìn)行比較,全部相等則相等成立,否則不相等。

            但對于json本身就是各種不確定的數(shù)據(jù)組成,如果我們限制死必須取某些key, 那么這個通用性就很差了。所以,我們要想比較兩個json是否相等,還應(yīng)該要有另外的依據(jù)。

            另外,當(dāng)我們將有序json寫入文件之后,當(dāng)key的數(shù)據(jù)非常多時,有序?qū)嶋H上可以輔助我們快速找到對應(yīng)的key所在的位置。這是有序性帶來的好處,快速查找!

            比如下面的例子,對比兩個結(jié)果集是否相等,你覺得結(jié)果當(dāng)如何呢?

          @Test
          ????public?void?testJsonObjectOrder()?{
          ????????String?res1,?res2;
          ????????List>?nList;
          ????????Map?data?=?new?HashMap<>();
          ????????data.put("d",?"cd");
          ????????data.put("a",?1);
          ????????data.put("b",?0.45);
          ????????data.put("total",?333);
          ????????List>?list?=?new?ArrayList<>();
          ????????Map?item1?=?new?HashMap<>();
          ????????item1.put("aa",?1);
          ????????item1.put("ee",?5);
          ????????item1.put("bb",?6);
          ????????item1.put("nn",?null);
          ????????list.add(item1);
          ????????Map?item2?=?new?HashMap<>();
          ????????item2.put("xxx",?"000");
          ????????item2.put("q",?2);
          ????????item2.put("a",?"aa");
          ????????list.add(item2);
          ????????data.put("sub",?list);


          ????????Map?nData?=?new?HashMap<>(data);
          ????????nData.put("c",?null);
          ????????nData.put("abc",?null);

          ????????res1?=?JSONObject.toJSONString(data);
          ????????res2?=?JSONObject.parseObject(JSONObject.toJSONString(nData)).toJSONString();
          ????????Assert.assertEquals("序列化結(jié)果不相等default",?res1,?res2);

          ????????res2?=?JSONObject.toJSONString(JSONObject.parseObject(
          ????????????????JSONObject.toJSONString(nData,?SerializerFeature.SortField)),
          ????????????????SerializerFeature.SortField);
          ????????Assert.assertEquals("序列化結(jié)果不相等sort",?res1,?res2);

          ????????nList?=?new?ArrayList<>();
          ????????nList.add(item2);
          ????????nList.add(item1);
          ????????nData.put("sub",?nList);
          ????????res2?=?JSONObject.parseObject(JSONObject.toJSONString(nData)).toJSONString();
          ????????Assert.assertEquals("序列化結(jié)果不相等array",?res1,?res2);

          ????}

            以上是fastjson庫進(jìn)行json序列化的處理方式,json的數(shù)據(jù)結(jié)構(gòu)大部分使用可以用map進(jìn)行等價,除了純數(shù)組的結(jié)構(gòu)以外。以上測試中,除了最后一個array的位置調(diào)換,導(dǎo)致的結(jié)果不一樣之外,總體還是相等的。糾其原因,是因?yàn)樵紨?shù)據(jù)結(jié)構(gòu)是一致的,而fastjson從一定程度上維持了這個有序性。

            

          3. fastjson維護(hù)json的有序性的實(shí)現(xiàn)

            很顯然,讓我們自行寫json的工具類,還是有一定的難度的,至少要想高效完整地寫json是困難的。所以,一般我們都是借助一些現(xiàn)有的開源類庫。

            上一節(jié)中說到,fastjson維護(hù)了json一定的順序性,但是并非完整維護(hù)了順序性,它的順序性要體現(xiàn)在,相同的數(shù)據(jù)結(jié)構(gòu)序列化的json,總能得到相同的反向的相同數(shù)據(jù)結(jié)構(gòu)的數(shù)據(jù)。比如,ArrayList 的順序性被維護(hù),map的順序性被維護(hù)。

            但是很明顯,這些順序性是根據(jù)數(shù)據(jù)結(jié)構(gòu)的特性而定的,而非所謂的字典序,那么,如果我們想維護(hù)一個保持字典序的json如何處理呢?看看下面的實(shí)現(xiàn):

          public?class?JsonObjectTest?{

          ????@Test
          ????public?void?testJsonObjectOrder()?{
          ????????String?res1,?res2;
          ????????List>?nList;
          ????????Map?data?=?new?HashMap<>();
          ????????data.put("d",?"cd");
          ????????data.put("a",?1);
          ????????data.put("b",?0.45);
          ????????data.put("total",?333);
          ????????List>?list?=?new?ArrayList<>();
          ????????Map?item1?=?new?HashMap<>();
          ????????item1.put("aa",?1);
          ????????item1.put("ee",?5);
          ????????item1.put("bb",?6);
          ????????item1.put("nn",?null);
          ????????list.add(item1);
          ????????Map?item2?=?new?HashMap<>();
          ????????item2.put("xxx",?"000");
          ????????item2.put("q",?2);
          ????????item2.put("a",?"aa");
          ????????list.add(item2);
          ????????data.put("sub",?list);


          ????????Map?nData?=?new?HashMap<>(data);
          ????????nData.put("c",?null);
          ????????nData.put("abc",?null);

          ????????res1?=?JSONObject.toJSONString(data);
          ????????res2?=?JSONObject.parseObject(JSONObject.toJSONString(nData)).toJSONString();
          ????????Assert.assertEquals("序列化結(jié)果不相等default",?res1,?res2);

          ????????res2?=?JSONObject.toJSONString(JSONObject.parseObject(
          ????????????????JSONObject.toJSONString(nData,?SerializerFeature.SortField)),
          ????????????????SerializerFeature.SortField);
          ????????Assert.assertEquals("序列化結(jié)果不相等sort",?res1,?res2);

          ????????res2?=?JSONObject.toJSONString(JSONObject.parseObject(
          ????????????????JSONObject.toJSONString(nData,?SerializerFeature.WriteMapNullValue)),
          ????????????????SerializerFeature.WriteMapNullValue);
          ????????Assert.assertEquals("序列化結(jié)果不相等null",?res1,?res2);

          ????????nList?=?new?ArrayList<>();
          ????????nList.add(item2);
          ????????nList.add(item1);
          ????????nData.put("sub",?nList);
          ????????res2?=?JSONObject.parseObject(JSONObject.toJSONString(nData)).toJSONString();
          ????????Assert.assertEquals("序列化結(jié)果不相等array",?res1,?res2);

          ????????nList?=?new?ArrayList<>();
          ????????nList.add(item2);
          ????????nList.add(item1);
          ????????nData.put("sub",?nList);
          ????????res1?=?transformDataToJSONAsOrderWay(data);
          ????????res2?=?transformDataToJSONAsOrderWay(nData);
          ????????Assert.assertEquals("序列化結(jié)果不相等array-s",?res1,?res2);



          ????}

          ????/**
          ?????*?將原始數(shù)據(jù)轉(zhuǎn)換為有序的集合
          ?????*/

          ????private?String?transformDataToJSONAsOrderWay(Map?data)?{
          ????????TreeMap?transformedData?=?new?TreeMap<>();
          ????????for?(Map.Entry?entry?:?data.entrySet())?{
          ????????????if(entry.getValue()?==?null)?{
          ????????????????continue;
          ????????????}
          ????????????if(entry.getValue()?instanceof?List)?{
          ????????????????TreeMap?tmpMap?=?new?TreeMap<>();
          ????????????????List?value?=?(List)?entry.getValue();
          ????????????????for?(int?i?=?0;?i?????????????????????Object?it1?=?value.get(i);
          ????????????????????//?假設(shè)只支持二維數(shù)組嵌套
          ????????????????????tmpMap.put(transformDataToJSONAsOrderWay((Map)?it1),?i);
          ????????????????}
          ????????????????List?orderedList?=?new?ArrayList<>(tmpMap.size());
          ????????????????for?(Integer?listNo?:?tmpMap.values())?{
          ????????????????????orderedList.add(value.get(listNo));
          ????????????????}
          ????????????????transformedData.put(entry.getKey(),?orderedList);
          ????????????????continue;
          ????????????}
          ????????????transformedData.put(entry.getKey(),?entry.getValue());
          ????????}
          ????????return?JSONObject.toJSONString(transformedData);
          ????}
          }

            以上就是完整的基于fastjson實(shí)現(xiàn)的json字典序維持的實(shí)現(xiàn)了,其實(shí)就是 transformDataToJSONAsOrderWay() 方法,其原理也簡單,因fastjson的有序性,依賴于輸入的數(shù)據(jù)結(jié)構(gòu),那么只要維護(hù)好輸入結(jié)構(gòu)的字典序就好了。TreeMap 是以字典序排序key的一種數(shù)據(jù)結(jié)構(gòu),符合這需求,另外,將list這種數(shù)據(jù)結(jié)構(gòu),轉(zhuǎn)化為kv這種數(shù)據(jù)結(jié)構(gòu),將整個item作為key排序后,再將其放入對應(yīng)位置,從而保證了整體的順序性。但這種list的順序性,不一定是大家所理解的字典序,但一定可以保證得到相同的順序。

            另外,fastjson中還考慮了對于null值的處理,比如json中有null值的數(shù)據(jù)與沒有null值的數(shù)據(jù),你說是相等呢還是不相等呢?

          ?

          4. hashmap數(shù)據(jù)結(jié)構(gòu)的順序迭代原理

            map是一種kv型的數(shù)據(jù)結(jié)構(gòu)存儲,一般可以認(rèn)為其是無序的。但我們可以額外的維護(hù)一些屬性,以保證它能夠以某種順序輸出數(shù)據(jù),順序性主要體現(xiàn)在進(jìn)行迭代時,如使用 keyset(), values(), entrySet() 等方法。針對額外維護(hù)順序性的數(shù)據(jù)結(jié)構(gòu)而言,其迭代自然是基于其額外字段。但針對無序的hashmap這種數(shù)據(jù)結(jié)構(gòu)而言,我們知道其底層數(shù)據(jù)是根據(jù)hash值亂序存儲的。簡單來說就是根據(jù)一個hash值,然后求余定位到一個數(shù)組下標(biāo)中。即對hashmap所分配的數(shù)組對象的下標(biāo),有可能有值,有可能沒有值,那么在做迭代的時候如何做呢?多次做迭代的順序一致嗎?一個最簡單的思路自然是依次遍歷數(shù)據(jù)的每個元素,直到數(shù)據(jù)的最大值。這樣,肯定是可以保證多次遍歷的順序性的。那么,hashmap是否是這樣實(shí)現(xiàn)的呢?

          //?java.util.HashMap#forEach
          ????@Override
          ????public?void?forEach(BiConsumersuper?K,???super?V>?action)?{
          ????????Node[]?tab;
          ????????if?(action?==?null)
          ????????????throw?new?NullPointerException();
          ????????if?(size?>?0?&&?(tab?=?table)?!=?null)?{
          ????????????int?mc?=?modCount;
          ????????????//?1.?迭代所有數(shù)組元素
          ????????????//?2.?迭代hash沖突時的鏈表
          ????????????for?(int?i?=?0;?i?????????????????for?(Node?e?=?tab[i];?e?!=?null;?e?=?e.next)
          ????????????????????action.accept(e.key,?e.value);
          ????????????}
          ????????????if?(modCount?!=?mc)
          ????????????????throw?new?ConcurrentModificationException();
          ????????}
          ????}
          ????????//?java.util.HashMap.EntrySet#forEach
          ????????public?final?void?forEach(Consumersuper?Map.Entry>?action)?{
          ????????????Node[]?tab;
          ????????????if?(action?==?null)
          ????????????????throw?new?NullPointerException();
          ????????????if?(size?>?0?&&?(tab?=?table)?!=?null)?{
          ????????????????int?mc?=?modCount;
          ????????????????for?(int?i?=?0;?i?????????????????????for?(Node?e?=?tab[i];?e?!=?null;?e?=?e.next)
          ????????????????????????action.accept(e);
          ????????????????}
          ????????????????if?(modCount?!=?mc)
          ????????????????????throw?new?ConcurrentModificationException();
          ????????????}
          ????????}
          ????????//?keySet(),?換成了取key的處理
          ????????//?java.util.HashMap.KeySet#forEach
          ????????public?final?void?forEach(Consumersuper?K>?action)?{
          ????????????Node[]?tab;
          ????????????if?(action?==?null)
          ????????????????throw?new?NullPointerException();
          ????????????if?(size?>?0?&&?(tab?=?table)?!=?null)?{
          ????????????????int?mc?=?modCount;
          ????????????????for?(int?i?=?0;?i?????????????????????for?(Node?e?=?tab[i];?e?!=?null;?e?=?e.next)
          ????????????????????????action.accept(e.key);
          ????????????????}
          ????????????????if?(modCount?!=?mc)
          ????????????????????throw?new?ConcurrentModificationException();
          ????????????}
          ????????}
          ????????//?values()?換成了取value的處理
          ????????//?java.util.HashMap.Values#forEach
          ????????public?final?void?forEach(Consumersuper?V>?action)?{
          ????????????Node[]?tab;
          ????????????if?(action?==?null)
          ????????????????throw?new?NullPointerException();
          ????????????if?(size?>?0?&&?(tab?=?table)?!=?null)?{
          ????????????????int?mc?=?modCount;
          ????????????????for?(int?i?=?0;?i?????????????????????for?(Node?e?=?tab[i];?e?!=?null;?e?=?e.next)
          ????????????????????????action.accept(e.value);
          ????????????????}
          ????????????????if?(modCount?!=?mc)
          ????????????????????throw?new?ConcurrentModificationException();
          ????????????}
          ????????}

            LinkedHashMap是按照插入的順序排列的一種map, 它與ArrayList這種有序性有相似性,但相對難以理解一些。因?yàn)閘ist這種數(shù)據(jù)結(jié)構(gòu),你說先插入哪個元素,后插入哪個元素,是顯而易見的。然而像map這種數(shù)據(jù)結(jié)構(gòu),你很想像它是先插入某元素,再插入另一個元素的,這是一種先入為主的概念導(dǎo)致的。但它并不影響我們理解map有序性的實(shí)現(xiàn),LinkedHashMap的迭代實(shí)現(xiàn)如下:

          //?java.util.TreeMap#forEach
          ????@Override
          ????public?void?forEach(BiConsumersuper?K,???super?V>?action)?{
          ????????Objects.requireNonNull(action);
          ????????int?expectedModCount?=?modCount;
          ????????//?從最小的key開始取,進(jìn)行二叉樹的中序遍歷
          ????????//?因該二叉樹在插入時維護(hù)了有序性,進(jìn)行遍歷時也就有了順序了
          ????????for?(Entry?e?=?getFirstEntry();?e?!=?null;?e?=?successor(e))?{
          ????????????action.accept(e.key,?e.value);

          ????????????if?(expectedModCount?!=?modCount)?{
          ????????????????throw?new?ConcurrentModificationException();
          ????????????}
          ????????}
          ????}
          ????
          ????/**
          ?????*?Returns?the?successor?of?the?specified?Entry,?or?null?if?no?such.
          ?????*/

          ????static??TreeMap.Entry?successor(Entry?t)?{
          ????????if?(t?==?null)
          ????????????return?null;
          ????????else?if?(t.right?!=?null)?{
          ????????????Entry?p?=?t.right;
          ????????????while?(p.left?!=?null)
          ????????????????p?=?p.left;
          ????????????return?p;
          ????????}?else?{
          ????????????Entry?p?=?t.parent;
          ????????????Entry?ch?=?t;
          ????????????while?(p?!=?null?&&?ch?==?p.right)?{
          ????????????????ch?=?p;
          ????????????????p?=?p.parent;
          ????????????}
          ????????????return?p;
          ????????}
          ????}

            ok, 到此我們分析了多個類型的map的有序性的實(shí)現(xiàn)。從內(nèi)部解釋了為什么我們使用TreeMap數(shù)據(jù)結(jié)構(gòu)時,就可以使json保持字典序了。因?yàn)閒astjson在寫json數(shù)據(jù)時,針對map的寫入,就是通過entrySet()迭代元素進(jìn)行寫入的了。


          粉絲福利:Java從入門到入土學(xué)習(xí)路線圖

          ??????

          ??長按上方微信二維碼?2 秒


          感謝點(diǎn)贊支持下哈?

          瀏覽 29
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    国产成人黄色毛片不卡在线看 | 无码污污网站 | 日日夜夜狠狠 | 竹菊影视一区二区 | 国产中文字幕在线播放 |