HashMap详解

hashMap的1.7和1.8的底层实现原理是不同的,需要分开讨论

【HashMap源码解读】阿里P8级别的架构师全套视频教程(2021最新版)_哔哩哔哩_bilibili(b站视频参考)

 

HashMap的长度为什么要是2的n次方_zs319428的博客-CSDN博客_hashmap2的n次方(HashMap的长度为什么要是2的n次方)

Java集合容器面试题(2020最新版)_ThinkWon的博客-CSDN博客_java集合(Java集合容器面试题(2020最新版))

HashMap之1.7和1.8的区别_weijian、Li的博客-CSDN博客_hashmap1.7和1.8的区别(HashMap之1.7和1.8的区别)

(1)美团面试题:Hashmap的结构,1.7和1.8有哪些区别,史上最深入的分析_依本多情的博客-CSDN博客_hashmap1.7和1.8的区别(美团面试题:Hashmap的结构,1.7和1.8有哪些区别,史上最深入的分析)

hashmap的如果不设定默认容量初始值是16加载因子是0.75

1.7 put详解

public V put(K key, V value) {
    if (table == EMPTY_TABLE) {               
        inflateTable(threshold);
    }
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key);                                             
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        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;
}

----inflateTable方法,懒加载put的时候才去初始化表,判断长度是否为空,去初始化表table数组 Entry<K,V>[],初始化的数组的长度是2的n次方数

----如果key为null,添加key为null的节点,在链表的第一个位置

/**
 * Inflates the table.
 */
private void inflateTable(int toSize) {
    // Find a power of 2 >= toSize
    int capacity = roundUpToPowerOf2(toSize);

    threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
    table = new Entry[capacity];
    initHashSeedAsNeeded(capacity);
}
private static int roundUpToPowerOf2(int number) {
    // assert number >= 0 : "number must be non-negative";
    return number >= MAXIMUM_CAPACITY
            ? MAXIMUM_CAPACITY
            : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;  --左移以为相当于乘以2
}
public static int highestOneBit(int i) {
    // HD, Figure 3-1
    i |= (i >>  1);
    i |= (i >>  2);
    i |= (i >>  4);
    i |= (i >>  8);
    i |= (i >> 16);
    return i - (i >>> 1);
}    // |运算的逻辑是有1则为1

如果一个数是2的次方数,那么它用二进制表示只有最高位是1,其他位都是0,highestOneBit其实就是找到小于或等于初始值的最小2次方数字,通过这种计算后就能找到最高位是1但是其他位都是0的数了。

2的n次方实际就是1后面n个0,2的n次方-1  实际就是n个1;

比如说16 : 1 0000      15: 0 1111

roundUpToPowerOf2 找到>=初始值的2的幂次方的数

那为什么要2的n次方幂呢??因为2的n-1用二进制标识就是0 1111111

其实就是按位“与”的时候,每一位都能  &1  ,也就是和1111……1111111进行与运算,这样能增加散列性。

----hash 算出hash值,hash方法里面的位运算移动以及异或运算时提高散列性,防止链表过长。让高位参与运算,因为容器长度时2的n此方法-1,高位是0低位是1。则h & (length-1),hashcode的高位和0位与都是0,不会参与到运算,所以将高位右移变成低位,高位也参与到运算中来了。

final int hash(Object k) {
    int h = hashSeed;
    if (0 != h && k instanceof String) {
        return sun.misc.Hashing.stringHash32((String) k);
    }

    h ^= k.hashCode();

    // This function ensures that hashCodes that differ only by
    // constant multiples at each bit position have a bounded
    // number of collisions (approximately 8 at default load factor).
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

----indexFor 算出数组下标

static int indexFor(int h, int length) {
    // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
    return h & (length-1);
}

----for循环这一段就是去遍历链表,用equals方法判断是否相等,相等则覆盖,并且返回原理值

----addEntry方法添加节点与扩容有关

void addEntry(int hash, K key, V value, int bucketIndex) {
    if ((size >= threshold) && (null != table[bucketIndex])) {
        resize(2 * table.length);
        hash = (null != key) ? hash(key) : 0;
        bucketIndex = indexFor(hash, table.length);
    }

    createEntry(hash, key, value, bucketIndex);
}

新的节点添加到链表头部,并且进行移动

其中的resize方法进行扩容 resize(2 * table.length),创建一个新数组,长度是原来的两倍。transfer方法遍历原有的Entry数组,将所有的元素重新Hash到新数组中

void resize(int newCapacity) {
    Entry[] oldTable = table;
    int oldCapacity = oldTable.length;
    if (oldCapacity == MAXIMUM_CAPACITY) {
        threshold = Integer.MAX_VALUE;
        return;
    }

    Entry[] newTable = new Entry[newCapacity];
    transfer(newTable, initHashSeedAsNeeded(newCapacity));
    table = newTable;
    threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
/**
 * Transfers all entries from current table to newTable.
 */
void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        while(null != e) {
            Entry<K,V> next = e.next;
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}

hash到新数组以后是链表上的元素是逆序的,这种的话多线程的情况下可能会出现环形链表死循环问题。

1.8引入红黑树就是防止链表过长导致查询效率get变低

1.8插入元素是尾插法,为什么要用尾插法,因为既然要遍历链表判断长度是不是大于8,既然要查询为什么不直接尾插呢,1.7头插法是因为效率高,不然的话还要去遍历链表判断是不是最后一个节点,这样反而让效率变低。

Node<K,V>[] table数组是Node了,1.7是Entry[]

初始化inflateTable()和扩容resize()合并成一个resize()

hash(散列)算法:这比在JDK 1.7中,更为简洁,相比在1.7中的4次位运算,5次异或运算(9次扰动),在1.8中,只进行了1次位运算和1次异或运算(2次扰动)

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    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;
        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 {
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {                                      --- 判断是不是最后一个节点,因为next为null的肯定是最后一个节点
                    p.next = newNode(hash, key, value, null);                    --- 是的话查到尾部
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st         --- 如果元素个数大于8,树话
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    break;
                p = e;
            }
        }
        if (e != null) { // existing mapping for key
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            afterNodeAccess(e);
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
        resize();
    afterNodeInsertion(evict);
    return null;
}

shixinzhang

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值