一.介绍

1.HashMap和HashTable的区别

1.相同点

  • 二者都实现了Map接口。
  • 底层都是哈西表

2.不同点

  • Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。

  • Hashtable 第一次创建对象的时候就会给底层数组开辟空间 Entry[] 11

    HashMap 创建对象时 没有给底层数组进行空间开辟

  • HashMap把Hashtable的contains方法去掉了。改成containsValue和containsKey

    Hashtable则保留了contains,containsValue和containsKey三个方法,其中contains和containsValue功能相同。

  • Hashtable中,key和value都不允许出现null值

    HashMap中,null可以作为键,这样的键只有一个

  • 哈希值的使用不同,HashTable直接使用对象的hashCode

    HashMap重新计算hash值。

  • HashTable在不指定容量的情况下的默认容量为11

    HashMap为16

  • Hashtable不要求底层数组的容量一定要为2的整数次幂

    HashMap则要求一定为2的整数次幂。

  • Hashtable扩容时,将容量变为原来的2倍加1

    HashMap扩容时,将容量变为原来的2倍。

  • Hashtable 中的方法是Synchronize的。

    HashMap线程不安全

  • 计算index的方法不同:

    • HashTable: index = (hash & 0x7FFFFFFF) % tab.length
    • HashMap:index = hash & (tab.length – 1)

2.HashTable

像HashMap一样,Hashtable在哈希表中存储键/值对。当使用一个哈希表,要指定用作键的对象,以及要链接到该键的值。

然后,该键经过哈希处理,所得到的散列码被用作存储在该表中值的索引

二.源码部分

1.基本属性

继承Dictionary 类

是一个抽象类,用来存储键/值对,作用和Map类相似。

给出键和值,你就可以将值存储在Dictionary对象中。一旦该值被存储,就可以通过它的键来获取它。所以和Map一样, Dictionary 也可以作为一个键/值对列表。

Cloneable是标记型的接口,它们内部都没有方法和属性,实现 Cloneable来表示该对象能被克隆

java.io.Serializable接口:

