HashMap源码分析三
HashMap的源码,在jdk1.5中相比jdk1.4,改动不大,有几个方面
1 jdk1.5中引入了范型,在HashMap中也有体现
2 引入了另一个hash值的计算方式,不过默认是关闭状态,可以通过设置jvm的参数开启
private static int oldHash(int h) { h += ~(h << 9); h ^= (h >>> 14); h += (h << 4); h ^= (h >>> 10); return h; } private static int newHash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); } static int hash(int h) { return useNewHash ? newHash(h) : oldHash(h); } // -XX:+UseNewHashFunction or -XX:+AggressiveOpts 启动新的hash计算 private static final boolean useNewHash; static { useNewHash = false; }
3 代码的小优化
public V get(Object key) { // key为null时,取value的过程抽离到一个方法里 if (key == null) return getForNullKey(); int hash = hash(key.hashCode()); for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; } return null; }
HashMap的源码,在jdk1.6中相比于jdk1.5也没什么改动,在这个版本开始,开始启用新的hash计算方式,把以前的废弃了
static int hash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
在jdk1.7中,相比与jdk1.6,变动也不是很大。
在jdk1.7中,HashMap的初始数组长度,没有设置了。可能出于内存有效使用的考虑。
// 构造方法里并没有初始化数组 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); this.loadFactor = loadFactor; // 数组增长阀值,先设置成数组的初始长度 threshold = initialCapacity; init(); } public V put(K key, V value) { // 如果数组没有初始化,开始初始化 if (table == EMPTY_TABLE) { inflateTable(threshold); } .... }
这里引入了一个新方法inflateTable,初始化数组
// 初始化数组 private void inflateTable(int toSize) { // 计算确认初始化数组的长度 int capacity = roundUpToPowerOf2(toSize); // 重新给数组长度的阀值赋值 threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); table = new Entry[capacity]; // 这里是判断是否需要给hash值生成添加生成因子,减少hash碰撞的概率 initHashSeedAsNeeded(capacity); } // 获得初始化数组的长度 private static int roundUpToPowerOf2(int number) { // 分几种情况 // 1 number超过数组长度限制最大值,则返回数组长度的最大值 // 2 number小于或者等于1, 则返回1 // 3 否则返回Integer.highestOneBit((number - 1) << 1),这个表示number二进制表示时,最左边位数的值为1,其他位为0时的值 // 比如 0000 0000 0000 0000 1011 0011 0000 0000 计算后值为 0000 0000 0000 0000 1000 0000 0000 0000 return number >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1; } // 初始化是否使用hash值生成的生成因子 final boolean initHashSeedAsNeeded(int capacity) { // 判断是否已经使用了生成因子,hashSeed=0表示未使用 boolean currentAltHashing = hashSeed != 0; // 判断是否使用生成因子 boolean useAltHashing = sun.misc.VM.isBooted() && (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD); boolean switching = currentAltHashing ^ useAltHashing; if (switching) { hashSeed = useAltHashing ? sun.misc.Hashing.randomHashSeed(this) : 0; } return switching; }
这里在看下hash值的生成
final int hash(Object k) { int h = hashSeed; // 如果key对象是字符串,则换个方式生成hash值 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); }
上面提到一个方法Integer.highestOneBit,可能看着挺困惑的,这个是Integer里的一个方法。
// 或者i最高位为1,其他位为0时的值 // 如:0000 0000 0000 0000 1011 0011 0000 0000 计算后值为 0000 0000 0000 0000 1000 0000 0000 0000 public static int highestOneBit(int i) { // 开始位运算 i |= (i >> 1);// i左移1位,在和i值或运算,得到值的最高位和第2位都为1 (如果最高为到最右端不到2位,则最高位到最右端都为1) i |= (i >> 2);// 新的i左移2位,在和i值或运算,得到值的最高位到第4位都为1 (如果最高为到最右端不到4位,则最高位到最右端都为1) i |= (i >> 4);// 新的i左移4位,在和i值或运算,得到值的最高位到第8位都为1 ( 如果最高为到最右端不到8位,则最高位到最右端都为1) i |= (i >> 8);// 新的i左移8位,在和i值或运算,得到值的最高位到第16位都为1 (如果最高为到最右端不到16位,则最高位到最右端都为1) i |= (i >> 16);// 新的i左移16位,在和i值或运算,得到值的最高位到第32位都为1 (如果最高为到最右端不到32位,则最高位到最右端都为1) // 非最高位设置为0 return i - (i >>> 1); }
松下问童子,言师采药去。
只言此山中,云深不知处。