首先,先上Hashtable.class中的代码,所有的Java实现方法都在这个文件中了。
当然,对于这个文件的内容,你现在无需看懂它,如果你看懂了,我们下面也就没有意义讲解了。
现在,来让我们回顾下,在Java中使用Hashtable中的方法:
接下来,让我们从上面的使用方法说起。
对于,构造Hashtable的那一行,我们看看对应到Hashtable.class文件中的调用,如下:
/** * Constructs a new, empty hashtable with a default initial capacity (11) * and load factor (0.75). */ public Hashtable() { this(11, 0.75f); } /** * Constructs a new, empty hashtable with the specified initial * capacity and the specified load factor. * * @param initialCapacity the initial capacity of the hashtable. * @param loadFactor the load factor of the hashtable. * @exception IllegalArgumentException if the initial capacity is less * than zero, or if the load factor is nonpositive. */ 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) initialCapacity = 1; this.loadFactor = loadFactor; table = new Entry[initialCapacity]; threshold = (int)(initialCapacity * loadFactor); }
这里注意,当你使用无参的构造函数时,默认传入的initialCapacity=11,loadFactor=0.75.
Ok,你一定想问initialCapacity和loadFactor都是什么?我们看到构造函数中有这么两句语句:
这里的table变量时一个Entry数组,Entry你可以认为是一条记录,所以从第一句,我们就可以知道Hashtable就是一个Entry的数组。那么既然是数组,为什么还要提出Hashtable的概念?这个问题先放放,我们先来看loadFactor,看threshold的那句,你就能明白,其实loadFactor是用来判断是否要扩建的。所以,我们可以认为这里,当Hashtable的75%被使用时,就扩建。
现在让我们来解答,这个Entry数组为什么是Hashtable,而不仅仅是数组。让我们来看看Entry的定义:
/** * Hashtable collision list. */ private static class Entry<K,V> implements Map.Entry<K,V> { int hash; 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; } protected Object clone() { return new Entry<K,V>(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 ^ (value==null ? 0 : value.hashCode()); } public String toString() { return key.toString()+"="+value.toString(); } }
这里,我们关注它的四个字段,hash、key、value、next。这里的hash也就是之所以叫Hashtable的原因之一。这个hash数值是根据特定的hash算法得出的,详细内容可以找相应的hash算法资料,这里就不介绍了。接下来说,next字段,next字段是当hash产生重复的时候,以链表保存新传入的hash值对应的key和value。看图说话,就应该很明白了。
上面的图应该就已经很清楚了。列表和数组共存的方法。
这里有个题外话,就是前段时间,有个关于post大量相同hash的数据,使得hashtable大量退化成链表。导致查询速度奇慢的问题。也就是因为这里的这个机制。具体,怎么会产生hash碰撞,这和语言本身采用的hash算法有关。
看完了初始化,我们接下来看下Hashtable的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<K,V>(hash, key, value, e); count++; return null; }
其他地方都没有什么好说的,关键看for语句这里,这里就是查询到对应的Entry后,再将对应的value赋给链表中正确的对应项。
紧接着的下面的if语句,就是扩建机制,也没有什么可以多说的。最后一段内容,就是如果原有的Hashtable中没有对应项,就等于新增。
有了put的基础,我们再来看看remove的情况,你应该很容易就能看懂。
/** * 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; }
同样,这个也没有可以多说的地方,仍然是for语句。逻辑和put的差不多。就是找到对应的项,然后做操作。
基本上整个Hashtable的实现也就如此了。