Java HashTable学习

注:这里使用java 1.6版本


Hashtable和HashMap很相似;最开始使用的是Hashtable,后来HashMap被设计出来替代它。目前在使用中建议使用HashMap,有同步需求时建议使用ConcurrentHashMap。目前已经不建议使用Hashtable了。


下面看看Hashtable的实现情况。注意到,在定义名称的时候,Hashtable的table是小写字母开头,而HashMap是更标准的驼峰定义。


1.    Hashtable继承Dictionary类,实现Map,Cloneable和Serializable接口。


2.    Hashtable的内部实现仍然是Entry数组,同样有count、threshold、loadFactor和modCount变量。


Hashtable的Entry和HashMap的Entry不同,这里说两点:
1)    Hashtable的Entry不接受null值,当value为null时,会抛出NPE异常。这也是Hashtable整体上不接受null的原因。不接受null值,是Hashtable和HashMap不同的地方之一

	public V setValue(V value) {
	    if (value == null)
		throw new NullPointerException();

	    V oldValue = this.value;
	    this.value = value;
	    return oldValue;
	}

 
2)    Hashtable的Entry有clone方法,会把next指向的所有Entry都进行一次clone。

	protected Object clone() {
	    return new Entry<K,V>(hash, key, value,
				  (next==null ? null : (Entry<K,V>) next.clone()));
	}

 
3.    Hashtable的初始化

 

Hashtable同样拥有4中初始化的方法:
1)    public Hashtable(int initialCapacity, float loadFactor)
2)    public Hashtable(int initialCapacity)
3)    public Hashtable()
4)    public Hashtable(Map<? extends K, ? extends V> t)

 

前三种初始化是简单的通过定义Hashtable的基本参数的方法进行初始化的;最后的一个初始化是通过现有的Map进行初始化。


默认情况下,Hashtable的loadFactor为0.75F,和HashMap一样;Hashtable的默认大小是11,而HashMap是16,这一点不同。


通过现有Map初始化一个Hashtable,流程上,先会初始化一个Hashtable,然后通过putAll方法,将Map中的内容写入Hashtable。如下。
 

    /**
     * Constructs a new hashtable with the same mappings as the given
     * Map.  The hashtable is created with an initial capacity sufficient to
     * hold the mappings in the given Map and a default load factor (0.75).
     *
     * @param t the map whose mappings are to be placed in this map.
     * @throws NullPointerException if the specified map is null.
     * @since   1.2
     */
    public Hashtable(Map<? extends K, ? extends V> t) {
	this(Math.max(2*t.size(), 11), 0.75f);
	putAll(t);
    }

 
将所有元素写入Hashtable的putAll方法,仅仅是一个简单的循环。这里用的是常见的EntrySet的方法。

    /**
     * Copies all of the mappings from the specified map to this hashtable.
     * These mappings will replace any mappings that this hashtable had for any
     * of the keys currently in the specified map.
     *
     * @param t mappings to be stored in this map
     * @throws NullPointerException if the specified map is null
     * @since 1.2
     */
    public synchronized void putAll(Map<? extends K, ? extends V> t) {
        for (Map.Entry<? extends K, ? extends V> e : t.entrySet())
            put(e.getKey(), e.getValue());
    }

 
如果深入去对比会发现,Hashtable的这种putAll的方法会触发Hashtable的结构变化;而HashMap的putAllForCreate并不会。当然,开始的时候定义的初始化数据的长度足够的话,不会马上触发到。但是为什么HashMap会分开两种方法进行,而Hashtable用一种方法来处理?
下面看看Hashtable的put方法。


4.    将元素写入Hashtable
实现过程如下所示。
 

    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;
    }

 
第一步,检查将写入的value非空;再次确认,Hashtable不允许出现null值;
第二步,查找对应的key,如果key存在,则将value写入,同时将旧值返回;如果key不存在,则转第三步;
第三部,此时需要进行hash结构的变化,所以先进行modCount++的操作。如果此时需要进行hash结构的变化(count>=threshold);否则新建一个Entry对象,将它插入key对应的链表的首部。完成后,count++,表示元素增加;同时返回null。


需要注意两点:a.如果put返回的结果是null,表示正常的加入成功;否则是value的值的替换。b.整个Hashtable在进行put操作的过程中是使用synchronize进行同步的,所以可能性能比较差。后续我们还会看到Hashtable的多个操作中都会有synchronize的同步。使用synchronize进行同步,保证了Hashtable是线程安全的,但是也降低了Hashtable的性能,这是Hashtable和HashMap重要的一个不同点。


