第二部分、处理海量数据问题之六把密匙
密匙一、分而治之/Hash 映射+ Hash 统计+ 堆/快速/归并排序
实例:
以下列出题目;
题目解答见
1、海量日志数据,提取出某日访问百度次数最多的那个IP。
2、寻找热门查询:搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,
每个查询串的长度为 1-255 字节。
3、有一个 1G 大小的一个文件,里面每一行是一个词,词的大小不超过 16 字节,内存限
制大小是 1M。返回频数最高的 100 个词。
分而治之+hash统计+堆/快速排序这个套路
4、海量数据分布在100台电脑中,想个办法高效统计出这批数据的 TOP10。
5、有 10 个文件,每个文件 1G,每个文件的每一行存放的都是用户的 query,每个文件的query都可能重复。
要求你按照 query 的频度排序。
6、给定a、b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你找出a、b文件共同的url?
7、怎么在海量数据中找出重复次数最多的一个?
8、上千万或上亿数据(有重复),统计其中出现次数最多的钱 N 个数据。
9、一个文本文件,大约有一万行,每行一个词,要求统计出其中最频繁出现的前 10 个词,
请给出思想,给出时间复杂度分析。
10. 1000 万字符串,其中有些是重复的,需要把重复的全部去掉,保留没有重复的字符串。
请怎么设计和实现?
讨论:
1、Hash 取模是一种等价映射,不会存在同一个元素分散到不同小文件中去的情况,
即这里采用的是mod1000 算法,那么相同的IP在hash后,只可能落在同一个文件中,不可能被分散的。
2、那到底什么是hash 映射呢?简单来说,就是为了便于计算机在有限的内存中处理big数据,
从而通过一种映射散列的方式让数据均匀分布在对应的内存位置(如大数据通过取余的方式映射成小树存放在内存中,或大文件映射成多个小文件),
而这个映射散列方式便是我们通常所说的hash函数,设计的好的 hash函数能让数据均匀分布而减少冲突。
尽管数据映射到了另外一些不同的位置,但数据还是原来的数据,只是代替和表示这些原始数据的形式发生了变化而已。
3、rbtree PK hashtable
当数据量基本上int型key时,hash table是rbtree的3- 4倍,但hash table一般会浪费大概一半内存。
因为hash table所做的运算就是个%,而rbtree要比较很多,比如rbtree要看 value的数据,
每个节点要多出3个指针(或者偏移量)如果需要其他功能,比如,统计某个范围内的key的数量,
就需要加一个计数成员。且1s rbtree 能进行大概50w+次插入,hash table大概是差不多200w次。
不过很多的时候,其速度可以忍了,例如倒排索引差不多也是这个速度,而且单线程,且倒排表的拉链长度不会太大。
正因为基于树的实现其实不比hashtable慢到哪里去,所以数据库的索引一般都是用的 B/B+树,
而且B+树还对磁盘友好(B 树能有效降低它的高度,所以减少磁盘交互次数)。
比如现在非常流行的NoSQL数据库,像MongoDB也是采用的B树索引。
密匙二、双层桶划分
双层桶划分----其实本质上还是分而治之的思想,重在“分”的技巧上!
适用范围:
第k大,中位数,不重复或重复的数字
基本原理及要点:
因为元素范围很大,不能利用直接寻址表,所以通过多次划分,逐步确定范围,
然后最后在一个可以接受的范围内进行。可以通过多次缩小,双层只是一个例子。
问题实例:
题目解答见
1、2.5 亿个整数中找出不重复的整数的个数,内存空间不足以容纳这 2.5 亿个整数。
2、5亿个int找它们的中位数。
密匙三:Bloom filter/Bitmap
关于什么是 Bloom filter? 请看:海量数据处理之 Bloom Filter详解
适用范围:
可以用来实现数据字典,进行数据的判重,或者集合求交集
基本原理及要点:
位数组+k个独立hash函数。将hash函数对应的值的位数组置1,查找时如果发现所有hash函数对应位都是1说明存在,
很明显这个过程并不保证查找结果是100%正确的。同时也不支持删除一个已经插入的关键字,
因为该关键字对应的位会牵动到其他的关键字。所以一个简单的改进就是counting Bloom filter,
用一个 counter数组代替位数组,就可以支持删除了。
还有一个比较重要的问题,如何根据输入元素个数 n,确定位数组m的大小及hash函数个数。
当hash函数个数 k=(ln2)*(m/n)时错误率最小。在错误率不大于E的情况下,m至少要等于 n*lg(1/E)才能表示任意n个元素的集合。
但m还应该更大些,因为还要保证 bit数组里至少一半为0,则m应该>=nlg(1/E)*lge大概就是nlg(1/E)1.44 倍(lg表示以2为底的对数)。
举个例子我们假设错误率为0.01,则此时m应大概是n的13 倍。这样k大概是8个。
注意这里m与n的单位不同,m是bit为单位,而n则是以元素个数为单位(准确的说是不同元素的个数)。
通常单个元素的长度都是有很多 bit的。所以使用 bloom filter 内存上通常都是节省的。
扩展:
Bloom filter 将集合中的元素映射到位数组中,用k (k 为哈希函数个数)个映射位是否全1表示元素在不在这个集合中。
Counting bloom filter(CBF)将位数组中的每一位扩展为一个 counter,从而支持了元素的删除操作。
Spectral Bloom Filter(SBF)将其与集合元素的出现次数关联。SBF采用 counter 中的最小值来近似表示元素的出现频率。
问题实例:
1、给你 A,B 两个文件,各存放50亿条URL,每条URL占用64字节,内存限制是4G,
让你找出 A,B 文件共同的 URL。如果是三个乃至n个文件呢?
Bitmap
关于什么是 Bitmap,请看 blog 内此文第二部分:
问题实例:
1、在 2.5 亿个整数中找出不重复的整数,注,内存不足以容纳这 2.5 亿个整数。
2、腾讯面试题:给40亿个不重复的 unsigned int 的整数,没排过序的,然后再给一个
数,如何快速判断这个数是否在那40亿个数当中?
密匙四、Trie 树/数据库/倒排索引
Trie 树
适用范围:
数据量大,重复多,但是数据种类小可以放入内存
基本原理及要点:
实现方式,节点孩子的表示方式
扩展:
压缩实现。
问题实例:
问题实例:
1.寻找热门查询:查询串的重复度比较高,虽然总数是 1 千万,但如果除去重复后,不超过 3 百万个,每个不超过 255 字节。
2.有10个文件,每个文件 1G,每个文件的每一行都存放的是用户的query,每个文件的 query 都可能重复。要你按照 query 的频度排序。
3. 1000万字符串,其中有些是相同的(重复),需要把重复的全部去掉,保留没有重复的字符串。请问怎么设计和实现?
4. 一个文本文件,大约有一万行,每行一个词,要求统计出其中最频繁出现的前10个词。
其解决方法是:用 trie 树统计每个词出现的次数,时间复杂度是 O(n*le)(le 表示单词的平准长度),然后是找出出现最频繁的前 10 个词。
数据库索引
适用范围:
大数据量的增删改查
基本原理及要点:
利用数据的设计实现方法,对海量数据的增删改查进行处理。
archive/2011/08/17/mass- data- topic- 7- index- and - optimize.html;
关于 MySQL 索引背后的数据结构及算法原理,这里还有一篇很好的文章:http://
www.codinglabs.org/html/theory- of - mysql- index.html;
关于 B 树、B+ 树、B* 树及 R 树,
倒排索引(Inverted index)
适用范围:
搜索引擎,关键字查询
基本原理及要点:
为何叫倒排索引?一种索引方法,被用来存储在全文搜索下某个单词
在一个文档或者一组文档中的存储位置的映射。
以英文为例,下面是要被索引的文本:
T0 = "it is what it is"
T1 = "what is it"
T2 = "it is a banana"
我们就能得到下面的反向文件索引:
"a": {2}
"banana": {2}
"is": {0, 1, 2}
"it": {0, 1, 2}
"what": {0, 1}
检索的条件"what","is"和"it"将对应集合的交集。
正向索引开发出来用来存储每个文档的单词的列表。正向索引的查询往往满足每个文档
有序频繁的全文查询和每个单词在校验文档中的验证这样的查询。在正向索引中,文档占据
了中心的位置,每个文档指向了一个它所包含的索引项的序列。也就是说文档指向了它包含
的那些单词,而反向索引则是单词指向了包含它的文档,很容易看到这个反向的关系。
扩展:
问题实例:
文档检索系统,查询那些文件包含了某单词,比如常见的学术论文的关键字搜索。
密匙五、外排序
适用范围:
大数据的排序,去重
基本原理及要点:
外排序的归并方法,置换选择败者树原理,最优归并树
扩展:
问题实例:
1).有一个 1G 大小的一个文件,里面每一行是一个词,词的大小不超过 16 个字节,内
存限制大小是 1M。返回频数最高的 100 个词。
这个数据具有很明显的特点,词的大小为 16 个字节,但是内存只有 1M 做 hash 明显
不够,所以可以用来排序。内存可以当输入缓冲区使用。
密匙六、分布式处理之 Mapreduce
MapReduce 是一种计算模型,简单的说就是将大批量的工作(数据)分解(MAP)执
行,然后再将结果合并成最终结果(REDUCE)。这样做的好处是可以在任务被分解后,可
以通过大量机器进行并行计算,减少整个操作的时间。但如果你要我再通俗点介绍,那么,
说白了,Mapreduce 的原理就是一个归并排序。
适用范围:
数据量大,但是数据种类小可以放入内存
基本原理及要点:
将数据交给不同的机器去处理,数据划分,结果归约。
扩展:
问题实例:
1. The canonical example application of MapReduce is a process to count the
appearances of each different word in a set of documents:
2. 海量数据分布在 100 台电脑中,想个办法高效统计出这批数据的 TOP10。
3. 一共有 N 个机器,每个机器上有 N 个数。每个机器最多存 O(N)个数并对它们操作。
如何找到 N^2 个数的中数(median)?
最后一题:
非常大的文件,装不进内存。每行一个 int 类型数据,现在要你随机取 100 个数。
我们发现上述这道题,无论是以上任何一种模式/方法都不好做,那有什么好的别的方法呢?
我们可以看看:操作系统内存分页系统设计(说白了,就是映射+建索引)。
Windows 2000使用基于分页机制的虚拟内存。每个进程有4GB的虚拟地址空间。
基于分页机制,这 4GB 地址空间的一些部分被映射了物理内存,一些部分映射硬盘上的交换文件,
一些部分什么也没有映射。程序中使用的都是 4GB 地址空间中的虚拟地址。而访问物理内存,
需要使用物理地址。
关于什么是物理地址和虚拟地址,请看:
物理地址(physical address):
放在寻址总线上的地址。放在寻址总线上,如果是读,电路根据这个地址每位的值就将相应地址的物理内存中的数据放到数据总线中传输。
如果是写,电路根据这个地址每位的值就将相应地址的物理内存中放入数据总线上的内容。
物理内存是以字节(8 位)为单位编址的。
虚拟地址(virtual address): 4G虚拟地址空间中的地址,程序中使用的都是虚拟地址。
使用了分页机制之后,4G 的地址空间被分成了固定大小的页,每一页或者被映射到物理内存,
或者被映射到硬盘上的交换文件中,或者没有映射任何东西。对于一 般程序来说,4G的地址空间,
只有一小部分映射了物理内存,大片大片的部分是没有映射任何东西。物理内存也被分页,来映射地址空间。对于 32bit 的 Win2k,
页的大小是 4K 字节。CPU 用来把虚拟地址转换成物理地址的信息存放在叫做页目录和页表的结构里。
物理内存分页,一个物理页的大小为4K字节,第0个物理页从物理地址0x00000000处开始。
由于页的大小为4KB,就是0x1000字节,所以第1页从物理地址0x00001000处开始。
第2页从物理地址0x00002000处开始。可以看到由于页的大小是4KB,所以只需要32bit的地址中高20bit来寻址物理页。
返回上面我们的题目:非常大的文件,装不进内存。每行一个 int 类型数据,现在要你
随机取 100 个数。针对此题,我们可以借鉴上述操作系统中内存分页的设计方法,做出如
下解决方案:
操作系统中的方法,先生成 4G 的地址表,在把这个表划分为小的 4M 的小文件做个索
引,二级索引。30 位前十位表示第几个 4M 文件,后 20 位表示在这个 4M 文件的第几个,
等等,基于 key value 来设计存储,用 key 来建索引。
但如果现在只有 10000 个数,然后怎么去随机从这一万个数里面随机取 100 个数?请
读者思考。