HashTable

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

HashMap只实现了Map接口,而HashTable还继承了Dictionary类。但实际上Dictionary类只是一个历史遗留问题,任何新的键值对集合都只需要实现Map接口。

1. 构造方法

  1. /**
  2. * Constructs a new, empty hashtable with a default initial capacity (11)
  3. * and load factor (0.75).
  4. */
  5. public Hashtable() {
  6. this(11, 0.75f);
  7. }

HashTable的默认容量是11,默认负载因子是0.75。HashMap的这两个值分别是16和0.75。

2. Properties类和Void类

在阅读到HashTable的构造函数时,我看到了一个奇怪的构造函数:

  1. /**
  2. * A constructor chained from {@link Properties} keeps Hashtable fields
  3. * uninitialized since they are not used.
  4. *
  5. * @param dummy a dummy parameter
  6. */
  7. Hashtable(Void dummy) {}

首先,在参数列表中出现了一个我从来没见过的类:Void类。我们进入查看这个类的定义。

  1. package java.lang;
  2. /**
  3. * The {@code Void} class is an uninstantiable placeholder class to hold a
  4. * reference to the {@code Class} object representing the Java keyword
  5. * void.
  6. *
  7. * @author unascribed
  8. * @since 1.1
  9. */
  10. public final class Void {
  11. /**
  12. * The {@code Class} object representing the pseudo-type corresponding to
  13. * the keyword {@code void}.
  14. */
  15. @SuppressWarnings("unchecked")
  16. public static final Class<Void> TYPE = (Class<Void>) Class.getPrimitiveClass("void");
  17. /*
  18. * The Void class cannot be instantiated.
  19. */
  20. private Void() {}
  21. }

可以看到,Void类是一个final类,同时只有一个私有的构造函数。显然,这个类既不可以被继承,也不可以被实例化。在doc中我们得知,这是一个占位符类,用于保存对表示Java关键字voidClass对象的引用。

我们可以想到,假如将Void类作为一个方法的返回类型,这意味着这个方法只能返回null。

  1. public Void f(){
  2. ...
  3. }

那么在这里作为参数的Void类又起到怎样的功能呢?继续阅读构造方法上方的doc,我们发现,这个函数是供java.util.Properties类使用的,Properties类中有下面这个构造函数。

  1. private Properties(Properties defaults, int initialCapacity) {
  2. // use package-private constructor to
  3. // initialize unused fields with dummy values
  4. super((Void) null);
  5. map = new ConcurrentHashMap<>(initialCapacity);
  6. this.defaults = defaults;
  7. // Ensure writes can't be reordered
  8. UNSAFE.storeFence();
  9. }

Properties类是HashTable类的子类,在这个构造函数中它调用了HashTable中包级私有的构造方法。如果没有这个super语句的话,就会默认调用父类HashTable的无参构造函数,但显然,这是没有必要的。通过在HashTable中添加一个伪构造方法供Properties类调用,可以避免这种无必要的开销。

这里顺便说明一下Properties类。

  • Properties类表示一组持久的属性。表示一个持久的属性集,属性列表中每个键及其对应值都是一个字符串。

  • Properties 类被许多 Java 类使用。例如,在获取环境变量时它就作为 System.getProperties() 方法的返回值。

  • Properties 定义如下实例变量.这个变量持有一个 Properties 对象相关的默认属性列表。

    1. Properties defaults;

3. HashTable中实现线程安全的方式

HashTable中为几乎所有业务方法都添加了synchronized关键字,实现了线程安全。

  1. public synchronized int size() {...}
  2. public synchronized boolean isEmpty() {...}
  3. public synchronized V put(K key, V value) {...}
  4. public synchronized V get(Object key) {...}
  5. public synchronized V remove(Object key) {...}

4. keys方法和elements方法

  1. public synchronized Enumeration<K> keys() {
  2. return this.<K>getEnumeration(KEYS);
  3. }
  4. public synchronized Enumeration<V> elements() {
  5. return this.<V>getEnumeration(VALUES);
  6. }

返回了键集合和值集合的枚举。

  1. private <T> Enumeration<T> getEnumeration(int type) {
  2. if (count == 0) {
  3. return Collections.emptyEnumeration();
  4. } else {
  5. return new Enumerator<>(type, false);
  6. }
  7. }

