HashMap底层原理

HashMap底层原理分析


学习hashmap之前先了解一下arraylist
arrayList数据结构:查询效率高,增删效率高

ArrayList<String> list = new ArrayList<String>();
	list.add("1");
	list.add("2");
	list.add("3");
	list.add("4");
	list.add("5");
	for(int index=0;index<list.size();index++) {
		list.remove(index);
		System.out.println("remove");
	}
	System.out.println(list.size());

打印结果:(只删除三个元素)
remove
remove
remove
2
底层解释:
for循环第一次的时候,数组下标0,1,2,3,4,此时remove的正好是“a”,size=5;index=0

abcd

第一次结束之后数字会copy一遍,进行压缩,第二次数组下标是0,1,2,3,此时的size=4,index=1,此时remove的正好是“c”

bcde

同理copy数组,进行压缩,第三次数组下标是0,1,2,此时size=3,index=2,remove的正好是“e”

bde

同理copy数组,进行压缩,第四次数组下标是0,1,此时size=2,index=3,没有满足条件的remove数据,跳出循环,最终剩下了“b”,“d”,也就解释了上边删除了remove三次,size=2了

bd

linkelist数据结构,增删效率高,查询效率低

hashmap数据结构:
是一个综合的由:数组,链表,红黑树组成

hashmap的初始table(数组)长度是1<<4(1<<4,1左移四位得10000=2四次方=16),源码:

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

最大长度:

static final int MAXIMUM_CAPACITY = 1 << 30;

当map.put()多少数据时需要自动扩容,根据源码里的加载因子判断,即:

static final float DEFAULT_LOAD_FACTOR = 0.75f;//百分之七十五,四分之三

比如我们的初始值是16,则在我们put到160.75=12的时候,数组就会自动扩容162=32,以此类推
当链表节点足够长,会有个链表向红黑树转化的过程(为什么是8,根据查找的时间复杂度有关,大于8个节点时,红黑树的查找效率比链表的高)转化因子是:

static final int TREEIFY_THRESHOLD = 8;

同理,如果节点小于这个转化因子,会有个红黑树想链表转化的过程,转化因子是:

static final int UNTREEIFY_THRESHOLD = 6;

hashmap的算法
数组下标的计算方法:(n-1)&hash值,n代表一个hashmap里的数组长度,默认是16
hash值不同,但是(n-1)&hash的结果可能会是相等的,这就有个可能同一个位置下有一个链表形式的存数方式,如上图的X0位置
hash函数源码:经过hash函数运算得到key值与上(n-1)得到数组下标(index)值

 static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    	}

可以这么写:

static final int hash(Object key) {
if(key==null) return 0; //key的空值处理
int h = key.hashcode();//32位,任何一个对象都会有一个hashcode()方法,public native int 				   hashCode();返回值是int,所以是一个32位的
int temp = h>>>16;//向右位移16位,目的是为了使高位参与运算
int newHash = h^temp;//异或运算
reture newHash;//得到hash值
    	}

操作 原理
增加 1.先计算key的hash值2.根据hash值找到数组位置3.如果数组为空则插入,否则往链表的尾部添加元素
查找 1.先计算key的hash值2.根据hash值找到数组位置3.如果找到则返回,否则遍历链表找到对应的节点
删除 1.先计算key的hash值2.根据hash值找到数组位置3.如果找到则删除,否则从链表中找到对应节点,然后删除节点
put方法核心流程(JDK1.8之后的尾插法),源码解析
1.计算hash值找到数组位置
2.对应位置数组位空则插入
3.否则往链表的尾部添加元素

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
}
 
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
        Node<K,V>[] tab;  //定义一个数组tab
 Node<K,V> p;  //p-->上图中5位置第一次进来的那个值即node节点
 int n;  //数组长度
 int i;  //数组下标index
        if ((tab = table) == null || (n = tab.length) == 0)
     n = (tab = resize()).length;  //这两步,如果数组是空的就会数组初始化,得到数组长度n
 if ((p = tab[i = (n - 1) & hash]) == null)  //i=(n - 1) & hash计算数组下标的位置,这个位置如果为null
     tab[i] = newNode(hash, key, value, null);  //相当于直接插入到list里,且next=null
      			//Node<K,V> newNode(int hash, K key, V value, Node<K,V> next) {
      			//		  return new Node<>(hash, key, value, next);
    			//}
 else {  //else如图5的位置已经有”king“这个值了,就要在这个位置,就要往下链表形式存数据
            Node<K,V> e; K k;
        if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))//判断key值是否相等
         e = p;  //如果再次插入的key值跟之前的一样,把old节点p负值给e,直接跳到下边的if语句
     else if (p instanceof TreeNode)//链表转成红黑树的过程,树化的过程
         e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);//树化的过程
            else {
                for (int binCount = 0; ; ++binCount) {
                 if ((e = p.next) == null) {//判断上图中的king的next==null,
                 p.next = newNode(hash, key, value, null);//则把peter插入到king的后边
                        if (binCount >= TREEIFY_THRESHOLD - 1) // 判断如果链表过长,会树化过程
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))  //同理如果key相同,会值覆盖,并返回oldvalue
                        break;
                    p = e;//此时e=p.nextNode,否则一直这个for循环,找到next==null,把值put进去
                }
            }
         if (e != null) { // existing mapping for key,跳到这里
                    V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
         return oldValue;//新值覆盖旧值,然后返回oldvalue
            }
        }
        ++modCount;
        if (++size > threshold)  //threshold就是加载因子,如果size>threshold,则会把tab扩容
            resize();
        afterNodeInsertion(evict);
        return null;
    }

put方法中注意点
1.相同的key,会返回一个oldValue
2.链表过长,会进行一个树化过程
3.size过长,会根据加载因子计算什么时候扩容
4.hashmap为什么要扩容?
同一个下标位置下的链表过长,会影响查询效率
5.怎么扩容
原size*2
6.hashmap线程不安全
7.ConcurrenHashMap如何解决线程安全问题
CAS机制(cup会一直跑),synchronized锁
8.ConcurrentHashMap在JDK1.8中为什么要使用内置锁synchronized来代重入锁ReentrantLock
synchronized:多种状态
单线程(偏向锁标志1–哪个线程)
多线程交替执行(取消偏向锁)-----(升级)---->使用CAS锁(轻量级锁)-----(升级)----->重量级锁(没有抢到 锁,就阻塞)
CAS—>CPU响应时间很快,吞吐量不高
重量级锁,上下文切换,吞吐量比较高

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值