Java-API简析_java.util.Hashtable<K, V>类(基于 Latest JDK)(浅析源码)

【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权)
https://blog.csdn.net/m0_69908381/article/details/132522445
出自【进步*于辰的博客

  1. 依赖类:CollectionsFloat
  2. 本篇解析需要结合博文《散列表的数据结构以及对象在JVM堆中的存储过程》去理解,例如:属性含义。

1、概述

继承关系:

  • java.lang.Object
    • java.util.Dictionary<K,V>
      • java.util.Hashtable<K,V>

所有已实现的接口:
Serializable、Cloneable、Map<K,V>

直接已知子类:
Properties、UIDefaults


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

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

为了成功地在哈希表中存储和检索对象,用作键的对象必须实现 hashCode()equals()

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

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

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

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

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

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

要检索一个数字,可以使用以下代码:

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

自 Java 2 平台 v1.2 以来,此类已经改进为可以实现 Map,因此它变成了 Java Collections Framework 的一部分。与新集合的实现不同,Hashtable 是同步的。

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

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

此类是 Java Collections Framework 的成员。

从以下版本开始:
JDK1.0
另请参见:
Object.equals(java.lang.Object)Object.hashCode()rehash()、Collection、Map、HashMapTreeMap序列化表格

2、构造方法摘要

属性说明:

private transient Entry<?,?>[] table;// 此哈希表的“数组”
private transient int count;// 当前容量,即 “桶”个数
private float loadFactor;// 加载因子
private int threshold;// 扩容阈值,值 = 容量 * 加载因子,即 initialCapacity * loadFactor
private transient int modCount = 0;// 此哈希表在结构上被修改的次数。用于支持迭代器“快速失败”

2.1 null

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

2.2 int initialCapacity

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

说明:前2个构造方法都仅有this(),指定initialCapacity(初始容量)为11loadFactor(加载因子)为0.75,故threshold(扩容阈值)为8

2.3 int initialCapacity, float 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)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
}

isNaN()见Float类的4.15项。

min()是对“扩容阈值”的限制。

MAX_ARRAY_SIZE的值是Integer.MAX_VALUE - 8,即VM分配给table的最大长度。至于为何threshold的最大值是MAX_ARRAY_SIZE + 1,暂未可知。

2.4 Map<? extends K,? extends V> t

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

public Hashtable(Map<? extends K, ? extends V> t) {
    this(Math.max(2*t.size(), 11), 0.75f);
    putAll(t);// 调用 3.15项
}

为何要乘以2?因为“扩容”机制是“翻倍”。

3、方法摘要

3.1 void clear()

将此哈希表清空,使其不包含任何键。

3.2 Object clone()

创建此哈希表的浅表复制。

3.3 boolean contains(Object value)

测试此映射表中是否存在与指定值关联的键。

3.4 boolean containsKey(Object key)

测试指定对象是否为此哈希表中的键。

3.5 boolean containsValue(Object value)

如果此 Hashtable 将一个或多个键映射到此值,则返回 true。

3.6 boolean equals(Object o)

按照 Map 接口的定义,比较指定 Object 与此 Map 是否相等。

3.7 V get(Object key)

返回此哈希表中指定键所映射到的值。

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

table是此类的底层数据存储结构。

  • int hash = key.hashCode()是加载过程的第一步,hash就是 h a s h c o d e \color{Orange}{hashcode} hashcode。若key的类型是 String,见String类的第2.22向。
  • int index = (hash & 0x7FFFFFFF) % tab.length就是第二步中的“运算”。
    其中,(hash & 0x7FFFFFFF)的结果就是hash,我暂不理解为什么如此运算。
    index即所定位的“桶”的索引。
  • for循环是第三步,只是我不理解为什么还要判断e.hash == hash。因为已经定位了“桶”,在同一个“桶”内,hash相同。

3.8 int hashCode()

按照 Map 接口的定义,返回此 Map 的哈希码值。

3.9 boolean isEmpty()

测试此哈希表是否没有键映射到值。

3.10 V put(K key, V value)

将指定 key 映射到此哈希表中的指定 value

