数据结构:
数组+链表+红黑树
- 为啥用数组:数组使用连续存储单元存储数据,查找复杂度为O(1),非常快,但是移动效率低,为O(n)
- 通过hash算法得到数组下标,index=hashcode%数组长度,但是hash算法有可能会对不同的key算出同样的hash值,导致hash冲突
- 为了解决hash冲突,引入线性链表:单向链表:插入删除效率很高,O(1),查找效率低O(n)
- 当链表中元素越来越多,链表会越来越长,为了优化查询效率,引入红黑树
- 红黑树确保没有一条路径会比其他路径长出两倍,从而是接近平衡的,对于插入、查找,时间复杂度最坏为O(log(n))
容量:
- 默认初始容量16
- 在调用put时初始化,一般的取模运算index=hashcode%数组长度效率太低,用的是位运算,位运算要求数组长度必须是2的指数次幂
- 扩容会扩到原来的两倍,扩容会需要rehash,会增加处理时间
- jdk7当高并发场景下多线程同时对一个hashmap扩容,会导致死锁
- 死锁原因:移动链表内容时采用头插法,节点顺序倒置,一旦两个线程同时操作,有可能进行两次倒置,彼此next互指,造成死锁
- jdk8扩容不再需要rehash,不再采用倒置的头插法,避免了死锁,再加上红黑树的优势,效率比7提高15%,
- 当链表中节点超过8个就会转红黑树,当原表中元素超过75%时即触发扩容,扩容因子=0.75
为什么扩容因子是0.75:提高空间利用率和 减少查询成本的折中,主要是泊松分布,0.75的话碰撞最小
为什么阈值是8:在理想情况下,使用随机哈希码,节点出现的频率在hash桶中遵循泊松分布,按照泊松分布的计算公式计算出了桶中元素个数和概率的对照表,可以看到链表中元素个数为8时的概率已经非常小,再多的就更少了,所以原作者在选择链表元素个数时选择了8,是根据概率统计而选择的
HashDos攻击问题:
- 原因
无论我们服务端使用什么语言,我们拿到json格式的数据之后都需要做jsonDecode(),将json串转换为json对象,而对象默认会存储于Hash Table,而Hash Table很容易被碰撞攻击。我只要将攻击数据放在json中,服务端程序在做jsonDecode()时必定中招,中招后CPU会立刻飙升至100%。16核的CPU,16个请求就能达到DoS的目的。
- 解决
首先我们需要增加权限验证,最大可能的在jsonDecode()之前把非法用户拒绝。其次在jsonDecode()之前做数据大小与参数白名单验证。旧项目的改造与维护成本如果很高,建议自己重写jsonDecode()方法。