代码版本

JDK每一版本都在改进。本文讨论的HashMap和HashTable基于JDK 1.7.0_67

1. 时间

HashTable产生于JDK 1.1,

而HashMap产生于JDK 1.2。

从时间的维度上来看,HashMap要比HashTable出现得晚一些。

2. 作者

以下是HashTable的作者:

以下代码及注释来自java.util.HashTable
* @author Arthur van Hoff
* @author Josh Bloch
* @author Neal Gafter

以下是HashMap的作者:

以下代码及注释来自java.util.HashMap
* @author Doug Lea
* @author Josh Bloch
* @author Arthur van Hoff
* @author Neal Gafter

可以看到HashMap的作者多了大神Doug Lea。不了解Doug Lea的.

3. 对外的接口(API)

HashMap和HashTable都是基于哈希表来实现键值映射的工具类。讨论他们的不同,我们首先来看一下他们暴露在外的API有什么不同。

3.1 Public Method

下面两张图,我画出了HashMap和HashTable的类继承体系,并列出了这两个类的可供外部调用的公开方法。

从图中可以看出,两个类的继承体系有些不同。虽然都实现了Map、Cloneable、Serializable三个接口。但是HashMap继承自抽象类AbstractMap,而HashTable继承自抽象类Dictionary。其中Dictionary类是一个已经被废弃的类,这一点我们可以从它代码的注释中看到:

以下代码及注释来自java.util.Dictionary* <strong>NOTE: This class is obsolete. New implementations should
* implement the Map interface, rather than extending this class.</strong>

同时我们看到HashTable比HashMap多了两个公开方法。一个是elements,这来自于抽象类Dictionary,鉴于该类已经废弃,所以这个方法也就没什么用处了。另一个多出来的方法是contains,这个多出来的方法也没什么用,因为它跟containsValue方法功能是一样的。代码为证:

以下代码及注释来自java.util.HashTablepublic synchronized boolean contains(Object value) {if (value == null) {throw new NullPointerException();}Entry tab[] = table;for (int i = tab.length ; i-- > 0 ;) {for (Entry<K,V> e = tab[i] ; e != null ; e = e.next) {if (e.value.equals(value)) {return true;}}}return false;}public boolean containsValue(Object value) {return contains(value);}

所以从公开的方法上来看,这两个类提供的,是一样的功能。都提供键值映射的服务,可以增、删、查、改键值对,可以对建、值、键值对提供遍历视图。支持浅拷贝,支持序列化。

3.2 Null Key & Null Value

HashMap是支持null键和null值的,而HashTable在遇到null时,会抛出NullPointerException异常。这并不是因为HashTable有什么特殊的实现层面的原因导致不能支持null键和null值,这仅仅是因为HashMap在实现时对null做了特殊处理,将null的hashCode值定为了0,从而将其存放在哈希表的第0个bucket中。我们一put方法为例,看一看代码的细节:

以下代码及注释来自java.util.HashTablepublic synchronized V put(K key, V value) {// 如果value为null,抛出NullPointerExceptionif (value == null) {throw new NullPointerException();}// 如果key为null,在调用key.hashCode()时抛出NullPointerException// ...
}以下代码及注释来自java.util.HasMappublic V put(K key, V value) {if (table == EMPTY_TABLE) {inflateTable(threshold);}// 当key为null时,调用putForNullKey特殊处理if (key == null)return putForNullKey(value);// ...
}private V putForNullKey(V value) {// key为null时,放到table[0]也就是第0个bucket中for (Entry<K,V> e = table[0]; e != null; e = e.next) {if (e.key == null) {V oldValue = e.value;e.value = value;e.recordAccess(this);return oldValue;}}modCount++;addEntry(0, null, value, 0);return null;
}

4. 实现原理

本节讨论HashMap和HashTable在数据结构和算法层面,有什么不同。

4.1 数据结构

HashMap和HashTable都使用哈希表来存储键值对。在数据结构上是基本相同的,都创建了一个继承自Map.Entry的私有的内部类Entry,每一个Entry对象表示存储在哈希表中的一个键值对。

