【Java集合框架库】HashTable类

HashTable类

HashTable同样是基于哈希表实现的,其中的每个元素是一个key-value对,内部也是通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长。它在很大程度上和HashMap的实现差不多,不过相对的功能较少,目前已经很少使用了,下面来看看HashTable的具体内容。

HashTable的特点

HashTable的继承关系:

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键值对"接口。即接口中的每个元素都是一对, 以key-value的形式保存,并且Key是不重复的,元素的存储位置由key决定。也就是可以通过key去寻找key-value的位置,从而得到value的值。适合做查找工作。
  • 实现了Cloneable接口,能被克隆。Cloneable是标记型的接口,它们内部都没有方法和属性,实现 Cloneable来表示该对象能被克隆,能使用Object.clone()方法。如果没有实现 Cloneable的类对象调用clone()就会抛出CloneNotSupportedException。
  • 实现了Serializable接口,它支持序列化。public interface Serializable类通过实现 java.io.Serializable 接口以启用其序列化功能。
HashTable的存储结构
/**
 * The hash table data.
 */
private transient Entry<?,?>[] table;

/**
 * Hashtable bucket collision list entry
 */
private static class Entry<K,V> implements Map.Entry<K,V> {
    final int hash;
    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;
    }
}
  • 从上面的结构可以看出。HashTable的存储结构与HashMap的存储结构相同,这里就不多介绍了。
HashTable的遍历方式

HashTable提供了两种遍历方式:

private class Enumerator<T> implements Enumeration<T>, Iterator<T> { }

其中:

  • Enumeration<T>:利用枚举的方式从前向后遍历集合,不支持快速失败机制。Enumeration迭代器只能遍历Vector、Hashtable这种古老的集合,因此通常不要使用它,除非在某些极端情况下,不得不使用Enumeration,否则都应该选择Iterator迭代器。
  • Iterator<T>:迭代器遍历方式。支持快速失败机制,目前使用较多的遍历方式。
HashTable源码分析
1、成员变量
{
    private transient Entry<?,?>[] table;
    private transient int count;
    private int threshold;
    private float loadFactor;
    private transient int modCount = 0;
}
  • table:为一个Entry[]数组类型,Entry代表了“拉链”的节点,每一个Entry代表了一个键值对,哈希表的"key-value键值对"都是存储在Entry数组中的。
  • count:HashTable的大小,注意这个大小并不是HashTable的容器大小,而是他所包含Entry键值对的数量。
  • threshold:Hashtable的阈值,用于判断是否需要调整Hashtable的容量。threshold的值=“容量*加载因子”。
  • loadFactor:加载因子。
  • modCount:用来实现“fail-fast”机制的(也就是快速失败)。所谓快速失败就是在并发集合中,其进行迭代操作时,若有其他线程对其进行结构性的修改,这时迭代器会立马感知到,并且立即抛出ConcurrentModificationException异常,而不是等到迭代完成之后才告诉你。
2、构造函数
  • 默认构造函数,容量为11,加载因子为0.75。
/**
 * Constructs a new, empty hashtable with a default initial capacity (11)
 * and load factor (0.75).
 */
public Hashtable() {
    this(11, 0.75f);
}
  • 用指定初始容量和默认的加载因子 (0.75) 构造一个新的空哈希表。
/**
 * Constructs a new, empty hashtable with the specified initial capacity
 * and default load factor (0.75).
 *
 * @param     initialCapacity   the initial capacity of the hashtable.
 * @exception IllegalArgumentException if the initial capacity is less
 *              than zero.
 */
public Hashtable(int initialCapacity) {
    this(initialCapacity, 0.75f);
}
  • 用指定初始容量和指定加载因子构造一个新的空哈希表。其中initHashSeedAsNeeded方法用于初始化hashSeed参数,其中hashSeed用于计算key的hash值,它与key的hashCode进行按位异或运算。这个hashSeed是一个与实例相关的随机值,主要用于解决hash冲突。
/**
 * Constructs a new, empty hashtable with the specified initial
 * capacity and the specified load factor.
 *
 * @param      initialCapacity   the initial capacity of the hashtable.
 * @param      loadFactor        the load factor of the hashtable.
 * @exception  IllegalArgumentException  if the initial capacity is less
 *             than zero, or if the load factor is nonpositive.
 */
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);
}
  • 构造一个与给定的 Map 具有相同映射关系的新哈希表。
/**
 * 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);
}
3、HashTable的API
  • 首先我们先看put方法:将指定 key 映射到此哈希表中的指定 value。注意这里键key和值value都不可为空。
    • put方法的整个处理流程是:计算key的hash值,根据hash值获得key在table数组中的索引位置,然后迭代该key处的Entry链表(我们暂且理解为链表),若该链表中存在一个这个的key对象,那么就直接替换其value值即可,否则在将改key-value节点插入该index索引位置处。
/**
 * Maps the specified <code>key</code> to the specified
 * <code>value</code> in this hashtable. Neither the key nor the
 * value can be <code>null</code>. <p>
 *
 * The value can be retrieved by calling the <code>get</code> method
 * with a key that is equal to the original key.
 *
 * @param      key     the hashtable key
 * @param      value   the value
 * @return     the previous value of the specified key in this hashtable,
 *             or <code>null</code> if it did not have one
 * @exception  NullPointerException  if the key or value is
 *               <code>null</code>
 * @see     Object#equals(Object)
 * @see     #get(Object)
 */
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;
    @SuppressWarnings("unchecked")
    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);
    return null;
}
  • get方法:
    • 处理过程就是计算key的hash值,判断在table数组中的索引位置,然后迭代链表,匹配直到找到相对应key的value,若没有找到返回null。
