[JDK1.6] JAVA集合 Hashtable 源码浅析

版权声明:转载请注明出处 https://blog.csdn.net/weixin_39554102/article/details/85805699

源码来自 jdk1.6

(一) 简介:

此类实现一个哈希表,该哈希表将键映射到相应的值。任何非 null 对象都可以用作键或值。

为了成功地在哈希表中存储和获取对象,用作键的对象必须实现 hashCode 方法和 equals 方法。

Hashtable 的实例有两个参数影响其性能:初始容量 和加载因子。容量 是哈希表中桶 的数量,初始容量 就是哈希表创建时的容量。注意,哈希表的状态为 open:在发生“哈希冲突”的情况下,单个桶会存储多个条目,这些条目必须按顺序搜索。加载因子 是对哈希表在其容量自动增加之前可以达到多满的一个尺度。初始容量和加载因子这两个参数只是对该实现的提示。关于何时以及是否调用 rehash 方法的具体细节则依赖于该实现。

通常,默认加载因子(.75)在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查找某个条目的时间(在大多数 Hashtable 操作中,包括 getput 操作,都反映了这一点)。

初始容量主要控制空间消耗与执行 rehash 操作所需要的时间损耗之间的平衡。如果初始容量大于 Hashtable 所包含的最大条目数除以加载因子,则永远 不会发生 rehash 操作。但是,将初始容量设置太高可能会浪费空间。

如果很多条目要存储在一个 Hashtable 中,那么与根据需要执行自动 rehashing 操作来增大表的容量的做法相比,使用足够大的初始容量创建哈希表或许可以更有效地插入条目。

下面这个示例创建了一个数字的哈希表。它将数字的名称用作键:

   Hashtable<String, Integer> numbers = new Hashtable<String, Integer>();
   numbers.put("one", 1);
   numbers.put("two", 2);
   numbers.put("three", 3);

要获取一个数字,可以使用以下代码:

   Integer n = numbers.get("two");
     if (n != null) {
         System.out.println("two = " + n);
     }

由所有类的“collection 视图方法”返回的 collectioniterator 方法返回的迭代器都是快速失败 的:在创建 Iterator 之后,如果从结构上对 Hashtable 进行修改,除非通过 Iterator 自身的 remove 方法,否则在任何时间以任何方式对其进行修改,Iterator 都将抛出ConcurrentModificationException。因此,面对并发的修改,Iterator 很快就会完全失败,而不冒在将来某个不确定的时间发生任意不确定行为的风险。由 Hashtable 的键和元素方法返回的 Enumeration 不 是快速失败的。

注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException。因此,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误做法:迭代器的快速失败行为应该仅用于检测程序错误。

从Java 2 平台 v1.2起,此类就被改进以实现 Map 接口,使它成为 Java Collections Framework 中的一个成员。不像新的 collection 实现,Hashtable 是同步的

Hashtable 体系结构:

注意: Dictionary 类已经过时, 这里不再讨论, 我们只简述 Map 的方法
在这里插入图片描述

Hashtable 的实现原理与 HashMap 原理几乎没有差异.有兴趣的可以看这批文章:
HashMap 源码浅析

Hashtable 字段属性:

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable {

    /**
     * 用于存放哈希表的数据
     */
    private transient Entry[] table;

    /**
     * 哈希表中的条目总数。
     */
    private transient int count;

    /**
     * (阈值)
     * 当表的大小超过此阈值时,表将重新进行。 (该字段的值为 (int)(capacity * loadFactor)。)
     */
    private int threshold;

    /**
     * 哈希表的加载因子.(当表的元素数量 count > (table.length * loadFactor) 时进行扩容)
     */
    private float loadFactor;

    /**
     * 此Hashtable已被结构修改的次数结构修改是指更改Hashtable中的条目数或以其他方式修改其内部结构
     * (例如,重新散列)的修改。 此字段用于在Hashtable的Collection-views上快速生成迭代器。 
     * (请参阅ConcurrentModificationException)。
     */
    private transient int modCount = 0;

    // Views

    /**
     * 初始化每个字段以在第一次请求此视图时包含相应视图的实例。 
     * 视图是无状态的,因此没有理由创建多个视图。
     */
    private transient volatile Set<K> keySet = null;
    private transient volatile Set<Map.Entry<K,V>> entrySet = null;
    private transient volatile Collection<V> values = null;
}

Hashtabl 存储 (键-值) 的节点 Entry:

    private static class Entry<K,V> implements Map.Entry<K,V> {
		int hash;			// key 的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;
		}
		
		其他方法省略......
    }

构造方法:

默认容量 11, 负载因子loadFactor 为 0.75的构造方法

    public Hashtable() {
		this(11, 0.75f);
    }

指定初始容量的构造方法,默认负载因子loadFactor 为 0.75

    public Hashtable(int initialCapacity) {
		this(initialCapacity, 0.75f);
    }

指定初始容量,或负载因子loadFactor 的构造方法

    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);
    }
    public Hashtable(Map<? extends K, ? extends V> t) {
		this(Math.max(2*t.size(), 11), 0.75f);
		putAll(t);
    }

存储 键-值 put(K, V)

据说 Hashtable 性能差, 因为Hashtable 是线程安全的是同步的;来看看源码就清楚了;
put 的方法被 synchronized 关键字整个套主了, 即存存储数据是, 整个对象锁住了.
这样子性能当然会很差, 据说慢了 100 倍.
不允许 null 值;

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

扩容 rehash:

Hashtable 每一次扩容后的容量为旧容量的 2倍 + 1;
table 数组被查询分配容量. 旧的table数据会被拷贝到行的table中;
首先迭代 table 数组, 获取 entry 链节点的第一个节点指定, 再迭代entry链的所有元素

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

获取数据 get(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;
    }

移除数据 remove(Object)

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

其他方法

获取元素数量: size()

    public synchronized int size() {
		return count;
    }

判断是否有元素: isEmpty()

    public synchronized boolean isEmpty() {
		return count == 0;
    }

toString() 方法: 内部实现掉用了迭代器完成展示元素的视图

    public synchronized String toString() {
		int max = size() - 1;
		if (max == -1)
		    return "{}";
	
		StringBuilder sb = new StringBuilder();
		Iterator<Map.Entry<K,V>> it = entrySet().iterator();
	
		sb.append('{');
		for (int i = 0; ; i++) {
		    Map.Entry<K,V> e = it.next();
	            K key = e.getKey();
	            V value = e.getValue();
	            sb.append(key   == this ? "(this Map)" : key.toString());
		    sb.append('=');
		    sb.append(value == this ? "(this Map)" : value.toString());
	
		    if (i == max)
			return sb.append('}').toString();
		    sb.append(", ");
		}
    }

是否包含这个 value 值: 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;
    }

如果包含这个值返回 true: containsValue

    public boolean containsValue(Object value) {
		return contains(value);
    }

判断是否包含这个键: containsKey

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

把另一个Map集合全部添加到这个map: putAll

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

清空所有元素: clear()

    public synchronized void clear() {
		Entry tab[] = table;
		modCount++;
		for (int index = tab.length; --index >= 0; )
		    tab[index] = null;
		count = 0;
    }

总结:

正如你所看见的, Hashtable 支持并发操作, 但是它的效率是低下的, 因为它所有的方法都套上了 synchronized 关键字, 即方法被调用时,这个对象被锁定了.要直到这个方法调用完成返回后才能执行其他方法.

猜你喜欢

转载自blog.csdn.net/weixin_39554102/article/details/85805699