Java集合篇:Hashtable原理详解(JDK1.8)

Java集合篇:Hashtable原理详解(JDK1.8)

(本文使用的源码基于JDK1.8的)

 

一、Hashtable的基本方法:

这部分参考博客:https://blog.csdn.net/chenssy/article/details/22896871

1、定义:

HashTable在Java中的定义如下:

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

从中可以看出HashTable继承Dictionary类,实现Map接口。其中Dictionary类是任何可将键映射到相应值的类(如 Hashtable)的抽象父类。每个键和每个值都是一个对象。在任何一个 Dictionary 对象中,每个键至多与一个值相关联。Map是”key-value键值对”接口。

HashTable采用”拉链法”实现哈希表,它定义了几个重要的参数:table、count、threshold、loadFactor、modCount。

table:为一个Entry[]数组类型,Entry代表了“拉链”的节点,每一个Entry代表了一个键值对,哈希表的”key-value键值对”都是存储在Entry数组中的。

 count:HashTable的大小,注意这个大小并不是HashTable的容器大小,而是他所包含Entry键值对的数量。

 threshold:Hashtable的阈值,用于判断是否需要调整Hashtable的容量。threshold的值=”容量*加载因子”。

loadFactor:加载因子。

modCount:用来实现“fail-fast”机制的(也就是快速失败)。所谓快速失败就是在并发集合中,其进行迭代操作时,若有其他线程对其进行结构性的修改,这时迭代器会立马感知到,并且立即抛出ConcurrentModificationException异常,而不是等到迭代完成之后才告诉你已经出错了。

2、构造方法:

在HashTabel中存在4个构造函数。通过这4个构造函数我们构建出一个我想要的HashTable。

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

 默认构造函数,容量为11,加载因子为0.75。

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

用指定初始容量和默认的加载因子 (0.75) 构造一个新的空哈希表。

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,获得大小为initialCapacity的table数组
        table = new Entry[initialCapacity];
        //计算阀值
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    }

用指定初始容量和指定加载因子构造一个新的空哈希表。

public Hashtable(Map<? extends K, ? extends V> t) {
        //设置table容器大小,其值==t.size * 2 + 1
        this(Math.max(2*t.size(), 11), 0.75f);
        putAll(t);
    }

构造一个与给定的 Map 具有相同映射关系的新哈希表。

3、主要方法:

 HashTable的API对外提供了许多方法,这些方法能够很好帮助我们操作HashTable,但是这里我只介绍两个最根本的方法:put、get。

(1)首先我们先看put方法:将指定 key 映射到此哈希表中的指定 value。注意这里键key和值value都不可为空。

public synchronized V put(K key, V value) {
        // 确保value不为null
        if (value == null) {
            throw new NullPointerException();
        }
 
        /*
         * 确保key在table[]是不重复的
         * 处理过程:
         * 1、计算key的hash值,确认在table[]中的索引位置
         * 2、迭代index索引位置,如果该位置处的链表中存在一个一样的key,则替换其value,返回旧值
         */
        Entry tab[] = table;
        int hash = hash(key);    //计算key的hash值
        int index = (hash & 0x7FFFFFFF) % tab.length;     //确认该key的索引位置
        //迭代,寻找该key,替换
        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();
            tab = table;
            hash = hash(key);
            index = (hash & 0x7FFFFFFF) % tab.length;
        }
 
        // 在索引位置处插入一个新的节点
        Entry<K,V> e = tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        //容器中元素+1
        count++;
        return null;
    }

put方法的整个处理流程是:计算key的hash值,根据hash值获得key在table数组中的索引位置,然后迭代该key处的Entry链表(我们暂且理解为链表),若该链表中存在一个这个的key对象,那么就直接替换其value值即可,否则在将改key-value节点插入该index索引位置处。

Hashtable的扩容操作,在put方法中,如果需要向table[]中添加Entry元素,会首先进行容量校验,如果容量已经达到了阀值,HashTable就会进行扩容处理rehash(),如下:

protected void rehash() {
        int oldCapacity = table.length;
        //元素
        Entry<K,V>[] oldMap = table;
 
        //新容量=旧容量 * 2 + 1
        int newCapacity = (oldCapacity << 1) + 1;
        if (newCapacity - MAX_ARRAY_SIZE > 0) {
            if (oldCapacity == MAX_ARRAY_SIZE)
                return;
            newCapacity = MAX_ARRAY_SIZE;
        }
        
        //新建一个size = newCapacity 的HashTable
        Entry<K,V>[] newMap = new Entry[newCapacity];
 
        modCount++;
        //重新计算阀值
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
 
        table = newMap;
        //将原来的元素拷贝到新的HashTable中
        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()方法中我们可以看到容量扩大两倍+1,同时需要将原来HashTable中的元素一一复制到新的HashTable中,这个过程是比较消耗时间的,同时还需要重新计算 index 的,毕竟容量已经变了。这里对阀值啰嗦一下:比如初始值11、加载因子默认0.75,那么这个时候阀值threshold=8,当容器中的元素达到8时,HashTable进行一次扩容操作,容量 = 8 * 2 + 1 =17,而阀值threshold=17*0.75 = 13,当容器元素再一次达到阀值时,HashTable还会进行扩容操作,依次类推。

(2)get方法就会比较简单,处理过程就是计算key的hash值,判断在table数组中的索引位置,然后迭代链表,匹配直到找到相对应key的value,若没有找到返回null。

public synchronized V get(Object key) {
        Entry tab[] = table;
        int hash = hash(key);
        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;
    }

 

二、Hashtable的三种遍历方式:

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
 
public class HashTableTest {
	public static void main(String args[]){
		Hashtable<String, Integer> table = new Hashtable<String, Integer>();
		
		table.put("zhangsan", 22);
		table.put("lisi", 33);
		table.put("wangwu", 44);	
		
		//[1]Iterator遍历方式1--键值对遍历entrySet()
		Iterator<Entry<String, Integer>> iter = table.entrySet().iterator();
		while(iter.hasNext()){
			Map.Entry<String, Integer> entry = (Map.Entry<String, Integer>)iter.next();
			String key = entry.getKey();
			int value = entry.getValue();
			System.out.println("entrySet:"+key+" "+value);
		}
		
		System.out.println("====================================");
		
		//[2]Iterator遍历方式2--key键的遍历
		Iterator<String> iterator = table.keySet().iterator();
		while(iterator.hasNext()){
			String key = (String)iterator.next();
			int value = table.get(key);
			System.out.println("keySet:"+key+" "+value);
		}
		
		System.out.println("====================================");
		
		//[3]通过Enumeration来遍历Hashtable
		Enumeration<String> enu = table.keys();
		while(enu.hasMoreElements()) {
		    System.out.println("Enumeration:"+table.keys()+" "+enu.nextElement());
		} 	
	}
}
输出结果:
entrySet:zhangsan 22
entrySet:lisi 33
entrySet:wangwu 44
====================================
keySet:zhangsan 22
keySet:lisi 33
keySet:wangwu 44
====================================
Enumeration:java.util.Hashtable$Enumerator@139a55 zhangsan
Enumeration:java.util.Hashtable$Enumerator@1db9742 lisi
Enumeration:java.util.Hashtable$Enumerator@106d69c wangwu

 

三、Hashtable与HashMap的区别详解:

参考博客:https://blog.csdn.net/wangxing233/article/details/79452946?utm_source=blogxgwz5

1、继承的父类不同:

Hashtable继承的是Dictionary类,HashMap继承的是AbstractMap,但两者都实现了Map接口。

2、是否允许null:

HashMap可以允许存在一个 null 的 key 和任意个 null 的 value,不过建议尽量避免这样使用null作为 key,HashMap以null作为key时,总是存储在table数组的第一个节点上;Hashtable中的 key 和 value 都不允许为 null 。

在HashMap中,当get()方法返回null值时,可能是 HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。

(1)当HashMap遇到为null的key时,它会调用putForNullKey方法来进行处理。对于value没有进行任何处理,只要是对象都可以。

if (key == null)
            return putForNullKey(value);

(2)如果在Hashtable中有类似put(null,null)的操作,编译时可以通过,因为key和value都是Object类型,但运行时会抛出NullPointerException异常。

if (value == null) {
            throw new NullPointerException();
        }

3、Hashtable的方法是线程安全的,底层的每个方法都使用synchronized的),而HashMap的方法多线程不安全。

虽然HashMap不是线程安全的,但是它的效率会比Hashtable要好很多。当需要多线程操作的时候可以使用线程安全的ConcurrentHashMap。ConcurrentHashMap虽然也是线程安全的,但是它的效率比Hashtable要高好多倍。因为ConcurrentHashMap使用了分段锁,并不对整个数据进行锁定。

4、遍历不同:HashMap仅支持Iterator的遍历方式,Hashtable支持Iterator和Enumeration两种遍历方式。

(1)HashMap 的Iterator 使用的是fail-fast 迭代器,当有其他线程改变了 HashMap 的结构(增加、删除、修改元素),将会抛出ConcurrentModificationException。

(2)JDK8之前的版本中,Hashtable是没有fast-fail机制的。在JDK8及以后的版本中 ,HashTable也是使用fast-fail的, 源码如下: 

 if (expectedModCount != modCount) {
     throw new ConcurrentModificationException();
  }

modCount 的使用类似于并发编程中的 CAS( Compare and Swap) 技术,每次在发生增删改操作的时候,都会出现modCount++的动作,而modcount可以理解为是当前hashtable的状态。每发生一次操作,状态+1。设置这个状态,主要是用于hashtable 等容器类在迭代时,判断数据是否过时时使用的。尽管hashtable采用了原生的同步锁来保护数据安全。但是在出现迭代数据的时候,则无法保证边迭代,边正确操作。于是使用这个值来标记状态。一旦在迭代的过程中状态发生了改变,则会快速抛出一个异常,终止迭代行为。

5、是否提供contains方法:

(1)HashMap把Hashtable的contains()方法去掉了,改成containsValue 和 containsKey ,因为contains() 方法容易让人引起误解;

(2)Hashtable则保留了contains,containsValue 和 containsKey 三个方法 ,其中 contains 和 containsValue 功能相同。

6、内部实现使用的数值初始化 和 扩容方式不同:

(1)两者的默认负载因子都是0.75,但Hashtable扩容时,容量变为原来的2倍+1,HashMap扩容时,将容量变成原来的2倍;Hashtable在不制定容量的情况下默认容量是11,也就是说Hashtable会尽量使用素数、奇数,而HashMap 的默认容量 为16,Hashtable不要求底层数组的容量为2的整数次幂,而 HashMap 要求一定为2的整数次幂。

(2) 之所以会有这样的不同,是因为Hashtable和HashMap设计时的侧重点不同。Hashtable的侧重点是哈希的结果更加均匀,使得哈希冲突减少。当哈希表的大小为素数时,简单的取模哈希的结果会更加均匀。而HashMap则更加关注hash的计算效率问题。在取模计算时,如果模数是2的幂,那么我们可以直接使用位运算来得到结果,效率要大大高于做除法。HashMap为了加快hash的速度,将哈希表的大小固定为了2的幂。当然这引入了哈希分布不均匀的问题,所以HashMap为解决这问题,又对hash算法做了一些改动。这从而导致了Hashtable和HashMap的计算hash值的方法不同。

7、hash 值不同:

(1)Hashtable直接使用Object的hashCode(),hashCode是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值,然后再使用去取模运算来获得最终的位置。 这里一般先用 hash & 0x7FFFFFFF 后,再对length取模,&0x7FFFFFFF的目的是为了将负的hash值转化为正值,因为hash值有可能为负数,而 hash & 0x7FFFFFFF 后,只有符号外改变,而后面的位都不变。Hashtable在计算元素的位置时需要进行一次除法运算,而除法运算是比较耗时的。 

 int hash = key.hashCode();
 int index = (hash & 0x7FFFFFFF) % tab.length;

(2)为了提高计算效率,HashMap 将哈希表的大小固定为了2的幂,这样在取模预算时,不需要做除法,只需要做位运算。位运算比除法的效率要高很多。HashMap的效率虽然提高了,但是hash冲突却也增加了。因为它得出的hash值的低位相同的概率比较高,HashMap的效率虽然提高了,但是hash冲突却也增加了。因为它得出的hash值的低位相同的概率比较高。而计算位运算为了解决这个问题,HashMap重新根据hashcode计算hash值后,又对hash值做了一些运算来打散数据。使得取得的位置更加分散,从而减少了hash冲突。当然了,为了高效,HashMap只做了一些简单的位处理。从而不至于把使用2 的幂次方带来的效率提升给抵消掉。

  static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

 

四、Hashtable 部分源码注释:

这部分摘自博客:https://blog.csdn.net/ns_code/article/details/36191279

package java.util;  
import java.io.*;  
 
public class Hashtable<K,V>  
    extends Dictionary<K,V>  
    implements Map<K,V>, Cloneable, java.io.Serializable {  
 
    // 保存key-value的数组。  
    // Hashtable同样采用单链表解决冲突,每一个Entry本质上是一个单向链表  
    private transient Entry[] table;  
 
    // Hashtable中键值对的数量  
    private transient int count;  
 
    // 阈值,用于判断是否需要调整Hashtable的容量(threshold = 容量*加载因子)  
    private int threshold;  
 
    // 加载因子  
    private float loadFactor;  
 
    // Hashtable被改变的次数,用于fail-fast机制的实现  
    private transient int modCount = 0;  
 
    // 序列版本号  
    private static final long serialVersionUID = 1421746759512286392L;  
 
    // 指定“容量大小”和“加载因子”的构造函数  
    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(int initialCapacity) {  
        this(initialCapacity, 0.75f);  
    }  
 
    // 默认构造函数。  
    public Hashtable() {  
        // 默认构造函数,指定的容量大小是11;加载因子是0.75  
        this(11, 0.75f);  
    }  
 
    // 包含“子Map”的构造函数  
    public Hashtable(Map<? extends K, ? extends V> t) {  
        this(Math.max(2*t.size(), 11), 0.75f);  
        // 将“子Map”的全部元素都添加到Hashtable中  
        putAll(t);  
    }  
 
    public synchronized int size() {  
        return count;  
    }  
 
    public synchronized boolean isEmpty() {  
        return count == 0;  
    }  
 
    // 返回“所有key”的枚举对象  
    public synchronized Enumeration<K> keys() {  
        return this.<K>getEnumeration(KEYS);  
    }  
 
    // 返回“所有value”的枚举对象  
    public synchronized Enumeration<V> elements() {  
        return this.<V>getEnumeration(VALUES);  
    }  
 
    // 判断Hashtable是否包含“值(value)”  
    public synchronized boolean contains(Object value) {  
        //注意,Hashtable中的value不能是null,  
        // 若是null的话,抛出异常!  
        if (value == null) {  
            throw new NullPointerException();  
        }  
 
        // 从后向前遍历table数组中的元素(Entry)  
        // 对于每个Entry(单向链表),逐个遍历,判断节点的值是否等于value  
        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;  
    }  
 
    public boolean containsValue(Object value) {  
        return contains(value);  
    }  
 
    // 判断Hashtable是否包含key  
    public synchronized boolean containsKey(Object key) {  
        Entry tab[] = table;  
		//计算hash值,直接用key的hashCode代替
        int hash = key.hashCode();    
        // 计算在数组中的索引值 
        int index = (hash & 0x7FFFFFFF) % tab.length;  
        // 找到“key对应的Entry(链表)”,然后在链表中找出“哈希值”和“键值”与key都相等的元素  
        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {  
            if ((e.hash == hash) && e.key.equals(key)) {  
                return true;  
            }  
        }  
        return false;  
    }  
 
    // 返回key对应的value,没有的话返回null  
    public synchronized V get(Object key) {  
        Entry tab[] = table;  
        int hash = key.hashCode();  
        // 计算索引值,  
        int index = (hash & 0x7FFFFFFF) % tab.length;  
        // 找到“key对应的Entry(链表)”,然后在链表中找出“哈希值”和“键值”与key都相等的元素  
        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;  
    }  
 
    // 调整Hashtable的长度,将长度变成原来的2倍+1 
    protected void rehash() {  
        int oldCapacity = table.length;  
        Entry[] oldMap = table;  
 
		//创建新容量大小的Entry数组
        int newCapacity = oldCapacity * 2 + 1;  
        Entry[] newMap = new Entry[newCapacity];  
 
        modCount++;  
        threshold = (int)(newCapacity * loadFactor);  
        table = newMap;  
		
		//将“旧的Hashtable”中的元素复制到“新的Hashtable”中
		for (int i = oldCapacity ; i-- > 0 ;) {  
            for (Entry<K,V> old = oldMap[i] ; old != null ; ) {  
                Entry<K,V> e = old;  
                old = old.next;  
				//重新计算index
                int index = (e.hash & 0x7FFFFFFF) % newCapacity;  
                e.next = newMap[index];  
                newMap[index] = e;  
            }  
        }  
    }  
 
    // 将“key-value”添加到Hashtable中  
    public synchronized V put(K key, V value) {  
        // Hashtable中不能插入value为null的元素!!!  
        if (value == null) {  
            throw new NullPointerException();  
        }  
 
        // 若“Hashtable中已存在键为key的键值对”,  
        // 则用“新的value”替换“旧的value”  
        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;  
                }  
        }  
 
        // 若“Hashtable中不存在键为key的键值对”,
        // 将“修改统计数”+1  
        modCount++;  
        //  若“Hashtable实际容量” > “阈值”(阈值=总的容量 * 加载因子)  
        //  则调整Hashtable的大小  
        if (count >= threshold) {
            rehash();  
 
            tab = table;  
            index = (hash & 0x7FFFFFFF) % tab.length;  
        }  
 
        //将新的key-value对插入到tab[index]处(即链表的头结点)
        Entry<K,V> e = tab[index];         
        tab[index] = new Entry<K,V>(hash, key, value, e);  
        count++;  
        return null;  
    }  
 
    // 删除Hashtable中键为key的元素  
    public synchronized V remove(Object key) {  
        Entry tab[] = table;  
        int hash = key.hashCode();  
        int index = (hash & 0x7FFFFFFF) % tab.length;  
		
        //从table[index]链表中找出要删除的节点,并删除该节点。
		//因为是单链表,因此要保留带删节点的前一个节点,才能有效地删除节点
        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;  
    }  
 
    // 将“Map(t)”的中全部元素逐一添加到Hashtable中  
    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  
    // 将Hashtable的table数组的值全部设为null  
    public synchronized void clear() {  
        Entry tab[] = table;  
        modCount++;  
        for (int index = tab.length; --index >= 0; )  
            tab[index] = null;  
        count = 0;  
    }  
 
    // 克隆一个Hashtable,并以Object的形式返回。  
    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) {   
            throw new InternalError();  
        }  
    }  
 
    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(", ");  
        }  
    }  
 
    // 获取Hashtable的枚举类对象  
    // 若Hashtable的实际大小为0,则返回“空枚举类”对象;  
    // 否则,返回正常的Enumerator的对象。 
    private <T> Enumeration<T> getEnumeration(int type) {  
    if (count == 0) {  
        return (Enumeration<T>)emptyEnumerator;  
    } else {  
        return new Enumerator<T>(type, false);  
    }  
    }  
 
    // 获取Hashtable的迭代器  
    // 若Hashtable的实际大小为0,则返回“空迭代器”对象;  
    // 否则,返回正常的Enumerator的对象。(Enumerator实现了迭代器和枚举两个接口)  
    private <T> Iterator<T> getIterator(int type) {  
        if (count == 0) {  
            return (Iterator<T>) emptyIterator;  
        } else {  
            return new Enumerator<T>(type, true);  
        }  
    }  
 
    // Hashtable的“key的集合”。它是一个Set,没有重复元素  
    private transient volatile Set<K> keySet = null;  
    // Hashtable的“key-value的集合”。它是一个Set,没有重复元素  
    private transient volatile Set<Map.Entry<K,V>> entrySet = null;  
    // Hashtable的“key-value的集合”。它是一个Collection,可以有重复元素  
    private transient volatile Collection<V> values = null;  
 
    // 返回一个被synchronizedSet封装后的KeySet对象  
    // synchronizedSet封装的目的是对KeySet的所有方法都添加synchronized,实现多线程同步  
    public Set<K> keySet() {  
        if (keySet == null)  
            keySet = Collections.synchronizedSet(new KeySet(), this);  
        return keySet;  
    }  
 
    // Hashtable的Key的Set集合。  
    // KeySet继承于AbstractSet,所以,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();  
        }  
    }  
 
    // 返回一个被synchronizedSet封装后的EntrySet对象  
    // synchronizedSet封装的目的是对EntrySet的所有方法都添加synchronized,实现多线程同步  
    public Set<Map.Entry<K,V>> entrySet() {  
        if (entrySet==null)  
            entrySet = Collections.synchronizedSet(new EntrySet(), this);  
        return entrySet;  
    }  
 
    // Hashtable的Entry的Set集合。  
    // EntrySet继承于AbstractSet,所以,EntrySet中的元素没有重复的。  
    private class EntrySet extends AbstractSet<Map.Entry<K,V>> {  
        public Iterator<Map.Entry<K,V>> iterator() {  
            return getIterator(ENTRIES);  
        }  
 
        public boolean add(Map.Entry<K,V> o) {  
            return super.add(o);  
        }  
 
        // 查找EntrySet中是否包含Object(0)  
        // 首先,在table中找到o对应的Entry链表  
        // 然后,查找Entry链表中是否存在Object  
        public boolean contains(Object o) {  
            if (!(o instanceof Map.Entry))  
                return false;  
            Map.Entry entry = (Map.Entry)o;  
            Object key = entry.getKey();  
            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.equals(entry))  
                    return true;  
            return false;  
        }  
 
        // 删除元素Object(0)  
        // 首先,在table中找到o对应的Entry链表
        // 然后,删除链表中的元素Object  
        public boolean remove(Object o) {  
            if (!(o instanceof Map.Entry))  
                return false;  
            Map.Entry<K,V> entry = (Map.Entry<K,V>) o;  
            K key = entry.getKey();  
            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.equals(entry)) {  
                    modCount++;  
                    if (prev != null)  
                        prev.next = e.next;  
                    else 
                        tab[index] = e.next;  
 
                    count--;  
                    e.value = null;  
                    return true;  
                }  
            }  
            return false;  
        }  
 
        public int size() {  
            return count;  
        }  
 
        public void clear() {  
            Hashtable.this.clear();  
        }  
    }  
 
    // 返回一个被synchronizedCollection封装后的ValueCollection对象  
    // synchronizedCollection封装的目的是对ValueCollection的所有方法都添加synchronized,实现多线程同步  
    public Collection<V> values() {  
    if (values==null)  
        values = Collections.synchronizedCollection(new ValueCollection(),  
                                                        this);  
        return values;  
    }  
 
    // Hashtable的value的Collection集合。  
    // ValueCollection继承于AbstractCollection,所以,ValueCollection中的元素可以重复的。  
    private class ValueCollection extends AbstractCollection<V> {  
        public Iterator<V> iterator() {  
        return getIterator(VALUES);  
        }  
        public int size() {  
            return count;  
        }  
        public boolean contains(Object o) {  
            return containsValue(o);  
        }  
        public void clear() {  
            Hashtable.this.clear();  
        }  
    }  
 
    // 重新equals()函数  
    // 若两个Hashtable的所有key-value键值对都相等,则判断它们两个相等  
    public synchronized boolean equals(Object o) {  
        if (o == this)  
            return true;  
 
        if (!(o instanceof Map))  
            return false;  
        Map<K,V> t = (Map<K,V>) o;  
        if (t.size() != size())  
            return false;  
 
        try {  
            // 通过迭代器依次取出当前Hashtable的key-value键值对  
            // 并判断该键值对,存在于Hashtable中。  
            // 若不存在,则立即返回false;否则,遍历完“当前Hashtable”并返回true。  
            Iterator<Map.Entry<K,V>> i = entrySet().iterator();  
            while (i.hasNext()) {  
                Map.Entry<K,V> e = i.next();  
                K key = e.getKey();  
                V value = e.getValue();  
                if (value == null) {  
                    if (!(t.get(key)==null && t.containsKey(key)))  
                        return false;  
                } else {  
                    if (!value.equals(t.get(key)))  
                        return false;  
                }  
            }  
        } catch (ClassCastException unused)   {  
            return false;  
        } catch (NullPointerException unused) {  
            return false;  
        }  
 
        return true;  
    }  
 
    // 计算Entry的hashCode  
    // 若 Hashtable的实际大小为0 或者 加载因子<0,则返回0。  
    // 否则,返回“Hashtable中的每个Entry的key和value的异或值 的总和”。  
    public synchronized int hashCode() {  
        int h = 0;  
        if (count == 0 || loadFactor < 0)  
            return h;  // Returns zero  
 
        loadFactor = -loadFactor;  // Mark hashCode computation in progress  
        Entry[] tab = table;  
        for (int i = 0; i < tab.length; i++)  
            for (Entry e = tab[i]; e != null; e = e.next)  
                h += e.key.hashCode() ^ e.value.hashCode();  
        loadFactor = -loadFactor;  // Mark hashCode computation complete  
 
        return h;  
    }  
 
    // java.io.Serializable的写入函数  
    // 将Hashtable的“总的容量,实际容量,所有的Entry”都写入到输出流中  
    private synchronized void writeObject(java.io.ObjectOutputStream s)  
        throws IOException  
    {  
        // Write out the length, threshold, loadfactor  
        s.defaultWriteObject();  
 
        // Write out length, count of elements and then the key/value objects  
        s.writeInt(table.length);  
        s.writeInt(count);  
        for (int index = table.length-1; index >= 0; index--) {  
            Entry entry = table[index];  
 
            while (entry != null) {  
            s.writeObject(entry.key);  
            s.writeObject(entry.value);  
            entry = entry.next;  
            }  
        }  
    }  
 
    // java.io.Serializable的读取函数:根据写入方式读出  
    // 将Hashtable的“总的容量,实际容量,所有的Entry”依次读出  
    private void readObject(java.io.ObjectInputStream s)  
         throws IOException, ClassNotFoundException  
    {  
        // Read in the length, threshold, and loadfactor  
        s.defaultReadObject();  
 
        // Read the original length of the array and number of elements  
        int origlength = s.readInt();  
        int elements = s.readInt();  
 
        // Compute new size with a bit of room 5% to grow but  
        // no larger than the original size.  Make the length  
        // odd if it's large enough, this helps distribute the entries.  
        // Guard against the length ending up zero, that's not valid.  
        int length = (int)(elements * loadFactor) + (elements / 20) + 3;  
        if (length > elements && (length & 1) == 0)  
            length--;  
        if (origlength > 0 && length > origlength)  
            length = origlength;  
 
        Entry[] table = new Entry[length];  
        count = 0;  
 
        // Read the number of elements and then all the key/value objects  
        for (; elements > 0; elements--) {  
            K key = (K)s.readObject();  
            V value = (V)s.readObject();  
                // synch could be eliminated for performance  
                reconstitutionPut(table, key, value);  
        }  
        this.table = table;  
    }  
 
    private void reconstitutionPut(Entry[] tab, K key, V value)  
        throws StreamCorruptedException  
    {  
        if (value == null) {  
            throw new java.io.StreamCorruptedException();  
        }  
        // Makes sure the key is not already in the hashtable.  
        // This should not happen in deserialized version.  
        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)) {  
                throw new java.io.StreamCorruptedException();  
            }  
        }  
        // Creates the new entry.  
        Entry<K,V> e = tab[index];  
        tab[index] = new Entry<K,V>(hash, key, value, e);  
        count++;  
    }  
 
    // Hashtable的Entry节点,它本质上是一个单向链表。  
    // 也因此,我们才能推断出Hashtable是由拉链法实现的散列表  
    private static class Entry<K,V> implements Map.Entry<K,V> {  
        // 哈希值  
        int hash;  
        K key;  
        V value;  
        // 指向的下一个Entry,即链表的下一个节点  
        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()));  
        }  
 
        public K getKey() {  
            return key;  
        }  
 
        public V getValue() {  
            return value;  
        }  
 
        // 设置value。若value是null,则抛出异常。  
        public V setValue(V value) {  
            if (value == null)  
                throw new NullPointerException();  
 
            V oldValue = this.value;  
            this.value = value;  
            return oldValue;  
        }  
 
        // 覆盖equals()方法,判断两个Entry是否相等。  
        // 若两个Entry的key和value都相等,则认为它们相等。  
        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();  
        }  
    }  
 
    private static final int KEYS = 0;  
    private static final int VALUES = 1;  
    private static final int ENTRIES = 2;  
 
    // Enumerator的作用是提供了“通过elements()遍历Hashtable的接口” 和 “通过entrySet()遍历Hashtable的接口”。  
    private class Enumerator<T> implements Enumeration<T>, Iterator<T> {  
        // 指向Hashtable的table  
        Entry[] table = Hashtable.this.table;  
        // Hashtable的总的大小  
        int index = table.length;  
        Entry<K,V> entry = null;  
        Entry<K,V> lastReturned = null;  
        int type;  
 
        // Enumerator是 “迭代器(Iterator)” 还是 “枚举类(Enumeration)”的标志  
        // iterator为true,表示它是迭代器;否则,是枚举类。  
        boolean iterator;  
 
        // 在将Enumerator当作迭代器使用时会用到,用来实现fail-fast机制。  
        protected int expectedModCount = modCount;  
 
        Enumerator(int type, boolean iterator) {  
            this.type = type;  
            this.iterator = iterator;  
        }  
 
        // 从遍历table的数组的末尾向前查找,直到找到不为null的Entry。  
        public boolean hasMoreElements() {  
            Entry<K,V> 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;  
        }  
 
        // 获取下一个元素  
        // 注意:从hasMoreElements() 和nextElement() 可以看出“Hashtable的elements()遍历方式”  
        // 首先,从后向前的遍历table数组。table数组的每个节点都是一个单向链表(Entry)。  
        // 然后,依次向后遍历单向链表Entry。  
        public T nextElement() {  
            Entry<K,V> et = entry;  
            int i = index;  
            Entry[] t = table;  
            /* Use locals for faster loop iteration */ 
            while (et == null && i > 0) {  
                et = t[--i];  
            }  
            entry = et;  
            index = i;  
            if (et != null) {  
                Entry<K,V> 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的判断是否存在下一个元素  
        // 实际上,它是调用的hasMoreElements()  
        public boolean hasNext() {  
            return hasMoreElements();  
        }  
 
        // 迭代器获取下一个元素  
        // 实际上,它是调用的nextElement()  
        public T next() {  
            if (modCount != expectedModCount)  
                throw new ConcurrentModificationException();  
            return nextElement();  
        }  
 
        // 迭代器的remove()接口。  
        // 首先,它在table数组中找出要删除元素所在的Entry,  
        // 然后,删除单向链表Entry中的元素。  
        public void remove() {  
            if (!iterator)  
                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;  
 
                for (Entry<K,V> e = tab[index], 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();  
            }  
        }  
    }  
 
 
    private static Enumeration emptyEnumerator = new EmptyEnumerator();  
    private static Iterator emptyIterator = new EmptyIterator();  
 
    // 空枚举类  
    // 当Hashtable的实际大小为0;此时,又要通过Enumeration遍历Hashtable时,返回的是“空枚举类”的对象。  
    private static class EmptyEnumerator implements Enumeration<Object> {  
 
        EmptyEnumerator() {  
        }  
 
        // 空枚举类的hasMoreElements() 始终返回false  
        public boolean hasMoreElements() {  
            return false;  
        }  
 
        // 空枚举类的nextElement() 抛出异常  
        public Object nextElement() {  
            throw new NoSuchElementException("Hashtable Enumerator");  
        }  
    }  
 
 
    // 空迭代器  
    // 当Hashtable的实际大小为0;此时,又要通过迭代器遍历Hashtable时,返回的是“空迭代器”的对象。  
    private static class EmptyIterator implements Iterator<Object> {  
 
        EmptyIterator() {  
        }  
 
        public boolean hasNext() {  
            return false;  
        }  
 
        public Object next() {  
            throw new NoSuchElementException("Hashtable Iterator");  
        }  
 
        public void remove() {  
            throw new IllegalStateException("Hashtable Iterator");  
        }  
 
    }  
} 

 

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/114663.html原文链接:https://javaforall.net

(0)
上一篇 2021年10月4日 下午12:00
下一篇 2021年10月4日 下午1:00


相关推荐

  • linux futex浅析[通俗易懂]

    linux futex浅析[通俗易懂]Futex,FastUserspacemuTEXes,作为linux下的一种快速同步(互斥)机制,已经存在了很长一段时间了(sincelinux2.5.7)。它有什么优势?又提供了怎样一些功能,本文就简单探讨一下。futex诞生之前在futex诞生之前,linux下的同步机制可以归为两类:用户态的同步机制和内核同步机制。用户态的同步机制基本上就是利用原子指令实现的spinlock。最简单的实现就是使用一个整型数,0表示未上锁,1表示已上锁。trylock操作就利用原子指令尝试将0改为1

    2026年2月9日
    4
  • 使用jQuery使Div居中

    使用jQuery使Div居中Div 居中是一个比较常见的需求 下面介绍一种使用 JQuery 使 Div 居中的方法先假设有这样一个 Div test 首先是要把需要居中的 Div 进行绝对定位 如 nbsp nbsp nbsp nbsp nbsp nbsp nbsp d nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp position absolute nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp width 500 nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp height 300

    2026年3月17日
    2
  • magento soap api

    magento soap apiSOAP:simpleobjectaccessprotocol;WSDL:webservicedescriptionlanguage;MagentoSoapV1v1扩展案例step1:在etc下新建api.xml,内容如下

    2022年7月13日
    28
  • 解决Navicat for MySQL 连接 Mysql 8.0.11 出现1251- Client does not support authentication protocol 错误

    解决Navicat for MySQL 连接 Mysql 8.0.11 出现1251- Client does not support authentication protocol 错误今天在电脑上安装了Mysql8.0.11,然后又屁颠屁颠地安装了NavicatforMySQL,打开Navicat准备链接数据库的时候出现了如下提示:上网搜索解决方案,网上说出现这种情况的原因是:mysql8之前的版本中加密规则是mysql_native_password,而在mysql8之后,加密规则是caching_sha2_password,解决问题方法有两种:方法…

    2022年5月30日
    42
  • golang go语言 反向 websocket 代理演示代码

    golang go语言 反向 websocket 代理演示代码golanggo语言反向websocket代理演示代码通过go语言实现websocket反向代理功能packagemainimport( “fmt” “github.com/fasthttp/websocket” “github.com/valyala/fasthttp” proxy”github.com/yeqown/fasthttp-reverse-proxy” “log”)varupgraders=&websocket.FastHTTPUpgrad

    2022年7月26日
    25
  • C语言 整数与字符串的相互转换

    C语言 整数与字符串的相互转换C语言整数与字符串的相互转换一、简述C语言中整数与字符串的相互转换,有广泛应用的拓展函数(非标准库),也可以自己尝试简单的实现。二、整数转字符串1、拓展函数itoaitoa(表示integertoalphanumeric)是把整型数转换成字符串的一个函数。windows环境下,在&lt;stdlib.h&gt;头文件中有c…

    2022年6月6日
    49

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号