HashMap中get/put方法源码分析

HashMap

无序,存储与取出的顺序不相同;不可重复,存储的数据不能重复。

底层数据结构为哈希表,哈希表又称为散列表,是由数组和单向链表的组合形成的数据结构。JDK8之后,当链表长度达到8时,则会转化为红黑树。

存取原理:

1、put(k, v)

第一步:先将key,value封装到一个node节点中。

第二步:底层调用hashCode()计算hash的值,并通过哈希函数的算法将hash值转化为数组下标,如果下标位置上没有任何元素,就把Node添加到这里;如果下标位置上有链表,会用指定key与链表上的节点中的key进行比对,如果相同,则将value覆盖,如果不同,就将这个新节点添加在这个链表的末尾。

2、get(k)

通过hashCode()方法计算出指定key对应的hash值,再通过哈希算法将其转化为数组下标,然后通过数组下标找到哈希表中数组对应的位置,如果这个位置上什么都没有,则返回null;如果有单向链表,再通过equals方法对比这个下标所在的位置上的链表中的所有节点的key值,如果所有都返回false,则返回null,如果指定key值与某个节点的key通过equals方法返回true,则将这个节点的value值返回。

综上,注意放在HashMap中key部分的元素和HashSet中的元素,要同时重写hashCode()和equals()。

初始化容量

HashMap的初始化容量为16,指定容量时必须是2的倍数,这是因为达到散列均匀同时为了提高HashMap的存取效率。默认加载因子是0.75。

​ 默认加载因子是指 当HashMap集合底层哈希表中的数组容量达到75%时,数组开始扩容。

JDK8之后,HashMap中单链表的长度大于8时,会将其改为红黑树的数据结构,小于6的时候会变回单向链表。为了提高检索效率。

源码分析

1、get

public V get(Object key) {
    Node<K,V> e;
    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;
    
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) { 
        //通过key的哈希值与数组长度-1进行按位与运算得到数组下标
        
        //如果该处的结点的哈希值与指定key的哈希值一致并且key相同,则返回这个结点
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        
        //如果不是该处的结点,会顺着链表寻找下一个,判断下一个结点是否为空
        if ((e = first.next) != null) {
            
            //链表长度大于8的情况下在这个结点在红黑树中,那么返回这个红黑树中指定key的树结点
            if (first instanceof TreeNode)
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            
            //如果hash值,key值都与指定的相同,那么返回这个结点否则继续顺着链表寻找
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

2、put

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; 
    Node<K,V> p;
    int n, i;
    //如果数组为初始化,长度为0则进行扩容
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    
    //判断桶中第一个元素(数组中的)是否为null,是null就将新节点放在这里
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    
    //数组中已存在元素
    else {
        Node<K,V> e; K k;
        //比较桶中的元素(数组中的)与新插入的元素hash值,key值是否相同
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            //将桶中的第一个元素赋值给e,用e来记录
            e = p;
        //不相等又处于红黑树的情况下
        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) {
                    p.next = newNode(hash, key, value, null);
                    //链表达到了最大阈值,转为红黑树
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                //判断链表中是否存在这个结点,如果存在直接跳出循环修改value
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;//与前面的判断中e=p.next配合遍历链表
            }
        }
        //桶中存在与新插入的结点key、hash值相同的结点(结点已存在)
        if (e != null) { // existing mapping for key
            //记录e的value
            V oldValue = e.value;	
            //onlyIfAbsent为false(可以更改已存在的value)或者e的value为null
            if (!onlyIfAbsent || oldValue == null)
                //将value覆盖,新值替换旧值
                e.value = value;
            //访问后回调
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    //插入后实际大小大于阈值则重新扩容
    if (++size > threshold)
        resize();
    
    afterNodeInsertion(evict);
    return null;
}

分析不到位的地方望大家多多指正!

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
HashMap 是 JDK 提供的一个哈希表实现类,其底层实现使用数组+链表(或红黑树)的方式实现。下面是 HashMap put() 和 get() 方法分析: 1. put() 方法码: ```java public V put(K key, V value) { // 计算 key 的哈希值 int hash = hash(key); // 根据哈希值计算数组下标 int i = indexFor(hash, table.length); // 如果该位置还没有元素,则直接将新元素插入 for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; // 如果 key 已经存在,则替换原有的值 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } // 如果该位置已经有元素,则将新元素插入链表头部 modCount++; addEntry(hash, key, value, i); return null; } ``` 2. get() 方法码: ```java public V get(Object key) { if (key == null) return getForNullKey(); // 计算 key 的哈希值 int hash = hash(key); // 根据哈希值计算数组下标 for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; // 如果 key 已经存在,则返回对应的值 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; } // 如果 key 不存在,则返回 null return null; } ``` 可以看出,put() 方法先根据 key 的哈希值计算数组下标,然后遍历该位置的链表,如果找到了已经存在的 key,则替换对应的 value,如果没有找到,则将新元素插入链表头部。get() 方法也是先根据 key 的哈希值计算数组下标,然后遍历该位置的链表,如果找到了已经存在的 key,则返回对应的 value,如果没有找到,则返回 null。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值