HashMap核心源码

put(K key, V value)
  • hash(key)-扰动函数
    将指定的key存入map中,重复则更新,另外还有一个putIfAbsent()方法则是遇到重复key不更新。
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

注意到里面的hash(key)方法,实际上入参key已经包含了一个hashCode(通过key.hashCode()即可获取),此处再次进行hash是为了增加扰动性。通常情况下key.hashCode()的值非常大,但我们最后放到HashMap中的数组的长度可能只是16,32,64…这样的级别,就导致了key.hashCode()的高位部分没有参与到运算,增加了碰撞概率。此处的扰动函数的作用就是将原key.hashCode()高位部分也参与运算,生成的新hash(的低16位)同时具备原key.hashCode()的高16位和低16位的信息,而且新hash的高16位保存的是原key.hashCode()高16位部分。

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
  • putVal(…)
    put(k, v)的核心代码
//参数evict用于LinkeHashMap,它是HashMap的子类
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
    //tab:当前HashMap中的数组引用
    //p:HashMap中一个一个元素
    //n:数组长度length,i:路由寻址结果-数组的index
	Node<K,V>[] tab; Node<K,V> p; int n, i;
	//如果tab还是null,那么就初始化tab,只是一种懒初始化的逻辑,可降低系统内存的开销
	if ((tab = table) == null || (n = tab.length) == 0)
	    n = (tab = resize()).length;
	//如果目标位置还没有存在元素,那么直接将改元素放置在目标位置即可
	if ((p = tab[i = (n - 1) & hash]) == null)
	    tab[i] = newNode(hash, key, value, null);
	else {
	    Node<K,V> e; K k;	//定义的临时变量
	    //桶位中的元素key与带插入的元素的key相等
	    //虽然都指向tab[i]同一位置,但由于采用hash & (n - 1)运算,所以也不能保证p.hash与hash相等
	    //"||"两侧是两种相等的实现方式,满足其一即可。
	    //注意: null是不能调用.equal()方法的,故增加了非null判断
	    //实际上"&&"后面为true,前面肯定也为true,这样写的目的在于提升算法效率,先通过e.hash == hash
	    //将桶中很多不符合的节点pass掉,如果这个检查通过,再来"||"左侧的检查,最后才是"||"右侧的检查,因为
	    //.equals(k)方法的执行相当耗时
	    if (p.hash == hash &&
	        ((k = p.key) == key || (key != null && key.equals(k))))
	        e = p;
	    //tab[i]此位置已经树化(红黑树),插入一个节点(暂略过)
	    else if (p instanceof TreeNode)
	        e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
	    //tab[i]非空,非树 -> 那么就是链表,且头元素与待插入的元素不同
	    else {
	    	//循环遍历链表
	        for (int binCount = 0; ; ++binCount) {
	        	//遍历到最后一个元素(e: null)
	            if ((e = p.next) == null) {
	                p.next = newNode(hash, key, value, null);	//添加待插入的node
	                //bigCount从0开始的,大于TREEIFY_THRESHOLD需要树化
	                if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
	                    treeifyBin(tab, hash);
	                break;
	            }
	            //找到了相同的key,此时也到达元素(e: !null)
	            if (e.hash == hash &&
	                ((k = e.key) == key || (key != null && key.equals(k))))
	                break;
	            p = e;
	        }
	    }
	    //针对e==null的情况,上述代码已经创建了新节点,故此处仅针对e!=null的情况
	    //此处的e.key与待插入的key相同,有两种情况
	    if (e != null) { // existing mapping for key
	        V oldValue = e.value;
	        //如果e里面的value为null,onlyIfAbsent==false那么直接存入目标value
	        if (!onlyIfAbsent || oldValue == null)
	            e.value = value;
	        afterNodeAccess(e);
	        //将原来的value返回
	        return oldValue;
	    }
	}
	//有两种情况会到这儿来
	++modCount;	//HashMap结构被修改的次数(替换不算)
	//size达到扩容阈值,触发扩容
	if (++size > threshold)
	    resize();
	afterNodeInsertion(evict);
	return null;
}

注意:这里面有两个方法的调用,afterNodeAccess(e)afterNodeInsertion(evict),在HashMap中这两个方法为空方法体,它们的具体实现在HashMap的子类LinkedHashMap

  • resize()扩容
    当桶里面装的链表长度太长,查询速度就从O(1)提高到了O(N),因此需要扩容。
