当初来公司时候,被问了一个问题,叫重写一个hashMap怎么写。数据结构捉鸡。。。所以,现在决定补上这东西。
好啦,首先,先写点东西。
HashMap 和 HashSet 是 Java Collection Framework 的两个重要成员,其中 HashMap 是 Map 接口的常用实现类,HashSet 是 Set 接口的常用实现类。虽然 HashMap 和 HashSet 实现的接口规范不同,但它们底层的 Hash 存储机制完全一样,甚至 HashSet 本身就采用 HashMap 来实现的。
通过 HashMap、HashSet 的源代码分析其 Hash 存储机制
实际上,HashSet 和 HashMap 之间有很多相似之处,对于 HashSet 而言,系统采用 Hash 算法决定集合元素的存储位置,这样可以保证能快速存、取集合元素;对于 HashMap 而言,系统 key-value 当成一个整体进行处理,系统总是根据 Hash 算法来计算 key-value 的存储位置,这样可以保证能快速存、取 Map 的 key-value 对。
在介绍集合存储之前需要指出一点:虽然集合号称存储的是 Java 对象,但实际上并不会真正将 Java 对象放入 Set 集合中,只是在 Set 集合中保留这些对象的引用而言。也就是说:Java 集合实际上是多个引用变量所组成的集合,这些引用变量指向实际的 Java 对象。
一、从put分析table结构:
public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } if (key == null) return putForNullKey(value); int hash = hash(key); int i = indexFor(hash, table.length); for (Entry<K,V> 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是一个Entry<K,V>[],键值对数组。存放方法如下:


也就是说,用Entry,存放key、value、nextEntry,三个核心数据,就可以用table数组来实现hashMap。
二、大小、负载因子
读jdk源码,我们发现HashMap这个类,有几个常量如下:
/** * 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;
这就是我们说的容量和负载因子,什么意思呢?
所谓容量,就是之前所说的「table」数组的大小,也就是说,HashMap初始默认的大小为16,一个size为16的Entry数组。而负载因子,决定table中装载Entry链的负载程度,默认就是16*0.75,一旦超过这个值,我们在put(k,v)中找到了方法 addEntry(hash, key, value, i),从中找到了方法resize():
void addEntry(int hash, K key, V value, int bucketIndex) { if ((size >= threshold) && (null != table[bucketIndex])) { resize(2 * table.length); hash = (null != key) ? hash(key) : 0; bucketIndex = indexFor(hash, table.length); } createEntry(hash, key, value, bucketIndex); }
一旦Map的size大于threshold(一般为容量*负载因子),则扩容2倍。
三、确定索引的算法
好啦,那么put(k,v)是怎么决定index(table中的索引)的呢?
int hash = hash(key); int i = indexFor(hash, table.length);
那我们看到两个方法,来确定index,这两个分别长什么样子?hash算法是什么呢?
/** * Retrieve object hash code and applies a supplemental hash function to the * result hash, which defends against poor quality hash functions. This is * critical because HashMap uses power-of-two length hash tables, that * otherwise encounter collisions for hashCodes that do not differ * in lower bits. Note: Null keys always map to hash 0, thus index 0. */ final int hash(Object k) { int h = hashSeed; if (0 != h && k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } h ^= k.hashCode(); // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor). h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } /** * Returns index for hash code h. */ 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); }
+_+,说到底,这个算法就是让得到的索引值总是位于 table 数组的索引之内。这个算法,实现的很“散”,突出了“散列表”的主题。
四、HashMap的使用
待续。。。