上周是硬件,本周终于来到软件领域,明确的欠一个账,文件系统这块由于东西比较多,我还没彻底总结好,先欠着~java
本周,让咱们作一些准备,来谈谈映射。计算机就是个分型的系统,而映射这种数据结构,是计算机中很是基础和常见的一种数据结构,从cpu到文件存储,再到分布式文件存储,其核心都是映射。算法
抄书:映射就是:使得对A中的每一个元素a,按法则f,在B中有惟一肯定的元素b与之对应,则称f为从A到B的映射,记做f:A→B。哈哈,数学上的定义是最清晰和明确的。也能够写做y=f(x)sql
举个例子:给定一个映射M,令1–>a,2–>c,3->d。求3所对应的元素的时候,M应该返回的元素是d。数据库
为何在准备章节,须要先理解映射呢?天然是由于用的地方太多了--。。数组
当你但愿从文件系统中找到标记为ADEADDAAD12AS的块所对应的数据的时候,你须要用到映射。网络
当你写的程序须要用到方法指针的时候,你须要用到映射。数据结构
当你从网络中获取了数据的时候,你须要用到映射。架构
当你须要从数据库内取出一条记录的时候,你须要用到映射。nosql
甚至当你敲击键盘的时候,你也同样须要用到映射,键盘会将你的按键映射到计算机内的一种电信号。分布式
天然而然的,在咱们的海量数据处理中,映射也是整个体系中最为重要的核心组成部分,不管是RDBMS,图数据库,仍是NoSQL引擎,他们底层的核心都是映射,而一个通过仔细优化的映射实现将可以直接的决定咱们存储实现的全部技术特性。
怎样?可以感觉到映射的重要性了么?既然这么重要还不赶忙往下看?!~
想实现这样的映射关系,在计算机程序里已经有了现成的方式了,那就是一个方法(method)
intget(intkey){}
这就是一个最简单的方法了。“get”这就是方法的名字(能够对应函数下标),intkey是入参(能够对应函数输入条件x),返回值是个int,也就是y。
下面咱们举一个例子,来实际的感觉一下如何实现一套高效的映射。
假设咱们须要找到一系列的函数实现,可以使得2->4,1->2,4–>8,3->6,这样,当我给定key=1的时候,这个函数应该返回2,而给定key=2的是时候,函数应该返回4。咱们来看看,在计算机领域有哪些比较经常使用的方式和方法。
1.使用:
这也应该是最容易想到的一种存放映射关系的方法了吧。在数据结构中,有不少种结构体能够支持这样的一个容器,好比使用数组,链表,二叉树,均可以使用相似的方式来存放这些数据。他们的特征各不相同,这也是我在这一章的后面要介绍的结构体中的重要主题。这里,为了帮助理解,咱们以数组做为最简单的实现样例吧。
咱们可让数组里的每个元素都是一个key->valuepair。以下图所示
2->4 |
1->2 |
4->8 |
3->6 |
这样,当我要寻找key=2所对应的数据的时候,只须要遍历这个数组,找到key=2的元素,而后返回这个元素的value=4就好了。
这种方式的适应性是最好的,由于不须要理解这些元素的内在联系。不过也有代价,就是须要付出不少的空间成本。
2.定义一个函数
对于2->4,1->2,4–>8,3->6这样的两组数据之间的对应关系,咱们能够用一个算法f(x)=2x.x属于1~4
用上面的算法,就能够表示上面的这个映射的关系了。使用这种方式的好处不少,好比空间节省,复杂度比较低,可是他有一个比较大的劣势,就是否是全部的数据均可以很容易的表示为上面的那种方式,由于须要理解元素之间内在的联系,而且这些元素自己必须有规律可以被认识才行。
3.写一段穷举的算法
咱们仍然使用1->2,2->4,3->6,4–>8这样的两组数据之间的对应关系。
使用如下算法:
f(x) = { if(x == 1) return 2; else if(x == 2) return 4; else if(x == 3) return 6; else if (x == 4) return 8; else throw exception; }
使用这种算法,能够针对一些特殊状况进行特殊的处理,不过主要的代价是。。每次增长一个函数都须要咱去写代码儿。。这事儿就有点儿二了。。。哈哈
在这三类中,后面这两类不大适合在咱们的数据存储领域内使用,因此,咱们将映射的概念进行一下收缩,后面的主要篇幅,就来介绍一下集合类。
咱们还以刚才的例子来作分析。
假设咱们须要找到一系列的函数实现,可以使得2->4,1->2,4–>8,3->6,这样,当我给定key=1的时候,这个函数应该返回2,而给定key=2的是时候,函数应该返回4。
在开始的时候,咱们只采用了简单的数组结构,让数组里的每个元素都是一个key->valuepair。以下图所示
2->4 |
1->2 |
4->8 |
3->6 |
在查询的时候,咱们选择的方式是遍历整个数组,找到要求的key后,返回这个key对应的value。
这种方式虽然可以正确的返回数据,可是,效率明显是过低了。好比,若是这个数组的写入了100W的数据,那么若是要找到一个要求的数据,在最坏状况下,须要遍历整个数组才能取到全部数据。效率过低了这。。
因而,就天然而然的有个需求:可否找到更快的方式来查找数据呢?
在数据查找领域,核心的算法就俩,一个么叫二分查找,时间复杂度O(log2N),一个么就是hash..O(1)
二分查找的核心要求主要有仨:
1.数据必须有序。
2.能够快速的从数据中找到指定位置的数据。
3.能够获知数据总个数
为了查找效率可以到达log2N,咱们来看看,若是要查找到key=3这个数据,具体如何操做。
咱们先对数据排序
1->2 |
2->4 |
3->6 |
4->8 |
而后,就能够进行折半查找了。
另一种方案就是hash
主要策略实际上是利用hash函数对原始数据作一次预先的计算映射。
好比我能够选择一个hash函数key%3
每次插入数据的时候,都先算一下hash函数后,才插入到一个size是3的数组里面
3->6 |
1->2 |
2->4 |
这几个数字相对的比较好处理,可是4->8这个数据怎么办呢?
若是这个数据计算一下hash函数key%3=4%3=1,这个数据应该也被写入到位置为1的这个的地方,可是,这个地方已经有一个数据1->2了,应该怎么办呢?
这在hash函数里面有个专业的名词,就叫碰撞。
碰撞有不少种不一样的处理方式,不过这里我只介绍一种,在java中比较常见的模式:作一个链表放在后面
这样,在查询的时候,若是须要4->8这个数据,那么也先运算一下hash函数
key%3=4%3=1。因此在位置等于1的槽位上进行查找,由于第一个值是1->2不符合要求,因此指针下移,找到4->8,符合要求,返回便可。
上面,咱们就介绍了可以提高查询速度的两套主要的思路。
看起来挺简单,其实否则,虽然核心思路简单,可是须要有不少其余的领域的不一样选择,致使了彻底不同的算法结构。而面向的问题不一样,解决的方案也就不一样,咱们来看看还有哪些主要的需求致使了咱们实现上的不一样呢?
1.是否支持范围查找?
有些时候,一个数据结构须要进行范围查询,好比以时间做为key的数据,那么通常来讲都会须要查询某个时间范围内的全部结果,对于这类的查询,支持范围查询是个必要的条件,不过有些数据结构则可能不可以支持范围查询。
2.集合是否可以随着数据的增加而自动扩展?
大部分状况下,其实咱们都很难在开始的时候预测到咱们的这些数据结构中到底会有多少的数据,若是可以随着数据的增加而自动扩展,咱们就不须要担忧集合过小,数据太多,也不须要担忧预先申请的空间太大,资源浪费了~惋惜,数组不能自动扩展==。。
3.读写性能指标?
这应该是全部集合类都要努力追求和优化的东西~~~hoho
4.是否面向磁盘结构?
这是个很重要的指标,什么叫面向磁盘的结构呢?为何btree,LSM就适合在磁盘上存储呢?本章后面章节会有详细介绍。
5.并行指标?
当前数据结构是否可以支持并行写入和读取,在多核架构下是很是重要的一个指标。
6.内存占用
以上的结构基本上可以涵盖一个集合的大部分技术特征了,而不一样的集合类在以上这些特征上的不一样选择也直接决定了性能的好坏,而不管是nosql仍是RDBMS,其性能最终的决定性因素都在于集合上的选择。在后面,我会选择几类常见的,比较典型的存储结构,以上面的这几个维度来进行一下分析,但愿可以让你们在理解了这些存储的技术特性后,也可以对目前市面上常见的存储的性能进行更准确和更客观的估测:)