JDK1.7之HashMap源码学习

本文主要探讨了HashMap在Java中的基础概念,包括其无序性、键值对存储方式以及非线程安全性。作者指出,虽然HashMap在JDK1.7中以数组+链表实现,但JDK1.8后引入了红黑树优化。通过分析HashMap的构造函数、get和put方法的源码,展示了HashMap的工作原理。同时,提到了在多线程环境下如何确保HashMap的安全性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言:第一次写学习贴,看了很多的帖子,然后也去看了hashMap的源码,想尝试自己去写一下自己对于这个东西的理解,并不一定是对的,可能也就是写出来让自己加深一下基础印象,如果有些的不好的地方请指出。

此博客应该是以JDK1.7为基础的,但是我是以1.8来说的不要混淆了(只因为我懒得打开1.8的源码

1.HashMap是什么:让我们重新认识一下这个小哥儿

HashMap是java中AbstractMap的子类,是键值对集合java.util.Map的实现类,是作为Map家族中被使用频率最高的小哥哥

HashMap是java面试时基础中被询问频率极高的你不得不懂得面试题1.2.3

2.HashMap基础了解:HashMap家中拥有着好几个兄弟姐妹,但是咱们只是来了解它的,就不多介绍了,先说的感觉最基础的

HashMap是一个无序的键值对集合,是以Key-Value的形式存储数据的,它只允许一个Key为null值,允许多个value为null值

HashMap是一个非线程安全的键值对集合,即当你使用多线程去操作它时,他可能会出现数据不一致,但是如果你需要它达到线程安全的条件时他也是可以做到的,可以用 Collections(肯来克辛斯)的synchronizedMap(神奎耐日德Map)方法使HashMap具有线程安全的能力,或是选择使用ConcurrentHashMap(肯卡韧特HashMap)这样一个线程安全的Map集合达到目的。

最开始HashMap底层是由数组加链表来实现的,但是在JDK1.8以后在原有的基础数引入了红黑树这样一个二叉树结构的树来优化HashMap

3.让我们去源码层参观一下HashMap

HashMap的默认长度

/**
 * The default initial capacity - MUST be a power of two.
 */
static final int DEFAULT_INITIAL_CAPACITY = 16;

HashMap的默认负载因子

/**
 * The load factor used when none specified in constructor.
 */
static final float DEFAULT_LOAD_FACTOR = 0.75f;

HashMap的最大长度

/**
 * The maximum capacity, used if a higher value is implicitly specified
 * by either of the constructors with arguments.
 * MUST be a power of two <= 1<<30.
 */
static final int MAXIMUM_CAPACITY = 1 << 30;

HashMap中修改map长度和负载因子的构造函数(中文注释是我自己加的,个人理解)

--initialCapacity:长度

--loadFactor :负载因子

public HashMap(int initialCapacity, float loadFactor) {
    /*如果初始化的长度小于0则抛出异常*/
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    /*如果初始化的长度大于最大值常量那么就以最大值常量为默认长度*/
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    /*如果初始化负载因子小于等于0或则不是一个数字那么抛出异常*/
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);

    // Find a power of 2 >= initialCapacity
    /*这里的算法我开始没看懂,然后我把代码拿出来测试了一下,我觉得应该是求比你初始化长度更大的一个2的平方根的值
      比如我initialCapacity初始化值是15那么capacity的值就是16,如果我的值>16且小于16X2 那么他的值就一定是32(推测的,未经       测试)之所以这么做这么个算法的原因是因为它本身对于Hash冲突的算法的原因,如果后面有聊到这个我们在解释它本身的hash算法
    */
    int capacity = 1;
    while (capacity < initialCapacity)
        capacity <<= 1;
    /*赋值这个就不需要我多解释了吧*/
    this.loadFactor = loadFactor;
    /*我觉得这里应该是求一个阈值,就是达到这个值以后map就会进行扩容,threshold是一个全局的变量*/
    threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
    /*看到new关键字就不提了吧,初始化一个Entry的素组长度为capacity*/
    table = new Entry[capacity];
    useAltHashing = sun.misc.VM.isBooted() &&
            (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
    init();
}

到此这个构造函数就结束了,去看看其他的

发现一个get方法,让我们来看看

public V get(Object key) {
    /*这行代码证明HashMap是可以有null的Key的*/
    if (key == null)
        return getForNullKey();
    /*对象获取,源码往下*/
    Entry<K,V> entry = getEntry(key);
    /*这个对象不为空就返回这个对象得value值*/
    return null == entry ? null : entry.getValue();
}

--获取HashMap中key值为空的value源码

private V getForNullKey() {
   /*循环取HashMap*/
    for (Entry<K,V> e = table[0]; e != null; e = e.next) {
  	/*如果这个key值==null就返回这个key值得value值,最后则返回null*/
        if (e.key == null)
            return e.value;
    }
    return null;
}

--获取HashMap中key值对应得Value源码

final Entry<K,V> getEntry(Object key) {
    /*运用三目表达式获取到这个key值得hash码*/
    int hash = (key == null) ? 0 : hash(key);
   /*根据hash码进行运算得到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;
    }
    return null;
}

--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();

    // 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);
}

再看下put方法

public V put(K key, V value) {
    /*同理于上面得get空值的key,HashMap是允许一个主键为空的*/
    if (key == null)
        return putForNullKey(value);
    /*获取hash码*/
    int hash = hash(key);
    /*通过算法获取坐标*/
    int i = indexFor(hash, table.length);
    /*这个Entry我觉得有必要解释一波,这是一个实现了Map.Entrty的链表结构对象,上文也说了Hash底层是数组+链表+红黑树实现的(1.8                   才有的红黑树,我这里是1.7的源码)*/
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        /*如果put的key值存在*/
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
        /*进行覆盖,然后返回旧的Value*/
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
   /*如果key值不存在走这里*/
    modCount++;
    addEntry(hash, key, value, i);
    return null;
}

--源码addEntry

void addEntry(int hash, K key, V value, int bucketIndex) {
    /*判断是否需要扩容*/
    if ((size >= threshold) && (null != table[bucketIndex])) {
        /*每次扩容都会是之前长度X2*/
        resize(2 * table.length);
       /*获取key值hash码*/
        hash = (null != key) ? hash(key) : 0;
      /*对hash码进行算法运算以后得到坐标*/
        bucketIndex = indexFor(hash, table.length);
    }
    /*新增*/
    createEntry(hash, key, value, bucketIndex);
}

  /*这里的代码就是一个新的对象添加到了HashMap的数组中,然后数组长度++*/

void createEntry(int hash, K key, V value, int bucketIndex) {
    Entry<K,V> e = table[bucketIndex];
    table[bucketIndex] = new Entry<>(hash, key, value, e);
    size++;
}

今天就到这里吧,有什么问题欢迎指出

外贴上我看的两篇对于HashMap写的比较好的(1.8)帖子以供大家共同学习

Java8系列,重新认识HashMap

一个HashMap如何面试聊上半小时

大家回见

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值