概述

和 HashMap 一样,Hashtable 也是一个散列表,它存储的内容是键值对。

Hashtable 在 Java 中的定义为:

  1. public class Hashtable<K,V>
  2. extends Dictionary<K,V>
  3. implements Map<K,V>, Cloneable, java.io.Serializable{}

从源码中,我们可以看出,Hashtable 继承于 Dictionary 类,实现了 Map, Cloneable, java.io.Serializable接口。其中Dictionary类是任何可将键映射到相应值的类(如 Hashtable)的抽象父类,每个键和值都是对象(源码注释为:The Dictionary class is the abstract parent of any class, such as Hashtable, which maps keys to values. Every key and every value is an object.)。但在这一点我开始有点怀疑,因为我查看了HashMap以及TreeMap的源码,都没有继承于这个类。不过当我看到注释中的解释也就明白了,其 Dictionary 源码注释是这样的:NOTE: This class is obsolete. New implementations should implement the Map interface, rather than extending this class. 该话指出 Dictionary 这个类过时了,新的实现类应该实现Map接口。

Hashtable 源码解读

成员变量

Hashtable是通过"拉链法"实现的哈希表。它包括几个重要的成员变量:table, count, threshold, loadFactor, modCount。

  • table是一个 Entry[] 数组类型,而 Entry(在 HashMap 中有讲解过)实际上就是一个单向链表。哈希表的"key-value键值对"都是存储在Entry数组中的。
  • count 是 Hashtable 的大小,它是 Hashtable 保存的键值对的数量。
  • threshold 是 Hashtable 的阈值,用于判断是否需要调整 Hashtable 的容量。threshold 的值="容量*加载因子"。
  • loadFactor 就是加载因子。
  • modCount 是用来实现 fail-fast 机制的。

关于变量的解释在源码注释中都有,最好还是应该看英文注释。

  1. /**
  2. * The hash table data.
  3. */
  4. private transient Entry<K,V>[] table;
  5. /**
  6. * The total number of entries in the hash table.
  7. */
  8. private transient int count;
  9. /**
  10. * The table is rehashed when its size exceeds this threshold. (The
  11. * value of this field is (int)(capacity * loadFactor).)
  12. *
  13. * @serial
  14. */
  15. private int threshold;
  16. /**
  17. * The load factor for the hashtable.
  18. *
  19. * @serial
  20. */
  21. private float loadFactor;
  22. /**
  23. * The number of times this Hashtable has been structurally modified
  24. * Structural modifications are those that change the number of entries in
  25. * the Hashtable or otherwise modify its internal structure (e.g.,
  26. * rehash). This field is used to make iterators on Collection-views of
  27. * the Hashtable fail-fast. (See ConcurrentModificationException).
  28. */
  29. private transient int modCount = 0;

构造方法

