热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

java1.7集合源码赏析系列:HashTable、ConcurrentHashMap、HashMap差异分析

HashTable与ConcurrentHashMap均可实现HashMap的功能,对外提供了键值对存储的数据结构。但是在内部结构及实现上有何区别,性能上的差异到底在哪里又是如何导致的

HashTable与ConcurrentHashMap均可实现HashMap的功能,对外提供了键值对存储的数据结构。但是在内部结构及实现上有何区别,性能上的差异到底在哪里又是如何导致的,让我们从源码的角度上来进行分析。

1. 声明的区别

//hashtable的声明
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable
//ConcurrentHashMap的声明
public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
implements ConcurrentMap<K, V>, Serializable
//hashmap的声明
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable

Hashtable继承了Dictionary类,实现了Map接口,提供了clone方法。
ConcurrentHashMap继承了AbstractMap类,实现了ConcurrentMap接口。
HashMap继承了AbstractMap类,实现了Map接口,提供了clone方法。
三者之间最大的差别在于实现了不同的接口ConcurrentMap和Map。
再看看ConcurrentMap。

//ConcurrentMap的声明
public interface ConcurrentMap<K, V> extends Map<K, V>

小结:在一定的程度上可以理解为Hashtable在提供的方法上与HashMap并无不同,ConcurrentHashMap则提供了更多的方法,或者说功能更加强大。

2.方法的区别

从最为常用的方法来看,即get和put。

//依然是方法声明,实现代码略去
//HashMap
public V get(Object key){
}
public V put(K key, V value) {
}

//Hashtable
public synchronized V get(Object key){
}
public synchronized V put(K key, V value) {
}

//ConcurrentHashMap
public V get(Object key) {
}
public V put(K key, V value){
}

小结:ConcurrentHashMap与HashMap没有区别。HashTable则在方法级别上加入了同步锁synchronized,并且读写都有。由此可以看出HashTable虽然解决了线程安全的问题,但是性能却是急剧下降的。

3.数据结构的区别

//HashTable的数据存储在Entry数组中,每个Entry元素是一个单向链表。
private transient Entry[] table;
//Entry的属性
private static class Entry<K,V> implements Map.Entry<K,V> {
int hash;
final K key;
V value;
Entry next;
}
//HashMap的数据存储在Entry数组中,每个Entry元素是一个单向链表。
transient Entry[] table = (Entry[]) EMPTY_TABLE;
//Entry的属性
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry next;
int hash;
}

//ConcurrentHashMap使用一个Segment数组存放数据,每一个Segment元素又维护着一个HashEntry链表数组
final Segment[] segments;
//Segment的属性
transient volatile HashEntry[] table;
//HashEntry的属性
static final class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry next;
}

小结:HashMap的数据结构与HashTable一致,ConcurrentHashMap则是维护着多个HashMap。

4.数据操作的区别

//HashTable
public synchronized V put(K key, V value) {
//HashTable不允许空值,虽然Hashtable没有对key为空做处理,但是如果key是null时会抛空指针
// Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}

// Makes sure the key is not already in the hashtable.
Entry tab[] = table;
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
V old = e.value;
e.value = value;
return old;
}
}

modCount++;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();

tab = table;
hash = hash(key);
index = (hash & 0x7FFFFFFF) % tab.length;
}

// Creates the new entry.
Entry e = tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
return null;
}
//HashMap
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}

modCount++;
addEntry(hash, key, value, i);
return null;
}

hashmap和hashtable的put操作是一样的,此处不再做讲解,传送门java1.7集合源码赏析系列:HashMap

//ConcurrentHashMap
//值不能为空,key为空时会抛空指针
public V put(K key, V value) {
Segment s;
if (value == null)
throw new NullPointerException();
//第一次hash
int hash = hash(key);
int j = (hash >>> segmentShift) & segmentMask;
if ((s = (Segment)UNSAFE.getObject // nonvolatile; recheck
(segments, (j <null) // in ensureSegment
s = ensureSegment(j);
//取到对应的segment再put
return s.put(key, hash, value, false);
}

//segment的put方法
//这里的操作与hashmap类似,不同的是在put里面有了锁
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
HashEntry node = tryLock() ? null :
scanAndLockForPut(key, hash, value);
V oldValue;
try {
HashEntry[] tab = table;
int index = (tab.length - 1) & hash;
HashEntry first = entryAt(tab, index);
for (HashEntry e = first;;) {
if (e != null) {
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
else {
if (node != null)
node.setNext(first);
else
node = new HashEntry(hash, key, value, first);
int c = count + 1;
if (c > threshold && tab.length rehash(node);
else
setEntryAt(tab, index, node);
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
unlock();
}
return oldValue;
}

小结:Hashtable、ConcurrentHashMap的key和value都不能为空,HashMap的key和value都可以为空。Hashtable和HashMap的put操作是一样的,ConcurrentHashMap则略微复杂点。

ConcurrentHashMap的结构图:
结构图
HashMap和Hashtable的结构是一样的segment元素中存储的就是HashMap的结构,此处不再赘述。

总结:1.HashTable与HashMap结构一样,Hashtable是串行操作,多线程情况下慢于HashMap。
2.ConcurrentHashMap通过再hash的方式将数据划分成更细粒度的HashMap,在细粒度上控制并发,因此多线程情况下快于Hashtable。而它的读操作又是支持并发的,因此性能是远远超于Hashtable的。
3.多线程情况下建议使用ConcurrentHashMap
4.使用HashMap,数据量较大的情况下,建议1)初始化指定初始容量,如果在业务确定的情况下,考虑也初始化加载因子。2)对key的hashcode需做深入考虑,避免雪崩效应,方法有string、整型、或者重写。


推荐阅读
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • JavaSE笔试题-接口、抽象类、多态等问题解答
    本文解答了JavaSE笔试题中关于接口、抽象类、多态等问题。包括Math类的取整数方法、接口是否可继承、抽象类是否可实现接口、抽象类是否可继承具体类、抽象类中是否可以有静态main方法等问题。同时介绍了面向对象的特征,以及Java中实现多态的机制。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
author-avatar
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有