final Node<K,V>[] resize() {
	//扩容前的table引用
    Node<K,V>[] oldTab = table;
    //扩容前table的capacity(长度)计算
    //由于新建的HashMap中的table为null,在第一次insert时,需要扩容
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    //扩容之前的阈值,也就是达到该阈值后触发的本次resize扩容
    //此阈值threshold扩容后也会跟着改变
    int oldThr = threshold;
    //扩容后的长度、阈值
    int newCap, newThr = 0;
    //1.计算新的newCap、newThr 
    //之前已经resize过了,此次需对其再次进行resize
    if (oldCap > 0) {
    	//达到最大容量,返回Integer.MAX_VALUE,确保下次不会不在调用resize方法
    	//极少会发生
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        //newCap = oldCap * 2
        //newThr = oldThr * 2
        //当HashMap是以HashMap(initialCapacity, loadFactor)构造时,有可能不满足>=DEFAULT..条件
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && //........[1]
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    //oldCap == 0(HashMap中的table还未初始化) && oldThr > 0
    //除了默认的构造方法public HashMap(),其余的3种构造方法都会出现oldCap == 0 && oldThr > 0
    else if (oldThr > 0) // initial capacity was placed in threshold //........[2]
    	//oldThr = threshold,其中threshold已经在构造方法中计算过
        newCap = oldThr;
    //oldCap == 0 && oldThr == 0
    //默认的构造方法public HashMap()第一次扩容时会出现这种情况
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    //如上[1][2]两种情况下newThr == 0
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        //多数情况下ft = (int)ft
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;
    
    //2.迁移原数据
    @SuppressWarnings({"rawtypes","unchecked"})
    //创建一个更大的数组,长度为newCap(也可能是第一次创建)
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    //本次扩容之前原来有数据,若原本无数据则无需处理
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {
            Node<K,V> e;
            //桶位中有数据才处理,三种情况:单个数据、链表、红黑树
            if ((e = oldTab[j]) != null) {
            	//原数组该位置置为null,方便GC回收
                oldTab[j] = null;
                //单个数据
                if (e.next == null)
                    newTab[e.hash & (newCap - 1)] = e;
                //红黑树
                else if (e instanceof TreeNode)
                    ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
               //链表 
                else { // preserve order
                	//扩容之后,原来位于同一桶位的链表元素会拆分到两个地方,原位置,原位置+oldCap
                	//假设oldCap=16,newCap=32
                	//那么table[15]的链表会拆分到table[15]与table[31]
                	//table[15]链表中的元素hash值可能为0 1111 OR 1 1111,& 1 1111(31)也就有两种情况
                    Node<K,V> loHead = null, loTail = null;	//低位头结点、尾结点
                    Node<K,V> hiHead = null, hiTail = null;	//高位头结点、尾结点
                    Node<K,V> next;
                    do {
                        next = e.next;
                        //hash值为0 1111,& 1 1111为0 1111,存放在table[15]
                        if ((e.hash & oldCap) == 0) {
                            if (loTail == null)	//只有第一次时未null
                                loHead = e;	//因为此时链表为null,设置头结点
                            else
                                loTail.next = e;
                            loTail = e;	//尾结点后移(第一次时,头结点和尾节点指向一致)
                        }
                        //hash值为1 1111,& 1 1111为1 1111,存放在table[31]
                        //内部逻辑与tail一致
                        else {
                            if (hiTail == null)
                                hiHead = e;
                            else
                                hiTail.next = e;
                            hiTail = e;
                        }
                    } while ((e = next) != null);
                   	//当然也存在loTail == null的情况
                    if (loTail != null) {
                    	//由于loTail可能是从原链表的中间捞出来的,故loTail.next指向了一个无用的链表元素
                        loTail.next = null;	
                        newTab[j] = loHead;	//将loHead置于桶中
                    }
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}
  • get获取value
public V get(Object key) {
    Node<K,V> e;
    //put的时候使用的扰动函数计算,因此get的时候也需要计算
    //由于不同的key可能会得到同一个hash,故还需要传key
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    //tab[(n - 1) & hash]: 路由至桶中相应index
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        //为何还要有这一句(first.hash == hash),之前已经解释
        //运气好:桶位中的元素就是要get的
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        //桶位中的元素不是要get的,且后续还有元素
        if ((e = first.next) != null) {
       		//红黑树
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            //链表搜索:直到找到为止,否则返回null
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}
  • remove删除节点
    包含两个函数
//只需匹配key
public V remove(Object key) {
    Node<K,V> e;
    //为什么此处需要三目运算:若e==null,那么将无法调用.value
    return (e = removeNode(hash(key), key, null, false, true)) == null ?
        null : e.value;
}
//key与value均需匹配后才能删除
public boolean remove(Object key, Object value) {
    return removeNode(hash(key), key, value, true, true) != null;
}
final Node<K,V> removeNode(int hash, Object key, Object value,
                           boolean matchValue, boolean movable) {
    Node<K,V>[] tab; Node<K,V> p; int n, index;
    //路由算法找到对应桶位
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (p = tab[index = (n - 1) & hash]) != null) {
        //node:查找到的结果,e:当前node的下一个node
        Node<K,V> node = null, e; K k; V v;
        //该元素恰好位于桶位中
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            node = p;
        else if ((e = p.next) != null) {
        	//红黑树
            if (p instanceof TreeNode)
                node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
            //遍历查找 
            else {
                do {
                	//找到目标
                    if (e.hash == hash &&
                        ((k = e.key) == key ||
                         (key != null && key.equals(k)))) {
                        node = e;
                        break;
                    }
                    p = e;
                } while ((e = e.next) != null);
            }
        }
		//准备删除
		//此处matchValue == true时需匹配node.value==v?
        if (node != null && (!matchValue || (v = node.value) == value ||
                             (value != null && value.equals(v)))) {
            //红黑树
            if (node instanceof TreeNode)
                ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
            //待删除元素恰好位于桶位上
            else if (node == p)
                tab[index] = node.next;
            //位于链表中
            //node为待删除的目标节点,p为node的上一个节点
            else
                p.next = node.next;
            ++modCount;
            --size;
            afterNodeRemoval(node);	//用于LinkedHashMap
            return node;
        }
    }
    return null;
}
  • replace替换
@Override
public V replace(K key, V value) {
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) != null) {
        V oldValue = e.value;
        e.value = value;
        afterNodeAccess(e);
        return oldValue;
    }
    return null;
}
//相比于上一个方法多了一个需同时满足node.value的匹配
@Override
public boolean replace(K key, V oldValue, V newValue) {
    Node<K,V> e; V v;
    if ((e = getNode(hash(key), key)) != null &&
        ((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) {
        e.value = newValue;
        afterNodeAccess(e);
        return true;
    }
    return false;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值