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

时间:2022-09-25 00:20:10

  一、个人学习后的见解:

    首先表明学习源码后的个人见解,后续一次依次进行分析:

    1、线程安全:HashMap是非线程安全的,HashTable是线程安全的(HashTable中使用了synchronized关键字进行控制),HashMap对应的线程安全的有concurrentHashMap,但如果不用concurrentHashMap的话,也可以只用Collections.synchronizedMap(Map)进行转换。

    2、key值为null时的不同处理方式:HashMap允许key值为null,并会把key值为null放在Entry数组中的第一个bucket中;HashTable不允许存放key为null的存放,如果为null会抛出异常。

    3、数据结构:HashMap和HashTable都使用哈希表来存储键值对。后边具体分析。

    4、算法:其一、HashMap中对于key值的定位有内部封装的hash算法,而HashTable中是直接使用.hashcode获取hash值;其二、关于两者容量大小的定义也决定了两者在算法方面的不同效果。

    5、效率问题:单线程情况下_耗时:HashMap.put > HashTable.put;HashMap.get < HashTable.get

    6、根据HashTable注释表名其相当于被弃用了。

  二、对上面简介的分析:

    1、第一点不用解释了,源码中直接体现出来了,想了解者可观看源码。

    2、如图为HashMap的源码部分

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

      对于HashMap的key为null时会调用putForNullKey方法(想了解者可查看源码)进行处理,将value值放入Entry数组的第一个bucket中。

      下图为HashTable的源码部分

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

       我们可以明确的看到,HashTable的put方法如果key值为null时,会抛出NullPointerException空指针异常。

      从源码出我们可以看出来对于两者对null的不同待遇只是因为代码处理不同,并没有对效率或者其他有影响。

  3、数据结构:

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

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

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
以下代码及注释来自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、算法

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
以下代码及注释来自java.util.HashTable
 
// 哈希表默认初始大小为11
public Hashtable() {
    this(11, 0.75f);
}
 
protected void rehash() {
    int oldCapacity = table.length;
    Entry<K,V>[] oldMap = table;
 
    // 每次扩容为原来的2n+1
    int newCapacity = (oldCapacity << 1) + 1;
    // ...
}
 
 
以下代码及注释来自java.util.HashMap
 
// 哈希表默认初始大小为2^4=16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
 
void 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算法做了一些改动。具体我们来看看,在获取了key对象的hashCode之后,HashTable和HashMap分别是怎样将他们hash到确定的哈希桶(Entry数组位置)中的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
以下代码及注释来自java.util.HashTable
 
// hash 不能超过Integer.MAX_VALUE 所以要取其最小的31个bit
int hash = hash(key);
int index = (hash & 0x7FFFFFFF) % tab.length;
 
// 直接计算key.hashCode()
private int hash(Object k) {
    // hashSeed will be zero if alternative hashing is disabled.
    return hashSeed ^ k.hashCode();
}
 
 
以下代码及注释来自java.util.HashMap
int hash = hash(key);
int i = indexFor(hash, table.length);
 
// 在计算了key.hashCode()之后,做了一些位运算来减少哈希冲突
final int hash(Object k) {
    int h = hashSeed;
    if (0 != h && k instanceof String) {
        return sun.misc.Hashing.stringHash32((String) k);
    }
 
    h ^= k.hashCode();
 
    // This function ensures that hashCodes that differ only by
    // constant multiples at each bit position have a bounded
    // number of collisions (approximately 8 at default load factor).
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}
 
// 取模不再需要做除法
static int indexFor(int h, int length) {
    // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
    return h & (length-1);
}

    正如我们所言,HashMap由于使用了2的幂次方,所以在取模运算时不需要做除法,只需要位的与运算就可以了。但是由于引入的hash冲突加剧问题,HashMap在调用了对象的hashCode方法之后,又做了一些位运算在打散数据。关于这些位计算为什么可以打散数据的问题,本文不再展开了。感兴趣的可以看这里。

    如果你有细心读代码,还可以发现一点,就是HashMap和HashTable在计算hash时都用到了一个叫hashSeed的变量。这是因为映射到同一个hash桶内的Entry对象,是以链表的形式存在的,而链表的查询效率比较低,所以HashMap/HashTable的效率对哈希冲突非常敏感,所以可以额外开启一个可选hash(hashSeed),从而减少哈希冲突。因为这是两个类相同的一点,所以本文不再展开了,感兴趣的看这里。事实上,这个优化在JDK 1.8中已经去掉了,因为JDK 1.8中,映射到同一个哈希桶(数组位置)的Entry对象,使用了红黑树来存储,从而大大加速了其查找效率。

  5、关于效率问题:

    结论已经在文章开始时表名了,此处博主水准较低,并不是很能确认是因为以下几点导致:

    HashMap.put > HashTable.put:

      参照数据结构介绍,hasmMap的容量永远为2^*,所以HashMap在计算key所在位置时是采用了以为进行处理的;但此处会导致相同的hashcode变多,每个bucket(entry)的深度增加,所以后续put耗时较长。

    HashMap.get < HashTable.get:

       由于HashTable.get也做了同步处理,在这里对于锁的处理时间时HashTable的耗时过长(对于我自己不是很有说服力,因为在put时也有锁机制处理)

  6、HashTable已经配淘汰:

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

    如图为Hashtable(jdk1.7版本)的注释,已经很明确的表名了如果是单线程情况下建议使用HashMap,如果是多线程的情况下建议使用ConcurrentHashMap,此处可表明HashTable自己都不建议使用自己。

  关于第五点的具体原因希望有感兴趣的或者大牛帮忙解答下~~