可以启用其序列化功能。未实现次接口的类无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身都是可序列化的。

  1. public class Hashtable<K,V>
  2. extends Dictionary<K,V>
  3. implements Map<K,V>, Cloneable, java.io.Serializable {
  4. /**
  5. * The hash table data.哈希表数据
  6. */
  7. private transient Entry<?,?>[] table;
  8. /**
  9. * The total number of entries in the hash table.哈希表中enyty的总数
  10. */
  11. private transient int count;
  12. /**
  13. * The table is rehashed when its size exceeds this threshold. (The
  14. * value of this field is (int)(capacity * loadFactor).)
  15. * 阈值,边界值
  16. * @serial
  17. */
  18. private int threshold;
  19. /**
  20. * The load factor for the hashtable.
  21. * 加载因子、负载因子
  22. * @serial
  23. */
  24. private float loadFactor;
  25. /**
  26. * The number of times this Hashtable has been structurally modified
  27. * Structural modifications are those that change the number of entries in
  28. * the Hashtable or otherwise modify its internal structure (e.g.,
  29. * rehash). This field is used to make iterators on Collection-views of
  30. * the Hashtable fail-fast. (See ConcurrentModificationException).
  31. */
  32. private transient int modCount = 0;
  33. /** use serialVersionUID from JDK 1.0.2 for interoperability */
  34. private static final long serialVersionUID = 1421746759512286392L;
  35. /**
  36. * The maximum size of array to allocate.
  37. * 要分配的数组的最大大小。
  38. * Some VMs reserve some header words in an array.
  39. * Attempts to allocate larger arrays may result in
  40. * OutOfMemoryError: Requested array size exceeds VM limit
  41. */
  42. private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

2.构造函数

  1. /**
  2. * 无参构造,默认容量为11,加载因子为0.75
  3. * Constructs a new, empty hashtable with a default initial capacity (11)
  4. * and load factor (0.75).
  5. */
  6. public Hashtable() {
  7. this(11, 0.75f);
  8. }
  1. /**
  2. * Constructs a new, empty hashtable with the specified initial capacity
  3. * and default load factor (0.75).
  4. * 创建指定大小的哈希表
  5. * 初始容量用户自定义,加载因子为默认的0.75
  6. * @param initialCapacity the initial capacity of the hashtable.
  7. * @exception IllegalArgumentException if the initial capacity is less
  8. * than zero.
  9. */
  10. public Hashtable(int initialCapacity) {
  11. this(initialCapacity, 0.75f);
  12. }
  1. /**
  2. * Constructs a new, empty hashtable with the specified initial
  3. * capacity and the specified load factor.
  4. * 创建了一个指定大小的哈希表,并且通过fillRatio指定填充比例
  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. //初始容量合法判断,否:异常
  12. if (initialCapacity < 0)
  13. throw new IllegalArgumentException("Illegal Capacity: "+
  14. initialCapacity);
  15. //加载因子判断,不在正确范围内抛出异常
  16. if (loadFactor <= 0 || Float.isNaN(loadFactor))
  17. throw new IllegalArgumentException("Illegal Load: "+loadFactor);
  18. //当初始化容量为0的时候,将初始容量设置为1
  19. if (initialCapacity==0)
  20. initialCapacity = 1;
  21. //加载因子赋值
  22. this.loadFactor = loadFactor;
  23. //创建一个新的大小为初始容量的enytry数组
  24. table = new Entry<?,?>[initialCapacity];
  25. //阈值为initialCapacity * loadFactor或最大值
  26. threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
  27. }
  1. /**
  2. * Constructs a new hashtable with the same mappings as the given
  3. * Map. The hashtable is created with an initial capacity sufficient to
  4. * hold the mappings in the given Map and a default load factor (0.75).
  5. * 第四个构造方法创建了一个以M中元素为初始化元素的哈希表。
  6. * 哈希表的容量被设置为M的两倍,或是11
  7. * @param t the map whose mappings are to be placed in this map.
  8. * @throws NullPointerException if the specified map is null.
  9. * @since 1.2
  10. */
  11. public Hashtable(Map<? extends K, ? extends V> t) {
  12. this(Math.max(2*t.size(), 11), 0.75f);
  13. putAll(t);
  14. }

3.contains()

  1. /**
  2. * Tests if some key maps into the specified value in this hashtable.
  3. * This operation is more expensive than the {@link #containsKey
  4. * containsKey} method.
  5. * 测试此映射表中是否存在与指定值关联的键。
  6. * <p>Note that this method is identical in functionality to
  7. * {@link #containsValue containsValue}, (which is part of the
  8. * {@link Map} interface in the collections framework).
  9. *
  10. * @param value a value to search for
  11. * @return <code>true</code> if and only if some key maps to the
  12. * <code>value</code> argument in this hashtable as
  13. * determined by the <tt>equals</tt> method;
  14. * <code>false</code> otherwise.
  15. * @exception NullPointerException if the value is <code>null</code>
  16. */
  17. public synchronized boolean contains(Object value) {
  18. //如果传入的value值为空,则抛出异常
  19. if (value == null) {
  20. throw new NullPointerException();
  21. }
  22. //2层循环遍历数组及其链表
  23. Entry<?,?> tab[] = table;
  24. for (int i = tab.length ; i-- > 0 ;) {
  25. for (Entry<?,?> e = tab[i] ; e != null ; e = e.next) {
  26. if (e.value.equals(value)) {
  27. return true;
  28. }
  29. }
  30. }
  31. return false;
  32. }

4.put()

  1. /**
  2. * Maps the specified <code>key</code> to the specified
  3. * <code>value</code> in this hashtable. Neither the key nor the
  4. * value can be <code>null</code>. <p>
  5. *
  6. * The value can be retrieved by calling the <code>get</code> method
  7. * with a key that is equal to the original key.
  8. * 将指定 key 映射到此哈希表中的指定 value。
  9. * @param key the hashtable key
  10. * @param value the value
  11. * @return the previous value of the specified key in this hashtable,
  12. * or <code>null</code> if it did not have one
  13. * @exception NullPointerException if the key or value is
  14. * <code>null</code>
  15. * @see Object#equals(Object)
  16. * @see #get(Object)
  17. */
  18. public synchronized V put(K key, V value) {
  19. // Make sure the value is not null确保该值不为空
  20. if (value == null) {
  21. throw new NullPointerException();
  22. }
  23. // Makes sure the key is not already in the hashtable.
  24. Entry<?,?> tab[] = table;
  25. //得到哈希值
  26. int hash = key.hashCode();
  27. //得到数组下标
  28. int index = (hash & 0x7FFFFFFF) % tab.length;
  29. //忽略警告
  30. @SuppressWarnings("unchecked")
  31. //将对应的数组元素保留起来
  32. Entry<K,V> entry = (Entry<K,V>)tab[index];
  33. //当该位置已有元素,则发生哈希冲突,遍历链表找通过equal方法找到key
  34. for(; entry != null ; entry = entry.next) {
  35. if ((entry.hash == hash) && entry.key.equals(key)) {
  36. //覆盖原来的值
  37. V old = entry.value;
  38. entry.value = value;
  39. //返回原来的值
  40. return old;
  41. }
  42. }

5.addEntry()

  1. private void addEntry(int hash, K key, V value, int index) {
  2. modCount++;
  3. Entry<?,?> tab[] = table;
  4. //如果数组有效值等于阈值,将进行扩容操作,然后在计算新的index
  5. if (count >= threshold) {
  6. // Rehash the table if the threshold is exceeded 如果超过阈值,请重新散列表
  7. rehash();
  8. tab = table;
  9. hash = key.hashCode();
  10. index = (hash & 0x7FFFFFFF) % tab.length;
  11. }
  12. // Creates the new entry.创建新条目。count++
  13. @SuppressWarnings("unchecked")
  14. Entry<K,V> e = (Entry<K,V>) tab[index];
  15. tab[index] = new Entry<>(hash, key, value, e);
  16. count++;
  17. }

6.扩容机制

  1. /**
  2. * Increases the capacity of and internally reorganizes this
  3. * hashtable,增加hashtable的容量并对其重组
  4. * in order to accommodate and access its entries more
  5. * efficiently.
  6. * 以便高效地访问更多的条目
  7. * This method is called automatically when the number of keys in the hashtable exceeds this hashtable's capacity and load factor.
  8. * 当哈希表中的键数超过该哈希表的容量和负载因子时,将自动调用此方法。
  9. */
  10. @SuppressWarnings("unchecked")
  11. protected void rehash() {
  12. //先将数组原来的容量保存起来
  13. int oldCapacity = table.length;
  14. Entry<?,?>[] oldMap = table;
  15. // overflow-conscious code
  16. //新的容量为原来的一半 + 1
  17. int newCapacity = (oldCapacity << 1) + 1;
  18. //如果新的容量超出了最大值,则将新容量变为最大值
  19. if (newCapacity - MAX_ARRAY_SIZE > 0) {
  20. //如果原来的容量就是最大值则将不扩容,return
  21. if (oldCapacity == MAX_ARRAY_SIZE)
  22. // Keep running with MAX_ARRAY_SIZE buckets
  23. return;
  24. newCapacity = MAX_ARRAY_SIZE;
  25. }
  26. //新建一个新容量大小的数组
  27. Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
  28. modCount++;
  29. //阈值也重新赋值,如若已经超出最大值,则为MAX_ARRAY_SIZE + 1
  30. threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
  31. //将table指向新数组
  32. table = newMap;
  33. //遍历原来的hashtable,将其拷贝过来
  34. for (int i = oldCapacity ; i-- > 0 ;) {
  35. for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
  36. Entry<K,V> e = old;
  37. old = old.next;
  38. //数组下标值不同于hashmap,都重新计算
  39. int index = (e.hash & 0x7FFFFFFF) % newCapacity;
  40. e.next = (Entry<K,V>)newMap[index];
  41. newMap[index] = e;
  42. }
  43. }
  44. }

三.总结

  • Hash:是一种信息摘要算法,它还叫做哈希,或者散列。我们平时使用的MD5,SHA1都属于Hash算法,通过输入key进行Hash计算,就可以获取key的HashCode(),比如我们通过校验MD5来验证文件的完整性。
  • Hashtable 实现并发安全的原理是通过 synchronized 关键字
  • 当线程数量增加的时候,Hashtable 的性能会急剧下降,因为每一次修改都需要锁住整个对象,而其他线程在此期间是不能操作的。不仅如此,还会带来额外的上下文切换等开销,所以此时它的吞吐量甚至还不如单线程的情况。

HashTable源码学习的更多相关文章

  1. Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结

    2017年的秋招彻底结束了,感觉Java上面的最常见的集合相关的问题就是hash--系列和一些常用并发集合和队列,堆等结合算法一起考察,不完全统计,本人经历:先后百度.唯品会.58同城.新浪微博.趣分 ...

  2. HashMap与HashTable源码学习及效率比较分析

    一.个人学习后的见解: 首先表明学习源码后的个人见解,后续一次依次进行分析: 1.线程安全:HashMap是非线程安全的,HashTable是线程安全的(HashTable中使用了synchroniz ...

  3. Dapper源码学习和源码修改

    之前ORM比较火热,自己也搞了个WangSql,但是感觉比较low,大家都说Dapper性能好,所以现在学习学习Dapper,下面简单从宏观层面讲讲我学习的Dapper. 再了解一个东西前,先得学会使 ...

  4. Hadoop源码学习笔记(1) ——第二季开始——找到Main函数及读一读Configure类

    Hadoop源码学习笔记(1) ——找到Main函数及读一读Configure类 前面在第一季中,我们简单地研究了下Hadoop是什么,怎么用.在这开源的大牛作品的诱惑下,接下来我们要研究一下它是如何 ...

  5. 并发-HashMap和HashTable源码分析

    HashMap和HashTable源码分析 参考: https://blog.csdn.net/luanlouis/article/details/41576373 http://www.cnblog ...

  6. HashMap的源码学习以及性能分析

    HashMap的源码学习以及性能分析 一).Map接口的实现类 HashTable.HashMap.LinkedHashMap.TreeMap 二).HashMap和HashTable的区别 1).H ...

  7. Java入门系列之集合Hashtable源码分析(十一)

    前言 上一节我们实现了散列算法并对冲突解决我们使用了开放地址法和链地址法两种方式,本节我们来详细分析源码,看看源码中对于冲突是使用的哪一种方式以及对比我们所实现的,有哪些可以进行改造的地方. Hash ...

  8. Java并发包源码学习系列:JDK1.8的ConcurrentHashMap源码解析

    目录 为什么要使用ConcurrentHashMap? ConcurrentHashMap的结构特点 Java8之前 Java8之后 基本常量 重要成员变量 构造方法 tableSizeFor put ...

  9. JUC源码学习笔记4——原子类,CAS,Volatile内存屏障,缓存伪共享与UnSafe相关方法

    JUC源码学习笔记4--原子类,CAS,Volatile内存屏障,缓存伪共享与UnSafe相关方法 volatile的原理和内存屏障参考<Java并发编程的艺术> 原子类源码基于JDK8 ...

