转载请声明出处:http://blog.csdn.net/qq_24692041/article/details/65438687
我们先说说HashMap的几个特点,然后再来慢慢分析!
- HashMap是基于Hash 表实现的。Hash表其实是一个Hash数组+多个单链表组成。HashMap会将Key算出hash值,然后每一个hash值对应一个单链表,单链表中的每一个节点就用于我们存入的数据。然后将这些单链表的头结点存放在Hash数组中。
这就是hash表的结构,紫色的是hash数组,就是源码中的table。绿色的是链表,就是HashMapEntry节点连接起来的。数据的存储方式就是将=计算key值的hash值,然后将hash值相等的数据存放在同一个链表中(注意:hash值相等不代表key值相等,具体的可以去看看hash值的算法)。对于链表认识的不够的小伙伴可以去《数据结构之Java单链表反转》一文中看看,保证让你看懂!
- HashMap通过实现Map接口,提过所有对map的操作。
- HashMap允许key值和value值为null,并且将为null的key存放在hash数组的第1个(下标为0)也即是table[0]的链表中。
- HashMap不保证操作(根据插入的顺序取出)的顺序,因为在插入的时候内部是通过计算key值的hash值,将hash值相同的数据存放在相同的链表中,并且是通过hash值来排序的。并不是跟链表一样,保证插入的顺序。
- HashMap性能方面主要考虑capacity容量和loadFactor加载因子,默认容量capacity为4,默认加载因子loadFactor为 0.75。如果没有手动设置这两个值就会用默认值。capacity表示hash数组也就是table的长度,当容量不够的时候通过调用resize方法扩容。loadFactor加载因子代表数据的散列情况,值越大散列的越好,但是遍历速度越慢,因为链表的长度变长了。
- HashMap实现了Serializable接口,因此它支持序列化,实现了Cloneable接口,能被克隆。
- HashMap除了key值和value值允许为空,不支持线程安全外跟HashTable基本等同。
- hash数组的容量一定是2的偶数次幂,因为hashMap是将数据根据key值算出hash值,创建链表,然后散列在table表中。所以计算出来的这个索引必须可以是奇数也可是偶数,才能完全充分的利用内存,否则内存只能使用一半。而计算索引的indexFor方法中的算法是固定的,必须保证table数组的length是2的偶数次幂才能保证这一点。
下面我们摘取一部分源代码,具体的来看看,搞清楚HashMap内部的结构和具体的原理,建议网友在看源代码的时候以构造函数,put,get方法为入口顺藤摸瓜,一点点看下去就简单了:
public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable { /** * map的默认容量 */ static final int DEFAULT_INITIAL_CAPACITY = 4; /** * 最大容量,2的30次方,当存入过大时,将使用这个容量,并且保证是2的偶数次方 */ static final int MAXIMUM_CAPACITY = 1 << 30; /** * 加载因子默认值,代表着map的散列情况 */ static final float DEFAULT_LOAD_FACTOR = 0.75f; /** * 空表 */ static final HashMapEntry<?, ?>[] EMPTY_TABLE = {}; /** * hash数组用于存储key值计算出来的hash值对应的链表的头指针,容量必须是2的偶数幂 */ transient HashMapEntry<K, V>[] table = (HashMapEntry<K, V>[]) EMPTY_TABLE; /** * 已经使用的容量槽(已经存放的数据的量) */ transient int size; /** * HashMap的阈值,用于判断是否需要增加容量 */ // If table == EMPTY_TABLE then this is the initial capacity at which the // table will be created when inflated. int threshold; /** * hash表的实际加载因子 */ // Android-Note: We always use a load factor of 0.75 and ignore any explicitly // selected values. final float loadFactor = DEFAULT_LOAD_FACTOR; /** * HashMap被改变的次数(包括已经存放的值的数量的改变和hashMap内部构造的改变) */ transient int modCount; /** * 构造方法,指定容量和加载因子 */ 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(); } /** * Constructs an empty <tt>HashMap</tt> with the specified initial * capacity and the default load factor (0.75). * * @param initialCapacity the initial capacity. * @throws IllegalArgumentException if the initial capacity is negative. */ public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } /** * Constructs an empty <tt>HashMap</tt> with the default initial capacity * (16) and the default load factor (0.75). */ public HashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); } /** * 传入一个Map集合,会创建一个新的Map集合,并且根据传进来的Map集合的大小对新的Map扩容,然后将数据全部给新Map */ public HashMap(Map<? extends K, ? extends V> m) { this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1, DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); inflateTable(threshold); putAllForCreate(m); } /** * 处理扩充hash数组的大小数据,保证容量是2的偶次幂 */ private static int roundUpToPowerOf2(int number) { // assert number >= 0 : "number must be non-negative"; int rounded = number >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : (rounded = Integer.highestOneBit(number)) != 0 ? (Integer.bitCount(number) > 1) ? rounded << 1 : rounded : 1; return rounded; } /** * 对hash数组table扩容 */ 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 <clinit> of runtime, at which point the native libraries // needed by Float.* might not be loaded. //阈值计算,等于hash数组的容量乘以加载因子 float thresholdFloat = capacity * loadFactor; if (thresholdFloat > MAXIMUM_CAPACITY + 1) { thresholdFloat = MAXIMUM_CAPACITY + 1; } threshold = (int) thresholdFloat; //因为table还是空数组,所以初始化table table = new HashMapEntry[capacity]; } // internal utilities /** * Initialization hook for subclasses. This method is called * in all constructors and pseudo-constructors (clone, readObject) * after HashMap has been initialized but before any entries have * been inserted. (In the absence of this method, readObject would * require explicit knowledge of subclasses.) */ void init() { } /** * Returns index for hash code h. * 返回传入的hash值在table数组中的索引 */ 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); } /** * Returns the number of key-value mappings in this map. * * @return the number of key-value mappings in this map */ public int size() { return size; } /** * Returns <tt>true</tt> if this map contains no key-value mappings. * * @return <tt>true</tt> if this map contains no key-value mappings */ public boolean isEmpty() { return size == 0; } /** * 获取key所对应的value值 * * @see #put(Object, Object) */ public V get(Object key) { //如果key为null,遍历集合取出key值为null的value值 if (key == null) return getForNullKey(); //如果Key值不为null,获取这个key所在的节点 Entry<K, V> entry = getEntry(key); //通过节点拿到value值返回 return null == entry ? null : entry.getValue(); } /** * key值为null的数据存放在第1个链表也即是table[0]的链表, * 从table中取出链表遍历获取key值为null的value值 */ private V getForNullKey() { if (size == 0) { return null; } for (HashMapEntry<K, V> e = table[0]; e != null; e = e.next) { if (e.key == null) return e.value; } return null; } /** * 查找key值所在的节点,判断集合中是否包含该数据 * * @param key The key whose presence in this map is to be tested * @return <tt>true</tt> if this map contains a mapping for the specified * key. */ public boolean containsKey(Object key) { return getEntry(key) != null; } /** * 如果key值为null,hash直接为0,存入到table[0]中存放的链表。 * 如果不为0计算key值的hash值,并找到该hash值所在的链表,从这个链表中找到key所在的节点 */ final Entry<K, V> getEntry(Object key) { if (size == 0) { return null; } //计算传入的key值的hash值 int hash = (key == null) ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key); //在table中取出在hash值所对应的链表,遍历查找key值所在的节点返回 for (HashMapEntry<K, V> 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; } /** * 往map中以键值对的形式加入数据 */ public V put(K key, V value) { //如果Hash数组是空的,就扩容 if (table == EMPTY_TABLE) { inflateTable(threshold); } //如果key值是空,将该数据存放在table[0]中的链表 if (key == null) return putForNullKey(value); //如果不为null,计算key值的hash值 int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key); //找到hash值在数组中的索引 int i = indexFor(hash, table.length); //根据索引取出链表,遍历链表 for (HashMapEntry<K, V> e = table[i]; e != null; e = e.next) { Object k; //如果在该hash值对应的链表中已经存在key的节点,直接替换oldValue,并将oldValue返回,结束该方法 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; //在hashMap类中,这个方法什么也没做,这个方法是给子类LinkedHashMap用的,用于保证操作排序 e.recordAccess(this); return oldValue; } } /* *如果在hash值所在的链表中没有key值对应的节点,就创建新的节点,将key-value放进去 */ //增加更改次数 modCount++; //增加当前链表的节点,将key-value放进去 addEntry(hash, key, value, i); return null; } /** * 将空key值插入到table[0]链表中 */ private V putForNullKey(V value) { for (HashMapEntry<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; } /** * This method is used instead of put by constructors and * pseudoconstructors (clone, readObject). It does not resize the table, * check for comodification, etc. It calls createEntry rather than * addEntry. */ private void putForCreate(K key, V value) { int hash = null == key ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key); int i = indexFor(hash, table.length); /** * Look for preexisting entry for key. This will never happen for * clone or deserialize. It will only happen for construction if the * input Map is a sorted map whose ordering is inconsistent w/ equals. */ for (HashMapEntry<K, V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { e.value = value; return; } } createEntry(hash, key, value, i); } private void putAllForCreate(Map<? extends K, ? extends V> m) { for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) putForCreate(e.getKey(), e.getValue()); } /** * 创建出一个更大的table,将旧的table中的数据全部复制到新的table中 */ void resize(int newCapacity) { HashMapEntry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } //创建如一个新的table HashMapEntry[] newTable = new HashMapEntry[newCapacity]; //复制数据到新的table中 transfer(newTable); //将新的table赋给整个hashMap中的table table = newTable; //更改阈值,在默认最大空间+1和新的容量乘上加载因子中取最小值作为新的阈值 threshold = (int) Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); } /** * 将旧table中的链表和链表中的数据全部复制到新table中 */ void transfer(HashMapEntry[] newTable) { int newCapacity = newTable.length; //遍历table数组 for (HashMapEntry<K, V> e : table) { //取出数组中的链表,复制到新的table中 while (null != e) { HashMapEntry<K, V> next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } } } /** * 将一个map中的所有数据全部复制hashMap中 * * @param m mappings to be stored in this map * @throws NullPointerException if the specified map is null */ public void putAll(Map<? extends K, ? extends V> m) { int numKeysToBeAdded = m.size(); if (numKeysToBeAdded == 0) return; if (table == EMPTY_TABLE) { inflateTable((int) Math.max(numKeysToBeAdded * loadFactor, threshold)); } /* * Expand the map if the map if the number of mappings to be added * is greater than or equal to threshold. This is conservative; the * obvious condition is (m.size() + size) >= threshold, but this * condition could result in a map with twice the appropriate capacity, * if the keys to be added overlap with the keys already in this map. * By using the conservative calculation, we subject ourself * to at most one extra resize. */ if (numKeysToBeAdded > threshold) { int targetCapacity = (int) (numKeysToBeAdded / loadFactor + 1); if (targetCapacity > MAXIMUM_CAPACITY) targetCapacity = MAXIMUM_CAPACITY; int newCapacity = table.length; while (newCapacity < targetCapacity) newCapacity <<= 1; if (newCapacity > table.length) resize(newCapacity); } for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) put(e.getKey(), e.getValue()); } /** * 通过key值删除已经存在的对应数据 * */ public V remove(Object key) { Entry<K, V> e = removeEntryForKey(key); return (e == null ? null : e.getValue()); } /** * Removes and returns the entry associated with the specified key * in the HashMap. Returns null if the HashMap contains no mapping * for this key. */ final Entry<K, V> removeEntryForKey(Object key) { if (size == 0) { return null; } int hash = (key == null) ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key); int i = indexFor(hash, table.length); HashMapEntry<K, V> prev = table[i]; HashMapEntry<K, V> e = prev; while (e != null) { HashMapEntry<K, V> next = e.next; Object k; if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { modCount++; size--; if (prev == e) table[i] = next; else prev.next = next; //这个方法在HashMap中跟recordAccess一样,啥都没做是为了给自雷linkedHashMap用的 e.recordRemoval(this); return e; } prev = e; e = next; } return e; } /** * Special version of remove for EntrySet using {@code Map.Entry.equals()} * for matching. */ final Entry<K, V> removeMapping(Object o) { if (size == 0 || !(o instanceof Map.Entry)) return null; Map.Entry<K, V> entry = (Map.Entry<K, V>) o; Object key = entry.getKey(); int hash = (key == null) ? 0 : sun.misc.Hashing.singleWordWangJenkinsHash(key); int i = indexFor(hash, table.length); HashMapEntry<K, V> prev = table[i]; HashMapEntry<K, V> e = prev; while (e != null) { HashMapEntry<K, V> next = e.next; if (e.hash == hash && e.equals(entry)) { modCount++; size--; if (prev == e) table[i] = next; else prev.next = next; e.recordRemoval(this); return e; } prev = e; e = next; } return e; } /** * 通过将table中的所有链表置null来清空当前HashMap */ public void clear() { modCount++; Arrays.fill(table, null); size = 0; } /** * 遍历table中所有的链表 * 通过调用Object的equals方法来判断是否包含该value值 */ public boolean containsValue(Object value) { if (value == null) return containsNullValue(); HashMapEntry[] tab = table; for (int i = 0; i < tab.length; i++) for (HashMapEntry e = tab[i]; e != null; e = e.next) if (value.equals(e.value)) return true; return false; } /** * 遍历整个table中所有的链表,查找value为null的数据,只要有一个null就直接返回true */ private boolean containsNullValue() { HashMapEntry[] tab = table; for (int i = 0; i < tab.length; i++) for (HashMapEntry e = tab[i]; e != null; e = e.next) if (e.value == null) return true; return false; } /** * Returns a shallow copy of this <tt>HashMap</tt> instance: the keys and * values themselves are not cloned. * * @return a shallow copy of this map */ public Object clone() { HashMap<K, V> result = null; try { result = (HashMap<K, V>) super.clone(); } catch (CloneNotSupportedException e) { // assert false; } if (result.table != EMPTY_TABLE) { result.inflateTable(Math.min( (int) Math.min( size * Math.min(1 / loadFactor, 4.0f), // we have limits... HashMap.MAXIMUM_CAPACITY), table.length)); } result.entrySet = null; result.modCount = 0; result.size = 0; result.init(); result.putAllForCreate(this); return result; } /** * @hide */ // Android added. static class HashMapEntry<K, V> implements Map.Entry<K, V> { final K key; V value; HashMapEntry<K, V> next; int hash; /** * Creates new entry. */ HashMapEntry(int h, K k, V v, HashMapEntry<K, V> n) { value = v; next = n; key = k; hash = h; } public final K getKey() { return key; } public final V getValue() { return value; } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } public final boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry) o; Object k1 = getKey(); Object k2 = e.getKey(); if (k1 == k2 || (k1 != null && k1.equals(k2))) { Object v1 = getValue(); Object v2 = e.getValue(); if (v1 == v2 || (v1 != null && v1.equals(v2))) return true; } return false; } public final int hashCode() { return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue()); } public final String toString() { return getKey() + "=" + getValue(); } /** * 在put方法中调用,在HashMap中没有做任何操作,主要是给子类LinkedHashMap重写,用于保证插入顺序 */ void recordAccess(HashMap<K, V> m) { } /** * 删除元素时调用,一样的在hashMap中没有做任何操作,为LinkedHashMap准备 */ void recordRemoval(HashMap<K, V> m) { } } /** * 在已有的链表中为传入的key值新增一个节点 */ void addEntry(int hash, K key, V value, int bucketIndex) { //如果容量槽不够用,并且当前操作的链表不为null if ((size >= threshold) && (null != table[bucketIndex])) { //将table的容量扩充为之前的两倍 resize(2 * table.length); hash = (null != key) ? sun.misc.Hashing.singleWordWangJenkinsHash(key) : 0; //为当前hash值计算出在table中的索引 bucketIndex = indexFor(hash, table.length); } //创建新节点 createEntry(hash, key, value, bucketIndex); } /** *新创建一个节点,并且将该节点作为当前操作hash值对应链表的头结点 */ void createEntry(int hash, K key, V value, int bucketIndex) {......}HashMapEntry<K, V> e = table[bucketIndex];//将hash值对应的链表在table中取出来
//创建一个新节点用于存放新的vey-value,并且将该节点作为当前操作链表的头结点
table[bucketIndex] = new HashMapEntry<>(hash, key, value, e);
size++;
}
好了,到这儿具体的源代码差不多够了。足够搞清楚HashMap内部的构造和原理。下面我们将其中比较重要的几个方法拿出来,重点讲解一下:
put方法,用于往hashMap中以key-value的形式添加新的数据, 首先会判断当前表示否为空表,然后会判断当前插入的key值是否为null,如果为null,会将该数据插入到表头,也即是table[0]所在的链表。如果不为null就会算出hash值,取出hash值对应的链表,遍历这个链表,如果该链表中已经存在这个key值,就会替换旧的value,如果没有这个key就会创建一个新的节点存放数据,并且会将新创建的节点放在链表的头结点。
/** * 往map中以键值对的形式加入数据 */ public V put(K key, V value) { //如果Hash数组是空的,就扩容 if (table == EMPTY_TABLE) { inflateTable(threshold); } //如果key值是空,将该数据存放在table[0]中的链表 if (key == null) return putForNullKey(value); //如果不为null,计算key值的hash值 int hash = sun.misc.Hashing.singleWordWangJenkinsHash(key); //找到hash值在数组中的索引 int i = indexFor(hash, table.length); //根据索引取出链表,遍历链表 for (HashMapEntry<K, V> e = table[i]; e != null; e = e.next) { Object k; //如果在该hash值对应的链表中已经存在key的节点,直接替换oldValue,并将oldValue返回,结束该方法 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; //在hashMap类中,这个方法什么也没做,这个方法是给子类LinkedHashMap用的,用于保证操作排序 e.recordAccess(this); return oldValue; } } /* *如果在hash值所在的链表中没有key值对应的节点,就创建新的节点,将key-value放进去 */ //增加更改次数 modCount++; //增加当前链表的节点,将key-value放进去 addEntry(hash, key, value, i); return null; }
addEntry方法:这个方法是用于为链表增加节点的,在put方法中,会先去取出hash值对应的链表,遍历,如果已经对应的key值,就更改旧的value值。但是如果没有这个key值,就要调用该方法增加一个节点存放新插入的数据。
/** * 在已有的链表中为传入的key值新增一个节点 */ void addEntry(int hash, K key, V value, int bucketIndex) { //如果容量槽不够用,并且当前操作的链表不为null if ((size >= threshold) && (null != table[bucketIndex])) { //将table的容量扩充为之前的两倍 resize(2 * table.length); hash = (null != key) ? sun.misc.Hashing.singleWordWangJenkinsHash(key) : 0; //为当前hash值计算出在table中的索引 bucketIndex = indexFor(hash, table.length); } //创建新节点 createEntry(hash, key, value, bucketIndex); }
createEntry方法:这个方法才是创建新的节点的具体实现,取出当前hash值对应的链表,创建一个新的节点用于存放新插入的数据,并且会将新的节点设置为当前链表的头结点。
/** *新创建一个节点,并且将该节点作为当前操作hash值对应链表的头结点 */ void createEntry(int hash, K key, V value, int bucketIndex) {HashMapEntry<K, V> e = table[bucketIndex];//将hash值对应的链表在table中取出来
//创建一个新节点用于存放新的vey-value,并且将该节点作为当前操作链表的头结点
table[bucketIndex] = new HashMapEntry<>(hash, key, value, e);
size++;
}
get方法:get方法很简单,就是计算key的hash值,找到这个链表,遍历链表取出值。
/** * 获取key所对应的value值 * * @see #put(Object, Object) */ public V get(Object key) { //如果key为null,遍历集合取出key值为null的value值 if (key == null) return getForNullKey(); //如果Key值不为null,获取这个key所在的节点 Entry<K, V> entry = getEntry(key); //通过节点拿到value值返回 return null == entry ? null : entry.getValue(); }
resize方法:这个方法是用于将整个hash表扩容的,会先创建一个新的hash数组,然后将旧的hash数组中的所有链表复制到新的hash表中。这个方法是一个数组的复制过程,这是一个耗时的过程,并且在addEntry方法中调用resize方法的时候是将当前容量扩充两倍的。所以我们在使用的时候最好先约估一下容量的大小。
/** * 创建出一个更大的table,将旧的table中的数据全部复制到新的table中 */ void resize(int newCapacity) { HashMapEntry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } //创建如一个新的table HashMapEntry[] newTable = new HashMapEntry[newCapacity]; //复制数据到新的table中 transfer(newTable); //将新的table赋给整个hashMap中的table table = newTable; //更改阈值,在默认最大空间+1和新的容量乘上加载因子中取最小值作为新的阈值 threshold = (int) Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1); }
好了,HashMap基本上就这些东西了。没有更多的需要将的了!