<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序列化的順序性?

          共 12220字,需瀏覽 25分鐘

           ·

          2021-03-14 08:26

          走過路過不要錯過

          點擊藍字關(guān)注我們


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

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

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

          1:保持json有序的思路

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

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

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

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

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

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

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

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

          為什么要保持json有序呢?json相當于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的值,然后進行比較,全部相等則相等成立,否則不相等。

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

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

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

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

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


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

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

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

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

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

          Map<String, Object> 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<String, Object> data) { TreeMap<String, Object> transformedData = new TreeMap<>(); for (Map.Entry<String, Object> entry : data.entrySet()) { if(entry.getValue() == null) { continue; } if(entry.getValue() instanceof List) { TreeMap<String, Integer> tmpMap = new TreeMap<>(); List value = (List) entry.getValue(); for (int i = 0; i < (value).size(); i++) { Object it1 = value.get(i); // 假設(shè)只支持二維數(shù)組嵌套 tmpMap.put(transformDataToJSONAsOrderWay((Map<String, Object>) it1), i); } List<Object> 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實現(xiàn)的json字典序維持的實現(xiàn)了,其實就是 transformDataToJSONAsOrderWay() 方法,其原理也簡單,因fastjson的有序性,依賴于輸入的數(shù)據(jù)結(jié)構(gòu),那么只要維護好輸入結(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)存儲,一般可以認為其是無序的。但我們可以額外的維護一些屬性,以保證它能夠以某種順序輸出數(shù)據(jù),順序性主要體現(xiàn)在進行迭代時,如使用 keyset(), values(), entrySet() 等方法。針對額外維護順序性的數(shù)據(jù)結(jié)構(gòu)而言,其迭代自然是基于其額外字段。但針對無序的hashmap這種數(shù)據(jù)結(jié)構(gòu)而言,我們知道其底層數(shù)據(jù)是根據(jù)hash值亂序存儲的。簡單來說就是根據(jù)一個hash值,然后求余定位到一個數(shù)組下標中。即對hashmap所分配的數(shù)組對象的下標,有可能有值,有可能沒有值,那么在做迭代的時候如何做呢?多次做迭代的順序一致嗎?一個最簡單的思路自然是依次遍歷數(shù)據(jù)的每個元素,直到數(shù)據(jù)的最大值。這樣,肯定是可以保證多次遍歷的順序性的。那么,hashmap是否是這樣實現(xiàn)的呢?

              // java.util.HashMap#forEach    @Override    public void forEach(BiConsumer<? super K, ? super V> action) {        Node<K,V>[] 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 < tab.length; ++i) {                for (Node<K,V> 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(Consumer<? super Map.Entry<K,V>> action) {            Node<K,V>[] tab;            if (action == null)                throw new NullPointerException();            if (size > 0 && (tab = table) != null) {                int mc = modCount;                for (int i = 0; i < tab.length; ++i) {                    for (Node<K,V> 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(Consumer<? super K> action) {            Node<K,V>[] tab;            if (action == null)                throw new NullPointerException();            if (size > 0 && (tab = table) != null) {                int mc = modCount;                for (int i = 0; i < tab.length; ++i) {                    for (Node<K,V> 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(Consumer<? super V> action) {            Node<K,V>[] tab;            if (action == null)                throw new NullPointerException();            if (size > 0 && (tab = table) != null) {                int mc = modCount;                for (int i = 0; i < tab.length; ++i) {                    for (Node<K,V> e = tab[i]; e != null; e = e.next)                        action.accept(e.value);                }                if (modCount != mc)                    throw new ConcurrentModificationException();            }        }

          TreeMap基于key做排序處理,最符合有序性要求,其迭代實現(xiàn)如下:

              // java.util.TreeMap#forEach    @Override    public void forEach(BiConsumer<? super K, ? super V> action) {        Objects.requireNonNull(action);        int expectedModCount = modCount;        // 從最小的key開始取,進行二叉樹的中序遍歷        // 因該二叉樹在插入時維護了有序性,進行遍歷時也就有了順序了        for (Entry<K, V> 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 <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) { if (t == null) return null; else if (t.right != null) { Entry<K,V> p = t.right; while (p.left != null) p = p.left; return p; } else { Entry<K,V> p = t.parent; Entry<K,V> ch = t; while (p != null && ch == p.right) { ch = p; p = p.parent; } return p; } }

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

              // java.util.LinkedHashMap#forEach    public void forEach(BiConsumer<? super K, ? super V> action) {        if (action == null)            throw new NullPointerException();        int mc = modCount;        // 僅通過維護插入時的鏈表,即可實現(xiàn)有序迭代        for (LinkedHashMap.Entry<K,V> e = head; e != null; e = e.after)            action.accept(e.key, e.value);        if (modCount != mc)            throw new ConcurrentModificationException();    }

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




          往期精彩推薦



          騰訊、阿里、滴滴后臺面試題匯總總結(jié) — (含答案)

          面試:史上最全多線程面試題 !

          最新阿里內(nèi)推Java后端面試題

          JVM難學(xué)?那是因為你沒認真看完這篇文章


          END


          關(guān)注作者微信公眾號 —《JAVA爛豬皮》


          了解更多java后端架構(gòu)知識以及最新面試寶典


          你點的每個好看,我都認真當成了


          看完本文記得給作者點贊+在看哦~~~大家的支持,是作者源源不斷出文的動力


          作者:等你歸去來

          出處:https://www.cnblogs.com/yougewe/p/14258485.html

          瀏覽 43
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  99精品一区二区 | 激情自拍网 | 日本成人无码一本道视频 | 欧美性18| 日本在线高清 |