public synchronized V put(K key, V value) {
    if (value == null) {
        throw new NullPointerException();
    }

	// 寻找是否具有相同的 key
    Entry<?,?> tab[] = table;
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;// 在散列表的“有效桶”列表中寻找应插入位置(“有效桶”指有“条目”的“桶”)

    Entry<K,V> entry = (Entry<K,V>)tab[index];// 确定“桶/链表”表头
    for(; entry != null ; entry = entry.next) {
        if ((entry.hash == hash) && entry.key.equals(key)) {
            V old = entry.value;
            entry.value = value;
            return old;
        }
    }

    addEntry(hash, key, value, index);// 插入新“条目”,见第 4.1项
    return null;
}

暂不理解为何index = (hash & 0x7FFFFFFF) % tab.length,约定如此。

3.11 void putAll(Map<? extends K,? extends V> t)

将指定 Map 的所有映射关系复制到此 Hashtable 中,这些映射关系将替换(key)此 Hashtable 拥有的、针对当前指定 Map 中所有键的所有映射关系。

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());// 调用上 1项
}

3.12 V remove(Object key)

从哈希表中移除该键及其相应的值。

3.13 int size()

返回此哈希表中的键的数量。

3.14 String toString()

返回此 Hashtable 对象的字符串表示形式,其形式为 ASCII 字符 ", " (逗号加空格)分隔开的、括在括号中的一组条目。

public synchronizedString 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(", ");
    }
}

核心逻辑是将entrySet转换成字符串。

3.15 Set<Map.Entry<K, V>> entrySet()

返回此 Hashtable 中所包含的键的 Set 视图。

public Set<Map.Entry<K,V>> entrySet() {
    if (entrySet==null)
        entrySet = Collections.synchronizedSet(new EntrySet(), this);
    return entrySet;
}

3.16 Set<K> keySet()

返回此 Hashtable 中所包含的键的 Set 视图。

public Set<K> keySet() {
    if (keySet == null)
        keySet = Collections.synchronizedSet(new KeySet(), this);
    return keySet;
}

3.17 Collection<V> values()

返回此 Hashtable 中所包含值的 Collection 视图。

public Collection<V> values() {
    if (values==null)
        values = Collections.synchronizedCollection(new ValueCollection(),
                                                    this);
    return values;
}

说明:第15~17项

这3个方法返回的都是“迭代器”,底层是构造相应内部类,见【嵌套类摘要】,“迭代器”都由getIterator()生成。

3.18 Enumeration<K> keys()

返回此哈希表中的键的枚举。

public synchronized Enumeration<K> keys() {
    return this.<K>getEnumeration(KEYS);
}

3.19 Enumeration<V> elements()

返回此哈希表中的值的枚举。

public synchronized Enumeration<V> elements() {
    return this.<V>getEnumeration(VALUES);
}

说明:第18、19项

与第15~17项类似,相同的是都是由内部类 Enumerator 生成,不同的是返回的是“枚举”。

但无论“迭代器”还是“枚举”,底层逻辑相同。

4、方法摘要(不开放)

4.1 private void addEntry(int hash, K key, V value, int index)

(业务待明。)

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

    Entry<?,?> tab[] = table;
    if (count >= threshold) {
        rehash();// 见下 1项

		// 散列表“重构”,故相关属性和位置等都需重新计算
        tab = table;
        hash = key.hashCode();
        index = (hash & 0x7FFFFFFF) % tab.length;
    }

    // 构造新“条目”,插入至“桶”表头(与rebash()相同)
    Entry<K,V> e = (Entry<K,V>) tab[index];
    tab[index] = new Entry<>(hash, key, value, e);
    count++;
}

4.2 protected void rehash()

增加此哈希表的容量并在内部对其进行重组,以便更有效地容纳和访问其元素。

