都保存映射关系数据,即键值对。
/**
* Hashtable bucket collision list entry
*/
private static class Entry implements Map.Entry {
final int hash;
final K key;
V value;
Entry next;
protected Entry(int hash, K key, V value, Entry 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) 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 implements Map.Entry {
final int hash;
final K key;
V value;
Node next;
Node(int hash, K key, V value, Node 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
接口
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[] tab; Node 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 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)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 entry = (Entry)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
*/
需要注意的是 这个类早在jdk1.0就已经存在,并且在最有一个很明显加粗的描述,
这类应过时/废置,新的接口实现类应该实现Map接口而不是应该使用Dictionary。
由此,hashTable也是属于过时了的类,在实际开发中尽量是不应该去使用的。