相同:
都保存映射关系数据,即键值对。
- HashTable 键值对结构:
/**
* Hashtable bucket collision list entry
*/
private static class Entry<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Entry<K,V> next;
protected Entry(int hash, K key, V value, Entry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
@SuppressWarnings("unchecked")
protected Object clone() {
return new Entry<>(hash, key, value,
(next==null ? null : (Entry<K,V>) next.clone()));
}
// Map.Entry Ops
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public V setValue(V value) {
if (value == null)
throw new NullPointerException();
V oldValue = this.value;
this.value = value;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&
(value==null ? e.getValue()==null : value.equals(e.getValue()));
}
public int hashCode() {
return hash ^ Objects.hashCode(value);
}
public String toString() {
return key.toString()+"="+value.toString();
}
}
HashMap 键值对结构:
/**
* Basic hash bin node, used for most entries. (See below for
* TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
*/
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
保存键值对的数据都继承于 Map.Entry<K,V>
接口
- 都使用拉链法扩展
不同:
- HashMap 中 null 可以作为 key,并且会计算散列码,HashTable 中 null 不能作为 key
HashMap中(1.8之后)桶中元素达到阀值会转为树来存放。
HashMap put方法:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
HashTable put方法:
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
区别其实挺明显的,HashMap的话会对key为null的元素依旧会计算散列码,虽然最终计算结果都是0,并且是key和value均可为空。相比之下,HashTable在添加key为null的元素的时候,直接会抛出异常。源码中能看出,HashTable在当调用put当法value为空的时候,会显式地调用抛出异常的逻辑throw new NullPointerException()
,但是当key为空的时候择直接会是后续的key.hashCode()
抛出异常。
创建时,默认参数不同。
比较两者相同作用的参数:
HashMap://摘出来的阀值计算,超过后rehash /** * Returns a power of two size for the given target capacity. */ 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; } /** * 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; //扩容核心代码 int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { //假如大于默认最大值,择使用Int的最大值 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 //初始为0的时候 newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { float ft = (float)newCap * loadFactor; 取积 newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); //最大的整数 } threshold = newThr;
HashTable:
//从构造函数中找出来的rehash阀值计算方式 threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); /** * The maximum size of array to allocate. * Some VMs reserve some header words in an array. * Attempts to allocate larger arrays may result in * OutOfMemoryError: Requested array size exceeds VM limit */ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; /** * Constructs a new, empty hashtable with a default initial capacity (11) * and load factor (0.75). */ public Hashtable() { this(11, 0.75f); } //扩容核心代码 int oldCapacity = table.length; Entry<?,?>[] oldMap = table; // overflow-conscious code int newCapacity = (oldCapacity << 1) + 1; if (newCapacity - MAX_ARRAY_SIZE > 0) { if (oldCapacity == MAX_ARRAY_SIZE) // Keep running with MAX_ARRAY_SIZE buckets return; newCapacity = MAX_ARRAY_SIZE; } Entry<?,?>[] newMap = new Entry<?,?>[newCapacity]; modCount++; threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
可以看到HashMap的默认大小是16,默认的加载因子0.75f,rehash阀值是通过一个特别的位运算计算的,最后返回的是传入参数的下一个2的幂。扩容,对于HashMap,扩容的容量是有一个方法计算,详细见注释。
HashTable中 默认值是11 ,默人加载因子是0.75f,其阀值是 两者的积 与 最大容量+1 中较小的一个。对于HashMap,扩容后最终的容量可以看作是(当前容量*2+1)的结果。HashMap非线程安全,hashTable线程安全。
从上面put的源码大概能看出来 HashTable是在每一个操作table
的方法前面都会有synchronized,以保证每一个对table
修改的操作都是加了锁的。而这样加锁的处理使得HashTable在正常的操作时是要比HashMap慢的,所以当不存在多线程共同操作一个映射集合的时候应该优先使用HashMap。HashMap继承自抽象类AbstractMap,而HashTable继承自抽象类Dictionary
Dictionary 类是一个很久之前 在源码源码中有如下描述:/**
- The
Dictionary
class is the abstract parent of any - class, such as
Hashtable
, which maps keys to values. - Every key and every value is an object. In any one Dictionary
- object, every key is associated with at most one value. Given a
- Dictionary and a key, the associated element can be looked up.
- Any non-
null
object can be used as a key and as a value. - As a rule, the
equals
method should be used by - implementations of this class to decide if two keys are the same.
- NOTE: This class is obsolete. New implementations should
- implement the Map interface, rather than extending this class.
* - @author unascribed
- @see java.util.Map
- @see java.lang.Object#equals(java.lang.Object)
- @see java.lang.Object#hashCode()
- @see java.util.Hashtable
- @since JDK1.0
*/
- The
需要注意的是 这个类早在jdk1.0就已经存在,并且在最有一个很明显加粗的描述,
这类应过时/废置,新的接口实现类应该实现Map接口而不是应该使用Dictionary。
由此,hashTable也是属于过时了的类,在实际开发中尽量是不应该去使用的。