HashMap的源码分析

HashMap的源码分析

hashMap的底层实现是 数组+链表 的数据结构,数组是一个Entry<K,V>[] 的键值对对象数组,在数组的每个索引上存储的是包含Entry的节点对象,每个Entry对象是一个单链表结构,维护这下一个Entry节点的引用;有点绕,用个图来展示吧:

Entry<K,V>[] 数组部分保存的是首个Entry节点;Entry节点包含一个 K值引用  V值引用 以及 引用下一个Entry 节点的next引用;

Entry节点的java代码实现如下:

static class Entry<K,V> implements Map.Entry<K,V> {
        final K key;    //key 引用
        V value;         //value  引用
        Entry<K,V> next;   //下一个Entry 节点的引用
}

下面再看下HashMap 对象的java实现代码:

包含的属性有:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class HashMap<K,V>
     extends AbstractMap<K,V>
     implements Map<K,V>, Cloneable, Serializable
{
 
     /**
      * The default initial capacity - MUST be a power of two.
      */
     static final int DEFAULT_INITIAL_CAPACITY = 1 << 4 ; // aka 16
 
     /**
      * 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 ;
 
     /**
      * The load factor used when none specified in constructor.
      */
     static final float DEFAULT_LOAD_FACTOR = 0 .75f;
 
     /**
      * An empty table instance to share when the table is not inflated.
      */
     static final Entry<?,?>[] EMPTY_TABLE = {};
 
     /**
      * The table, resized as necessary. Length MUST Always be a power of two.
      */
     transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
 
     /**
      * The number of key-value mappings contained in this map.
      */
     transient int size;
 
     /**
      * The next size value at which to resize (capacity * load factor).
      * @serial
      */
     // If table == EMPTY_TABLE then this is the initial capacity at which the
     // table will be created when inflated.
     int threshold;
 
     /**
      * The load factor for the hash table.
      *
      * @serial
      */
     final float loadFactor;<br>}

 

 比较重要的属性是:

?
1
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;  表明这是一个 Entry<K,V>[] 的数组类型;<br>下面看其无参的构造器:
public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

进入以下的构造方法:

复制代码
 public HashMap(int initialCapacity, float loadFactor) {        //initialCapacity:16   loadFactor 0.75f
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)     //MAXIMUM_CAPACITY   1073741824  false
             initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))   false
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        this.loadFactor = loadFactor;      //赋值给loadFactor=0.75
        threshold = initialCapacity;       //赋值给threshold=16  当为16是自动扩容
        init();
    }
复制代码

下面再看看put(E e)的方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public V put(K key, V value) {    //如插入  key="city"   value="shanghai"
         if (table == EMPTY_TABLE) {     //true
             inflateTable(threshold);   //参数为16
         }
         if (key == null )        // false
             return putForNullKey(value);
         int hash = hash(key);    //返回key值的hash码; 比如返回为 337
         int i = indexFor(hash, table.length);  //将hash 取模与16 获得的结果为 1
         for (Entry<K,V> e = table[i]; e != null ; e = e.next) {  //遍历 Entry[1] 中的链表节点对象 包含 原先有的节点和新增进去的节点
             Object k;
             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {  //当Entry中包含 相同的hash码的key 并且key和要添加的key相等即可以是否重复  则进入以下逻辑:新节点替换重复的节点
                 V oldValue = e.value;
                 e.value = value;
                 e.recordAccess( this );
                 return oldValue;
             }
         }

 下面是 inflateTable(threshold) 方法的源码;

?
1
2
3
4
5
6
7
8
9
10
11
/**
      * Inflates the table.
      */
     private void inflateTable( int toSize) {   //toSize 16
         // Find a power of 2 >= toSize
         int capacity = roundUpToPowerOf2(toSize);  //capacity=16
 
         threshold = ( int ) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1 ); // threshold=16*0.75
         table = new Entry[capacity];     //创建 Entry[] 数组长度为16  
         initHashSeedAsNeeded(capacity);   //这个方法可以暂时不用深究
     }  

下面是  roundUpToPowerOf2(int i) 源码

?
1
2
3
4
5
6
private static int roundUpToPowerOf2( int number) {    //  number=16
         // assert number >= 0 : "number must be non-negative";
         return number >= MAXIMUM_CAPACITY   //fase  返回 16
                 ? MAXIMUM_CAPACITY
                 : (number > 1 ) ? Integer.highestOneBit((number - 1 ) << 1 ) : 1 ;   //number=16>1  返回 16
     } 

put 方法的源码分析完了之后,接下来再看一下get(Object key) 的方法; 源码:

复制代码
public V get(Object key) {    //如 key="name"
        if (key == null)      //false
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);

        return null == entry ? null : entry.getValue();
    }
复制代码
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
final Entry<K,V> getEntry(Object key) {   //key=name
        if (size == 0 ) {    //false
            return null ;
        }
 
        int hash = (key == null ) ? 0 : hash(key);   //例如 返回hash=337
        for (Entry<K,V> e = table[indexFor(hash, table.length)];  //indexFor(hash, table.length)上面分析过 返回值为 1;遍历 table[1] 中的节点
             e != null ;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))   //如果存在key的hash码相等,并且对象也相等则返回 对应的Entry 节点
                return e;
        }
        return null ;   //否则返回null
    } 

到此,hashMap 的源码基本分析完毕了,通过源码分析我们知道HashMap的底层是 数组+链表结构来存数数据的,添加节点存储的位置是根据 key 取hash值 再取模于数组长度:返回的数值就是Entry接在在数组的哪个位置;这种方式的存储方式减少了存储的时间和空间的复杂度;

知道了hashMap是由 数组+链表 的数据结构存储数据后,我们也很容易明白hashMap 的遍历方式:

?
1
<br><br>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值