HashMap

HashMap是一个哈希表结构,以键值对形式存取数据,允许为null值,key重复则覆盖,非同步,线程不安全,而且不保证顺序,如要保证顺序可使用LinkedHashMap,要保证安全可使用ConcurrentHashMap

1.默认容量

HashMap默认容量为16,但是是一个懒加载的机制,第一次put时才会申请内存空间,负载因子为0.75,初始化时也可自己设定默认容量,但是必须是2的次幂,不然会被自动调成2的次幂;

坑: 准备用HashMap存1w条数据,构造时传10000还会触发扩容吗?

不会扩容,因为如果构造时传1W,1W不是2的次幂,会自动转为2的14次幂,即16384,乘以负载因子0.75=12288,存入1W条数据绰绰有余。

为什么必须是2的n次方

如果用取模运算会让数据分布比较均匀,但是消耗比较大,所以hashMap是用hash&(n - 1) 来计算下标的,其中n是Map数组的长度,而在length是2的n次幂时hash&(n - 1)等价于hash%length,效率会更高 只有它的长度是2的n次方的时候,我们对它进行-1操作才能拿到二进制位数全部是1的值,这样对他进行按位与(&)才能够非常快速的用位运算的方式拿到数组的下标,并且分布还是均匀的。

2. 计算hash值的方法

ini 复制代码
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

向右移16位且按位异或的操作,为了降低哈希碰撞的几率

JDK1.7 的时候是一堆异或操作和右移操作

3. JDK1.7 与 1.8 的区别

(1)JDK1.7采用头插法,JDK 1.8 采用尾插法。而改用尾插法的原因本人理解有两点:一是头插法会造成循环链表,二是需要在链表长度大于 8 时进行树化判断,也不能把链表的长度存在 map 中,那样会增加开支,所以需要遍历一遍来判断链表的长度是否达到 8 。

头插法就是插入新的节点,是从头部插入,原本的都往后排,createEntry方法

less 复制代码
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash,key,value,e)  //新节点的下一个节点等于原本的第一个节点
size++

所以多线程情况下容易变成环形链表,会导致查询一个不存在key,最终导CPU100%

原因为在JDK1.7的hashMap的resize()方法里有个transfer()方法

ini 复制代码
/**
 * Transfers all entries from current table to newTable.
 */
void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        while(null != e) { 
            // 这行也是重点
            Entry<K,V> next = e.next;		--------------(1)
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            //计算出重新存放的位置
            int i = indexFor(e.hash, newCapacity);
            // 重点这三行
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}

这个过程为,本来有链表 1------>2,然后线程一和线程二同时走到transfe方法的while循环里的(1)处, 此e = 1,e.next=2,然后线程二挂起,线程一把循环执行完之后并把线程一生成的新table放在内存里,此时链表为2------>1,接着线程二执行,但是现在线程二还是e = 1,e.next=2,然后执行重点三行代码会出现1.next=2,就变成了1------>2------>1,循环链表就形成了

还有一种情况:通过精心构造的而已请求引发Dos,比如可以通过精心构造的http请求,就是一些hash值相同的key,让tomcat内部产生大量的链表,消耗大量CPU发生Dos(拒绝服务攻击)

(2)出现哈希冲突时,1.7把数据存放在链表,1.8是先放在链表,链表长度达到8并且数组的长度不小于64时 就转成红黑树,低于6重新转换为链表

为什么超过8会变成红黑树?

红黑树占用空间是链表的两倍,空间损耗比较大,所以没有直接使用红黑树,源码里说是符合泊松分布,哈希冲突八次的几率较小,转为红黑树是因为要保证在冲突比较多的时候的查询效率

(3) 1.7的addEntry()方法里的扩容条件是map的元素大于阈值且存在哈希冲突,1.8扩容条件是

HashMap中的是元素个数大于阈值 或 数组中的链表长度达到8且Node数组长度小于64时


(4)1.7添加新元素是先扩容并重新计算下标,再进行插入,1.8后是先进行插入,如果 ++size > threshold再按照扩容后的规律统一计算。扩容后的统一规律:

(5)new HashMap()之后并不会在堆区申请内存空间,因为构造方法里没有 new 那个数组 ,JDK1.7中 ,put 方法中的 inflateTable() 方法里如果是 EMPTY_TABLE 时才会进行 new Entry,而1.8中是在 put() 方法里如果数组的长度是0的时候调用 resize() 方法,在 resize() 中进行 new Node[] 的。

4. 树化的条件

链表长度达到8的时候执行treeifyBin() 方法,但还会进行判断,tab.length >= 64,的时候才会树化,不然只会进行扩容

遍历HashMap的方式

vbnet 复制代码
 public static void main(String[] args) {
        HashMap<Integer,Integer> hashMap = new HashMap();
        for (int i = 0; i < 10; i++) {
            hashMap.put(i,i);
        }

//        Set set = hashMap.keySet();
//        for (Object o : set) {
//            System.out.println("Key: "+o+" Value: "+hashMap.get(o));
//        }
        Set<Map.Entry<Integer, Integer>> entries = hashMap.entrySet();
        for(Map.Entry<Integer, Integer> entry : entries){
            System.out.println("key: "+ entry.getKey() + "; value: " + entry.getValue());
        }
    }

LinkedHashMap怎么,保持有序的?

LinkedHashMap有一个head变量,就是头结点,tail,尾节点,还定义了一个Entry类,该类继承了HashMap.Node类,添加了一个before,after,就和linkedList相似,指向他的前一个Entry和后一个Entry

LinkedHashMap 重写了 HashMap 的 newNode() 方法,在new Node() 时,调用了 linkNodeLast()。

将新元素的 before 指向 tail 节点,并把 tail 节点变为最新的节点,实现了双向链表功能,保证有序。

LinkedHashMap 比 HashMap 多了一个 accessOrder ,accessOrder 为 true ,那么除了 new Node() 时会调整链表结构,新增修改和查看等操作都会通过 被重写的 afterNodeAccess() 方法修改链表结构。

TreeMap怎么保持有序?

TreeMap是基于红黑树实现的,保证二楼键的有序性,迭代时根据键的大小排序

红黑树性质

1.每个节点都有两个子节点,但是子节点可能为空

2.每个节点不是黑色就是红色,根节点一定是黑色

3.每个红色节点的两个子节点都是黑色,不可能有连在一起的红色节点

每次插入和删除的时候,如果打破红黑树的平衡,都会自动做旋转和颜色变化,以达到保持平衡,新插入的节点的位置是根据key的大小来判断的

相关推荐
LuckyLay12 分钟前
Spring学习笔记_27——@EnableLoadTimeWeaving
java·spring boot·spring
向阳121825 分钟前
Dubbo负载均衡
java·运维·负载均衡·dubbo
Gu Gu Study34 分钟前
【用Java学习数据结构系列】泛型上界与通配符上界
java·开发语言
WaaTong1 小时前
《重学Java设计模式》之 原型模式
java·设计模式·原型模式
m0_743048441 小时前
初识Java EE和Spring Boot
java·java-ee
AskHarries1 小时前
Java字节码增强库ByteBuddy
java·后端
小灰灰__1 小时前
IDEA加载通义灵码插件及使用指南
java·ide·intellij-idea
夜雨翦春韭1 小时前
Java中的动态代理
java·开发语言·aop·动态代理
程序媛小果2 小时前
基于java+SpringBoot+Vue的宠物咖啡馆平台设计与实现
java·vue.js·spring boot
追风林2 小时前
mac m1 docker本地部署canal 监听mysql的binglog日志
java·docker·mac