Map中的一些算法与数据结构简析

一、Hash算法

1、 什么是Hash

Hash散列,将任一长度的输入,通过一种算法,变成固定长度的输出。可以理解为压缩的映射。

MD5、SHA、取余都属于散列算法。譬如将1W个数据映射到10个区域,每个区域平均会有1000个数据,这叫Hash碰撞,映射是否够均衡是衡量一种散列算法好坏的重要依据。

2、HashCode与equals

例如内存中有这样的位置
0 1 2 3 4 5 6 7
  而有个类,这个类有个字段叫ID,要把这个类存放在以上8个位置之一,如果不用HashCode而任意存放,那么当查找时就需要到这八个位置里挨个去找,或者用二分法一类的算法。但如果用HashCode那就会使效率提高很多。
我们这个类中有个字段叫ID,那么我们就定义我们的hashcode为ID%8,然后把我们的类存放在取得得余数那个位置。比如我们的ID为9,9除8的余数为1,那么我们就把该类存在1这个位置,如果ID是13,求得的余数是5,那么我们就把该类放在5这个位置。这样,以后在查找该类时就可以通过ID除8求余数直接找到存放的位置了。
  但是如果两个类有相同的hashcode怎么办那(我们假设上面的类的ID不是唯一的),例如9除以8和17除以8的余数都是1,那么这是不是合法的,回答是:可以这样。那么如何判断呢?在这个时候就需要定义 equals了。
也就是说,我们先通过 hashcode来判断两个类是否存放某个桶里,但这个桶里可能有很多类,那么我们就需要再通过 equals 来在这个桶里找到我们要的类。
那么,重写了equals(),为什么还要重写hashCode()呢?
想想,你要在一个桶里找东西,你必须先要找到这个桶啊,你不通过重写hashcode()来找到桶,光重写equals()有什么用啊!

简单归纳一下:
hashCode是用于查找使用的,而equals是用于比较两个对象的是否相等的。一个正确覆写这两个方法的类,equals()相同,hashCode一定相同;hashCode()相同,equals不一定相同

二、HashMap简析

1、实现原理

在JDK1.8之前,HashMap采用数组+链表实现,即使用链表处理hash值冲突,同一hash值的节点都存储在一个链表里。
但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。

数组+链表.jpg

而JDK1.8中,HashMap采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。具体参看HashMap的源码分析

数组+列表+红黑树.png

2、并发下使用HashMap的问题

  在多线程环境下,使用HashMap进行put操作如果同时有多个线程扩容数组时,会引起死循环,导致CPU利用率接近100%。因为其会导致形成环形数据结构,Entry的next节点永远不为空,就会产生死循环获取Entry。
  HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。一个线程会阻塞其他线程的get、put方法。
  当然HashMap有个方法原子性的实现了防止并发写相同的功能,

putIfAbsent(key,value)
如果key对应的value不存在,则put进去,返回null。否则不put,返回已存在的value

  JDK1.8后,除了对Hashmap增加红黑树结果外,对原有造成死锁的关键原因点(新table复制在头端添加元素)改进为依次在末端添加新的元素。虽然JDK1.8后添加红黑树改进了链表过长查询遍历慢问题和resize时出现导致put死循环的bug,但还是非线性安全的,比如数据丢失等等。因此多线程情况下还是建议使用ConcurrentHashMap。

三、ConcurrentHashMap简析

  JDK1.7中使用分段锁的设计思想。
  ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment实际是一种可重入锁(ReentrantLock),HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组。Segment的结构和HashMap类似,是一种数组和链表结构。一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素,每个Segment守护着一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得与它对应的Segment锁。


JDK1.7中的Segment.png

  ConcurrentHashMap初始化方法是通过initialCapacity、loadFactor和concurrencyLevel(参数concurrencyLevel是用户估计的并发级别,就是说你觉得最多有多少线程共同修改这个map,根据这个来确定Segment数组的大小concurrencyLevel默认是DEFAULT_CONCURRENCY_LEVEL = 16)。
  ConcurrentHashMap完全允许多个读操作并发进行,读操作并不需要加锁。ConcurrentHashMap实现技术是保证HashEntry几乎是不可变的。HashEntry代表每个hash链中的一个节点,可以看到其中的对象属性要么是final的,要么是volatile的。

JDK1.8中
改进一:取消segments字段,直接采用transient volatile HashEntry<K,V>[] table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率。
改进二:即HashMap在1.8中改进。将原先table数组+单向链表的数据结构,变更为table数组+单向链表+红黑树的结构。
具体参看ConcurrentHashMap源码分析

四、ConcurrentSkipListMap中的跳表

  简单的说ConcurrentSkipListMap是TreeMap的并发实现,ConcurrentSkipListSet是TreeSet的并发实现。
这里要分析的是它们使用了一种SkipList(跳表)的数据结构。

什么是跳表?
跳表就是将链表的某些节点向上建立多级索引,提高查询效率。即空间换时间的思想。

比如下图插入节点3,插入本身很容易,关键查找其位置必须逐一比较。数据量一大就很费时间


链表的查找很慢.jpeg

于是我们打算给某些关键节点建立索引,譬如插入节点4,只需先比较索引节点就能很快定位到底层链表位置。整整快了一倍有木有。


一级索引.jpeg

如果要更快呢?那就在一级索引基本上再选出二级索引,二级索引基础上选出三级索引,直到最上面的索引层只有两个关键节点为止。
多级索引.jpeg

那么哪些节点可以提拔为索引呢?我们使用“投硬币”50%的随机概率来决定。因为跳跃表的删除和添加节点是不可预测的,很难又一种算法保证索引部分的均衡,随机抛硬币虽然不能保证索引绝对分布均衡,但却可以让大体趋于均匀。


随机抛币选取多级索引.jpeg

随机抛币选取多级索引.jpeg

五、总结

让我回顾一下整篇文章:
  ArrayList以数组形式实现,顺序插入、查找快,插入、删除较慢;
  LinkedList以链表形式实现,顺序插入、查找较慢,插入、删除方便;
  有没有什么数据结构能够结合上面的两者的优点呢?有,即HashMap,它实际上是一个“链表散列”的数据结构,即数组和链表的结合体,后来发现当同一个数组下的链表节点越来越长时,查询还是慢了,故JDK1.8中HashMap又引入了红黑二叉树。
  二分查找要求元素可以随机访问,所以决定了需要把元素存储在连续内存。这样查找确实很快,但是插入和删除元素的时候,为了保证元素的有序性,就需要大量的移动元素了。如果需要的是一个能够进行二分查找,又能快速添加和删除元素的数据结构,首先就是二叉查找树,二叉查找树在最坏情况下可能变成一个链表。于是,就出现了平衡二叉树(AVL树),根据平衡算法的不同有AVL树,B-Tree,B+Tree,红黑树等,但是AVL树实现起来比较复杂,平衡操作较难理解。
  这时候就可以用SkipList跳跃表结构,它是实现简单,查找与增删也都达到了趋于AVL树的性能。目前开源软件 Redis(Sorted-set结构) 和 Lucence都有用到它。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容