来源:https://www.cnblogs.com/beppezhang/p/6567478.html
hashMap的底层实现是 数组+链表 的数据结构,数组是一个Entry<K,V>[] 的键值对对象数组,在数组的每个索引上存储的是包含Entry的节点对象,每个Entry对象是一个单链表结构,维护这下一个Entry节点的引用;有点绕,用个图来展示吧:
![](https://i-blog.csdnimg.cn/blog_migrate/817ccdc0f2fabbb7ae140a5ef186e748.jpeg)
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);
}
进入以下的构造方法:
![复制代码](https://i-blog.csdnimg.cn/blog_migrate/48304ba5e6f9fe08f3fa1abda7d326ab.gif)
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();
}
![复制代码](https://i-blog.csdnimg.cn/blog_migrate/48304ba5e6f9fe08f3fa1abda7d326ab.gif)
下面再看看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) 的方法; 源码:
![复制代码](https://i-blog.csdnimg.cn/blog_migrate/48304ba5e6f9fe08f3fa1abda7d326ab.gif)
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();
}
![复制代码](https://i-blog.csdnimg.cn/blog_migrate/48304ba5e6f9fe08f3fa1abda7d326ab.gif)
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 的遍历方式: