Java 数据结构 -- 21.Java 8 数据结构补充介绍 Hashtable

本文深入介绍了 Java 中的线程安全数据结构 Hashtable,它是 HashMap 的线程安全版本,但建议在高并发场景下使用 ConcurrentHashMap。文章分析了 Hashtable 与 HashMap 的区别,包括初始化容量、扩容策略、查找方式、枚举对象以及迭代器实现等,并指出了其在现代 Java 开发中的应用局限性。
摘要由CSDN通过智能技术生成

前言

书接上文,上一篇中介绍并分析了 ArrayDeque,本篇将对线程安全的 Map 接口最终实现类 Hashtable 进行介绍与分析。

由于 Hashtable 继承自 Dictionary 抽象类,而在 Dictionary 抽象类中涉及到了 Enumaration 接口的只是,所以在查看 Hashtable 源码之前还需要看一下 Enumeration 接口和 Dictionary 抽象类。

/**
 * 一个实现了 Enumeration 接口的对象,产生一系列元素s,一次一个。连续调用 nextElement 方法返回这一系列的连续元素s。
 *
 * 比如,打印一个 Vector<E> v 中的所有元素s:
 * <pre>
 *   for (Enumeration<E> e = v.elements(); e.hasMoreElements();)
 *       System.out.println(e.nextElement());</pre>
 * <p>
 * 
 * 方法s被提供用来通过一个 vector 中的元素s枚举,一个 hashtable 的键s,和一个 hashtable 中的值s。Enumeration 也可以用来指定一个输入到为到一个 SequenceInputStream。
 * 
 * 注意:这个接口的功能与 Iterator 接口重复了。进一步说,Iterator 添加了一个可选择的 remove 操作,并且有更短的方法名s。新的实现类应该考虑优先使用 Iterator 而不是 Enumeration。
 */
public interface Enumeration<E> {
   
    /**
     * 测试当前 enumertion 是否包含更多元素s。
     */
    boolean hasMoreElements();

    /**
     * 如果当前 enumeration 对象还有至少一个对象可以提供,返回这个 enumeration 的下一个对象。
     */
    E nextElement();
}

通过 Enumeration 接口的源码可以看到,它的功能与 Iterator 接口基本类似,都是用来具体实现迭代操作的,但是 Enumeration 只能获取元素,而 Iterator 比它多了一个 remove 方法,并且 Iterator 中相同作用的方法名称更短含义更清晰,所以 Java doc 推荐使用 Iterator。

/**
 * Dictionary 类是任何映射键s和值s的类的抽象父类(这里应该是后来没有改),比如 Hashtable 类。每个键和每个
 * 值在一个对象中。在任何一个 Dictionary 对象中,每个键都关联到至多一个值。给定一个 Dictionay 和一个键,
 * 关联元素可以(就可以被)查询。任何 non-null 对象都可以被用来作为一个键和一个值。
 *
 * 作为一个规定,equals 方法应该被这个类的实现类用来决定两个键s是否相同。
 *
 * 注意:这个类是过时的。新的实现类应该实现 Map 接口,而不是继承自这个类。
 */
public abstract
class Dictionary<K,V> {
   
  
    /**
     * 唯一构造器。(通常用来隐式的被子类构造器s调用。)
     */
    public Dictionary() {
   
    }

    /**
     * 返回当前 dictionary 中条目s(唯一键s)的数量。
     */
    abstract public int size();

    /**
     * 测试当前 dictionary 是否没有键s映射到值。对于 isEmpty 方法的通常规约是当且仅当这个 dictionary 
     * 不包含任何条目s才返回 true。
     */
    abstract public boolean isEmpty();

    /**
     * 返回当前 dictionary 中的一个键s的枚举类型集合。对于 keys 方法的通常规约是返回一个生成当前 
     * dictionary 包含的所有的条目s的键s的 Enumeration 对象。
     */
    abstract public Enumeration<K> keys();

    /**
     * 返回当前 dictionary 中的一个值s的枚举类型集合。对于 elements 方法的通常规约是返回一个生成当前 
     * dictionary 中所有元素s包含在条目s中的一个 Enumeratin 对象。
     */
    abstract public Enumeration<V> elements();

    /**
     * 返回当前 dictionary 中这个键映射到的值。isEmpty 方法的通常规约是如果当前 dictionary 包含对于指定
     * 键的一个条目,关联的值被返回,不然的话,返回 null。
     */
    abstract public V get(Object key);

    /**
     * 在当前 dictionary 中映射指定键到指定值。键和值都不能为 null。
     *
     * 如果这个 dictionary 已经包含了一个指定键的条目,这个键返回的值已经在当前 dictionary 中,修改条目
     * 来包含新的元素。如果当前 dictionary 还没有一个对应于指定键的条目,一个对于指定键和值的条目将被创建,
     * 并且返回 null。
     *
     * 值可以被通过使用一个与原始键 equal 的键调用 get 方法返回。
     */
    abstract public V put(K key, V value);

    /**
     * 从当前 dictionary 中移除键(还有它的对应值)。如果键不在当前 dictionary 中则不做任何事。
     */
    abstract public V remove(Object key);
}

需要注意的是在 Dictionary 类中有两个方法 #keys 和 #elements,返回的是键s和条目s的枚举类型对象集合,了解了 Dictionary 抽象类之后就可以查看 Hashtable 的源码了。

/**
 * 这个类实现了一个 hash table,意味着映射键s到值s。任何非 null 的对象可以被用来作为一个键或者一个值。
 *
 * 为了成功地存储和从一个 hashtable 中遍历对象s,作为键s使用的对象s必须实现 hashCode 方法和 equals 方
 * 法。
 *
 * 一个 Hastable 实例有两个影响其性能的参数:初始化容量和加载因子。容量是在这个 hash table 中的桶的数量,
 * 初始化容量是这个 hash table 被创建时的容量。注意 这个 hash table 是开放的:发生 "hash 碰撞" 时,一个
 * 单桶存储了多个条目s,必须被按照顺序搜索。加载因子是一个这个 hash table 在它的容量被自动扩容之前允许到达
 * 多满的计量值。初始化容量和加载因子参数s仅仅是对实现类的暗示。具体的 rehash 方法子啊何时和如何被调用的细节
 * s是实现以来的。
 *
 * 通常的,默认加载因子 (.75) 提供了一个时间与空间话费的一个权衡。更高的值s减少了空间消耗但是提高了在查询一
 * 个条目(这将影响大部分 Hashtable 操作s,包括 get 和 put)时的时间消耗。
 *
 * 这个初始化容量控制一个为了 rehash 操作s的空间消耗和时间消耗的权衡,是时间消费的。如果初始化容量大于这个 
 * Hashtable 将包含的最大条目数量除以加载因子的值,rehash 操作s将不会发生。但是,甚至过高的初始化容量会浪
 * 费空间。
 * 
 * 如果在一个 Hashtable 中将要创建很多条目,通过一个足够大的容量而不是通过让它在需要扩容 table 时自动扩容
 * 来建立它可以允许条目s以更高效的方式被插入。
 *
 * 这个例子建立了一个 numbers 的 hashtable。它使用 numbers 的名称s作为键s:
 * <pre>   {@code
 *   Hashtable<String, Integer> numbers
 *     = new Hashtable<String, Integer>();
 *   numbers.put("one", 1);
 *   numbers.put("two", 2);
 *   numbers.put("three", 3);}</pre>
 *
 * <p>为了获得一个 number,使用下面的代码:
 * <pre>   {@code
 *   Integer n = numbers.get("two");
 *   if (n != null) {
 *     System.out.println("two = " + n);
 *   }}</pre>
 *
 * 通过 iterator 方法返回的   通过所有这个类的 “collection 视图方法s” 返回的 collections 迭代器s是 
 * fail-fast 的:如果这个 Hashtable 在迭代器被创建后的任意时间点被结构性的改动了,以除了通过迭代器自身 
 * remove 方法的方式,这个迭代器将抛出一个 {@link ConcurrentModificationException}。因此,因此,对于
 * 并发修改,迭代器失败的快速与干净,而不是在未来某个不确定的时间点冒险做任何不确定的行为。Hashtable 的键s和
 * 元素 elements 方法s返回的枚举对象s不是 fail-fast 的。
 *
 * 注意一个迭代器的 fail-fast 行为不能提供像它所描述的那样的保证,一般来说,不可能对于不同步的并发操作做任何
 * 硬性的保证。基于最好性能的基础,fail-fast 迭代器抛出一个 ConcurrentModificationException。因此,编 
 * 写一个依赖于这个异常来保证其正确性的程序是错误的:迭代器的 fail-fast 行为应该只被用于检查 bugs。
 *
 * Java Collections 框架。不像新的 collection 实现类s,{@code Hashtable} 是线程安全的。如果不需要一
 * 个线程安全的实现类,建议使用 {@link HashMap} 来替代 {@code Hashtable}。如果对于一个线程安全的实现类
 * 有很高的高并发需要,那么建议使用 {@link java.util.concurrent.ConcurrentHashMap} 来代替 {@code 
 * Hashtable}。
 * 
 * Hashtable 继承自 Dictionary,实现了 Map,Cloneable 和 Serializable 接口 
 */
public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable {
   

    /**
     * 这个 hash table 的数据。
     */
    private transient Entry<?,?>[] table;

    /**
     * 这个 hash table 中条目的总数。
     */
    private transient int count;

    /**
     * 当它的长度超过它的阈值时,这个 table 将被 rehashed。(这个类属性的值是 (int)(capacity * 
     * loadFactor))
     */
    private int threshold;

    /**
     * 这个 hashtable 的加载因子。
     */
    private float loadFactor;

    /**
     * 当前 Hashtable 已经被结构性变更的次数。结构性变更s是那些改变了当前 Hashtable 中条目数量或者改变了
     * 它的内部结构(比如rehash)的操作。这个类属性被用来使 Hashtable 的 Collection-views fail-
     * fast。(查看 ConcurrentModificationException)。
     */
    private transient int modCount = 0;

    private static final long serialVersionUID = 1421746759512286392L;

    /**
     * 使用指定的初始化容量和指定加载因子构造一个新的,空的 hashtable。
     */
    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)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    }

    /**
     * 使用指定初始化容量和默认加载因子(0.75)来构造一个新的,空的 hashtable。
     */
    public Hashtable(int initialCapacity) {
   
        this(initialCapacity, 0.75f);
    }

    /**
     * 使用一个默认初始化容量(11)和加载因子(0.75)来构造一个新的,空的 hashtable。这里和 HashMap 的默
     * 认初始化容量不同。
     */
    public Hashtable() {
   
        this(11, 0.75f);
    }

    /**
     * 使用于指定 Map 相同的映射s构造一个新的 hashtable。这个 hashtable 被使用一个足够装下指定 Map 
     * 中映射s的初始化容量和一个默认加载因子(0.75)创建。
     */
    public Hashtable(Map<? extends K, ? extends V> t) {
   
        this(Math.max(2*t.size(), 11), 0.75f);
        putAll(t);
    }

    /**
     * 返回当前 hashtable 中的键s数量。
     */
    public synchronized int size() {
   
        return count;
    }

    /**
     * 测试这个 hashtable 是否没有任何键s映射到值s。
     */
    public synchronized boolean isEmpty() {
   
        return count == 0;
    }

    /**
     * 返回当前 hashtable 中键s的枚举对象集合。
     */
    public synchronized Enumeration<K> keys() {
   
        return this.<K>getEnumeration(KEYS);
    }

    /**
     * 返回当前 hashtable 中值s的枚举对象集合。
     */
    public synchronized Enumeration<V> elements() {
   
        return this.<V>getEnumeration(VALUES);
    }

    /**
     * 判断是否一些键在当前 hashtable 中映射到了指定值。
     * 
     * 这个操作比 {@link #containsKey containsKey} 方法更昂贵。
     * 
     * 注意这个方法在功能上与 {@link #containsValue containsValue} 相同,(它是 collections 框架
     * 中 {@link Map} 接口的一部分)。
     *
     * 这里的处理与 HashMap 完全相同
     */
    public synchronized boolean contains(Object value) {
   
        if (value == null) {
   
            throw new NullPointerException();
        }

        Entry<?,?> tab[] = table;
        for (int i = tab.length ; i-- > 0 ;) {
    //循环数组
            for (Entry<?,?> e = tab[i] ; e != null ; e = e.next) {
    //遍历 Node 链下的所有 Nodes
                if (e.value.equals(value)) {
   
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 如果当前 hashtable 中有一个或者多个键s映射到这个值。
     *
     * 注意这个方法与 {@link#contains contains} 是等价的(早于 {@link Map} 接口)。
     */
    public boolean containsValue(Object value) {
   
        return contains(value);
    }

    /**
     * 测试指定对象是否是一个当前 hashtable 中键。
     */
    public synchronized boolean containsKey(Object key) {
   
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        /**
         * 这里 0x7FFFFFFF 是 int 类型的最大值(64 位,首位为 0,其余位都为 1),它计算条目在数组中
         * 的位置的方式也与 HashMap 中的不同。
         **/
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
    //找到对应位置的条目,链式遍历
            if ((e.hash == hash) && e.key.equals(key)) {
    
                return true;
            }
        }
        return false;
    }

    /**
     * 返回指定键映射到的值,或者如果当前 map 不包含这样的键则返回 {@code null}。
     *
     * 更一般地说,如果当前 map 的一个键 {@code k} 到一个值 {@code v} 存在 {@code 
     * (key.equals(k))} 的关系,那么当前方法返回 {@code v},不然的话它返回 {@code null}。(至多值
     * 能有一个这样的映射。
     */
    @SuppressWarnings("unchecked")
    public synchronized V get(Object key) {
   
        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.key.equals(key)) {
   
                return (V)e.value;
            }
        }
        return null;
    }

    /**
     * 可以分配的数组的最大长度。一些 VMs 保留一些头部字可能会导致 OutOfMemoryError:需要的数组长度超
     * 过了 VM 限制。
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    /**
     * 
     * 增加当前 hashtable 的容量并在内部组织此哈希表,为了更高效的容纳以及访问它的条目s。这个方法在 
     * hashtable 中的键s超过它的容量和加载因子时被调用。
     */
    @SuppressWarnings("unchecked")
    protected void rehash() {
   
        int oldCapacity = table.length;
        Entry<?,?>[] oldMap = table;

        // overflow-conscious code
        int newCapacity = (oldCapacity << 1) + 1; // oldCapacity * 2 + 1
        if (newCapacity - MAX_ARRAY_SIZE > 0) {
   
            if (oldCapacity == MAX_ARRAY_SIZE)
                // 继续使用 MAX_ARRAY_SIZE 桶s
                return;
            newCapacity = MAX_ARRAY_SIZE;
        }
        Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

        modCount++; //这里递增了 modCount,注意 HashMap resize 的时候并不会递增
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); //阈值
        table = newMap;

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

				//重新分配
                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                e.next = (Entry<K,V>)newMap[index];
                newMap[index] = e;
            }
        }
    }

    private void addEntry(int hash, K key, V value, int index) {
   
        modCount++;

        Entry<?,?> tab[] = table;
        if (count >= threshold) {
   
            // 如果超过了阈值,Rehash 当前 table
            rehash();

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

        // 创建新条目
        @SuppressWarnings("unchecked")
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值