A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

关于一道百度的面试题关于HashMap的内部实现机制,望各位大侠们指点指点,小弟不胜感激

评分

参与人数 1技术分 +1 收起 理由
admin + 1

查看全部评分

2 个回复

倒序浏览


1.    HashMap概述:
   HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。


2.    HashMap的数据结构:
   java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个链表散列的数据结构,即数组和链表的结合体。

file:///C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\ksohtml\wps_clip_image-15788.png
从上图中可以看出,HashMap底层就是一个数组结构,数组中的每一项又是一个链表。当新建一个HashMap的时候,就会初始化一个数组。

源码如下:
view source
file:///C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp\ksohtml\wps_clip_image-11086.png
print?
01

/**
02
* The table, resized as necessary. Length MUST Always be a power of two.

03
*/
04
transient Entry[] table;

05

06
static class Entry<K,V> implements Map.Entry<K,V> {

07
    final K key;
08
    V value;

09
    Entry<K,V> next;
10
    final int hash;

11
    ……
12
}
可以看出,Entry就是数组中的元素,每个 Map.Entry 其实就是一个key-value对,它持有一个指向下一个元素的引用,这就构成了链表。


[size=10.5000pt]
[size=10.5000pt]

评分

参与人数 1技术分 +1 收起 理由
admin + 1 我看晕了

查看全部评分

回复 使用道具 举报
HashMap底层是用一个Entry类型的数组实现的,每个Entry对象的内部又含有指向下一个Entry类型对象的引用,
下面是源码的部分分析,我看你提问,所以我也学习回忆了一下,0-0

static final int DEFAULT_INITIAL_CAPACITY = 16;
transient Entry[] table;

//源码中一个不带参数的构造方法,new了一个数组
  public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        table = new Entry[DEFAULT_INITIAL_CAPACITY];
        init();
    }
  table = new Entry[DEFAULT_INITIAL_CAPACITY];//构造方法new了一个Entry类型的数组,长度为16,table引用
     ----------------------------------------------------------

看Entry类型,内部拥有加入Map中的K,V,hash值,和自身的一个引用
  static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;
        V value;
        Entry<K,V> next; ///Entry类型内部有一个自己类型的引用,指向下一个Entry
        
        final int hash;
     ………………………………………………………………}


//看HashMap的put方法
  public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);//如果key为空,看下面进入putForNullKey方法
           
  int hash = hash(key.hashCode());//此hash值根据put加入的key的hashCode()值计算出来
  
  int i = indexFor(hash, table.length);//根据key的hash值,和table数组的长度计算出一个i,这个i就是确定了put的K,V放入到底层table数组的哪个位置
                                       // static int indexFor(int h, int length) {
                                             return h & (length-1); }
                                                                     
  
   //如果put的key不为空,下面遍历数组table中的每一个Entry,用Entry的hash值与加入的key值计算的hash值比较,或是直接用Entry的key值equals加入的key值,
   如果都满足相等(其实就是根据key的hashCode()和equals方法判断key是否重复),则返回Entry中的旧value,用新加入的value替换, e.value = value;
        for (Entry<K,V> e = table; 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);//put的key值与数组中Entry的key值都不相同时,则new一个新的Entry对象,加入到底层数组中
        return null;
    }
   
   //看addEntry方法
      void addEntry(int hash, K key, V value, int bucketIndex)
     {
             Entry<K,V> e = table[bucketIndex];//根据indexFor(hash, table.length)计算的值i确定在table数组中产生的新的Entry对象的位置,
                                                  把位置引用赋给一个Entry变量,(如果该位置上已经有一个Entry对象,把引用给e)
                                                 
       table[bucketIndex] = new Entry<K,V>(hash, key, value, e);///new了一个新的Entry对象,它站了引用位置table[bucketIndex],
                                                                   同时内部拥有了e(上一个该table[bucketIndex]位置上Entry对象的引用)
                                                                  如果new 了一个Entry B 则B在 table[bucketIndex]位置上,同时B内有上一个
                                                                  Entry对象A的引用,B又指向了A,如果又有一个对象C要加入到数组,位置也在
                                                                table[bucketIndex]上,则C又指向了B,看图例:
                                                               
        if (size++ >= threshold)
            resize(2 * table.length);
    }
   
   
    //看遍历table中的每一个Entry,如果key为空,则返回对应的旧value,用新put的value替换e.value=value
   private V putForNullKey(V value) {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }

评分

参与人数 1技术分 +5 收起 理由
admin + 5 先给分再看,必须的

查看全部评分

回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马