海量数据处理

0 概述

海量数据处理是指基于海量数据的存储和处理。

正因为数据量太大,所以导致要么无法在较短时间内迅速解决,要么无法一次性装入内存。

  • 对于时间问题,可以采用巧妙的算法搭配合适的数据结构(如布隆过滤器、哈希、位图、堆、 数据库、倒排索引、Trie 树)来解决;
  • 对于空间问题,可以采取分而治之(哈希映射)的方法,也就是说,把规模大的数据转化为规模小的,从而各个击破;

1 哈希分治

  1. 为了便于计算机在有限的内存中处理大数据,可以通过hash映射的方式让数据均匀分布在对应的内存位置(如大数据通过取余的方式映射成小数据存放在内存中, 或大文件映射成多个小文件),然后再针对各个小块数据通过 HashMap 进行统计或其他操作,好的 hash 函数能让数据均匀分布而减少冲突;

1.1 寻找 TOP IP

问题

海量日志数据,提取出某日访问百度次数最多的那个 IP。

思路

如果想一次性把所有 IP 数据装进内存处理,则内存容量通常不够,故针对数据太大,内存受限的情况,可以把大文件转化(取模映射)成小文件,从而大而化小,逐个处理。即:先映射,而后统计,最后排序。

步骤

  1. 先映射。首先将该日访问百度的所有 IP 从访问日志中提取出来,然后逐个写入到一个大文件中,接着采取 hash 映射的方法(比如 hash(IP)%1000),把整个大文件的数据映射到 1000 个小文件中。

  2. 统计。当大文件转化成了小文件,那么我们便可以采用 hashMap(ip,cnt) 来分别对 1000 个小文件中的 IP 进行频率统计,找出每个小文件中出现频率最大的 IP,总共 1000 个 IP。

  3. 堆排序/快速排序。统计出 1000 个频率最大的 IP 后,依据它们各自频率的大小进行排序,找出那个出现频率最大的 IP,即为所求。

1.2 寻找热门查询

问题

搜索引擎会通过日志文件把用户每次检索所使用的所有查询串都记录下来, 每个查询串的长度为 1-255 字节。假设目前有 1000 万个查询记录(但因为这些查询串的重复度比较高,所以虽然总数是 1000 万,但如果除去重复后,不超过 300 万个查询字符串),请统计其中最热门的 10 个查询串,要求使用的 内存不能超过 1G。

一个查询串的重复度越高,说明查询它的用户越多,也就是越热门。

思路

针对本题数据规模是比较小的,能一次性装入内存。虽然有 1000 万条查询记录,但是去除重复后,不超过 300 万条查询记录。占用的最大内存为 300 0000 * 255 B = 0.75 GB,可以将字符串都放入内存进行处理。

步骤

  1. 统计。使用 hashMap 进行频率统计。时间复杂度为 O(N);
  2. 堆排序。借助堆这个数据结构,找出 Top K,维护一个 K 大小的小根堆,然后遍历 300 万条查询记录,分别和根元素进行比较。时间复杂度为 N’O(log K)。

所以,最终的时间复杂度是:O(N)+N’O(log K),其中,N 为 1000 万,N’ 为 300 万,K 为 10。

1.3 寻找频数最高的 100 个词

问题

有一个 1G 大小的文件,里面每一行是一个词,词的大小不超过 16 字节,内存限制大小是 1M。返回频数最高的 100 个词。

思路

内存大小只有 1 M,而文件却有 1 GB,所有必须将该大文件切分为多个小于 1 M 的小文件。

步骤

  1. 哈希映射。按先后顺序读取文件,对于每个词 x,执行 hash(x)%5000,然后将该值存到 5000 个小文件(记为 x0, x1, …, x4999)中。如此每个文件的大小大概是 200k 左右。当然,如果其中有的小文件超过了 1M 大小,则可以按照类似的方法继续往下分,直到分解得到的小文件的大小都不超过 1M;
  2. 统计。对每个小文件中出现的词进行频率统计;
  3. 堆排序 / 归并排序。取出每个文件中出现频率最大的 100 个词(可以用含 100 个结点的小根堆)后,再把 100 个词及相应的频率存入文件,这样又得到了 5000 个文件。最后把这 5000 个文件进行归并(可以用归并排序);

1.4 寻找共同的 URL

问题

给定 a、b 两个文件,各存放 50 亿个 url,每个 url 各占 64 字节,内存限制是 4G,请找出 a、b 文件共同的 url。

思路

可以估计每个文件的大小为 5G * 64=320G,远远大于内存限制的 4G。所以不可能将其完全加载到内存中处理。

步骤

  1. 哈希映射。

    遍历文件 a,对每个 url 求取hash(url)%1000 ,然后根据所取得的值将 url 分别 存储到 1000 个小文件(记为 a1 a2 a3 a4)中。这样每个小文件大约为 300M。

    遍历文件 b,采取和 a 相同的方式将 url 分别存储到 1000 小文件中(记为 b1 b2 b3 b4)。

    这样处理后,所有可能相同的 url 都在对应的小文件url。然后我们只要求出 1000 对小文件中相同的 url 即可。

  2. 统计。

    求每对小文件中相同的 url 时,可以把其中一个小文件的 url 存储到 hashSet 中。然后遍历另一个小文件的每个 url,看其是否在刚才构建的 hashSet 中,如果是,那么就是共同的 url,存到文件里就可以了。

