相比于hashMap,HashTable有以下特点:
1、和HashMap一样,Hashtable 也是一个散列表,它存储的内容是键值对(key-value)映射。Hashtable 的实例有同样有两个参数影响其性能:初始容量和加载因子。容量是哈希表中桶的数量,初始容量就是哈希表创建时的容量。在发生“哈希冲突”的情况下,单个桶会存储多个条目,这些条目必须按顺序搜索;加载因子是对哈希表在其容量自动增加之前可以达到多满的一个尺度。初始容量和加载因子这两个参数只是对该实现的提示,通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折中。
2、Hashtable 继承于Dictionary,实现了Map、Cloneable、java.io.Serializable接口
public class Hashtable<K,V>extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable {
3、Hashtable 的函数都是同步的(用synchronized修饰),这意味着它是线程安全的。但是它的key、value都不可以为null。
Hashtable中的映射不是有序的。
构造方法
hashtable包括几个重要的成员变量:table, count, threshold, loadFactor, modCount(解释略,与hashmap类似)
public Hashtable(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load: "+loadFactor);
if (initialCapacity==0) //若初始容量为零,则会将初始变量赋值为1。不再为2的幂
initialCapacity = 1;
this.loadFactor = loadFactor;
//在构造函数中实例化hashtable,(hashmap在put方法中实例化)
table = new Entry<?,?>[initialCapacity];
//阈值则会相应的取值(初始的阈值不在是2的幂)
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
public Hashtable() { // 初始容量默认为11,加载因子默认为0.75
this(11, 0.75f);
}
public Hashtable(Map<? extends K, ? extends V> t) {
this(Math.max(2*t.size(), 11), 0.75f);//将初始容量与参数长度的3倍进行比较
putAll(t);
}
值得注意的:
- Hashtable的默认容量为11,默认负载因子为0.75.(HashMap默认容量为16,默认负载因子也是0.75)
- Hashtable的容量可以为任意整数,最小值为1,而HashMap的容量始终为2的n次方。
- 为避免扩容带来的性能问题,建议指定合理容量。
- 跟HashMap一样,Hashtable内部也有一个静态类叫Entry,其实是个键值对对象,保存了键和值的引用。
一般方法
1、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;
//ox7FFFFFFF为01111111111111111111111111111111(2147483647)。通过键的hashcode取得相应的索引
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) {
//必须得重写hashcode,equals方法.如果键已经存在,则会直接替换旧值,并返回
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
// 添加新值
addEntry(hash, key, value, index);
return null;
}
private void addEntry(int hash, K key, V value, int index) {
modCount++; //快速失败机制,标记修改
Entry<?,?> tab[] = table;
if (count >= threshold) { //若超出阈值,则扩容
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = key.hashCode();
index = (hash & 0x7FFFFFFF) % tab.length; //重新得到对应的索引值
}
// 创造新entry,其实用头部插入法(O(1))!!!(hashmap1.8使用的是尾部插入法(O(N))) @SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = new Entry<>(hash, key, value, e);//将新建的entry插到首部
count++;
}
值得注意的:
- Hasbtable并不允许值和键为空(null),若为空,会抛空指针。
- HashMap计算索引的方式是h&(length-1),而Hashtable用的是模运算,效率上是低于HashMap的。
- 另外Hashtable计算索引时将hash值先与上0x7FFFFFFF,这是为了保证hash值始终为正数。
- 特别需要注意的是这个方法包括下面要讲的若干方法都加了synchronized关键字,也就意味着这个Hashtable是个线程安全的类,这也是它和HashMap最大的不同点.
2、rehash方法:
protected void rehash() {
int oldCapacity = table.length;
Entry<?,?>[] oldMap = table;
// overflow-conscious code
int newCapacity = (oldCapacity << 1) + 1; //新容量变为2倍+1(hashmap为2倍)
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);
table = newMap;
for (int i = oldCapacity ; i-- > 0 ;) { //遍历表,重新赋值
for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
//重新添加的时候,由于采用的是首部插入法,所以链表的数据顺序相当于原数组树倒序的
Entry<K,V> e = old;
old = old.next;
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = (Entry<K,V>)newMap[index]; // 挂载尾部节点
newMap[index] = e; //首部插入
}
}
}
值得注意的:
- Hashtable每次扩容,容量都为原来的2倍加1,而HashMap为原来的2倍。
- hashtable采用的是遍历+尾部添加的方法,所以得到的链表元素是倒序的。每一个链表元素都需要重新计算index,
3、get方法
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}
如果你传的参数为null,是会抛空指针的。
4、remove方法
public synchronized V remove(Object key) {
Entry tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
// 找到“key对应的Entry(链表)”
// 然后在链表中找出要删除的节点,并删除该节点。
for (Entry<K,V> e = tab[index], prev = null ; e != null ; prev = e, e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
modCount++;
if (prev != null) {
prev.next = e.next;
} else {
tab[index] = e.next;
}
count--;
V oldValue = e.value;
e.value = null;
return oldValue;
}
}
return null;
}
使用了pre节点来保存待删除节点的前驱节点。
其余方法:
synchronized void clear()
synchronized Object clone()
boolean contains(Object value)
synchronized boolean containsKey(Object key)
synchronized boolean containsValue(Object value)
synchronized Enumeration<V> elements()
synchronized Set<Entry<K, V>> entrySet()
synchronized boolean equals(Object object)
synchronized V get(Object key)
synchronized int hashCode()
synchronized boolean isEmpty()
synchronized Set<K> keySet()
synchronized Enumeration<K> keys()
synchronized V put(K key, V value)
synchronized void putAll(Map<? extends K, ? extends V> map)
synchronized V remove(Object key)
synchronized int size()
synchronized String toString()
synchronized Collection<V> values()
hashTable迭代器:
首先看一下Enumeration的数据结构:
private class Enumerator<T> implements Enumeration<T>, Iterator<T> {
Entry<?,?>[] table = Hashtable.this.table; //使用数组的结构形式
int index = table.length;
Entry<?,?> entry;
Entry<?,?> lastReturned;
int type;
/**
* Indicates whether this Enumerator is serving as an Iterator
* or an Enumeration. (true -> Iterator).
*/
boolean iterator;
/**
* The modCount value that the iterator believes that the backing
* Hashtable should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
protected int expectedModCount = modCount;
Enumerator(int type, boolean iterator) {
this.type = type;
this.iterator = iterator;
}
public boolean hasMoreElements() {
Entry<?,?> e = entry;
int i = index;
Entry<?,?>[] t = table;
/* Use locals for faster loop iteration */
while (e == null && i > 0) {
e = t[--i];
}
entry = e;
index = i;
return e != null; //判断时候还有元素
}
@SuppressWarnings("unchecked")
public T nextElement() {
Entry<?,?> et = entry;
int i = index;
Entry<?,?>[] t = table;
/* Use locals for faster loop iteration */
while (et == null && i > 0) {
//从后向前遍历,若原数据的后继节点为空,则索引index前移
et = t[--i];
}
entry = et;
index = i;
if (et != null) { //链表是从前向后遍历
Entry<?,?> e = lastReturned = entry;
entry = e.next; //待返回节点的后继节点
return type == KEYS ? (T)e.key : (type == VALUES ? (T)e.value : (T)e);
}
throw new NoSuchElementException("Hashtable Enumerator");
}
// Iterator methods
public boolean hasNext() {
return hasMoreElements();
}
public T next() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
return nextElement();
}
public void remove() {
if (!iterator) //如果为false,则不允许删除操作
throw new UnsupportedOperationException();
if (lastReturned == null)
throw new IllegalStateException("Hashtable Enumerator");
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
synchronized(Hashtable.this) {
Entry<?,?>[] tab = Hashtable.this.table;
int index = (lastReturned.hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>)tab[index];
for(Entry<K,V> prev = null; e != null; prev = e, e = e.next) {
if (e == lastReturned) {
modCount++; //可以在遍历的时候,删除数据库
expectedModCount++; //同时增加元素
//删除链表对应的节点
if (prev == null)
tab[index] = e.next;
else
prev.next = e.next;
count--;
lastReturned = null;
return;
}
}
throw new ConcurrentModificationException();
}
}
}
遍历方式:
1、通过collections来进行迭代(collections.iterator)(可以remove操作)
2、通过Enumeration遍历Hashtable的值或者键(不可remove操作)
//在职进行建或者值遍历的时候,是不能进行删除操作,只能遍历
public synchronized Enumeration<K> keys() { //获取键对象
return this.<K>getEnumeration(KEYS);
}
public synchronized Enumeration<V> elements() { //获取值对象
return this.<V>getEnumeration(VALUES);
}
private <T> Enumeration<T> getEnumeration(int type) {
if (count == 0) {
return Collections.emptyEnumeration();
} else {
return new Enumerator<>(type, false); //若为false,则在迭代的时候,不能删除对象
}
}
3、根据entrySet()来获取Hashtable的“键值对”的Set集合(可以进行remove操作,本质上也是用枚举实现的)
public Set<Map.Entry<K,V>> entrySet() {
if (entrySet==null)
entrySet = Collections.synchronizedSet(new EntrySet(), this);
return entrySet;
}
private class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public Iterator<Map.Entry<K,V>> iterator() {
return getIterator(ENTRIES); //获得迭代器对象
}
....
}
private <T> Iterator<T> getIterator(int type) {
if (count == 0) {
return Collections.emptyIterator();
} else { //获取枚举迭代器
return new Enumerator<>(type, true);
}
}
4、使用keySet()来迭代(可以删除操作)
public Set<K> keySet() {
if (keySet == null)
keySet = Collections.synchronizedSet(new KeySet(), this);
return keySet;
}
private class KeySet extends AbstractSet<K> {
public Iterator<K> iterator() {
return getIterator(KEYS);
}
public int size() {
return count;
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
return Hashtable.this.remove(o) != null;
}
public void clear() {
Hashtable.this.clear();
}
}
值得注意的这种方法在获取值的时候,会遍历table,效率较低。
总结:
- Hashtable是个线程安全(synchronized)的类(HashMap线程不安全);
- Hasbtable并不允许值和键为空(null),若为空,会抛空指针(HashMap可以);
- Hashtable不允许键重复,若键重复,则新插入的值会覆盖旧值(同HashMap);
- Hashtable同样是通过链表法解决冲突;
- Hashtable根据hashcode计算索引时将hashcode值先与上0x7FFFFFFF,这是为了保证hash值始终为正数;
- Hashtable的容量为任意正数(最小为1),而HashMap的容量始终为2的n次方。Hashtable默认容量为11,HashMap默认容量为16;
- Hashtable每次扩容,新容量为旧容量的2倍加1,而HashMap为旧容量的2倍;
- Hashtable和HashMap默认负载因子都为0.75;
参考连接: