上午刚好碰到这个问题,简单的查了一下,然后记录一下。
由于前后台数据传递都是以json形式互相通信,所以就涉及json传递数据的属性排序问题。以下测试使用阿里的fastjson
。
首先使用JSONObject的put方法,一开始我以为是按照字符顺序排序的,但是经过测试发现并不是这样
根据测试结果可以看出,应该是内部实现的时候,排序方法就有一定的问题。
当我们在controller中直接返回LinkedHashMap的时候,得到的属性顺序就是按照map的put时的顺序,比如:
因此,当涉及到使用属性顺序的情况时,我们就需要尽量使用这种直接传递对象的形式。
那么能否让json串中的数据按照我们想要的格式进行排序呢?
我们通常会将对象转化为json串,然后再进行传递,所以我们可以在对象中对其属性进行排序,使用@JSONField(ordinal = n)
,即可返回按照设定顺序的json串,如下
当我们不使用这个注解的时候,就会使用JSONObject的默认排序规则,顺序就会打乱。
如下
分析
这时我们分析一下为什么会出现json的属性排序打乱这个问题。
首先在JSONObject的构造函数,我们可以看到,默认情况下JSONObject中的map使用的是HashMap
。
所以问题就转移为,为什么HashMap会出现次序被打乱的情况。
首先,根据我的测试,在jdk1.8中,使用HashMap进行测试。
通过这几张图就能发现貌似有什么不对。
那么,接下来我们就需要寻根溯源,一探HashMap
的源码。。。
经过我们对HashMap源码的探究,大概发现了HashMap中值的排序规则,主要就是依据HashCode来进行相关的排序。
而我们又知道HashMap中的HashCode是这样计算的。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
也就是对key值取hashCode,并与它的右移16位得到的值进行异或操作。
接着我进行了一下测试,发现确实如此。
然后再和容量进行hash操作,就能得到在HashMap中的hash值。我们的HashMap的容量为默认值16
"aaa"对应的hash为10111100001000000
,容量1111
(16-1),经过与操作得到的hash为0000
。
同理,"bbb"为0011
;"ccc"为0010
;"ddd"为0101
;"eee"为0100
。
根据这几个hash值得排序得到顺序为"aaa","ccc","bbb","eee","ddd"
,和我们在map得toString方法得到的顺序一致。
但是我们之前测试的"aa",“bb”…这个好像就不一样,当我们数据为9个的时候,貌似还有一个顺序,但是当数据为10个了,就会发现突然乱序了。
经过计算,我们发现"aa",“bb”…这些key值的最终hash为0000
,所以,它们都映射到hash tab中的一个元素上了,也就是一个链表,我们在HashMap源码的研究中发现,当数据量达到一个阈值时,就会将链表变为红黑树,那么,为了解决这个问题,我们接下来就需要了解红黑树了。
关于HashMap中的红黑树源码的解读,这里看到一篇很好的文章,所以我就不重复劳动了,而且看源码好费脑子和时间啊?
http://www.cnblogs.com/graywind/p/9471756.html
这里补一个插曲,因为我们在看HashMap源码的时候,有追踪到hashcode()方法,虽然不同的类都有自己的hashcode()方法实现,但是最基础的object类的hashcode()方法确是一个比较特殊的存在。
public native int hashCode();
从而引出了native
关键字,以及相关外部hashcode()源码是如何实现等知识,这里贴一些我看到的比较好的文章。
关于native
关键字:
https://www.cnblogs.com/Qian123/p/5702574.html
https://blog.csdn.net/Aphysia/article/details/80593654
关于hashcode()源码的解析:
http://www.cnblogs.com/dolphin0520/p/3681042.html
最后,让我们为整个问题划一个圆满的句号吧。
回顾我们上面的解读,仅剩下最后一个问题,就是当
Map map = new HashMap();
map.put("aa", 10);
map.put("bb", 9);
map.put("cc", 8);
map.put("dd", 7);
map.put("ee", 6);
map.put("ff", 5);
map.put("gg", 4);
map.put("hh", 3);
map.put("ii", 2);
map.put("jj", 1);
System.out.println(map);
打印出来的顺序变得很混乱,我们知道了在table.size()为16的时候,这些值最后映射的bin为0。(1111
与每个key的hash()结果进行与操作)
而我们在看putVal
源码的时候,发现当bin数量达到一定值时,就会执行treeifyBin
,也就是将链表进行红黑树化。为此,我还花费了很长时间去学习红黑树的知识,并且看了HashMap中红黑树的源码。
但是! 我发现结果并不是我想的那样,我们上面的例子最后并没有红黑树化。下面我们来分析为什么没有红黑树化。
我们先看putVal()
方法,我只截取其中的关键部分,也就是每次进行值插入时的代码块。
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
这部分代码就是统计bin链表中元素个数,并是否进行红黑树化的代码块。判断条件为:
if (binCount >= TREEIFY_THRESHOLD - 1)
但是这个条件只有在我们插入第9个值时才会进入,你可以手动模拟一下,接着就是执行treeifyBin
这个方法,所以我们进入这个方法看一下。
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
看到这时,我人晕了。首先就有这个条件:
// MIN_TREEIFY_CAPACITY = 64
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
而我们table初始长度为16啊,所以这里对table进行了一次扩容,但是并没有真正的红黑树化。当我们插入第10个值时,又进行了一次扩容,之后再插入值时,table结构已经发生了变化,所以也并没有红黑树化,直到满足条件的时候才会真正的红黑树化。
那么我们来分别分析插入第9个值和插入第10个值时,打印结果为什么会不同。
插入9个值结果
插入10个值结果
我们再看一下这些hash值的二进制:
aa: 1100 0010 0000
bb: 1100 0100 0000
cc: 1100 0110 0000
dd: 1100 1000 0000
ee: 1100 1010 0000
ff: 1100 1100 0000
gg: 1100 1110 0000
hh: 1101 0000 0000
ii: 1101 0010 0000
jj: 1101 0100 0000
然后插入第9个值,也就是"ii"的时候,进行了第一次扩容,此时table.size()为32,11111
与上面hash值进行&
操作。此时所有还是都为0,链表中有9个值,所以打印出来的结果还是按照hash排序的
插入第10个值,也就是"jj"时,进行了第二次扩容,此时size为64,111111
与上面hash值进行&
操作。这时得到的值为两组,分别为0
和32
,然后我们就会发现,0
组的值有:bb,dd,ff,hh,jj;32
组的有:aa,cc,ee,gg,ii。
至此,水落石出,我们终于查清,为什么会这样了。
一个小问题引出的惊天大案,至此完美落幕。