2 位图

  • 位图(Bit-map)就是用一个 bit 位来标记某个元素对应的 value, 而 key 即是该元素。由于采用了 bit 为单位来存储数据,因此在存储空间方面,可以大大节省;
  • 位图通过使用 bit 数组来表示某些元素是否存在,可进行数据的快速查找、判重、删除,一般来说数据范围是 int 的 10 倍以下;
  • 例如要对 {4,7,2,5,3} 进行排序,可以设置一个范围为 0~8 的比特数组,读入数据之后将比特数组第 2、3、4、5、7 位置设置为 1。最后从头遍历比特数组,将比特数组值为 1 的数据读出得到 {2,3,4,5,7} 这个已排序的数据;

2.1 2.5 亿个数的去重

问题

在 2.5 亿个整数中找出不重复的整数,注,内存不足以容纳这 2.5 亿个整数。

分析

采用 2-Bitmap(即每个整数分配 2 bit:00 表示不存在,01表示出现一次,10 表示多次,11 表示无意义)。

这样,所占内存 2 ^ 32 * 2 bit = 1 GB。

步骤

  1. 扫描。扫描这 2.5 亿个整数,查看 Bitmap 中相对应位, 如果是 00 变 01,01 变 10,10 保持不变。

  2. 输出。查看对应位为 01 的整数,输出即可。

2.2 整数的快速查询

问题

给 40 亿个不重复的 unsigned int 的整数,没排过序的,然后再给一个数,如何快速判断这个数是否在那 40 亿个数当中?

分析

使用位图的方法,40*108 = 4 *109 bit = 500 M,所以可以申请 512M 的内存,一个 bit 位代表一个 unsigned int 值。

步骤

读入 40 亿个数,设置相应的 bit 位,读入要查询的数,查看相应 bit 位是否为 1,为 1 表示存在,为 0 表示不存在。

3 布隆过滤器

  1. 布隆过滤器(BloomFilter)是一种空间效率很高的随机数据结构,可以看做是对位图(bit-map)的扩展;
  2. 原理是:当一个元素被加入集合时,通过 K 个 Hash 函数将这个元素映射成一个位阵列(Bitarray) 中的 K 个点,并将它们置为 1。在检索一个元素是否在一个集合中时,我们只要看看这些点是不是都是 1 就能大约判断出集合中有没有它了:如果这些点有任何一个 0,则被检索元素一定不在;如果都是 1,则被检索元素很可能存在集合中(因为哈希函数的特点,两个不同的数通过哈希函数得到的值可能相同);
  3. 布隆过滤器有一定的误判率:在判断一个元素是否属于某个集合时,有可能会把不属于这个集合的元素误认为属于这个集合。因此,它不适合那些“零错误”的应用场合。而在能容忍低错误率的应用场合下,布隆过滤器通过极少的错误换取了存储空间的极大节省;
  4. 补救方法:布隆过滤器存在一定的误识别率。常见的补救办法是建立白名单,存储那些可能被误判的元素。

3.1 寻找通过 URL

问题

给你 A,B 两个文件,各存放 50 亿条 URL,每条 URL 占用 64 字节,内存限制是 4G,让你找出 A,B 文件共同的 URL。

分析

如果允许有一定的错误率,可以使用布隆过滤器,4G 内存大概可以表示 320 亿 bit。

步骤

将其中一个文件中的 url 使用布隆过滤器映射为这 320 亿 bit,然后读取另外一个文件的 url,使用布隆过滤器进行判重,如果是重复的,那么该 url 就是共同的 url(允许有一定的错误率的情况)。

3.2 垃圾邮件过滤

问题

如何过滤垃圾邮件。

分析

比较直观的想法是把常见的垃圾邮件地址存到一个巨大的集合中,然后遇到某个新的邮件,将它的地址和集合中的全部垃圾邮件地址一一进行比较,如果有元素与之匹配,则判定新邮件为垃圾邮件。如果允许一定的误判率的话,我们可以使用布隆过滤器。

4 Trie 树(字典树)

Trie 树,即字典树,又称单词查找树或键树,是一种树形结构。

典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是最大限度地减少无谓的字符串比较,查询效率比较高。

Trie 的核心思想是空间换时间,利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。

它有 3 个基本性质:

  1. 根节点不包含字符,除根节点外每一个节点都只包含一个字符;
  2. 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串;
  3. 每个节点的所有子节点包含的字符都不相同;

4.1 10 个频繁出现的词(似乎HashMap更合适)

问题

一个文本文件,大约有一万行,每行一个词,要求统计出其中出现次数最频繁的 10 个词,请给出思路和时间复杂度的分析。

分析

用 Trie 树统计每个词出现的次数,时间复杂度是 O(n*l)(l 表示单词的平均长度),然后是找出出现最频繁的前 10 个词。

4.2 寻找热门查询(似乎HashMap更合适)

问题

搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为 1-255 字节。假设目前有一千万个记录,这些查询串的重复度比较高,虽然总数是 1 千万,但是如果去除重复和,不超过 3 百万个。一个查询串的重复度越高,说明查询它的用户越多,也就越热门。请你统计最热门的 10 个查询串,要求使用的内存不能超过 1G。

分析

3 百万字符占用的最大内存为 300 0000 * 255 B = 0.75 GB。

利用 Trie 树,关键字域存该查询串出现的次数,若没有出现则为 0。最后用 10 个元素的最小堆来对出现频率进行排序。

5 数据库

当遇到大数据量的增删改查时,一般把数据装进数据库中,从而利用数据的设计实现方法,对海量数据的增删改查进行处理。而数据库索引的建立则对查询速度起着至关重要的作用。

6 倒排索引

倒排索引((Inverted Index))是一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。

以英文为例,下面是要被索引的文本 T0、T1、T2:

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}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hellosc01

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值