HashMap

HashMap

  • 利用hash函数快速定位元素位置,以达到O1的读写速度
    • hash->数组->取余
    • hash:调用key的hashCode()方法
  • hash冲突
    • 高位参与运算,HashMap容量是2的倍数,因此只有低位才参与运算,高位不参与运算,可能造成hash冲突几率增加。
    • 拉链法:链表+红黑树
    • 线性探索法
  • 数组大小
    • 容量都是:2的幂
    • 初始默认容量:16
    • 链表转红黑树:8,容量64
    • 红黑树转链表:6
    • 扩容:只有扩容无缩容
      • 负载因子:默认0.75
      • 两倍扩容,迁移元素少,只需要迁移一半元素
        • 单节点,e.hash & (newCap - 1)
        • 链表,拆链表:
          • e.hash & oldCap==0,hash新增那个bit是0还是1,
          • 0则存到低位链表,1则存到高位链表,拆分为两个链表。
          • 尾插法,无循环链表问题。但是依旧线程不安全。
          • JDK1.7是头插法,倒序插入,多线程下可能形成循环链表
        • 红黑树:拆树,如果拆分后的树小于等于6,则转为链表

结构

在这里插入图片描述

机制

算法优化

寻找大于某个数的最小的2n
     static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
hash优化

int hash = (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)
int index = hash&(2n-1) = hash%2n
HashMap容量是2的倍数,因此只有低位才参与运算,高位不参与运算,可能造成hash冲突几率增加。

resize扩容时

e.hash & oldCap==0 判断是高位还是低位,直接存入对应链表

链表转红黑树

  • put后 链表长度>=8 并且 表的长度>=64

红黑树转链表

  • 扩容-拆树,如果拆分后的树小于等于6,则转为链表
  • 在红黑树的root节点为空 或者root的右节点、root的左节点、root左节点的左节点为空时

扩容

  • 默认数组长度16,负载因子0.75
  • 每次扩容两倍,初始化新table
  • 迁移数据
    • 单节点,节点在新table中的下标:e.hash & (newCap - 1)
    • 链表:拆链表,通过e.hash & oldCap==0,hash新增那个bit是0还是1,0则存到低位链表,1则存到高位链表,拆分为两个链表。
      移动只需将低位和高位链表的头插入到对应的槽位中即可(j,j + oldCap),无循环链表问题,但是依旧不是线程安全的
    • 红黑树:拆树,如果拆分后的树小于等于6,则转为链表
  • 扩容时机
    • 第一次put,初始化table
    • put后 链表长度>=9 并且 表的长度<64
    • put后 size 大于 数组长度*负载因子

流程

get

  • 大致流程为:
    • hash(key)%tab.size,取得元素下标,
    • 判断key是否相等,
      • 相等返回
      • 不等
        • 链表,循环元素,key相等返回
        • 红黑树,按hash值排序的,若hash和key均相等,则返回

put

在这里插入图片描述

remove待补充

红黑树转链

源码

    /* ---------------- 类属性 -------------- */
    // 默认容量16
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    // 最大容量
    static final int MAXIMUM_CAPACITY = 1 << 30;
    // 默认负载因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    // 链表转红黑树的阈值:9
    static final int TREEIFY_THRESHOLD = 8;
    // 红黑树转链表的阈值:6
    static final int UNTREEIFY_THRESHOLD = 6;
    // 转红黑树时,table的最小长度
    static final int MIN_TREEIFY_CAPACITY = 64;

    /* ---------------- 静态类 -------------- */
    // 
	static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;
        // 省略
    }
	
	// 红黑树节点
	// LinkedHashMap.Node
    //   static class Entry<K, V> extends Node<K, V> {
    //     LinkedHashMap.Entry<K, V> before;
    //     LinkedHashMap.Entry<K, V> after;
    //     省略
    //   }
	static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
        TreeNode<K,V> parent;  // red-black tree links
        TreeNode<K,V> left;
        TreeNode<K,V> right;
        TreeNode<K,V> prev;    // needed to unlink next upon deletion
        boolean red;
		//省略
     }
     
    /* ---------------- 实例属性 -------------- */
	// HashMap的用Node数组存放数据,结构为Node数组+链表+红黑树的结构
	transient Node<K,V>[] table;
	// 缓存
	transient Set<Map.Entry<K,V>> entrySet;
    // HashMap中存放Node的数量
    transient int size;
    // 负载因子
    final float loadFactor;
    // 下一次触发resize的值 = capacity(table size) * load factor
    int threshold;
    // 结构修改的次数 :HashMap中的映射数量或以其他方式修改其内部结构(例如,重新散列),用于实现快速失败机制
    transient int modCount;


jdk1.7和jdk1.8 HashMap的区别

jdk1.7循环链表死循环问题 https://blog.csdn.net/danxiaodeshitou/article/details/108175608

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值