5.    Rehash
当Hashtable或者HashMap的容量不足的时候,它们会通过rehash进行扩容。这是一种非常消耗性能的操作。下面看看Hashtable是怎么进行这个操作的。
 

    /**
     * Increases the capacity of and internally reorganizes this
     * hashtable, in order to accommodate and access its entries more
     * efficiently.  This method is called automatically when the
     * number of keys in the hashtable exceeds this hashtable's capacity
     * and load factor.
     */
    protected void rehash() {
	int oldCapacity = table.length;
	Entry[] oldMap = table;

	int newCapacity = oldCapacity * 2 + 1;
	Entry[] newMap = new Entry[newCapacity];

	modCount++;
	threshold = (int)(newCapacity * loadFactor);
	table = newMap;

	for (int i = oldCapacity ; i-- > 0 ;) {
	    for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
		Entry<K,V> e = old;
		old = old.next;

		int index = (e.hash & 0x7FFFFFFF) % newCapacity;
		e.next = newMap[index];
		newMap[index] = e;
	    }
	}
    }

 
每个rehash都会出发一次modCount的自增;


Hashtable的扩展是两倍的旧值再加一;而HashMap每次扩展都是旧值的两倍,保证了其大小始终是2的幂,可以尽量的提升性能。


另外,在将Map的元素写入Hashtable的时候,使用的还是头插法,即写入的新元素,每次都是写在链表的头部,这一点和HashMap是一致的。


6.    一些简单方法的实现
1)    size(),获取Hashtable的元素个数;实际是count的数量,synchronize同步。
2)    isEmpty(),判断Hashtable是否为空;检查count是否为0,synchronize同步。
3)    keys(),获取Hashtable的所有的key集合,返回结果是一个Enumeration,synchronize同步。
4)    elements(),获取Hashtable的所有value,也是Enumeration,synchronize同步。
5)    clear(),清空Hashtable的元素,和HashMap的实现一致,只是方法上使用了synchronize同步。


7.    Hashtable的Enumeration
不同于HashMap,Hashtable使用Enumeration进行内部的元素的迭代访问,而HashMap使用的是Iterator。
关于Enumeration更详细的可以看看Hashtable的实现代码;在遍历的时候,可以传入type进行区分,是需要下一个key,还是下一个value,亦或是下一个Entry。


8.    判断元素是否存在
Contains方法:

    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;
    }

 
简单的遍历实现,没有特别新奇的地方;另外,还是不接受null值,其实这里可以直接返回false,而不是抛出异常的。这里的方法同样是synchronize同步的。


containsValue方法:
对于contains方法的再次调用。


containsKey方法:
也是遍历实现。


在HashMap中,没有contains方法,这样确认看起来清晰一点。


9.    Get方法
实现如下:
 

    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;
    }

 
除了hash方法不同,并且使用synchronize同步之外,就是简单的遍历。
单独拿出来说仅仅是因为该方法对于map来说比较重要。


10.    Remove方法
实现如下:

    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;
    }

 
11.    Clone方法
实现如下。可以看到,元素的clone还是通过Entry的clone方法实现的。Entry的clone具有传递性,它会将链表指针指向的Entry都进行clone。

    /**
     * Creates a shallow copy of this hashtable. All the structure of the
     * hashtable itself is copied, but the keys and values are not cloned.
     * This is a relatively expensive operation.
     *
     * @return  a clone of the hashtable
     */
    public synchronized Object clone() {
	try {
	    Hashtable<K,V> t = (Hashtable<K,V>) super.clone();
	    t.table = new Entry[table.length];
	    for (int i = table.length ; i-- > 0 ; ) {
		t.table[i] = (table[i] != null)
		    ? (Entry<K,V>) table[i].clone() : null;
	    }
	    t.keySet = null;
	    t.entrySet = null;
            t.values = null;
	    t.modCount = 0;
	    return t;
	} catch (CloneNotSupportedException e) {
	    // this shouldn't happen, since we are Cloneable
	    throw new InternalError();
	}
    }

 
12.    其他的一些内部的类,平时很少使用到,这里不再详细说明了。


13.    为了支持序列化和反序列化,Hashtable最后实现了readObject和writeObject。


Hashtable中几乎所有的操作都需要进行同步,保证了一致性的同时,损失了很多性能。估计这也是它被HashMap替代的主要原因。

 

Hashtable 和 HashMap 还有一点很重要的不同,即是hash方法的不同。有兴趣可以深入了解。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值