如何高效的处理海量数据?

什么是海量数据?

何谓海量,就是数据量太大,要么是无法在较短时间内迅速解决,要么是数据太大,导致无法一次性装入内存。

  1. 针对时间,我们可以采用巧妙的算法搭配合适的数据结构,如 Bloom filter/Hash/bit-map/堆/数据库或倒排索引/trie树
  2. 针对空间,无非就一个办法:大而化小,分而治之,你不是说规模太大嘛,那简单啊,就把规模大化为规模小的,各个击破。

常用方法

分而治之/hash 映射 + hash 统计 + 堆/快速/归并排序

  1. 分而治之/hash映射:针对数据太大,内存受限,只能把大文件化成(取模映射)小文件
  2. hash_map 统计:当大文件转化为了小文件,那么我们便可以采用常规的 hash_map(key,value) 来进行频率统计。
  3. 堆/快速排序:统计完了之后,便进行排序(可采取堆排序),得到次数最多的 key。

多层划分

本质上还是分而治之的思想,重在"分"的技巧上。因为元素范围很大,不能利用直接寻址表,所以通过多次划分,逐步确定范围,然后最后在一个可以接受的范围内进行。

适用范围:top-k ,中位数,不重复或重复的数字

Bloom filter/Bitmap

  1. 布隆过滤器:可以用来实现数据字典,进行数据的判重,或者集合求交集
  2. Bit-map:用一个 bit 位来标记某个元素对应的 Value, 而 Key 即是该元素。由于采用了 bit 为单位来表示某个元素是否存在,因此可以大大节省存储空间。
    a. 将所有的位都置为 0,从而将集合初始化为空。
    b. 通过读入文件中的每个整数来建立集合,将每个对应的位都置为 1。
    c. 检验每一位,如果该位为 1,就输出对应的整数。

Trie 树/数据库/倒排索引

  1. Trie 树(前缀树):数据量大,重复多,但是数据种类小可以放入内存
    基本原理及要点:实现方式,节点孩子的表示方式
    扩展:压缩实现。
  2. 数据库索引:大数据量的增删改查
    基本原理及要点:利用数据的设计实现方法,对海量数据的增删改查进行处理。
  3. 倒排索引(Inverted index):搜索引擎,关键字查询
    基本原理及要点:一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。

分布式处理之 Hadoop/Mapreduce

MapReduce 是一种计算模型,简单的说就是将大批量的工作(数据)分解(MAP)执行,然后再将结果合并成最终结果(REDUCE)。这样做的好处是可以在任务被分解后,可以通过大量机器进行并行计算,减少整个操作的时间。

适用范围:数据量大,但是数据种类小可以放入内存

相关场景

海量日志数据,提取出某日访问百度次数最多的那个 IP,文件比较长上亿行,无法一次读入内存应该怎么做?

  1. 分而治之/hash映射:针对数据太大,内存受限,只能是把大文件化成(取模映射)小文件;按照 IP 地址的 Hash(IP)%1000 值,把海量 IP 日志分别存储到 1000 个小文件中。这样,每个小文件最多包含 4MB 个 IP 地址
  2. hash_map 统计:当大文件转化了小文件,那么我们便可以采用常规的 hash_map(ip,value) 来进行频率统计。
  3. 堆/快速排序:统计完了之后,可以得到 1024 个小文件中的出现次数最多的 IP,再依据常规的排序算法得到总体上出现次数最多的 IP;

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

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

有10个文件,每个文件1G,每个文件的每一行存放的都是用户的query,每个文件的query都可能重复,要求你按照query的频度排序

  1. hash 映射:顺序读取10个文件,按照 hash(query)%10 的结果将 query 写入到另外10个文件(记为a0,a1,...a9)中。这样新生成的文件每个的大小大约也 1G(假设hash函数是随机的)。
  2. hash_map 统计:找一台内存在 2G 左右的机器,依次对用 hash_map(query, query_count) 来统计每个 query 出现的次数。注:hash_map(query,query_count)是用来统计每个query的出现次数,不是存储他们的值,出现一次,则 count+1。
  3. 堆/快速/归并排序:利用快速/堆/归并排序按照出现次数进行排序,将排序好的 query 和对应的query_cout 输出到文件中,这样得到了 10 个排好序的文件(记为)。最后,对这 10 个文件进行归并排序(内排序与外排序相结合)

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

方案1:

  1. 分而治之/hash映射
  2. hashmap 统计
  3. 找出所有 value 为 1 的 key 值

方案2:

采用 2-Bitmap(每个数分配 2bit,00 表示不存在,01 表示出现一次,10 表示多次,11 无意义)进行,共需内存 2^32 * 2 bit=1 GB内存,还可以接受。然后扫描这 2.5亿个整数,查看 Bitmap 中相对应位,如果是 00变01,01变10,10保持不变。所描完事后,查看 bitmap,把对应位是 01 的整数输出即可。

10亿个QQ号,找出一个QQ号是不是在其中,时间复杂度要求O(1)

用 bitmap 来做这个问题。首先对数据进行预处理。定义 10 亿 bit 位个 int。在32位计算机下,一个int 是 32 位,10 亿位的话,就需要 10 亿除以 32 个int整数。大概有很多个。第一个 int 标记 0-31 这个数字范围的 QQ 号存不存在,比如说 0000001 这个QQ号,我就把第一个 int 的第 1 位置 1。第二个 int 能够标记 32-63 这个范围的QQ存不存在,以此类推。把这 10 亿个QQ号预处理一遍。然后计算你给我的这个QQ号,它是在哪个int里面,然后找到相应的数据位,看是1还是0,就能在O(1)的时间里找到。

相关推荐
Lee川18 分钟前
优雅进化的JavaScript:从ES6+新特性看现代前端开发范式
javascript·面试
Lee川4 小时前
从异步迷雾到优雅流程:JavaScript异步编程与内存管理的现代化之旅
javascript·面试
晴殇i6 小时前
揭秘JavaScript中那些“不冒泡”的DOM事件
前端·javascript·面试
绝无仅有6 小时前
Redis过期删除与内存淘汰策略详解
后端·面试·架构
绝无仅有6 小时前
Redis大Key问题排查与解决方案全解析
后端·面试·架构
AAA梅狸猫7 小时前
Looper.loop() 循环机制
面试
AAA梅狸猫7 小时前
Handler基本概念
面试
Wect8 小时前
浏览器缓存机制
前端·面试·浏览器
掘金安东尼8 小时前
Fun with TypeScript Generics:玩转 TS 泛型
前端·javascript·面试
掘金安东尼8 小时前
Next.js 企业级落地
前端·javascript·面试