hashmap源码总结

文章详细探讨了Java中hashCode和equals的区别与底层实现,解释了为何重写这两个方法对于避免HashMap内存泄漏的重要性。同时,介绍了HashMap的内部机制,包括时间复杂度、负载因子、链表与红黑树的转换,以及扩容策略。此外,还提到了LRU缓存淘汰算法在解决HashMap效率问题中的应用。
摘要由CSDN通过智能技术生成

==与equals之间区别与底层实现

hashCode方法: 是object 的native方法,底层是c语言编写,根据内存地址转换成整数类型。

equals: 

定律:如果两个对象hashCode值相同,内容不相等。如果用equals方法比较两个对象内容值相等,则hashCode相同

   有些疑问

hashMap如何避免内存泄漏: 

自定义对象作为key,重写equals和hashCode

时间复杂度:

O1 数组

On 链表

Ologn  红黑树

hashMap 无序 散列

  遍历所有key,将所有链表和红黑树都实现遍历,效率低 

缓存淘汰算法:

   清理最近不使用key:LRU算法

   方案1: 对每个key记录count值+1,效率低

   方案2: 访问该key的时候将key存放到链表最后的位置,链表最开头位置说明少使用。

为什么hashmap不实用取模运算?

   冲突概率大

hashmap降低hash碰撞概率:15 &hash   hash=hash^hahs >>> 16

HashMap核心参数:

// 初始容量
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;
//链表长度如果大于8,转成红黑树
static final int TREEIFY_THRESHOLD = 8;
//红黑树小于6转换成链表
static final int UNTREEIFY_THRESHOLD = 6;
//数组容量>64并且 链表长度>8
static final int MIN_TREEIFY_CAPACITY = 64;
//单向链表
static class Node<K,V> implements Map.Entry<K,V> 
    final int hash;   // 下次扩容的时候,能够计算key在新的table中index值
    final K key;
    V value;
    Node<K,V> next;
// 数组 类型 单项列表
transient Node<K,V>[] table;
// 大小
transient int size;
// 遍历hashmap集合防止多线程篡改数据
transient int modCount;

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    // tab,引用当前hashmap的散列表
    // p 表示当前散列表的元素
    // n 散列表的长度
    // i 寻址 结果
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    //第一次调用 初始化
    if ((tab = table) == null || (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 寻址找到🪣位,刚好是null,直接存
    if ((p = tab[i = (n - 1) & hash]) == null)
        tab[i] = newNode(hash, key, value, null);
    else {
        // e 不为null,找到一个与当前要插入的k v一样的元素
        // k 临时k
        Node<K,V> e; K k;
        // 表示,桶中元素与你当前元素key 完全一致,表示后续需要进行替换操作
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        //树
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        //
        else {
            //链表情况,头元素与我们要插入的key不一致
            for (int binCount = 0; ; ++binCount) {
                //条件成立的话,说明迭代到最后一个元素,也没有找到一个与你插入的key一致的node
                //说明需要加入到当前链表的末尾
                if ((e = p.next) == null) {
                    p.next = newNode(hash, key, value, null);
                    // 当前链表的长度,达到树化标准,需要进行树化
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    break;
                }
                //找到相同key的node元素,进行替换
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        //找到了一个 说明找到了一个与你插入元素key一直的
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    //表示散列表结构修改次数,替换不计数
    ++modCount;
    //插入新元素 size自增,如果自增后的值大于扩容值,扩容
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

扩容:

   为了解决hash冲突导致的链化影响查询效率。

final Node<K,V>[] resize() {
    //引用扩容前的哈希表
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length; //扩容前的数组长度
    int oldThr = threshold; //扩容之前的阈值
    int newCap, newThr = 0;// newCap 扩容之后数组的大小  newThr 扩容之后,下次出触发扩容条件
    //一次正常扩容情况
    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;
            return oldTab;
        }
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // double threshold
    }
    else if (oldThr > 0) // initial capacity was placed in threshold
        newCap = oldThr;
    else {               // zero initial threshold signifies using defaults
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    if (newThr == 0) {
        float ft = (float)newCap * loadFactor;
        newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                  (int)ft : Integer.MAX_VALUE);
    }
    threshold = newThr;

@SuppressWarnings({"rawtypes","unchecked"})
//创建数组
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) {
            oldTab[j] = null;// 方便gc回收
            //1,当前桶里只有一个元素,重新hash 放到数组
            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   //链表
                Node<K,V> loHead = null, loTail = null; //低位 存放扩容之后数组下标位置
                Node<K,V> hiHead = null, hiTail = null; // 高位 当前数组下标位置+扩容之前数组的长度
                Node<K,V> next;
                do {
                    next = e.next;
                    if ((e.hash & oldCap) == 0) {
                        if (loTail == null)
                            loHead = e;
                        else
                            loTail.next = e;
                        loTail = e;
                    }
                    else {
                        if (hiTail == null)
                            hiHead = e;
                        else
                            hiTail.next = e;
                        hiTail = e;
                    }
                } while ((e = next) != null);
                if (loTail != null) {
                    loTail.next = null;
                    newTab[j] = loHead;
                }
                if (hiTail != null) {
                    hiTail.next = null;
                    newTab[j + oldCap] = hiHead;
                }
            }
        }
    }
}
return newTab;

为什么重写了equals还要重写hashCode?

    为了 hashSet存出现问题   和hashmap内存泄漏

hashMap不实用遍历

linkedHashMap 参数设置true。  触发lru

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值