下面是作为HashTable类内部类的枚举类

  1. private class Enumerator<T> implements Enumeration<T>, Iterator<T> {
  2. final Entry<?,?>[] table = Hashtable.this.table;
  3. int index = table.length;
  4. Entry<?,?> entry;
  5. Entry<?,?> lastReturned;
  6. final int type;
  7. /**
  8. * Indicates whether this Enumerator is serving as an Iterator
  9. * or an Enumeration. (true -> Iterator).
  10. */
  11. final boolean iterator;
  12. /**
  13. * The modCount value that the iterator believes that the backing
  14. * Hashtable should have. If this expectation is violated, the iterator
  15. * has detected concurrent modification.
  16. */
  17. protected int expectedModCount = Hashtable.this.modCount;
  18. Enumerator(int type, boolean iterator) {
  19. this.type = type;
  20. this.iterator = iterator;
  21. }
  22. public boolean hasMoreElements() {
  23. ...
  24. }
  25. @SuppressWarnings("unchecked")
  26. public T nextElement() {
  27. Entry<?,?> et = entry;
  28. int i = index;
  29. Entry<?,?>[] t = table;
  30. /* Use locals for faster loop iteration */
  31. while (et == null && i > 0) {
  32. et = t[--i];
  33. }
  34. entry = et;
  35. index = i;
  36. if (et != null) {
  37. Entry<?,?> e = lastReturned = entry;
  38. entry = e.next;
  39. return type == KEYS ? (T)e.key : (type == VALUES ? (T)e.value : (T)e);
  40. }
  41. throw new NoSuchElementException("Hashtable Enumerator");
  42. }
  43. // Iterator methods
  44. public boolean hasNext() {
  45. return hasMoreElements();
  46. }
  47. public T next() {
  48. if (Hashtable.this.modCount != expectedModCount)
  49. throw new ConcurrentModificationException();
  50. return nextElement();
  51. }
  52. public void remove() {
  53. ...
  54. }
  55. }

5. HashTable中定位下标的方法

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

hash & 0x7FFFFFFF是为了保证hash值是一个正数,即二进制下第一位是0。

然后直接对length取模。

6. rehash方法和addEntry方法

  1. protected void rehash() {
  2. int oldCapacity = table.length;
  3. Entry<?,?>[] oldMap = table;
  4. // 扩容逻辑:乘二加一
  5. int newCapacity = (oldCapacity << 1) + 1;
  6. if (newCapacity - MAX_ARRAY_SIZE > 0) {
  7. if (oldCapacity == MAX_ARRAY_SIZE)
  8. // 原容量就满了,则直接返回
  9. return;
  10. newCapacity = MAX_ARRAY_SIZE;
  11. }
  12. // 创建新表
  13. Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
  14. modCount++;
  15. threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
  16. table = newMap;
  17. // 把旧表中的键值对复制到新表,并重新组织位置
  18. for (int i = oldCapacity ; i-- > 0 ;) {
  19. for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
  20. Entry<K,V> e = old;
  21. old = old.next;
  22. int index = (e.hash & 0x7FFFFFFF) % newCapacity;
  23. e.next = (Entry<K,V>)newMap[index];
  24. newMap[index] = e;
  25. }
  26. }
  27. }

该方法会增加哈希表的容量并在内部重新组织哈希表。当哈希表中的键数超过此哈希表的容量*负载因子时,将自动调用此方法。扩容的逻辑是容量*2+1。

下面的方法用于向表中假如键值对,在put等方法中都有调用。

  1. private void addEntry(int hash, K key, V value, int index) {
  2. Entry<?,?> tab[] = table;
  3. if (count >= threshold) {
  4. // 先调用rehash()
  5. rehash();
  6. tab = table;
  7. hash = key.hashCode();
  8. index = (hash & 0x7FFFFFFF) % tab.length;
  9. }
  10. // Creates the new entry.
  11. @SuppressWarnings("unchecked")
  12. Entry<K,V> e = (Entry<K,V>) tab[index];
  13. tab[index] = new Entry<>(hash, key, value, e);
  14. count++;
  15. modCount++;
  16. }

