• java源码阅读Hashtable


    1类签名与注释

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

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

    为了从散列表成功存储和检索对象,用作键的对象必须实现hashCode方法和equals方法。

    与HashMap类似,两个影响Hashtable性能的参数: 初始容量和负载因子 。 容量是哈希表中的桶数, 初始容量只是创建哈希表时的容量。 请注意 :在“哈希冲突”的情况下,单个存储桶存储多个条目,必须依次搜索。 负载因子(默认是0.75)是在容量自动增加之前允许哈希表得到满足的度量。 关于何时以及是否调用rehash方法的具体细节是依赖于实现的。

    所有这个类的“集合视图方法”返回的集合的iterator方法返回的迭代器是fail-fast的(fail-fast机制详见HashMap源码阅读)。

    Hashtable是同步的。 如果不需要线程安全的实现,建议使用HashMap代替Hashtable 。 如果需要线程安全的并发实现,那么建议使用ConcurrentHashMap代替Hashtable

    我理解官方文档这句话的意思大概是:我们尽量不要用Hashtable,可以用HashMap或ConcurrentHashMap代替该类。接下来我们只解读基本的put、get与remove的代码。

    2 put方法

     1 public synchronized V put(K key, V value) {
     2         // Make sure the value is not null
     3         if (value == null) {
     4             throw new NullPointerException();
     5         }
     6 
     7         // Makes sure the key is not already in the hashtable.
     8         Entry<?,?> tab[] = table;
     9         int hash = key.hashCode();
    10         int index = (hash & 0x7FFFFFFF) % tab.length;
    11         @SuppressWarnings("unchecked")
    12         Entry<K,V> entry = (Entry<K,V>)tab[index];
    13         for(; entry != null ; entry = entry.next) {
    14             if ((entry.hash == hash) && entry.key.equals(key)) {
    15                 V old = entry.value;
    16                 entry.value = value;
    17                 return old;
    18             }
    19         }
    20 
    21         addEntry(hash, key, value, index);
    22         return null;
    23     }

    put方法一开始就判断了value值不能为null,否则会报NullPointerException异常,但是没有判断key是否为空。我们写个代码测试一下:

    Hashtable<Integer,String> ht = new Hashtable<>();
    ht.put(null, "ouym");

    运行上面代码也会报NullPointerException异常,因为line9调用了key.hashCode(),若key为null当然会报空指针了。

    所以Hashtable是key和value都不能为null。

    接下来看Hashtable是如何根据hash值定位的呢?line10如下

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

    为什要& 0x7FFFFFFF ?(个人理解)0x7FFFFFFF=231,表示Integer的最大值。正常情况hash值应该是一个非负数,这种情况下hash & 0x7FFFFFFF的值与hash相等,但是不正常情况,例如求hash时越界的情况,hash变成了负数,hash & 0x7FFFFFFF等于求hash的补数(hash=-14,hash & 0x7FFFFFFF=231-14+1)。

    Hashtable通过% tab.length来定位,此时不得不想起HashMap的设计精妙之处了。通过取模有两个性能问题,首先,当tab.length的值较小的时候,决定定位的是hash值的低位,高位并没有起到作用,直接的后果是容易冲突。其次,取模操作效率低于与操作(HashMap是与操作)。

    然后检查key是否已经存在,若存在则覆盖已经存在的value,并返回旧的value。

    其他都没有问题,最后调用addEntry插入新的元素。

    3 get方法

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

    定位对应的桶之后开始遍历链表,找到匹配的key对应的value并返回。

    4 remove方法

     1 public synchronized V remove(Object key) {
     2         Entry<?,?> tab[] = table;
     3         int hash = key.hashCode();
     4         int index = (hash & 0x7FFFFFFF) % tab.length;
     5         @SuppressWarnings("unchecked")
     6         Entry<K,V> e = (Entry<K,V>)tab[index];
     7         for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) {
     8             if ((e.hash == hash) && e.key.equals(key)) {
     9                 modCount++;
    10                 if (prev != null) {
    11                     prev.next = e.next;
    12                 } else {
    13                     tab[index] = e.next;
    14                 }
    15                 count--;
    16                 V oldValue = e.value;
    17                 e.value = null;
    18                 return oldValue;
    19             }
    20         }
    21         return null;
    22     }

    line10 prev不为null表示要删除的元素不是链表中的第一个,那么只需要将待删除元素的前一个元素next指向删除元素的next即可。若prev为null,表示链表的第一个元素是待删除元素,至于要将链表的表头指向下一个元素即可(line13)。

    5总结

    由于改类使用情况不多,所以只做基本方法的解读。但还有一些问题需要注意

    (1)扩容问题

    当前容量超过总容量*负载因子(默认0.75)时进行扩容,容量变为原来的两倍。并rehash

    (2)为什么不推荐使用

    一个原因是实现的效率问题,上述代码分析了定位的实现过程确实不如HashMap的高效。那么其较HashMap唯一的优点线程安全又在ConcurrentHashMap被实现了。

    还有一个原因,Hashtable是基于古老的Dictionary实现的。Dictionary类已过时。 新的实现应该实现Map接口(如HashMap),而不是扩展这个类。

  • 相关阅读:
    Gym102028L
    CF985G
    三元环 & 四元环计数 学习笔记
    Hall 定理 学习笔记
    CF36E
    CF1110G
    P6071
    可持久化数据结构 学习笔记
    多项式全家桶
    c++ 编译zlib
  • 原文地址:https://www.cnblogs.com/ouym/p/9009339.html
Copyright © 2020-2023  润新知