HashMap

HashMap是基于动态数组和单向链表实现的,具体是怎么实现的呢,我们来看源码

public HashMap() {
    table = (HashMapEntry<K, V>[]) EMPTY_TABLE;
    threshold = -1; // Forces first put invocation to replace EMPTY_TABLE
}

首先,在构造方法中会创建一个默认大小为2存储对象为HashMapEntry的table数组

 private static final Entry[] EMPTY_TABLE
     = new HashMapEntry[MINIMUM_CAPACITY >>> 1];

这个HashMapEntry又是什么呢,它保存有4个参数

HashMapEntry(K key, V value, int hash, HashMapEntry<K, V> next) {
   this.key = key;
   this.value = value;
   this.hash = hash;
   this.next = next;
}

先了解到这,我们来看看put方法是怎么实现的

@Override 
public V put(K key, V value) {
    if (key == null) {
        return putValueForNullKey(value);
    }

    int hash = Collections.secondaryHash(key);
    HashMapEntry<K, V>[] tab = table;
    int index = hash & (tab.length - 1);
    for (HashMapEntry<K, V> e = tab[index]; e != null; e = e.next) {
         if (e.hash == hash && key.equals(e.key)) {
             preModify(e);
             V oldValue = e.value;
             e.value = value;
             return oldValue;
        }
    }

    // No entry for (non-null) key is present; create one
    modCount++;
    if (size++ > threshold) {
        tab = doubleCapacity();
        index = hash & (tab.length - 1);
    }
    addNewEntry(key, value, hash, index);
    return null;
}

首先,判断key值是否为空,如果为空,则用一个entryForNullKey对象来存储相应数据

private V putValueForNullKey(V value) {
     HashMapEntry<K, V> entry = entryForNullKey;
     if (entry == null) {
         addNewEntryForNullKey(value);
         size++;
         modCount++;
         return null;
     } else {
         preModify(entry);
         V oldValue = entry.value;
         entry.value = value;
         return oldValue;
    }
}
void addNewEntryForNullKey(V value) {
    entryForNullKey = new HashMapEntry<K, V>(null, value, 0, null);
}

else方法是对已有key为null的数据更新,preModify不用管,在HashMap中并无具体实现

继续往下看,我们先跳过for方法,回头再来看,doubleCapacity是对数组扩容的,也就不看了,直接来看addNewEntry方法

void addNewEntry(K key, V value, int hash, int index) {
    table[index] = new HashMapEntry<K, V>(key, value, hash, table[index]);
}

这边就是往数组中插入,并将原先位置的数据作为新数据的next变量,举个例子:
put("1","1"),put("qa","2s"),put两个数据,假设这两次的index值获取到的都为0,会怎么处理呢?首先第一次put时,table[0]会直接存相应数据,当第二次put时,table[0]的值就更新为第二次的数据,原先的table[0]会作为现在table[0]的next参数,形成单向链表存储,以后若还有indexi为0的数据过来,则继续按照这种规则规则存储。

再来看看for方法,先判断这个index是否存在数据,不存在则addNewEntry,若存在,则找到这个数据,判断key值和hash值是否相等,若相等,说明是要更新数据,若不等,则需要找到链表的下一个值,再次判断,周而复始。若最终链表循环结束,还是未找到,则走addNewEntry方法,这样就又跟举的例子一样的处理步骤了。

至于HashMap是无序的,那又是为什么呢?

一次主要因为这个index值,put的数据可能会存在table的任何位置上,而读取时是按照从小到大依次读取的,我们来看看读取的源码

HashMapEntry<K, V> nextEntry() {
     if (modCount != expectedModCount)
         throw new ConcurrentModificationException();
         if (nextEntry == null)
         throw new NoSuchElementException();

         HashMapEntry<K, V> entryToReturn = nextEntry;
         HashMapEntry<K, V>[] tab = table;
         HashMapEntry<K, V> next = entryToReturn.next;
         while (next == null && nextIndex < tab.length) {
             next = tab[nextIndex++];
        }
        nextEntry = next;
        return lastEntryReturned = entryToReturn;
}

看while方法,只有当next的值为空,即链表索引都结束了,tab的nextIndex才会加1,再继续找当前链表中的值,所以导致打印出来的所有key的顺序并不跟插入的一致

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • HashMap 是 Java 面试必考的知识点,面试官从这个小知识点就可以了解我们对 Java 基础的掌握程度。网...
    野狗子嗷嗷嗷阅读 11,680评论 9 107
  • 前言 这次我和大家一起学习HashMap,HashMap我们在工作中经常会使用,而且面试中也很频繁会问到,因为它里...
    liangzzz阅读 12,420评论 7 102
  • 实际上,HashSet 和 HashMap 之间有很多相似之处,对于 HashSet 而言,系统采用 Hash 算...
    曹振华阅读 7,214评论 1 37
  • 情绪和情绪之间,会互相转化,互相掩盖,情绪和情绪之间会层层叠叠地交织在一起。当人们担心自己的某个情绪或情感不被允许...
    清水微甜的日常阅读 1,621评论 0 0
  • 听一首舒缓的歌曲,让自己的心平静下来;品一杯淡茶,让自己的心沉稳下来;来一次深呼吸,让自己心里的闷气排出来;读一本...
    雪后山阅读 2,885评论 1 11

友情链接更多精彩内容