JDK源码阅读(5):HashTable类阅读笔记的更多相关文章

  1. 【java源码】解读HashTable类背后的实现细节

    HashTable这个类实现了哈希表从key映射到value的数据结构形式.任何非null的对象都可以作为key或者value. 要在hashtable中存储和检索对象,作为key的对象必须实现has ...

  2. 【并发编程】【JDK源码】JDK的(J.U.C)java.util.concurrent包结构

    本文从JDK源码包中截取出concurrent包的所有类,对该包整体结构进行一个概述. 在JDK1.5之前,Java中要进行并发编程时,通常需要由程序员独立完成代码实现.当然也有一些开源的框架提供了这 ...

  3. JDK源码那些事儿之浅析Thread上篇

    JAVA中多线程的操作对于初学者而言是比较难理解的,其实联想到底层操作系统时我们可能会稍微明白些,对于程序而言最终都是硬件上运行二进制指令,然而,这些又太过底层,今天来看一下JAVA中的线程,浅析JD ...

  4. JDK源码阅读(一):Object源码分析

    最近经过某大佬的建议准备阅读一下JDK的源码来提升一下自己 所以开始写JDK源码分析的文章 阅读JDK版本为1.8 目录 Object结构图 构造器 equals 方法 getClass 方法 has ...

  5. jdk源码阅读笔记-HashSet

    通过阅读源码发现,HashSet底层的实现源码其实就是调用HashMap的方法实现的,所以如果你阅读过HashMap或对HashMap比较熟悉的话,那么阅读HashSet就很轻松,也很容易理解了.我之 ...

  6. 如何阅读jdk源码?

    简介 这篇文章主要讲述jdk本身的源码该如何阅读,关于各种框架的源码阅读我们后面再一起探讨. 笔者认为阅读源码主要包括下面几个步骤. 设定目标 凡事皆有目的,阅读源码也是一样. 从大的方面来说,我们阅 ...

  7. JDK源码阅读顺序

      很多java开发的小伙伴都会阅读jdk源码,然而确不知道应该从哪读起.以下为小编整理的通常所需阅读的源码范围. 标题为包名,后面序号为优先级1-4,优先级递减 1.java.lang 1) Obj ...

  8. JDK源码阅读(三):ArraryList源码解析

    今天来看一下ArrayList的源码 目录 介绍 继承结构 属性 构造方法 add方法 remove方法 修改方法 获取元素 size()方法 isEmpty方法 clear方法 循环数组 1.介绍 ...

  9. 如何阅读JDK源码

    JDK源码阅读笔记: https://github.com/kangjianwei/LearningJDK 如何阅读源码,是每个程序员需要面临的一项挑战. 为什么需要阅读源码?从实用性的角度来看,主要 ...

  10. 关于JDK源码:我想聊聊如何更高效地阅读

    简介 大家好,我是彤哥,今天我想和大家再聊聊JDK源码的几个问题: 为什么要看JDK源码 JDK源码的阅读顺序 JDK源码的阅读方法 为什么要看JDK源码 一,JDK源码是其它所有源码的基础,看懂了J ...

随机推荐

  1. PHP 一个树为另一棵树的子结构 [TO BE CONTINUED]

    输入两棵二叉树A,B,判断B是不是A的子结构.(ps:我们约定空树不是任意一个树的子结构) <?php class TreeNode { private $val; private $left; ...

  2. SVN与LDAP服务器整合验证

    说明:svn的访问是以svn://协议访问的,一般都是用http协议访问,所以要使用apache的httpd服务器apache已经添加了对ldap服务器的支持,所以svn的认证过程是使用apache代 ...

  3. 『Python』面向对象(一)

    类和对象 类(class)是用来描述具有相同属性(attribute)和方法(method)的对象的集合,对象(object)是类(class)的具体实例.比如学生都有名字和分数,他们有着共同的属性. ...

  4. linux 客户机挂载vitualbox共享文件夹

    1. 安装增强功能包(Guest Additions) 安装好Ubuntu 9.10后,运行Ubuntu并登录.然后在VirtualBox的菜单里选择"设备(Devices)" - ...

  5. 双击tomcat8w.exe出现指定的服务未安装

    进入tomcat bin 目录下 打开cmd 输入命令 service.bat install   进行服务安装. 双击tomcat8w.exe 就可以打开了.

  6. mysql安装教程,mysql安装配置教程

    MySQL的安装教程 一.MYSQL的安装 首先登入官网下载mysql的安装包,官网地址:https://dev.mysql.com/downloads/mysql/ 一般下载这个就好,现在的最新版本 ...

  7. 数据库MHA故障分析

    一.故障分析 1.MHA故障以后是否正常:不正常 2.如果master恢复了?MHA还能自动恢复吗?:不能 3.主从恢复删除此文件 rm    saved_master_binlog_from_192 ...

  8. 树莓派3B搭建NODE-RED运行环境并构建数据流

    树莓派3B搭建NODE-RED运行环境并构建数据流 树莓派搭建Node-RED环境 树莓派自2015年开始是默认就带NODE-RED的,但是如今已是2018年:)自带的版本已经很老了,可通过下面的命令 ...

  9. FastAPI 学习之路(五)

    系列文章: FastAPI 学习之路(一)fastapi--高性能web开发框架 FastAPI 学习之路(二) FastAPI 学习之路(三) FastAPI 学习之路(四)  我们之前的文章分享了 ...

  10. 云原生的弹性 AI 训练系列之三:借助弹性伸缩的 Jupyter Notebook,大幅提高 GPU 利用率

    Jupyter Notebooks 在 Kubernetes 上部署往往需要绑定一张 GPU,而大多数时候 GPU 并没有被使用,因此利用率低下.为了解决这一问题,我们开源了 elastic-jupy ...