随机推荐

  1. ubuntu 升级node和npm 版本

    使用vue-cli 3 构建项目时会一直卡在拉取依赖不动,原因是node和npm版本过低,升级node版本即可 $ sudo npm cache clean -f $ sudo npm install ...

  2. react中关于create-react-app2里css相关配置

    先看 webpack.config.dev.js 里的相关代码: // style files regexes const cssRegex = /\.css$/; const cssModuleRe ...

  3. Windows 10 如何在当前位置打开 CMD 命令窗口?

    方法一 Win + R 键召唤出运行窗口,然后输入 "CMD" 打开命令提示符. 使用 cd 命令更改当前命令提示符的工作环境. 注释 cd/ - 退到当前所在盘符 cd.. - ...

  4. RocketMQ架构原理解析(四):消息生产端(Producer)

    RocketMQ架构原理解析(一):整体架构 RocketMQ架构原理解析(二):消息存储(CommitLog) RocketMQ架构原理解析(三):消息索引(ConsumeQueue & I ...

  5. Docker 与 K8S学习笔记(十 二)容器间数据共享

    数据共享是volume的关键特性,今天我们来看一下通过volume实现容器与host.容器与容器之间共享数据. 一.容器与host共享数据 在上一篇中介绍到的bind mount和docker man ...

  6. C# 季节判断

    编写一个控制台应用程序,可根据输入的月份判断所在季节. 代码如下 using System; using System.Collections.Generic; using System.Linq; ...

  7. opencv 4.0 + linux + cuda静态编译

    #下载最新的opencv git clone "https://github.com/opencv/opencv.git" git clone "https://gith ...

  8. golang中函数的参数

    1. 函数当做函数的参数 package main import "fmt" type HandleFunc func(int) (int, bool) func add10(nu ...

  9. ubuntu输入正确密码重新跳到登录界面

     原因一:/etc/profile或者/etc/enviroment  配置错误 (很多开发人员在配置完java环境之后容易出现这种情况) 解决办法(已验证): 1,开机后在登录界面按下shift+c ...

  10. SpringBoot+Minio搭建不再爆肝秃头的分布式文件服务器

    前言 1).有人一定会问,为什么不用FastDFS?众所周知,FastDFS的原生安装非常复杂,有过安装经验的人大体都明白,虽然可以利用别人做好的docker直接安装,但真正使用过程中也可能出现许多莫 ...