HashMap,HashTable和ConcurrentHashMap的基本原理与实现

Posted 每天进步一点点YL

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HashMap,HashTable和ConcurrentHashMap的基本原理与实现相关的知识,希望对你有一定的参考价值。

前言:这几天陆续把后面几篇集合的深入也写完了,先贴上寒假完成的那篇

先来段纯music吧,很好听(感觉听着纯音乐看着代码很享受(๑╹◡╹)ノ"""):



总体图:

HashMap

JDK7之前hashmap又叫散列链表:基于一个数组以及多个链表的实现,hash值冲突的时候,就将对应节点以链表的形式存储。

JDK7的源码解释自行看博客(后面一些问题也考虑到了1.8)


JDK8中,当同一个hash值的链表节点数大于等于8时,将不再以单链表的形式存储了,会被调整成一颗红黑树。

HashMap,HashTable和ConcurrentHashMap的基本原理与实现

下文是基于JDK8的源码解析


HashMap概述

HashMap是由哈希表构成,其实现原理为“拉链法”,横向是数组,纵向是链表组成

HashMap,HashTable和ConcurrentHashMap的基本原理与实现

    存储机制:<key1,value1>传入时,会对key进行hash算法(如hash(key)%len)得到一个值h,则<key1,value1>被存储到下标为h的table数组中,如果再传入<key2,value2>的hash值仍然为h,则将<key2,value2>存到h下标对应的链表中。

HashMap,HashTable和ConcurrentHashMap的基本原理与实现

HashMap中其存储容器为线性数组

HashMap重复的key会被覆盖,所以插入时会遍历判断key。

HashMap中允许存在null键,null值。会将其存在table[0]处。


HashMap的实现

HashMap里面实现一个静态内部类Node,其重要的属性有 hash,key , value, next;Node就是HashMap键值对实现的一个基础bean;上面说到HashMap的基础就是一个线性数组,这个数组就是Node[],Map里面的内容都保存在Node[]里面。

(1)私有属性

HashMap,HashTable和ConcurrentHashMap的基本原理与实现

HashMap,HashTable和ConcurrentHashMap的基本原理与实现


(2)构造方法

无参构造函数

HashMap,HashTable和ConcurrentHashMap的基本原理与实现

HashMap中数组的长度始终为2^n  


(3)Node<K,V>类

HashMap,HashTable和ConcurrentHashMap的基本原理与实现


(4)元素存储put()

4.1、put(K key, V value) 插入元素,当key存在时覆盖之前的value,返回旧的value值,当key不存在返回null

HashMap,HashTable和ConcurrentHashMap的基本原理与实现

  1. onlyIfAbsent表示只有在该key对应原来的value为null的时候才插入,也就是说如果value之前存在了,就不会被新put的元素覆盖。

  2. evict参数用于LinkedHashMap中的尾部操作,这里没有实际意义。

HashMap,HashTable和ConcurrentHashMap的基本原理与实现

HashMap,HashTable和ConcurrentHashMap的基本原理与实现

(注意:

  1. e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);  你可能好奇,这里怎么不遍历tree看看有没有key相同的节点呢?其实,putTreeVal内部进行了遍历,存在相同hash时返回被覆盖的TreeNode,否则返回null。

  2. 下面的部分代码,使得在链表中搜索时,可以不断的迭代

HashMap,HashTable和ConcurrentHashMap的基本原理与实现


  • tab = resize()初始化Node<K,V>[]函数及扩容函数

当调用无参构造函数,插入数据时会调用如下代码,初始化一个空间为16的数组

HashMap,HashTable和ConcurrentHashMap的基本原理与实现


扩容时:当HashMap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能


  • newNode(hash, key, value, null);函数

HashMap,HashTable和ConcurrentHashMap的基本原理与实现

  • putTreeValue,treeifyBin自己深入吧


(5)获取元素get()

难理解是put,get(),easy了许多

HashMap,HashTable和ConcurrentHashMap的基本原理与实现

HashMap,HashTable和ConcurrentHashMap的基本原理与实现


(6)Fail-Fast机制

    我们知道java.util.HashMap不是线程安全的,因此如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。

    这一策略在源码中的实现是通过modCount域,modCount顾名思义就是修改次数,对HashMap内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的expectedModCount。


(7)总结:

A:HashMap是基于Map接口的实现,存储的是以Node<K,V>为基础的键值对,它可以接收null的键值,是非同步的。

B:HashMap工作原理:

    put():插入一个值时,首先通过hash(key)算出对应的下标,并在初始化的Table[]中查找下标所对应的位置,如果当前位置为null,则插入。若不为null,分三种情况:①判断根节点是否为红黑树节点,是,putTreeValue插入(函数自带查重);不是,则遍历链表插入至链表最后,②插入后如果链表长度大于等于8,则转化为红黑树;③插入后如果链表长度小于8,则不变。另在遍历过程中,如果遇到插入key相同的Node节点,则退出遍历,并将新value值覆盖oldvalue。

    get():自己看代码,不说了。( ̄︶ ̄)↗




HashTable

HashTable概述

HashMap,HashTable和ConcurrentHashMap的基本原理与实现HashTable和HashMap采用相同的存储机制,二者的实现基本一

(HashTable与HashMap的1.7之前的版本相比较),不同的是:

  • HashTable是线程安全的内部的方法基本都是synchronized;HashMap是非线程安全的

  • HashTable不允许有null值的存在(HashTable中调用put方法时,如果key为null,直接抛出NullPointerException)。


HashTable的实现

这里就简单说一下(对比HashMap1.7之前版本的源码)

(1)私有属性

HashMap,HashTable和ConcurrentHashMap的基本原理与实现


(2)构造函数

HashMap,HashTable和ConcurrentHashMap的基本原理与实现

HashTable初始化大小为11


(3)Entry<K,V>



HashMap,HashTable和ConcurrentHashMap的基本原理与实现


(4)元素存储put()

4.1、put(K key, V value) 插入元素,当key存在时覆盖之前的value,返回旧的value值,当key不存在返回null

HashMap,HashTable和ConcurrentHashMap的基本原理与实现

HashMap,HashTable和ConcurrentHashMap的基本原理与实现

addEntry图解如下:

HashMap,HashTable和ConcurrentHashMap的基本原理与实现


(5)获取元素get()

HashMap,HashTable和ConcurrentHashMap的基本原理与实现


(6)删除元素remove()

HashMap,HashTable和ConcurrentHashMap的基本原理与实现


图解如下:




ConcurrentHashMap

  ConcurrentHashMap代替同步的Map(Collections.synchronized(new HashMap())),众所周知,HashMap是根据散列值分段存储的,同步Map在同步的时候锁住了所有的段,而ConcurrentHashMap加锁的时候根据散列值锁住了散列值锁对应的那段,因此提高了并发性能。

注意:为什么在java1.5中添加了util.concurrent,而不用Collections.synchronized(),因为synchronized锁住的容器,实际上使得并发下对容器的访问变成了串行化访问,这极大的影响了效率,而concurrent锁住的只是部分,容器的其实还是并发访问的


ConcurrentHashMap的内部结构

ConcurrentHashMap为了提高本身的并发能力,在内部采用了一个叫做Segment的结构,一个Segment其实就是一个类Hash Table的结构,Segment内部维护了一个链表数组(直接拿用)

    从上面的结构我们可以了解到,ConcurrentHashMap定位一个元素的过程需要进行两次Hash操作,第一次Hash定位到Segment,第二次Hash定位到元素所在的链表的头部,因此,这一种结构的带来的副作用是Hash的过程要比普通的HashMap要长,但是带来的好处是写操作的时候可以只对元素所在的Segment进行加锁即可,不会影响到其他的Segment。

    这样,在最理想的情况下,ConcurrentHashMap可以最高同时支持Segment数量大小的写操作(刚好这些写操作都非常平均地分布在所有的Segment上),所以,通过这一种结构,ConcurrentHashMap的并发能力可以大大的提高。



后记:

感谢参考的,提供给我思路的博客文章,大家能看懂吃透是最好的,如果有些地方小伙伴需要再深入的,迷茫的等可以上网进行深入搜索。





以上是关于HashMap,HashTable和ConcurrentHashMap的基本原理与实现的主要内容,如果未能解决你的问题,请参考以下文章

HashMap和HashTable的区别是什么

HashMap和Hashtable有什么区别?

HashMap和Hashtable的区别

HashMap 和 Hashtable 的区别

HashMap和Hashtable

HashMap和Hashtable