Entry对象唯一表示一个键值对,有四个属性:

-K key 键对象
-V value 值对象
-int hash 键对象的hash值
-Entry entry 指向链表中下一个Entry对象,可为null,表示当前Entry对象在链表尾部

可以说,有多少个键值对,就有多少个Entry对象,那么在HashMap和HashTable中是怎么存储这些Entry对象,以方便我们快速查找和修改的呢?请看下图。

上图画出的是一个桶数量为8,存有5个键值对的HashMap/HashTable的内存布局情况。可以看到HashMap/HashTable内部创建有一个Entry类型的引用数组,用来表示哈希表,数组的长度,即是哈希桶的数量。而数组的每一个元素都是一个Entry引用,从Entry对象的属性里,也可以看出其是链表的节点,每一个Entry对象内部又含有另一个Entry对象的引用。

这样就可以得出结论,HashMap/HashTable内部用Entry数组实现哈希表,而对于映射到同一个哈希桶(数组的同一个位置)的键值对,使用Entry链表来存储(解决hash冲突)。

以下代码及注释来自java.util.HashTable/*** The hash table data.*/
private transient Entry<K,V>[] table;以下代码及注释来自java.util.HashMap/*** The table, resized as necessary. Length MUST Always be a power of two.*/
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;

从代码可以看到,对于哈希桶的内部表示,两个类的实现是一致的。

4.2 算法

上一小节已经说了用来表示哈希表的内部数据结构。HashMap/HashTable还需要有算法来将给定的键key,映射到确定的hash桶(数组位置)。需要有算法在哈希桶内的键值对多到一定程度时,扩充哈希表的大小(数组的大小)。本小节比较这两个类在算法层面有哪些不同。

初始容量大小和每次扩充容量大小的不同。先看代码:

以下代码及注释来自java.util.HashTable// 哈希表默认初始大小为11
public Hashtable() {this(11, 0.75f);
}protected void rehash() {int oldCapacity = table.length;Entry<K,V>[] oldMap = table;// 每次扩容为原来的2n+1int newCapacity = (oldCapacity << 1) + 1;// ...
}以下代码及注释来自java.util.HashMap// 哈希表默认初始大小为2^4=16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16void addEntry(int hash, K key, V value, int bucketIndex) {// 每次扩充为原来的2n if ((size >= threshold) && (null != table[bucketIndex])) {resize(2 * table.length);
}

可以看到HashTable默认的初始大小为11,之后每次扩充为原来的2n+1。HashMap默认的初始化大小为16,之后每次扩充为原来的2倍。还有我没列出代码的一点,就是如果在创建时给定了初始化大小,那么HashTable会直接使用你给定的大小,而HashMap会将其扩充为2的幂次方大小。

也就是说HashTable会尽量使用素数、奇数。而HashMap则总是使用2的幂作为哈希表的大小。我们知道当哈希表的大小为素数时,简单的取模哈希的结果会更加均匀(具体证明,见这篇文章),所以单从这一点上看,HashTable的哈希表大小选择,似乎更高明些。但另一方面我们又知道,在取模计算时,如果模数是2的幂,那么我们可以直接使用位运算来得到结果,效率要大大高于做除法。所以从hash计算的效率上,又是HashMap更胜一筹。

所以,事实就是HashMap为了加快hash的速度,将哈希表的大小固定为了2的幂。当然这引入了哈希分布不均匀的问题,所以HashMap为解决这问题,又对hash算法做了特殊处理!