/**
 * Returns the value to which the specified key is mapped,
 * or {@code null} if this map contains no mapping for the key.
 *
 * <p>More formally, if this map contains a mapping from a key
 * {@code k} to a value {@code v} such that {@code (key.equals(k))},
 * then this method returns {@code v}; otherwise it returns
 * {@code null}.  (There can be at most one such mapping.)
 *
 * @param key the key whose associated value is to be returned
 * @return the value to which the specified key is mapped, or
 *         {@code null} if this map contains no mapping for the key
 * @throws NullPointerException if the specified key is null
 * @see     #put(Object, Object)
 */
@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;
}
  • remove方法:
/**
 * Removes the key (and its corresponding value) from this
 * hashtable. This method does nothing if the key is not in the hashtable.
 *
 * @param   key   the key that needs to be removed
 * @return  the value to which the key had been mapped in this hashtable,
 *          or <code>null</code> if the key did not have a mapping
 * @throws  NullPointerException  if the key is <code>null</code>
 */
public synchronized V remove(Object key) {
    Entry<?,?> tab[] = table;
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    @SuppressWarnings("unchecked")
    Entry<K,V> e = (Entry<K,V>)tab[index];
    for(Entry<K,V> 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;
}
HashTable与HashMap的对比

HashTable和HashMap存在很多的相同点,但是他们还是有几个比较重要的不同点。

  • 1、我们从他们的定义就可以看出他们的不同,HashTable基于Dictionary类,而HashMap是基于AbstractMap。Dictionary是什么?它是任何可将键映射到相应值的类的抽象父类,而AbstractMap是基于Map接口的骨干实现,它以最大限度地减少实现此接口所需的工作。
  • 2、HashMap可以允许存在一个为null的key和任意个为null的value,但是HashTable中的key和value都不允许为null。
//当HashMap遇到为null的key时,它会调用putForNullKey方法来进行处理。
//对于value没有进行任何处理,只要是对象都可以。
if (key == null)  return putForNullKey(value);

//而当HashTable遇到null时,他会直接抛出NullPointerException异常信息。
if (value == null)  throw new NullPointerException();
  • 3、Hashtable的方法是同步的,而HashMap的方法不是。所以有人一般都建议如果是涉及到多线程同步时采用HashTable,没有涉及就采用HashMap,但是在Collections类中存在一个静态方法:synchronizedMap(),该方法创建了一个线程安全的Map对象,并把它作为一个封装的对象来返回,所以通过Collections类的synchronizedMap方法可以模拟同步访问潜在的HashMap。
  • 4、是否提供contains方法,HashMap把Hashtable的contains方法去掉了,改成containsValue和containsKey,因为contains方法容易让人引起误解。Hashtable则保留了contains,containsValue和containsKey三个方法,其中contains和containsValue功能相同。
  • 5、两个遍历方式的内部实现上不同,Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。
  • 6、hash值不同,哈希值的使用不同,HashTable直接使用对象的hashCode。而HashMap重新计算hash值。Hashtable计算hash值,直接用key的hashCode(),而HashMap重新计算了key的hash值,Hashtable在求hash值对应的位置索引时,用取模运算,而HashMap在求位置索引时,则用与运算,且这里一般先用hash & 0x7FFFFFFF后,再对length取模,& 0x7FFFFFFF的目的是为了将负的hash值转化为正值,因为hash值有可能为负数,而 & 0x7FFFFFFF后,只有符号外改变,而后面的位都不变。
  • 7、内部实现使用的数组初始化和扩容方式不同,HashTable在不指定容量的情况下的默认容量为11,而HashMap为16,Hashtable不要求底层数组的容量一定要为2的整数次幂,而HashMap则要求一定为2的整数次幂。Hashtable扩容时,将容量变为原来的2倍加1,而HashMap扩容时,将容量变为原来的2倍。
对比项HashTableHashMap
继承的父类和接口继承Dictionary类,实现了Map, Cloneable, Serializable接口继承AbstractMap类,实现了Map, Cloneable, Serializable接口
默认容量1116
Table的初始化时机直接在构造对象时初始化第一次添加数据时初始化
线程是否安全由于加了synchronized,所以线程安全。(但并不是绝对安全,由于HashTable效率低下且已被淘汰,一般在需要线程安全的场景时选择ConcurrentHashMap线程不安全
遍历方式既可以使用Iterator,也可以使用Enumeration只可以使用Iterator
是否支持fast-fail(快速失败机制)使用Iterator遍历时支持,使用Enumeration遍历时不支持使用Iterator遍历时支持
是否可以添加重复的key不能,会使用新值替换旧值不能,会使用新值替换旧值
是否接受值为null的Key或ValueKey或Value都不可以为空,会抛出NullPointerException异常Key或Value都可以为空
根据hash值计算数组下标的算法没有扰动处理,哈希冲突重复率高,使用(hash & 0x7FFFFFFF) % tab.length;确保index为正数并且不会越界有扰动处理,哈希冲突重复率低,使用h & (length-1);确保index不会越界
Entry数组的长度1116(并且始终为2的幂次方)
LoadFactor负荷因子0.750.75
负荷超过阈值时,内部数据的调整方式将容量变为原来的2倍加1将容量变为原来的2倍
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值