hashMap源码阅读笔记1.7

这几天一直在看hashMap的源码,也借鉴了很多大佬的文章以便更好的理解,也从大佬文章中借鉴了很多的内容,如果侵权,请告知,我将立刻删除

hashMap继承AbstractMap,实现了map接口。

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

初始容量:哈希表创建时的容量,实际上就是Entry<k,v>[] table的容量
static final int DEFAULT_INITIAL_CAPACITY = 16;
最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
哈希表
transient Entry<K,V>[] table;
存在映射关系的条数
transient int size;
阈值
int threshold;
修改计数
transient int modCount;

hashMap的几个默认构造方法:
(1):构造了一个映射关系与指定map相同的hashMap,类似拷贝
public HashMap(Map<? extends K, ? extends V> m)
(2):指定初始容量和加载因子
public HashMap(int initialCapacity, float loadFactor)
(3):指定初始容量
public HashMap(int initialCapacity)
(4):默认构造方法
public HashMap()

计算hash值

final int hash(Object k) {
    int h = 0;
    if (useAltHashing) {
        if (k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }
        h = hashSeed;
    }
    h ^= k.hashCode();
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

返回索引下标

//1:h& (length-1)运算等价于对length取模,也就是h%length,但是&比%具有更高的效
//2:capacity 必须为2的n次幂,则length-1肯定为奇数,在位运算h & (length -1)唯一性更高,
//减少collision的发生,也就是保证bucketIndex低重复性
static int indexFor(int h, int length) {        
    return h & (length-1);
}

当前map的映射关系数目

public int size() {
    return size;
}

判断map是否为空,即映射条数是否为0

public boolean isEmpty() {
    return size == 0;
}

0.单项链表的实现 

    // 实现了单向链表
    static class Entry<K, V> implements Map.Entry<K, V> {
        final K key;
        V value;
        Entry<K, V> next;
        int hash;
        //Map.Entry保存一个键值对 和这个键值对持有指向下一个键值对的引用,如此就构成了链表了。
        Entry(int h, K k, V v, Entry<K, V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }
    }

1.get方法

当调用get方法,会首先判断key值是否为null,如果为null,调用getForNullKey()方法,这也是hashMap允许key值为null的原因 

public V get(Object key) {
    if (key == null){
        return getForNullKey();
    }
    Entry<K,V> entry = getEntry(key);
    return null == entry ? null : entry.getValue();
}

当get(null)调用该方法,如果存在key为null情况,就返回key为null的value值,否则,直接返回null

private V getForNullKey() {
    for (Entry<K, V> e = table[0]; e != null; e = e.next) {
        if (e.key == null){
            return e.value;
        }
    }
    return null;
}

 当get(!=null)调用此方法

final Entry<K,V> getEntry(Object key) {
    1.根据key计算hash值
    int hash = (key == null) ? 0 : hash(key);
    2.通过hash值找出entry
    // 1 indexFor(hash, table.length):获取bucketIndex
    // 2 遍历具体bucket[indexFor(hash, table.length)]下的单向链表
    // 3 如果这个key的hashCode一样同时key值一样,或者key对象相同,则返回
    for (Entry<K,V> e = table[indexFor(hash, table.length)];e != null;e = e.next) {
        Object k;
        if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))
            return e;
    }
    3.哈希表中不存在key的时候返回null:例如:containsKey方法
    return null;
}

2.put方法

    public V put(K key, V value) {
        if (key == null)
            //当key为null时,调用putForNullKey方法
            return putForNullKey(value);
        //计算hash值
        int hash = hash(key);
        //获取
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            //如果执行put操作的时候发现key已存在,就更新value
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        //增加修改次数
        //1:如果多个线程访问,当发现modCount不一致,就会导致ConcurrentModificationException
        //2:遍历的时候执行remove操作也会导致ConcurrentModificationException
        //可以使用iterator解决2问题,1问题也说明hashMap是线程不安全
        //使hashMap线程安全:Map map = Collections.synchronizeMap(hashMap);
        modCount++;
        //不存在就新增
        addEntry(hash, key, value, i);
        return null;
    }

        
    void addEntry(int hash, K key, V value, int bucketIndex) {
        //如果映射关系的数量已经超过了阈值,并且table的桶索引的值不为null
        if ((size >= threshold) && (null != table[bucketIndex])) {
           //扩大一倍
            resize(2 * table.length);
            //重新计算hash值:结构已经改变
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }

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

    void createEntry(int hash, K key, V value, int bucketIndex) {
        //断开链表,add结点,注意总是从链表的表头处插入新结点
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<>(hash, key, value, e);
        size++;
    }

参数链接:https://blog.csdn.net/lemon89/article/details/50994691

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
HashMap在JDK 1.7的底层实现原理是采用数组和链表的结合形式。它使用了一个Entry数组来存储键值对,并且每个Entry对象都是一个链表的头节点,用于解决哈希冲突。 具体实现过程如下: 1. 创建一个Entry数组用于存储键值对,初始化大小为默认值(16)或指定的初始容量。 2. 当添加一个键值对时,首先根据键的哈希值计算出在数组中的索引位置。 3. 如果该索引位置上没有任何元素,则直接将键值对作为一个新的Entry插入到该位置。 4. 如果该索引位置上已经存在元素,即发生了哈希冲突,使用链表的方式解决冲突。 - 遍历该位置上的链表,如果找到键与要插入的键相等的节点,则更新对应的值。 - 如果遍历完链表仍未找到相同键的节点,则将新的键值对插入到链表的末尾。 5. 当链表长度达到一定阈值(默认为8),或者数组大小达到扩容门槛(默认为75%),就会触发扩容操作。 6. 扩容操作会创建一个更大的Entry数组,并将旧数组中的元素重新计算索引,插入到新数组中。 7. 扩容后,所有键值对的索引位置都会发生变化,但链表结构不变。 8. 当需要获取某个键对应的值时,根据键的哈希值计算出在数组中的索引位置,并遍历该位置上的链表,找到对应的节点并返回值。 总结起来,JDK 1.7中的HashMap底层实现原理是使用数组和链表的组合结构,通过哈希值计算索引位置,并使用链表解决哈希冲突。但是这种实现方式在处理大量数据时会存在性能问题,因为链表的查询效率较低。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值