HashMap源码分析

HashMap是一个常用的类。它与HashTable类似,只不过它可以设置key和value为null,且它不是线程安全的。

它有两个参数影响性能,一个是initial capacity,一个是load factor

initial capacity 默认值是4,load factor的默认值是0.75

 

看下HashMap的构造方法

public HashMap() {

this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);

}

 

这个DEFAULT_INITIAL_CAPACITY

/**

* The default initial capacity - MUST be a power of two.

*/

static final int DEFAULT_INITIAL_CAPACITY = 4;

 

默认是4,必须为2的n次方

 

再看DEFAULT_LOAD_FACTOR

/**

* The load factor used when none specified in constructor.

*/

static final float DEFAULT_LOAD_FACTOR = 0.75f;

 

默认是0.75f

 

再看构造方法的具体实现

public HashMap(int initialCapacity, float loadFactor) {

if (initialCapacity < 0)

   throw new IllegalArgumentException("Illegal initial capacity: " +

   initialCapacity);

if (initialCapacity > MAXIMUM_CAPACITY) {

   initialCapacity = MAXIMUM_CAPACITY;

} else if (initialCapacity < DEFAULT_INITIAL_CAPACITY) {

   initialCapacity = DEFAULT_INITIAL_CAPACITY;

}



if (loadFactor <= 0 || Float.isNaN(loadFactor))

   throw new IllegalArgumentException("Illegal load factor: " +

   loadFactor);

// Android-Note: We always use the default load factor of 0.75f.



// This might appear wrong but it's just awkward design. We always call

// inflateTable() when table == EMPTY_TABLE. That method will take "threshold"

// to mean "capacity" and then replace it with the real threshold (i.e, multiplied with

// the load factor).

   threshold = initialCapacity;

   init();

}

 

可以看到这里把initalCapacity赋值给了threshold成员

向HashMap中添加key和value用到的是put方法

 

看下put方法的源码

public V put(K key, V value) {

   if (table == EMPTY_TABLE) {

       inflateTable(threshold);

   }

   if (key == null)

       return putForNullKey(value);

   int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key);

   int i = indexFor(hash, table.length);

   for (HashMapEntry e = table[i]; 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);

   return null;

}

 

第一句,如果table为空table,就加载一个table

 

看inflateTable方法

private void inflateTable(int toSize) {

// Find a power of 2 >= toSize

int capacity = roundUpToPowerOf2(toSize);



// Android-changed: Replace usage of Math.min() here because this method is

// called from the  of runtime, at which point the native libraries

// needed by Float.* might not be loaded.

float thresholdFloat = capacity * loadFactor;

if (thresholdFloat > MAXIMUM_CAPACITY + 1) {

thresholdFloat = MAXIMUM_CAPACITY + 1;

}



threshold = (int) thresholdFloat;

table = new HashMapEntry[capacity];

}

 

toSize就是threshold,在构造方法里,被赋值为4

 

那么这里table为一个4个元素的HashMapEntry数组

 

这个有个方法roundUpToPowerOf2(toSize),这个方法是找到最接近toSize的2的n次方的数

 

这个方法的原理后面细看

 

继续看put方法

 

如果key==null,则执行putForNullKey,说明HashMap是允许key为null的

 

看这个putForNullKey

