-
使用算法:
-
计算大于等于某个整数的第一个为 2^n 的数:
static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }-
分析:
一个数为 2^n, 则该数的二进制表示形式某一位为 1, 其后面的位全部为 0. 若想找到大于等于某个整数的第一个为 2^n 的数, 若该数恰好是 2^n, 则所求的数即该数; 若该数不是 2^n, 则所求的数为该数的二进制形式所占位数加 1 所能表示的最小整数. 例如 30 的二进制表示为
11110, 占用 5 位, 则所求数为100000. 求解这个数的过程为将该数的二进制表示形式的每一位都变成 1, 然后再加上 1. 上面的代码就是实现了该算法, 只不过上面的算法先减 1, 再加 1, 是为了包括该数恰好是 2^n 的情况.
-
-
快速求余数:
if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; // 这里通过 (n - 1) & hash 计算 hash % n 的余数 if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null);-
分析:
该算法只有除数为 2^n 才成立. 若除数为 2^n, 则被除数的最后 n 位就是最终的余数, 所以可以采用以上算法.
-
-
-
区别:
-
键和值:
HashMap的键可以为null, 并且只能有一个键为null, 因为HashMap的把键为null的hashCode处理为 0,HashMap可以有多个键的值为null;Hashtable的键不能为null, 虽然内部没有进行判断,Hashtable的值也不能为null, 这个是内部进行了判断的.// HashMap 内部的 hash 函数 static final int hash(Object key) { int h; // 若 key 为 null 则 hashCode 值为 0 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } // Hashtable 内部 // 这里判断了 value 不能为空 if (value == null) { throw new NullPointerException(); } Entry<?,?> tab[] = table; // 这里直接调用了 key 的 hashCode() 方法, 因此 key 不能为空 int hash = key.hashCode(); // hash 算法 int index = (hash & 0x7FFFFFFF) % tab.length; -
线程安全:
HashMap是线程不安全的, 需要使用Collections.synchronizedMap()包装成线程安全的;Hashtable是线程安全的, 它的每个方法都是使用synchronized关键字修饰的. -
扩容机制:
HashMap 在第一次保证初始化的容量为 2^n, 默认的容量为 16, 默认的负载因子为 0.75. 然后在扩容的时候将容量扩容为原来的两倍, 所以容量也总是保持为 2^n; Hashtable 默认容量为 11, 默认的负载因子为 0.75. 然后在扩容的时候将容量扩容为原来的两倍加 1.
// HashMap 的 resize() 方法 final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } // 这里扩容为原来的两倍 else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } // 省略其它代码 } // Hashtable 的 rehash 方法 protected void rehash() { int oldCapacity = table.length; Entry<?,?>[] oldMap = table; // 扩容为原来的两倍加 1 int newCapacity = (oldCapacity << 1) + 1; if (newCapacity - MAX_ARRAY_SIZE > 0) { if (oldCapacity == MAX_ARRAY_SIZE) return; newCapacity = MAX_ARRAY_SIZE; } } -
底层实现:
两者都是通过***除留余数法***来进行 hash 值的散列, 并通过***链地址法***解决冲突. 当
HashMap的链表长度大于默认的阈值 8 时, 会将链表转换成红黑树, 以提高查找效率.final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { // 省略其它代码 for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); // 链表长度大于默认的阈值, 则构建树形结构 if (binCount >= TREEIFY_THRESHOLD - 1) treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } // 省略其它代码 }
-
-
特点:
-
判断对象相等的标准:
两者都是当且仅当
hashCode()方法返回值和equals()方法返回值都相等时才会认为两个对象是同一个对象. -
Set 视图:
两者都可以通过
entrySet()方法返回一个内部数据的Set, 这和Arrays#asList()方法返回一个数组的List视图类似.
-