Java集合之Hashtable的实现原理
Hashtable是一个废弃的类,虽然基本上已经被弃用了,但是也有必要了解它的内部实现原理,尤其是跟HashMap对比的时候。和HashMap一样,Hashtable也是一个散列表,它存储的内容是键值对(key-value)映射。
Hashtable的实现原理跟HashMap的实现原理(Java 8之前)是一样的,里面的数据结构同样是一个数组+链表的结构。首先来看一下put的源码
/**
* Maps the specified <code>key</code> to the specified
* <code>value</code> in this hashtable. Neither the key nor the
* value can be <code>null</code>. <p>
*
* The value can be retrieved by calling the <code>get</code> method
* with a key that is equal to the original key.
*
* @param key the hashtable key
* @param value the value
* @return the previous value of the specified key in this hashtable,
* or <code>null</code> if it did not have one
* @exception NullPointerException if the key or value is
* <code>null</code>
* @see Object#equals(Object)
* @see #get(Object)
*/
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;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
V old = e.value;
e.value = value;
return old;
}
}
modCount++;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
Entry<K,V> e = tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
return null;
}
首先判断key是否为空,为空,则抛出空指针异常(这里是跟HashMap不同的地方);不为空,则把key的hashCode值拿出来,跟数组的长度进行一个hash运算,得到了这个key在数组中的下标index。得到下标后就可以取出对应数组位置的链表,然后就遍历这个链表。遍历的时候比较一下链表的hash值是否等于刚才传进来key的hash值,如果相等,则比较链表中字段key是否等于key(equals比较,hashCode和值要相等),如果还是相等的话,那么就说明包含了这个值,然后把这个链表的value值替换掉,返回之前的value。如果遍历完链表都没找到对应的链表,则表明没有这个值,需要添加到数组或者链表中。首先把已保存的个数加1,并且判断是否大于扩容的阈值条件,如果大于,则进行扩容(2n+1的方式扩容)。扩容之后,把index位置的链表(基本上为空)赋值到一个新的链表中,然后重新创建一个链表,并且把刚才那个空的链表赋值到该链表的next字段中(也是链表)。并且把该链表赋值到数组的index位置,然后返回null。
再来看看如何取数据,看源码
/**
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
*
* <p>More formally, if this map contains a mapping from a key
* {@code k} to a value {@code v} such that {@code (key.equals(k))},
* then this method returns {@code v}; otherwise it returns
* {@code null}. (There can be at most one such mapping.)
*
* @param key the key whose associated value is to be returned
* @return the value to which the specified key is mapped, or
* {@code null} if this map contains no mapping for the key
* @throws NullPointerException if the specified key is null
* @see #put(Object, Object)
*/
public synchronized V get(Object key) {
Entry tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return e.value;
}
}
return null;
}
其实跟put方法是差不多的,首先计算在数组中的位置index,然后取出链表,比较链表的hash值以及key的hash值以及值,如果都相等则返回该链表的value,如果遍历完了都没找到,那么则返回空。取数据还是比较简单的。
再来看看如何移除数据,同样,看源码
/**
* Removes the key (and its corresponding value) from this
* hashtable. This method does nothing if the key is not in the hashtable.
*
* @param key the key that needs to be removed
* @return the value to which the key had been mapped in this hashtable,
* or <code>null</code> if the key did not have a mapping
* @throws NullPointerException if the key is <code>null</code>
*/
public synchronized V remove(Object key) {
Entry tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
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;
}
移除的源码也简单,前面都是一样的,先找到那个链表,然后判断是否符合key的要求,如果符合,则判断它是否有前一个链表,如果有,则把它的下一个链表即next赋值给前一个链表的next,用来代替自己,然后返回当前链表的value;如果没有,则代表它是第一个链表,则把它下一个的链表放到数组下标为index的位置上,同时返回它的value。如果没有找到对应的key,则返回null。
以下是是否包含某个键值对的代码
/**
* Tests if some key maps into the specified value in this hashtable.
* This operation is more expensive than the {@link #containsKey
* containsKey} method.
*
* <p>Note that this method is identical in functionality to
* {@link #containsValue containsValue}, (which is part of the
* {@link Map} interface in the collections framework).
*
* @param value a value to search for
* @return <code>true</code> if and only if some key maps to the
* <code>value</code> argument in this hashtable as
* determined by the <tt>equals</tt> method;
* <code>false</code> otherwise.
* @exception NullPointerException if the value is <code>null</code>
*/
public synchronized boolean contains(Object value) {
if (value == null) {
throw new NullPointerException();
}
Entry tab[] = table;
for (int i = tab.length ; i-- > 0 ;) {
for (Entry<K,V> e = tab[i] ; e != null ; e = e.next) {
if (e.value.equals(value)) {
return true;
}
}
}
return false;
}
/**
* Tests if the specified object is a key in this hashtable.
*
* @param key possible key
* @return <code>true</code> if and only if the specified object
* is a key in this hashtable, as determined by the
* <tt>equals</tt> method; <code>false</code> otherwise.
* @throws NullPointerException if the key is <code>null</code>
* @see #contains(Object)
*/
public synchronized boolean containsKey(Object key) {
Entry tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return true;
}
}
return false;
}
从以上代码可以看出,不管是包含键还是值,最后都是要遍历链表,查找到是否有相等的值,如果有,则返回true,没有就返回false。
以上就是Hashtable几个重要方法的实现原理,有哪里不对的欢迎指正
从以上代码可以看到都有一个相同点,那就是每个方法都是synchronized,所以Hashtable是一个线程安全的类,但是这也导致了它的一个性能问题(可能就是因为这个原因导致弃用的),所以性能比HashMap慢很多。