Hashtable 一共提供了 4 个构造方法:

  • public Hashtable(int initialCapacity, float loadFactor): 用指定初始容量和指定加载因子构造一个新的空哈希表。useAltHashing 为 boolean,其如果为真,则执行另一散列的字符串键,以减少由于弱哈希计算导致的哈希冲突的发生。
  • public Hashtable(int initialCapacity):用指定初始容量和默认的加载因子 (0.75) 构造一个新的空哈希表。
  • public Hashtable():默认构造函数,容量为 11,加载因子为 0.75。
  • public Hashtable(Map<? extends K, ? extends V> t):构造一个与给定的 Map 具有相同映射关系的新哈希表。
  1. /**
  2. * Constructs a new, empty hashtable with the specified initial
  3. * capacity and the specified load factor.
  4. *
  5. * @param initialCapacity the initial capacity of the hashtable.
  6. * @param loadFactor the load factor of the hashtable.
  7. * @exception IllegalArgumentException if the initial capacity is less
  8. * than zero, or if the load factor is nonpositive.
  9. */
  10. public Hashtable(int initialCapacity, float loadFactor) {
  11. if (initialCapacity < 0)
  12. throw new IllegalArgumentException("Illegal Capacity: "+
  13. initialCapacity);
  14. if (loadFactor <= 0 || Float.isNaN(loadFactor))
  15. throw new IllegalArgumentException("Illegal Load: "+loadFactor);
  16. if (initialCapacity==0)
  17. initialCapacity = 1;
  18. this.loadFactor = loadFactor;
  19. table = new Entry[initialCapacity];
  20. threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
  21. useAltHashing = sun.misc.VM.isBooted() &&
  22. (initialCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
  23. }
  24. /**
  25. * Constructs a new, empty hashtable with the specified initial capacity
  26. * and default load factor (0.75).
  27. *
  28. * @param initialCapacity the initial capacity of the hashtable.
  29. * @exception IllegalArgumentException if the initial capacity is less
  30. * than zero.
  31. */
  32. public Hashtable(int initialCapacity) {
  33. this(initialCapacity, 0.75f);
  34. }
  35. /**
  36. * Constructs a new, empty hashtable with a default initial capacity (11)
  37. * and load factor (0.75).
  38. */
  39. public Hashtable() {
  40. this(11, 0.75f);
  41. }
  42. /**
  43. * Constructs a new hashtable with the same mappings as the given
  44. * Map. The hashtable is created with an initial capacity sufficient to
  45. * hold the mappings in the given Map and a default load factor (0.75).
  46. *
  47. * @param t the map whose mappings are to be placed in this map.
  48. * @throws NullPointerException if the specified map is null.
  49. * @since 1.2
  50. */
  51. public Hashtable(Map<? extends K, ? extends V> t) {
  52. this(Math.max(2*t.size(), 11), 0.75f);
  53. putAll(t);
  54. }

put 方法

put 方法的整个流程为:

  1. 判断 value 是否为空,为空则抛出异常;
  2. 计算 key 的 hash 值,并根据 hash 值获得 key 在 table 数组中的位置 index,如果 table[index] 元素不为空,则进行迭代,如果遇到相同的 key,则直接替换,并返回旧 value;
  3. 否则,我们可以将其插入到 table[index] 位置。

我在下面的代码中也进行了一些注释:

  1. public synchronized V put(K key, V value) {
  2. // Make sure the value is not null确保value不为null
  3. if (value == null) {
  4. throw new NullPointerException();
  5. }
  6. // Makes sure the key is not already in the hashtable.
  7. //确保key不在hashtable中
  8. //首先,通过hash方法计算key的哈希值,并计算得出index值,确定其在table[]中的位置
  9. //其次,迭代index索引位置的链表,如果该位置处的链表存在相同的key,则替换value,返回旧的value
  10. Entry tab[] = table;
  11. int hash = hash(key);
  12. int index = (hash & 0x7FFFFFFF) % tab.length;
  13. for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
  14. if ((e.hash == hash) && e.key.equals(key)) {
  15. V old = e.value;
  16. e.value = value;
  17. return old;
  18. }
  19. }
  20. modCount++;
  21. if (count >= threshold) {
  22. // Rehash the table if the threshold is exceeded
  23. //如果超过阀值,就进行rehash操作
  24. rehash();
  25. tab = table;
  26. hash = hash(key);
  27. index = (hash & 0x7FFFFFFF) % tab.length;
  28. }
  29. // Creates the new entry.
  30. //将值插入,返回的为null
  31. Entry<K,V> e = tab[index];
  32. // 创建新的Entry节点,并将新的Entry插入Hashtable的index位置,并设置e为新的Entry的下一个元素
  33. tab[index] = new Entry<>(hash, key, value, e);
  34. count++;
  35. return null;
  36. }

通过一个实际的例子来演示一下这个过程:

假设我们现在Hashtable的容量为5,已经存在了(5,5),(13,13),(16,16),(17,17),(21,21)这 5 个键值对,目前他们在Hashtable中的位置如下:

