海量数据排序
一、外部排序
海量数据不能一次性读入内存,在对海量数据进行排序时,首先需要将海量数据拆分到多台机器或者多个文件,这些机器或文件称为拆分节点;然后在每个拆分节点上将数据全部读入内存并使用快速排序等方法进行排序;最后在合并节点使用多路归并方法将所有拆分节点的部分排序结果整合成最终的排序结果。外部排序也可以被称为外部归并排序。
如果不进行额外处理,合并节点仍然无法将所有数据读入内存中。可以使用小顶堆来解决这个问题:
- 假设有 k 个拆分节点,从这 k 个拆分节点分别读取一个最小的数据到小顶堆中。
- 将堆顶数据移出堆并写入合并节点的最终结果文件中。
- 确定刚才从堆中移除的数据属于哪个拆分节点,并从该拆分节点再读入一个数据。
例如以下有三个拆分节点,分别从这三个拆分节点读入 1,2,3 数据之后构建小顶堆,将堆顶数据 1 移出堆,因为数据 1 属于第一个拆分元素,因此再从第一个拆分节点读取一个数据到堆中。
但是上面的做法需要频繁地读写磁盘,可以设置输入缓存和输出缓存来解决这个问题。为每个拆分节点都设置一个输入缓存,每次将一部分数据读入输入缓存中,只有当输入缓存数据为空时才再从磁盘读入数据。并设置一个输出缓存,只有输出缓存满时才将数据写出磁盘中。
二、BitMap
如果待排序的数据是整数,或者其它范围比较小的数据时,可以使用 BitMap 对其进行排序。BitMap 相当于一个比特数组,如果某个数据存在时就将对应的比特数组位置设置为 1,最后从头遍历比特数组就能得到一个排序的整数序列。
例如要对 {2,1,5} 进行排序,可以设置一个范围为 0~7 的比特数组,读入数据之后将比特数组第 1、2、5 位置设置为 1。最后从头遍历比特数组,将比特数组值为 1 的数据读出得到 {1,2,5} 这个已排序的数据。
这种方法只能处理数据不重复的情况,如果数据重复,就要将比特数组转换成整数数组用于计数,这种排序方法叫做计数排序。可以把整数数组看成 32 个比特数组,32 比特可以存放的计数最大值为 232 ,在某些场景下数据的重复量不会这么大,只需要几个比特数组就能完成计数操作。
32 位整数的需要的比特数组空间为 232 bit≈ 4.2▪109 bit≈5▪108 byte≈500Mb,一台机器就可以放得下。因此对无重复的海量整数数据进行排序时,只需要设置一个比特数组就能完成该排序操作。
三、Trie
如果海量数据是字符串的话,可以使用 Trie 树来完成排序操作。先读入海量字符串数据构建一个 Trie 树,最后按字典序先序遍历 Trie 树就能得到已排序的数据。为了处理数据重复问题,可以使用 Trie 树的节点存储计数信息。Trie 树的具体实现请参考本专栏的海量数据判重一文。