HashMap与HashTable源码学习及效率比较分析的更多相关文章

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

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

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

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

  3. Mybatis源码学习第六天&lpar;核心流程分析&rpar;之Executor分析

    今Executor这个类,Mybatis虽然表面是SqlSession做的增删改查,其实底层统一调用的是Executor这个接口 在这里贴一下Mybatis查询体系结构图 Executor组件分析 E ...

  4. (转)HashMap和HashTable源码

    转自: http://www.cnblogs.com/ITtangtang/p/3948406.html http://frankfan915.iteye.com/blog/1152091 一.Has ...

  5. HashMap和HashTable源码分析

    HashMap HashMap是一个实现了Map接口的Hash表.提供所有Map的操作,并且允许null key和null value.HashMap几乎等同于HashTable,只不过HashMap ...

  6. spring源码学习之路---深度分析IOC容器初始化过程(四)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. 最近由于工作和生活,学习耽搁 ...

  7. &lbrack;Android FrameWork 6&period;0源码学习&rsqb; Window窗口类分析

    了解这一章节,需要先了解LayoutInflater这个工具类,我以前分析过:http://www.cnblogs.com/kezhuang/p/6978783.html Window是Activit ...

  8. Mybatis源码学习第六天&lpar;核心流程分析&rpar;之Executor分析&lpar;补充&rpar;

    补充上一章没有讲解的三个Executor执行器; 还是贴一下之前的代码吧;我发现其实有些分析注释还是写在代码里面比较好,方便大家理解,之前是我的疏忽,不好意思 @Override public &lt ...

  9. Netty 源码学习——服务端流程分析

    在上一篇我们已经介绍了客户端的流程分析,我们已经对启动已经大体上有了一定的认识,现在我们继续看对服务端的流程来看一看到底有什么区别. 服务端代码 public class NioServer { pr ...

随机推荐

  1. IIS7配置Gzip压缩 JS压强失败的原因

    开启配置HTTP压缩(GZip) 在IIS7中配置Gzip压缩相比IIS6来说实在容易了许多,而且默认情况下就是启用GZip压缩的.如果没有,则可以再功能视图下找到“压缩”项,进入之后就会看到“静态内 ...

  2. Java 生成16&sol;32位 MD5

    http://blog.csdn.net/codeeer/article/details/30044831

  3. WPF学习05:2D绘图 使用Transform进行控件变形

    在WPF学习04:2D绘图 使用Shape绘基本图形中,我们了解了如何绘制基本的图形. 这一次,我们进一步,研究如何将图形变形. 例子 一个三角形,经Transform形成组合图形: XAML代码: ...

  4. Log4net 集成到MVC&plus;EF框架

    前提引用Log4Net.dll文件 1. [assembly: log4net.Config.XmlConfigurator(ConfigFile = "Web.config", ...

  5. &lpar;C&num;&rpar;与Windows用户账户信息的获取

    Console.WriteLine(Environment.UserName); //计算机NetBIOS名称 Console.WriteLine(Environment.MachineName); ...

  6. ZED-Board从入门到精通系列(八)——Vivado HLS实现FIR滤波器

    http://www.tuicool.com/articles/eQ7nEn 最终到了HLS部分.HLS是High Level Synthesis的缩写,是一种能够将高级程序设计语言C,C++.Sys ...

  7. SQLServer 大小写敏感配置

    设置表内大小写敏感 ALTER TABLE 表名 ) COLLATE Chinese_PRC_CI_AS --不区分大小写 ALTER TABLE tb ) COLLATE Chinese_PRC_C ...

  8. 线程的中断&period;interrupt

    线程对象.interrupt() 注意,异常分析中要有break,否则无法中断 public class Demo extends JFrame { private Thread thread;//定 ...

  9. php mysql 丢失更新

    php mysql 丢失更新问题,搜索整个互联网,很少有讲到,也许和php程序员出身一般都是非科班出身有关系吧. 另外php程序一般都是简单数据,很少有并发一致性问题,所以大家都没有谁专门提出这个问题 ...

  10. drupal7 的核心模块

    核心模块 Drupal 7 block Block(区块)模块提供了与区块相关的功能,通过区块可将内容放置在网站不同区域.Block模块是Drupal的基础模块之一,不能被禁用.它是通过单独的区块管理 ...