Map(存放KEY-VALUE的容器)

HashMap

HashMap是一种无序的KV存储结构。
1.存储结构
a.基本存储结构是一个可以扩容的动态数组,初始默认大小16,扩容的时机是,当数组的中的元素到达数组当前的0.75倍,就会扩容到当前数组长度的2倍。

b.数组中存的每个元素都是一个Node,他的属性有:当前位置的hash值,key,value,执向下一个节点的指针next。

2.put流程:
a.当插入一个Node节点的时候,首先会计算出当前要插入的KEY的hash值,计算规则:如果KEY为null,哈希值为0,所以HashMap只允许存在一个Key为NULL的值。计算的时候,先用通用的hashCode方法计算出一个值,然后右移16位,相当于把高16位移动到低位,高位补0,低位舍弃,最后将新值和原本的值做异或运算(0^0=0, 1^0=1, 0^1=1, 1^1=0)最后得到的值就是hash,这样就可以将高低位二进制特征混合起来。

b.计算完hash值后,开始调用put方法,首先会判断当前数组是否是null,然后初始化数组。因为HashMap应用到懒加载的机制,只有在第一次插入数据的时候才会初始化数组,这样的好处是防止初始化后不用造成的内存浪费。

c.然后开始计算Node要放入的槽位,计算的方法是(n - 1) & hash,当前数组的槽位减一然后和hash值做与运算(0&0=0, 0&1=0, 1&0=0, 1&1=1),因为我们平时用到的HashMap大小的这个值。一般来说高16位的值都是0,与运算只有在两个值都是1的时候才会得到1,所以这里就存在一个二进制码锁屏蔽的问题,也就是hash值得高16位参与不到运算中来,这也就是之前为什么计算hash值的时候要先右移然后做异或运算,这样把高低位的二进制特征混合起来的好处就是,让数据分布的更为均匀,减少哈希碰撞的几率。

e.当获取到Node要存放的槽位后,如果这个槽位上没有Node,那就直接放进去。如果这个槽位上已经有值,那么会判断当前KEY是否一致,如果一致,则进行覆盖操作,如果不一致则判断当前节点是TreeNode,如果是按照红黑树的方法插入,如果不是按照链表的方法插入,完成后会判断当前链表上的节点是否达到8个,到达8个,链表会转化位红黑树。

3.HashMap在多线程环境下会出现什么问题?
a.在多线程执行put操作时,如果出现哈希碰撞,导致两个线程算出来的槽位是相同的,就可能会出现数据的覆盖和丢失。

void addEntry(int hash, K key, V value, int bucketIndex) {
    //多个线程操作数组的同一个位置
    Entry e = table[bucketIndex];
        table[bucketIndex] = new Entry(hash, key, value, e);
        if (size++ >= threshold)
            resize(2 * table.length);
    }
 

b.在JDK1.7中,在发生哈希冲突,插入链表时使用的是头插法。在触发扩容条件下,当把整个链表的元素迁移到新数组中,会发生指针顺序的反转,在多个线程扩容的时候,会出现环形链表。当get方法查询到此链表的时候,就会发生死循环。JDK1.8改为尾插法,解决了此问题。

4.为什么建议声明hashMap的大小是2的N次方?
因为在计算槽位的时候,(n - 1) & hash,如果是2的N次方的话,n-1的二进制位上会出现更多的0,在进行与运算的时候会被更多位的0屏蔽,导致计算结果大大趋同,导致大量的哈希碰撞。反之,能让数据分布的更均匀。

5.HashMap的扩容机制为什么是2倍?
HashMap的扩容分为两步,第一先创建一个长度是原来两倍的数据,第二将原来的数据重新计算槽位,分配到新的数组中去,这一步需要消耗很大的计算资源。如果长度是两倍的话,按照计算方法(n - 1) & hash这里n-1实际也变成了原来的2倍,在二进制位上的表现就是在最高位上多了一个1,此时在进行与运算的时候,就不用计算其他位,只需要判断在在高位上的hash是0或者1,大大减少了计算量,降低了计算成本。

TreeMap

TreeMap是一种有序的KV存储结构。
1.存储结构
TreeMap底层是一颗红黑树,创建时需要定义比较器,如果没有定义则按照传入的类重写的比较器进行排序。
2.put流程
a.首先会判断当前root节点是否为null,如果为null,设置root节点。
b.如果不为NULL,会跟节点比较大小,小的继续跟左节点比较,大的继续跟右节点比较,然后循环直到找到自己的节点。
c.插入完成后会调整树的结构使其满足红黑树的规则。
红黑树规则:
节点分为红色或者黑色;
根节点必为黑色;
叶子节点都为黑色,且为null;
连接红色节点的两个子节点都为黑色(红黑树不会出现相邻的红色节点);
从任意节点出发,到其每个叶子节点的路径中包含相同数量的黑色节点;

HashTable
和HashMap差不多,只是对集合的操作都是synchronized的,效率极低。


MockingJay
7 声望3 粉丝