热门标签 | 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编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
  • Java之HashMap在多线程情况下导致死循环的问题
    PS:不得不说Java编程思想这本书是真心强大..学习内容:1.HashMap<K,V>在多线程的情况下出现的死循环现象当初学Java的时候只是知道HashMap< ... [详细]
  • 缓存这个东西就是为了提高运行速度的,由于缓存是在寸土寸金的内存里面,不是在硬盘里面,所以容量是很有限的。LRU这个算法就是把最近一次使用时间离现在时间最远的数据删除掉。先说说List:每 ... [详细]
  • Java面试 HashMap、HashSet源码解析
    本章所有源代码基于JDK1.8版本HashMap和HashSet是JavaCollectionFramework的两个重要成员,其中HashMap是Map接口的常用实现类,Hash ... [详细]
  • 从 .NET 转 Java 的自学之路:IO 流基础篇
    本文详细介绍了 Java 中的 IO 流,包括字节流和字符流的基本概念及其操作方式。探讨了如何处理不同类型的文件数据,并结合编码机制确保字符数据的正确读写。同时,文中还涵盖了装饰设计模式的应用,以及多种常见的 IO 操作实例。 ... [详细]
  • 转载自:http:www.blogjava.netCarpenterLeearchive20160427430268.html总体介绍之所以把HashSet和HashMa ... [详细]
  • 手写HashMap,快手面试官直呼内行
    手写HashMap,快手面试官直呼内行-手写HashMap?这么狠,面试都卷到这种程度了?第一次见到这个面试题,是在某个不方便透露姓名的Offer收割机大佬的文章:这……我当 ... [详细]
  • 深入解析Spring Cloud Ribbon负载均衡机制
    本文详细介绍了Spring Cloud中的Ribbon组件如何实现服务调用的负载均衡。通过分析其工作原理、源码结构及配置方式,帮助读者理解Ribbon在分布式系统中的重要作用。 ... [详细]
  • 本文深入探讨了 Java 中的 Serializable 接口,解释了其实现机制、用途及注意事项,帮助开发者更好地理解和使用序列化功能。 ... [详细]
  • 本文详细介绍了Akka中的BackoffSupervisor机制,探讨其在处理持久化失败和Actor重启时的应用。通过具体示例,展示了如何配置和使用BackoffSupervisor以实现更细粒度的异常处理。 ... [详细]
  • Java集合详解5:深入理解LinkedHashMap和LRU缓存
    Java集合详解5:深入理解LinkedHashMap和LRU缓存今天我们来深入探索一下LinkedHashMap的底层原理,并且使用linkedhashmap来实现LRU缓存。具体代码在我的 ... [详细]
  • 本篇文章给大家分享的是有关Java中怎么对HashMap按键值排序,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话 ... [详细]
  • 01Map集合概述A:Map集合概述:我们通过查看Map接口描述,发现Map接口下的集合与Collection接口下的集合,它们存储数据的形式不同a:Collection中的集合 ... [详细]
  • 写这篇文章起源于一道面试题,如何将自定义的类对象作为key存储到HashMap中,即考虑怎么判断key的唯一性。首先,我们看以下HashMap中put(…)方法的源码:public ... [详细]
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社区 版权所有