private V putForNullKey(V value) {

for (HashMapEntry 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;

}

 

这里看到,从table的第0个位置开始遍历数组

 

如果找到第一个key为null的位置,替换原来的value,并返回原有的value

 

如果遍历完数组都没有key为null的情况,则执行addEntry(0,null,value,0);

 

看这个addEntry方法

void addEntry(int hash, K key, V value, int bucketIndex) {

if ((size >= threshold) && (null != table[bucketIndex])) {

resize(2 * table.length);

hash = (null != key) ? sun.misc.Hashing.singleWordWangJenkinsHash(key) : 0;

bucketIndex = indexFor(hash, table.length);

}



createEntry(hash, key, value, bucketIndex);

}

 

if里暂时不去看,看这个createEntry方法

void createEntry(int hash, K key, V value, int bucketIndex) {

HashMapEntry e = table[bucketIndex];

table[bucketIndex] = new HashMapEntry<>(hash, key, value, e);

size++;

}

 

这里会在buketIndex的位置创建一个HashMapEntry对象,并且size++

 

看下这个HashMapEntry的构造方法

HashMapEntry(int h, K k, V v, HashMapEntry n) {

value = v;

next = n;

key = k;

hash = h;

}

 

HashMapEntry是HashMap的一个内部类,它有4个从成员,分别是value,next,key,hash

 

在createEntry方法里,可以看到,如果在table的bucketIndex位置原来就HashMapEntry对象的,那么会将新创建的HashMapEntry插入原有HashMapEntry的头部,构成一个链表

 

继续看put方法

 

如果key不是null的情况,则会通过sun.misc.Hashing.singleWordWangJenkinsHash(key)这个方法算出一个int 类型hash值

 

然后通过indexFor(hash,table.length)方法算出index

 

看这个indexFor方法

static int indexFor(int h, int length) {

// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";

return h & (length-1);

}

 

算法很简洁,就这么与一下就出来了,至于上面那个hash值是怎么算的,这要另外深入研究了

 

算出索引后,就要往table里插入数据了


   

for (HashMapEntry e = table[i]; 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;

       }

   }

 

看这里代码,从i位置开始遍历,如果找到hash值相同,key也相同的HashMapEntry,则认为是同一个key-value,那么替换原有的value为新的value,并返回原有的value,插入完成。

 

如果key相同,hash值肯定相同,但是反过来不一定,即hash值相同,但key不一定相同。

 

如果遍历完都没有找到符号以上条件的,则执行addEntry方法

 

回过头来继续看addEntry方法

 

addEntry第一行就是if语句,它这里判断当前的table的空间是否够用,

 

如果不够用,则要扩充table空间,在原来基础上增加一倍的空间

 

看resize方法

void resize(int newCapacity) {

HashMapEntry[] oldTable = table;

int oldCapacity = oldTable.length;

if (oldCapacity == MAXIMUM_CAPACITY) {

threshold = Integer.MAX_VALUE;

return;

}



HashMapEntry[] newTable = new HashMapEntry[newCapacity];

transfer(newTable);

table = newTable;

threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);

}

 

这里就是创建了一个新的数组,并且原有数组中元素搬到新的数组中,看transfer方法

void transfer(HashMapEntry[] newTable) {

int newCapacity = newTable.length;

for (HashMapEntry e : table) {

while(null != e) {

HashMapEntry next = e.next;

int i = indexFor(e.hash, newCapacity);

e.next = newTable[i];

newTable[i] = e;

e = next;

}

}

}

 

transfer方法会遍历原有数组,然后重新计算在新数组中的index,将HashMapEntry对象放入新的数组中

 

判断是否扩容后,然后就是createEntry,createEntry方法上面已经分析了

 

 

下面分析get方法

public V get(Object key) {

if (key == null)

return getForNullKey();

Entry entry = getEntry(key);



return null == entry ? null : entry.getValue();

}

 

如果key是null,就调用getForNullKey()

private V getForNullKey() {

if (size == 0) {

return null;

}

for (HashMapEntry e = table[0]; e != null; e = e.next) {

if (e.key == null)

return e.value;

}

return null;

}

 

可以看到,从table[0]位置取值,如果key为null则返回value

 

当key不为null的情况,调用getEntry

final Entry getEntry(Object key) {

if (size == 0) {

return null;

}



int hash = (key == null) ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key);

for (HashMapEntry 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值,然后再算出索引,再index出去判断,如果hash值相同且key相同的情况,就取出value

如果key相同,遍历链表,找出key相同的情况,如果遍历完了还找不到,则返回null

你可能感兴趣的:(HashMap源码分析)