HashMap 和 HashTable 哪不同 ?相关推荐

  1. HashMap 和 Hashtable 的 6 个区别,最后一个没几个人知道!

    HashMap 和 Hashtable 是 Java 开发程序员必须要掌握的,也是在各种 Java 面试场合中必须会问到的. 但你对这两者的区别了解有多少呢? 现在,栈长我给大家总结一下,或许有你不明 ...

  2. Java HashMap和Hashtable的区别

    HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,主要区别在于HashMap允许空(null)键值(key),由于非线程安全,效率上可能高于Hashtable ...

  3. HashMap、HashTable、ConcurrentHashMap、HashSet区别 线程安全类

    HashMap专题:HashMap的实现原理--链表散列 HashTable专题:Hashtable数据存储结构-遍历规则,Hash类型的复杂度为啥都是O(1)-源码分析 Hash,Tree数据结构时 ...

  4. HashMap和Hashtable及HashSet的区别

    Hashtable类     Hashtable继承Map接口,实现一个key-value映射的哈希表.任何非空(non-null)的对象都可作为key或者value.     添加数据使用put(k ...

  5. java源码之HashMap和HashTable的异同

    代码版本 JDK每一版本都在改进.本文讨论的HashMap和HashTable基于JDK 1.7.0_67 1. 时间 HashTable产生于JDK 1.1,而HashMap产生于JDK 1.2.从 ...

  6. HashMap和Hashtable的区别总结

    文章目录 前言 源码分析 继承关系的不同: HashMap Hashtable 是否支持键值为null不同: HashMap Hashtable 初始化和扩容的方式不同: Hashtable Hash ...

  7. HashMap、Hashtable、ConcurrentHashMap的原理与区别

    HashTable 底层数组+链表实现,无论key还是value都不能为null,线程安全,实现线程安全的方式是在修改数据时锁住整个HashTable,效率低,ConcurrentHashMap做了相 ...

  8. 一文读懂JDK7,8,JD9的HashMap,HashTable,ConcurrentHashMap及他们的区别

    内容和标题一样长哦,人家写了好久的.如无特别指明,内容对应的源码是jdk1.7(后面会和1.8对比) 1:hashmap简介(如下,数组-链表形式) HashMap的存储结构 图中,紫色部分即代表哈希 ...

  9. hashMap和hashTable的区别(个人总结)

    首先hashmap和hashtable继承的类不同: 第二:所以从公开的方法上来看,这两个类提供的,是一样的功能.都提供键值映射的服务,可以增.删.查.改键值对,可以对建.值.键值对提供遍历视图.支持 ...

  10. HashMap 和 HashTable 区别

    来源:http://www.importnew.com/7010.html HashMap和Hashtable的区别 HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚 ...

最新文章

  1. Bridge网络模式下Linux虚拟机和主机进行通信
  2. wxWidgets:wxArchiveIterator类用法
  3. VTK:Utilities之Variant
  4. 交流电的有效值rms值_交流电路的功率三角因数原来是这样理解的
  5. java 图形处理库_java中处理图片的类库
  6. java中exception_Java中的异常 Exceptions
  7. 结合MSDN理解windows service 服务安装的三个类。
  8. Intel:Larrabee浮点运算能力2TFlops
  9. 【小项目】学生信息登记系统
  10. 重学JavaScript系列之一_引用类型
  11. java 上溯造型与下塑造型
  12. 安防意识增强 澳洲迈向新发展
  13. 苹果4计算机错误怎么办,苹果刷机失败卡在恢复模式怎么办?
  14. ASP.NET网站部署详细步骤
  15. MySQL 支持表情字符
  16. Linux九阴真经之大伏魔拳残卷4 nginx(模型,安装配置,模块)
  17. SPA项目开发之首页导航+左侧菜单
  18. 模式识别基本概念小结(学习笔记)
  19. Chrome浏览器视频网站全屏黑屏解决方法
  20. 启动 jack server 出现 Could not find or load main class com.android.jack.launcher.ServerLauncher的问题

热门文章

  1. vivox80pro和vivox70pro+ 哪个值得入手参数对比
  2. 北邮的软件工程硕士!!!
  3. LifeCycle速成
  4. “秋老虎”来了:养生必备宝典
  5. Cocos Creator 入门篇-描摹小游戏(cocos描摹数字,描摹英语字母,描摹图形)
  6. 持久续航+反向充电,华为畅享MAX治好了低电量恐惧症
  7. python turtle库的使用_Python turtle库使用教程
  8. YCM(YouCompleteMe)常用命令大全
  9. graphviz java api_Graphviz 简易教程
  10. 如何给一小段视频添加音乐 1