Json底层一览

阿凡达
• 阅读 3156

在开始了解Json的原理之前,首先看一段代码,在这里以阿里的FastJson为例。

public class JsonRun {
    public static void main(String[] args) {
        JSONObject jsonObject =new JSONObject();
        jsonObject.put("id","a");
        jsonObject.put("name","b");
        System.out.println(jsonObject.toJSONString());
    }
}

当看到上述代码的时候,可能一般的程序员都会想到的是输出为如下Json

{"id":"a","name":"b"}
但是运行这段程序,你会发现控制台打印出来的是如下代码:
{"name":"b","id":"a"}

那么为什么会出现这种情况呢,翻开FastJson的源码便知道了,首先定位到 JsonObject 这个类的构造函数,如下:

public JSONObject(int initialCapacity, boolean ordered){
        if (ordered) {
            map = new LinkedHashMap<String, Object>(initialCapacity);
        } else {
            map = new HashMap<String, Object>(initialCapacity);
        }
    }

这里的 ordered 为一个构造参数,表示的是是否按照顺序添加,此处先不管,然后可以发现在阿里的FastJson中,其实默认的Json实现是一个Map,那么对于LinkedHashMap来讲,它是一个map和双向链表的整合体,所以在LinkedList中,每一个Node都会有一个前指针和一个后指针

HashMap

LinkedHashMap 是一个HashMap的变种,大家都知道,一个HashMap是由一个桶和一个桶后面的节点组成的,而桶其实是一个数组,每一个桶的索引所对应的值都是由Hash()函数计算得出的。那么这样就会导致桶的元素是一个乱序的存储的,例如在本段代码中的idname,它们所在的桶索引可能是:
Json底层一览

这样就导致了一个问题,就是Json的键的顺序是无法保证的,那么既然HashMap是无法保证的,为什么LinkedHashMap却可以保证顺序。

LinkedHashMap

翻开LinkedHashMap的源码可以发现,在其节点类里面,LinkedHashMap在 HashMap的Entry基础上又添加了一个beforeafter指针,

  static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }

那么这两个指针就是双向链表的指针。有了这两个指针之后,每一个新插入的节点都会知道他的前驱结点和后置节点,那么对于LinkedHashMap的插入顺序就会有保证了。所以其对应的数据结构如图:
Json底层一览

在这个结构里面,桶索引是id的第一个节点是一个头节点,在新插入name的时候,LinkedHashMap会将head节点的after指针指向name,所以虽然这是一个HashMap,但是它的顺序还是可以保证的。

LinkedHashMap的迭代

区别于HashMap以索引的方式进行迭代,LinkedHashMap是以链表的指针进行迭代的,如以下代码所示:

abstract class LinkedHashIterator {
        LinkedHashMap.Entry<K,V> next;
        LinkedHashMap.Entry<K,V> current;
        int expectedModCount;

        LinkedHashIterator() {
            next = head;
            expectedModCount = modCount;
            current = null;
        }


final LinkedHashMap.Entry<K,V> nextNode() {
            LinkedHashMap.Entry<K,V> e = next;  //next就是head节点
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            current = e;
            next = e.after; //此处每一次的迭代都是链表的after
            return e;
        }

可以看到在每一次迭代的时候LinkedHashMap都是以链表的next节点作为下一个迭代,那么HashMap呢?

HashMap的迭代

abstract class HashIterator {
        Node<K,V> next;        // next entry to return
        Node<K,V> current;     // current entry
        int expectedModCount;  // for fast-fail
        int index;             // current slot

HashIterator() {
            expectedModCount = modCount;
            Node<K,V>[] t = table;
            current = next = null;
            index = 0;
            if (t != null && size > 0) { // advance to first entry
                do {} while (index < t.length && (next = t[index++]) == null);
            }
        }


final Node<K,V> nextNode() {
            Node<K,V>[] t;
            Node<K,V> e = next;
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (e == null)
                throw new NoSuchElementException();
            if ((next = (current = e).next) == null && (t = table) != null) {
                do {} while (index < t.length && (next = t[index++]) == null);
            }
            return e;
        }

注意这一段代码

 if (t != null && size > 0) { // advance to first entry
        do {} while (index < t.length && (next = t[index++]) == null);
   }

这一段代码的作用是找出table[]中第一个不为null的桶,所以其实HashMap的迭代就是依据桶中的顺序来的,但是LinkedHashMap则是按找链表的顺序来的。

总结

其实每一个java的设计都是很精妙的...

点赞
收藏
评论区
推荐文章
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
Wesley13 Wesley13
4年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Wesley13 Wesley13
4年前
java 多文件打包压缩
publicstaticvoidmain(Stringargs){FilesrcFiles{newFile("E:\\testZip\\testZip.zip"),newFile("E:\\testZip\\test2.json"),newFile("E:\\testZip\\test3
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Wesley13 Wesley13
4年前
java指令重排序代码演示
Jdk版本:jdk1.8.0\_151代码publicclassSimpleHappenBefore{publicstaticvoidmain(Stringargs)throwsInterruptedException{for(inti0;i<5
CuterCorley CuterCorley
4年前
Java编程 经验技巧汇总
1.JSONArray数组如何循环遍历javapackagexxx;importnet.sf.json.JSONArray;importnet.sf.json.JSONObject;publicclassTest{publicstaticvoidmain(Stringargs){/author:命运的信徒da
Stella981 Stella981
4年前
Logstash 解析Json字符串,删除json嵌套字段
一、场景:此文以一个简单的json字符串文件为例,描述如何用logstash解析嵌套的json,并删除其中的某些字段我们在linux中test.json的内容如下:{"timestamp":"20180802T14:42:50.0844670800","flow_id":364959073190719,"in_iface":"e
Wesley13 Wesley13
4年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Stella981 Stella981
4年前
Qt JSON解析生成笔记(把JSON转成一个类对象)
对于这样一段json!复制代码(https://oscimg.oschina.net/oscnet/0e61d87c5c5dc58b98653560c75fb77fefd.gif){"name":"布衣食","gender":"Male","age":25,"attribute
Stella981 Stella981
4年前
Integer 中的 “==” 陷阱
先看一段代码:staticbooleantest(Integera,Integerb){returnab;}publicstaticvoidmain(Stringargs){System.out
Stella981 Stella981
4年前
FastJson 反序列化注意事项
问题描述使用fastJson对json字符串进行反序列化时,有几个点需要注意一下:反序列化内部类反序列化模板类0\.Getter/Setter问题如我们希望返回的一个json串为"name":"name","isDeleted":true,"isEmpty":1