protected void rehash() {
    int oldCapacity = table.length;
    Entry<?,?>[] oldMap = table;

    // “扩容”并创建新散列表
    int newCapacity = (oldCapacity << 1) + 1;// “扩容” 1倍,再加 1
    if (newCapacity - MAX_ARRAY_SIZE > 0) {
        if (oldCapacity == MAX_ARRAY_SIZE)
        	// 已达到JVM分配给散列表的最大长度,不能再扩容
        	// 不知为何不提前判断
            return;
        newCapacity = MAX_ARRAY_SIZE;
    }
    Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

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

注意:由于“拷贝条目”时,index是由newCapacity计算而来,而newCapacity在每次“扩容”时都是变化的,故index无规律。

4.3 private <T> Iterator<T> getIterator(int type)

private <T> Iterator<T> getIterator(int type) {
    if (count == 0) {
        return Collections.emptyIterator();
    } else {
        return new Enumerator<>(type, true);// 见第7.1项
    }
}

4.4 Enumeration<T> getEnumeration(int type)

private<T> Enumeration<T> getEnumeration(int type) {
    if (count == 0) {
        return Collections.emptyEnumeration();
    } else {
        return new Enumerator<>(type, false);// 见第7.1项
    }
}

5、嵌套类 Entry<K, V>

5.1 预览

哈希表桶冲突列表条目。

private static class Entry<K,V> implements Map.Entry<K,V> {
    final int hash;// 即 hashcode
    final 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;
    }

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

5.2 方法摘要

5.2.1 boolean equals(Object o)

判断此列表条目与指定列表条目是否相同。

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

为什么仅比较 \color{grey}{为什么仅比较} 为什么仅比较key 和 和 value ,不比较 \color{grey}{,不比较} ,不比较hash ? ? 因为hash是由内存地址经过一系列运算转化而来,而key存储的是“内存地址”。

6、嵌套类摘要

private class EntrySet extends AbstractSet<Map.Entry<K,V>> {
    public Iterator<Map.Entry<K,V>> iterator() {
        return getIterator(ENTRIES);
    }
    ...
}

private class KeySet extends AbstractSet<K> {
    public Iterator<K> iterator() {
        return getIterator(KEYS);
    }
    ...
}

private class ValueCollection extends AbstractCollection<V> {
    public Iterator<V> iterator() {
        return getIterator(VALUES);
    }
    ...
}

getIterator()见第4.3项,实参是一个常量,用于调用nextElement()(第7.2.2项)时判断应获取Entry哪部分数据。

7、嵌套类 Enumerator<T>

7.1 预览

private class Enumerator<T> implements Enumeration<T>, Iterator<T> {
    Entry<?,?>[] table = Hashtable.this.table;
    int index = table.length;
    Entry<?,?> entry;// 当前 entry
    Entry<?,?> lastReturned;// 最近(返回)的 entry
    int type;

	// true-迭代器,false-枚举
    boolean iterator;

    protected int expectedModCount = modCount;

    Enumerator(int type, boolean iterator) {
        this.type = type;
        this.iterator = iterator;
    }
    ...
}

7.2 方法摘要

7.2.1 boolean hasMoreElements()

判断是否存在下一个元素。

public boolean hasMoreElements() {
    Entry<?,?> e = entry;
    int i = index;
    Entry<?,?>[] t = table;
    while (e == null && i > 0) {// “倒序”遍历
        e = t[--i];
    }
    entry = e;
    index = i;
    return e != null;
}

与下1项连用,下1项可将entry置空。

7.2.2 public T nextElement()

返回下一个元素。

public T nextElement() {
    Entry<?,?> et = entry;
    int i = index;
    Entry<?,?>[] t = table;
    while (et == null && i > 0) {
        et = t[--i];
    }
    entry = et;
    index = i;
    if (et != null) {
        Entry<?,?> e = lastReturned = entry;
        entry = e.next;
        return type == KEYS ? (T)e.key : (type == VALUES ? (T)e.value : (T)e);
    }
    throw new NoSuchElementException("Hashtable Enumerator");
}

有待补充解析。

最后

本篇文章对 Hashtable 的理解与解析是基于本人的理解,不一定适合 Java 初学博友,如果需要,可查阅博文《Map 综述(四):彻头彻尾理解 HashTable》(转发)。

如果大家需要Java-API文档,我上传了《Java-API文档-包含5/8/11三个版本》。


本文暂缓更新。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进步·于辰

感谢打赏!很高兴可以帮到你!!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值