HashMap源码分析二
jdk1.2中HashMap的源码和jdk1.3中HashMap的源码基本上没变。在上篇中,我纠结的那个11和101的问题,在这边中找到答案了。
jdk1.2
public HashMap() { this(101, 0.75f); } public HashMap(Map t) { this(Math.max(2*t.size(), 11), 0.75f); putAll(t); }
jdk1.3
public HashMap() { this(11, 0.75f); } public HashMap(Map t) { this(Math.max(2*t.size(), 11), 0.75f); putAll(t); }
原来jdk1.2中的101是开发者的一个误会 😃
HashMap源码在jdk1.4中,相比于jdk1.2、jdk1.3,有些优化性的改动,更成熟,考虑更完善了,一些java的编程风格也慢慢形成了。
static final int DEFAULT_INITIAL_CAPACITY = 16; static final int MAXIMUM_CAPACITY = 1 << 30; static final float DEFAULT_LOAD_FACTOR = 0.75f;
定义了三个静态常量。之前这些都是直接分散到代码中或者没有做考虑的。
DEFAULT_INITIAL_CAPACITY 是HashMap内数组的默认初始长度,不是以前的11了,现在确定为16。也不知道以前的11,现在的16是怎么定下来的。
MAXIMUM_CAPACITY 是HashMap内数组的最大长度,1 << 30 , 往下能看到数组的增长是每次加2倍,1 << 30 是int里成倍加的最大值,在加就超过int最大值了。
DEFAULT_LOAD_FACTOR 数组增长提示阀值的计算系数,之前是分散在代码里的
构造方法也有做修改
public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); // 数组的初始长度,必须是大于1 << 4 小于 1 << 30 中的一个数,并且是通过1左移多少得来的,左移的位数在4到30之间。 int capacity = 1; while (capacity < initialCapacity) capacity <<= 1; this.loadFactor = loadFactor; threshold = (int)(capacity * loadFactor); table = new Entry[capacity]; init(); // 钩子函数 }
并且在全部的构造方法里,都藏有钩子函数 init() ; 内部实现为空实现。
/** * 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() { }
这里对key和value为null的情况,做了些装饰
static final Object NULL_KEY = new Object(); static Object maskNull(Object key) { return (key == null ? NULL_KEY : key); } static Object unmaskNull(Object key) { return (key == NULL_KEY ? null : key); }
一些工具方法
// 在jdk1.4中,计算对象hash值,抽离出一个公用方法了,同时,计算hash值的方式也有改变,后面会专门分析这个
static int hash(Object x) { int h = x.hashCode(); h += ~(h << 9); h ^= (h >>> 14); h += (h << 4); h ^= (h >>> 10); return h; } // 比较两个对象的是否相同,也是一个抽离相同代码的过程 static boolean eq(Object x, Object y) { return x == y || x.equals(y); } // 通过hash值,计算对应的数组下标,也是抽离相同代码的过程。 // 之前构造中确定了length为 1 << 4 到 1 << 30 之间的数,则 h & (length -1),相当于截取h值的后多少位的值 // 如果length为 1 << n ,n为4到30,这相当于截取h的后n位的值 static int indexFor(int h, int length) { return h & (length-1); }
这里在分析下jdk1.4中的添改查
// 判断HashMap中value是否存在 public boolean containsValue(Object value) { // 当value为null,判断是否存在 // 这里把判断是否存在value=null抽出了一个公用方法;特地全文搜索了下,现在只有这个地方用到了,可能当时有其他考虑 if (value == null) return containsNullValue(); Entry tab[] = table; // 遍历数组 // 这种遍历方式,相比以前的”for (int i = tab.length ; i-- > 0 ;)“,更习惯些 for (int i = 0; i < tab.length ; i++) // 遍历链表 for (Entry e = tab[i] ; e != null ; e = e.next) if (value.equals(e.value)) return true; return false; } // 判断是否存在value=null的实体 private boolean containsNullValue() { Entry tab[] = table; for (int i = 0; i < tab.length ; i++) for (Entry e = tab[i] ; e != null ; e = e.next) if (e.value == null) return true; return false; } // 判断key是否存在 public boolean containsKey(Object key) { // 装饰key Object k = maskNull(key); // 计算key的hash值 int hash = hash(k); // 计算key对应的数组下标值,这里把hash值的计算和数组下标的计算分别单独出一个方法,看着很清晰 int i = indexFor(hash, table.length); Entry e = table[i]; // 循环遍历链表 while (e != null) { if (e.hash == hash && eq(k, e.key)) return true; e = e.next; } return false; } // 获得key对应的值 public Object get(Object key) { // 装饰key Object k = maskNull(key); // 计算key的hash值 int hash = hash(k); // 计算key对应的数组下标值 int i = indexFor(hash, table.length); Entry e = table[i]; // 循环遍历链表 while (true) { if (e == null) return e; if (e.hash == hash && eq(k, e.key)) return e.value; e = e.next; } } // 获得key对应的实体,包括key和value等信息,同get方法类似 Entry getEntry(Object key) { Object k = maskNull(key); int hash = hash(k); int i = indexFor(hash, table.length); Entry e = table[i]; while (e != null && !(e.hash == hash && eq(k, e.key))) e = e.next; return e; } // 存储key和value值 public Object put(Object key, Object value) { Object k = maskNull(key); int hash = hash(k); int i = indexFor(hash, table.length); // 这里把key做了装饰,就不需要像以前的版本一样,key分为null和不为null两种情况来处理 // key对应的value值存在时的添加替换处理 for (Entry e = table[i]; e != null; e = e.next) { if (e.hash == hash && eq(k, e.key)) { Object oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; // key对应的value值不存在时的添加处理 addEntry(hash, k, value, i); return null; } // 添加实体 void addEntry(int hash, Object key, Object value, int bucketIndex) { table[bucketIndex] = new Entry(hash, key, value, table[bucketIndex]); // 如果已经超过阀值,这扩展数组,这里是在原基础2倍的扩展 if (size++ >= threshold) resize(2 * table.length); } // 只是添加key和value,不考虑数组的扩展和老value的返回 private void putForCreate(Object key, Object value) { Object k = maskNull(key); int hash = hash(k); int i = indexFor(hash, table.length); for (Entry e = table[i]; e != null; e = e.next) { if (e.hash == hash && eq(k, e.key)) { e.value = value; return; } } createEntry(hash, k, value, i); } // 只是添加key和value,不考虑数组的扩展和老value的返回 void createEntry(int hash, Object key, Object value, int bucketIndex) { table[bucketIndex] = new Entry(hash, key, value, table[bucketIndex]); size++; } // 批量只是添加key和value,不考虑数组的扩展和老value的返回 // 这里的使用场景在构造传入一个map时 void putAllForCreate(Map m) { for (Iterator i = m.entrySet().iterator(); i.hasNext(); ) { Map.Entry e = (Map.Entry) i.next(); putForCreate(e.getKey(), e.getValue()); } } // 扩展数组 void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return; } Entry[] newTable = new Entry[newCapacity]; // 这里把老表数据迁移到新表,抽离到一个方法里了 transfer(newTable); // 在jdk1.4里,有个改动,老表到新表,迁移完了之后在赋给HashMap使用 // 解决了之前在迁移过程中有可能取不到数据的bug table = newTable; threshold = (int)(newCapacity * loadFactor); } // 数组从老表到新表 void transfer(Entry[] newTable) { Entry[] src = table; int newCapacity = newTable.length; // 遍历数组 for (int j = 0; j < src.length; j++) { Entry e = src[j]; // 遍历链表,这版本中,很多地方把for改用成了while(while 或者 do while) // 其实后面jdk用推崇 for(;;),这都是向着效率越来越高,代码越来越简洁上优化 if (e != null) { src[j] = null; do { Entry next = e.next; int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } while (e != null); } } }
idk中还有个小优化点,以前HashMap中获取不同类型的HashIterator,需要用户在构造中传入类型,现在只要调用不同的方法,他们分别做重载,挺有意思的,对开发者来说,更友好了,可以学习下
private class ValueIterator extends HashIterator { public Object next() { return nextEntry().value; } } private class KeyIterator extends HashIterator { public Object next() { return nextEntry().getKey(); } } private class EntryIterator extends HashIterator { public Object next() { return nextEntry(); } } // Subclass overrides these to alter behavior of views' iterator() method Iterator newKeyIterator() { return new KeyIterator(); } Iterator newValueIterator() { return new ValueIterator(); } Iterator newEntryIterator() { return new EntryIterator(); }
松下问童子,言师采药去。
只言此山中,云深不知处。