hashmap是一种很经常使用的数据结构,其使用方便快捷,接下来笔者将给你们深刻解析这个数据结构,让你们能在用的时候知其然,也知其因此然。java
首先,从最基本的讲起,咱们先来认识一下map是个什么东西。在咱们写程序的时候常常会遇到数据检索等操做,对于几百个数据的小程序而言,数据的存储方式或是检索策略没有太大影响,但对于大数据,效率就会差很远。咱们来讨论一下这个问题。小程序
线性检索是最为直白的方法,把全部数据都遍历一遍,而后找到你所须要的数据。其对应的数据结构就是数组,链表等线性结构,这种方式对于大数据而言效率极低,其时间复杂度为O(n)。数组
二分搜索算是对线性搜索的一个改进,好比说对于【1,2,3,4,5,6,7,8】,我要搜索一个数(假设是2),我先将这个数与4(这个数通常选中位数比较好)比较,小于4则在4的左边【1,2,3】中查找,再与2比较,相等,就成功找到了,这种检索方式好处在于能够省去不少没必要要的检索,每次只用查找集合中一半的元素。其时间复杂度为O(logn)。但其也有限制,他的数排列自己就须要是有序的。数据结构
好了,重点来了,Hash表闪亮登场,这是一种时间复杂度为O(1)的检索,就是说无论你数据有多少只须要查一次就能够找到目标数据。是否是很神奇??好吧其实很弱智。你们请看下图。app
你们能够看到这个数组中的值就等于其下标,好比说我要存11,我就把它存在a[11]里面,这样我要找某个数字的时候就直接对应其下标就能够了。这实际上是一种牺牲空间换时间的方法,这样会对内存占用比较大,但检索速度极快,只须要搜索一次就能查到目标数据。函数
看了上面的Hash表你确定想问,若是我只存一个数10000,那我不是要存在a[10000],这样其余空间不是白白浪废了吗,好吧,不存在的。Hash表已经有了其应对方法,那就是Hash函数。Hash表的本质在于能够经过value自己的特征定位到查找集合的元素下标,从而快速查找。通常的Hash函数为:要存入的数 mod(求余) Hash数组长度。好比说对于上面那个长度为9的数组,12的位置为12 mod 9=3,即存在a3,经过这种方式就能够安放比较大的数据了。大数据
看了上面的讲解,机智的大家确定已经发现了一个问题,经过求余数获得的地址多是同样的。这种咱们称为Hash冲突,若是数据量比较大而Hash桶比较小,这种冲突就很严重。咱们采起以下方式解决冲突问题。
设计
咱们能够看到12和0的位置冲突了,而后咱们把该数组的每个元素变成了一个链表头,冲突的元素放在了链表中,这样在找到对应的链表头以后会顺着链表找下去,至于为何采用链表,是为了节省空间,链表在内存中并非连续存储,因此咱们能够更充分地使用内存。blog
上面讲了那么多,那跟咱们今天的主题HashMap有什么关系呢??好了盆友们不要方,进入正题。咱们知道HashMap中的值都是key,value对吧,其实这里的存储与上面的很像,key会被映射成数据所在的地址,而value就在以这个地址为头的链表中,这种数据结构在获取的时候就很快。但这里存在的问题就是若是hash桶较小,数据量较大,就会致使链表很是的长。好比说上面的长为11的空间我要放1000个数,不管Hash函数如何精妙,后面跟的链表都会很是的长,这样Hash表的优点就不复存在了,反而倾向于线性检索。好了,红黑树闪亮登场。递归
在jdk1.8版本后,java对HashMap作了改进,在链表长度大于8的时候,将后面的数据存在红黑树中,以加快检索速度,咱们接下来说一下红黑树。
要了解红黑树,先要知道avl树,要知道avl树,首先要知道二叉树,其实很简单,二叉树就是每一个父节点下面有零个一个或两个子节点,大体以下图。
咱们在向二叉树中存放数据的时候将比父节点大的数放在右节点,将比父节点小的数放在左节点,这样以后咱们在查找某个数的时候只须要将其与父节点比较,大则进入右边并递归调用,小则进入左边递归。但其存在不足,若是运气很很差个人数据自己就是有序的,好比【1,2,3,4,5,6,7】,这样就会致使树的不平衡,二叉树就会退化成为链表。因此咱们推出了avl树。
avl树即平衡树,他对二叉树作了改进,在咱们每插入一个节点的时候,必须保证每一个节点对应的左子树和右子树的树高度差不超过1。若是超过了就对其进行调平衡,具体的调平衡操做就不在这里讲了,无非就是四个操做——左旋,左旋再右旋,右旋再左旋。最终能够是二叉树左右两边的树高相近,这样咱们在查找的时候就能够按照二分查找来检索,也不会出现退化成链表的状况。
网上有不少讲解红黑树的文章,有各类各样的讲解方式,但博主喜欢把红黑树与二三树放在一块儿。先来看一下什么是二三树。
注:该图来自百度
其实很好理解,二三树与普通二叉树的不一样点在于他有二节点和三节点。二节点下面有两个子节点,二节点里面能够容纳一个值,而三节点下面有三个子节点,三节点里面能够容纳两个值。下面来讲一下二三数的构建。
注:图片依然来自百度,博主画图比较垃圾。
其实二三树的构建很简单,如图所示,图中M结点就是一个二节点,M左边的EJ节点是一个三节点。依然是大的数据放右边,小的数据放左边。此时咱们向该树重若是该数能够直接放入二节点中,就直接进去,但若是正好须要放在三节点中,就像图中同样,Z正好要放在SX中。那么咱们须要将该节点分裂成两个节点,并将中间的数提到父节点中去,就像图中将X放在了R旁边。固然若是将子节点提到父节点的时候致使了父节点里的数超过了两个,就继续向上提,直到知足了为止。
红黑树和二三树很相像,基本上就是二三树的一个变形。
红黑树比较传统的定义是须要知足如下五个特征:
(1)每一个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每一个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)若是一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的全部路径上包含相同数目的黑节点。
其特色在于给数的每个节点加上了颜色属性,在插入的过程当中经过颜色变换和节点旋转调平衡。其实博主不是很喜欢上面的定义,还有一种视角就是将它与二三树比较。
固然上面这张图也是搜来的。
红黑树还能够描述成:
⑴红连接均为左连接。
⑵没有任何一个结点同时和两条红连接相连。
⑶该树是完美黑色平衡的,即任意空连接到根结点的路径上的黑连接数量相同。
这里节点之间的链接分为红链接和黑链接,取代了红节点和黑节点的定义(本质是同样的),将以前的黑高度相等定义为了黑链接数相等。更为直观。
而如图所示,其实红黑树的每一步操做都对应了二三树的操做,若是是二节点就是黑链接,三节点的话里面的两个数之间就是红链接。
红黑树相比avl树,在检索的时候效率其实差很少,都是经过平衡来二分查找。但对于插入删除等操做效率提升不少。红黑树不像avl树同样追求绝对的平衡,他容许局部不多的不彻底平衡,这样对于效率影响不大,但省去了不少没有必要的调平衡操做,avl树调平衡有时候代价较大,因此效率不如红黑树,在如今不少地方都是底层都是红黑树的天下啦~
HashMap在里面就是链表加上红黑树的一种结构,这样利用了链表对内存的使用率以及红黑树的高效检索,是一种很happy的数据结构。
笔者之前用C++手写过avl树的实现,大二数据结构课程设计有点迷的朋友能够参考。