现在,我们插入一个新的键值对,put(16,22),假设key=16的索引为1.但现在索引1的位置有两个Entry了,所以程序会对链表进行迭代。迭代的过程中,发现其中有一个Entry的key和我们要插入的键值对的key相同,所以现在会做的工作就是将newValue=22替换oldValue=16,然后返回oldValue=16.

然后我们现在再插入一个,put(33,33),key=33的索引为3,并且在链表中也不存在key=33的Entry,所以将该节点插入链表的第一个位置。

get 方法

相比较于 put 方法,get 方法则简单很多。其过程就是首先通过 hash()方法求得 key 的哈希值,然后根据 hash 值得到 index 索引(上述两步所用的算法与 put 方法都相同)。然后迭代链表,返回匹配的 key 的对应的 value;找不到则返回 null。

  1. public synchronized V get(Object key) {
  2. Entry tab[] = table;
  3. int hash = hash(key);
  4. int index = (hash & 0x7FFFFFFF) % tab.length;
  5. for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
  6. if ((e.hash == hash) && e.key.equals(key)) {
  7. return e.value;
  8. }
  9. }
  10. return null;
  11. }

Hashtable 遍历方式

Hashtable 有多种遍历方式:

  1. //1、使用keys()
  2. Enumeration<String> en1 = table.keys();
  3. while(en1.hasMoreElements()) {
  4. en1.nextElement();
  5. }
  6. //2、使用elements()
  7. Enumeration<String> en2 = table.elements();
  8. while(en2.hasMoreElements()) {
  9. en2.nextElement();
  10. }
  11. //3、使用keySet()
  12. Iterator<String> it1 = table.keySet().iterator();
  13. while(it1.hasNext()) {
  14. it1.next();
  15. }
  16. //4、使用entrySet()
  17. Iterator<Entry<String, String>> it2 = table.entrySet().iterator();
  18. while(it2.hasNext()) {
  19. it2.next();
  20. }

Hashtable 与 HashMap 的简单比较

  1. HashTable 基于 Dictionary 类,而 HashMap 是基于 AbstractMap。Dictionary 是任何可将键映射到相应值的类的抽象父类,而 AbstractMap 是基于 Map 接口的实现,它以最大限度地减少实现此接口所需的工作。
  2. HashMap 的 key 和 value 都允许为 null,而 Hashtable 的 key 和 value 都不允许为 null。HashMap 遇到 key 为 null 的时候,调用 putForNullKey 方法进行处理,而对 value 没有处理;Hashtable遇到 null,直接返回 NullPointerException。
  3. Hashtable 方法是同步,而HashMap则不是。我们可以看一下源码,Hashtable 中的几乎所有的 public 的方法都是 synchronized 的,而有些方法也是在内部通过 synchronized 代码块来实现。所以有人一般都建议如果是涉及到多线程同步时采用 HashTable,没有涉及就采用 HashMap,但是在 Collections 类中存在一个静态方法:synchronizedMap(),该方法创建了一个线程安全的 Map 对象,并把它作为一个封装的对象来返回。

Hashtable 的实现原理的更多相关文章

  1. Java基础知识强化之集合框架笔记67:Hashtable的实现原理

    至于Hashtable的实现原理,直接参考网友的博客,总结很全面: 深入Java集合学习系列:Hashtable的实现原理

  2. 【C# 集合】HashTable .net core 中的Hashtable的实现原理

    上一篇我介绍了Hash函数 这篇我来说一下Hash函数在 HashTable中的应用. HashTable的特性: 1.装载因子:.net core 0.72 ,java 0.75 2.冲突解决方案: ...

  3. 集合总结五(Hashtable的实现原理)

    一.概述 上一篇介绍了Java8的HashMap,接下来准备介绍一下Hashtable. Hashtable可以说已经具有一定的历史了,现在也很少使用到Hashtable了,更多的是使用HashMap ...

  4. HashMap和ConcurrentHashMap和HashTable的底层原理与剖析

    HashMap  可以允许key为null,value为null,但HashMap的是线程不安全的  HashMap 底层是数组 + 链表的数据结构 在jdk 1.7 中 map集合中的每一项都是一个 ...

  5. HashTable的实现原理

    转载:http://wiki.jikexueyuan.com/project/java-collection/hashtable.html 概述 和 HashMap 一样,Hashtable 也是一个 ...

  6. Hashtable,HashMap实现原理

    http://blog.csdn.net/czh0766/article/details/5260360 昨天看了算法导论对散列表的介绍,今天看了一下Hashtable, HashMap这两个类的源代 ...

  7. 深入Java集合学习系列:Hashtable的实现原理

    第1部分 Hashtable介绍 和HashMap一样,Hashtable也是一个散列表,它存储的内容是键值对(key-value)映射.Hashtable继承于Dictionary,实现了Map.C ...

  8. HashTable & HashMap & ConcurrentHashMap 原理与区别

    一.三者的区别     HashTable HashMap ConcurrentHashMap 底层数据结构 数组+链表 数组+链表 数组+链表 key可为空 否 是 否 value可为空 否 是 否 ...

  9. HashMap和Hashtable的实现原理

    HashMap和Hashtable的底层实现都是数组+链表结构实现的,这点上完全一致 添加.删除.获取元素时都是先计算hash,根据hash和table.length计算index也就是table数组 ...

随机推荐

  1. 工作中,如何衡量一个人的 JavaScript 编码水平?

    1.立即执行函数 立即执行函数,即Immediately Invoked Function Expression (IIFE),正如它的名字,就是创建函数的同时立即执行.它没有绑定任何事件,也无需等待 ...

  2. NXNSAttack漏洞简析

    漏洞简介: 该漏洞为DNS 放大攻击,是 DDoS 攻击,攻击者利用 DNS 服务器中的漏洞将小查询转换为可能破坏目标服务器的更大负载. 在 NXNSAttack 的情况下,远程攻击者可以通过向易受攻 ...

  3. 深入浅出,遇见Windows Terminal(Windows终端器),体验及美化新一代终端神器

    Windows Terminal 简介 Windows Terminal is a new, modern, feature-rich, productive terminal application ...

  4. 『动善时』JMeter基础 — 52、使用JMeter测试Dubbo接口

    目录 1.Dubbo介绍 2.准备测试Dubbo接口的环境 3.Dubbo Sample界面详解 4.Dubbo Sample组件的使用 (1)测试计划内包含的元件 (2)使用zookeeper协议请 ...

  5. 7.6、openstack网络拓扑

    1.openstack官方架构图: 2.openstack服务常用服务的端口号: mysql:3306 keystone:5000 memcache:11211 rabbitmq:5672 rabbi ...

  6. 40、Linux文件误删除恢复操作

    rm -rf / #此方法删除不了/目录: rm -rf /* #此方法可以删除/目录下的所有内容,禁止使用: 40.1.前言: 作为一个多用户.多任务的操作系统,Linux下的文件一旦被删除,是难以 ...

  7. 友华新光猫PT924G破解telnet之路

    最近去找电信要了个新的千兆光猫(电信宽带300兆配100兆光猫真鸡贼),背后一看不是华为了,是友华PT924G,在http://192.168.1.1:8080/里看到了熟悉的电信界面 用teleco ...

  8. Java运算中的类型转换

    类型转换 运算中,不同类型的数据先转化为同一类型,然后进行运算 public class Dome04 { public static void main(String[] args) { //int ...

  9. 面试题五:Spring

    Spring IoC 什么是IoC? 容器创建Bean对象,将他们装配在一起,配置并且管理它们的完整生命周期. Spring容器使用依赖注入来管理组成应用程序的Bean对象: 容器通过提供的配置元数据 ...

  10. 大数据-Hadoop虚拟机的准备以及配置(三台机子)

    虚拟机的准备 修改静态IP(克隆的虚拟机) vim /etc/udev/rules.d/70-persistent-net.rules 配置网络: Vim /etc/sysconfig/network ...