问题:
最近在面试人的时候,我经常会问一个问题,题目如下:
现在有一批日志数据,大小为1TB,数据是某一天的生产的日志数据,现在只有一台单CPU及8G内存的机器,请使用算法对此日志数据按时间的先后顺序进行排序。
可面试了好多人,都没有回答出这个问题
针对这个问题今天来讲讲这个问题我的解法。
解决方案1:桶排序
它的原理是利用分区字段的特点,将数据分别写入到多个桶文件中,再针对每个桶文件使用内存排序算法(如快速排序),将文件内容进行排序输出到有序桶文件中。最后将这些文件按分桶的顺序组合组合起来,形成最终有序的文件。
示例
这样说可能有点抽象,我来具体举个简单的示例吧
桶排序过程:
- 确定分桶的数量,像本例中就是按年龄段划分,20-29,30-39,40-49,50,这样划分出4个桶
- 再将数据按桶的范围装入桶中。
- 对桶中的数据进行一遍排序。
- 按照分桶的顺序将文件依次读取写入到一个文件中,就形成了最终的有序数据
再来说说桶排序的特点:
- 桶排序的时间复杂度为O(n),假如有N个数据,划分出M个桶。每个桶内元素(T=N/M),如果桶内排序算法采用快速排序,时间复杂度O(T*logT),M个桶的时间复杂度就是O(M*T*logT),再加上T=N/M,即可得到N*log(N/M),当M与N接近时,时间复杂度为常量级,这时候桶排序时间复杂度接近O(N)
- 对排序的数据要求很苛刻,数据必须很容易的划分出M个桶,桶与桶之前有着天然的大小顺序。对每个桶内的数据排序完成后,桶与桶之前无需再进行排序。
- 桶内的数据在各个桶内须必较平均。如果经过桶的划分。桶内的数据非常不平均,有的非常多,有的非常少,时间复杂度将不再是O(N),可能退化为(N*logN)。
- 桶排序比较适合外部排序。即为对磁盘文件进行排序,因为一般磁盘文件数据量较大,无法一次全部加载到内存中
解答问题
回到开头提到的问题,桶排序如何解决针对1TB的数据进物排序的问题呢?
- 划分桶,在问题中,我们需要排序的是日志中的时间,而时间是很容易划分出桶的,而时间使用哪个维度来划分桶是我们所面临的问题?如果选择分钟可得到1440个桶,每个桶平均就是0.7G左右,但像日志中数据可能有数据集中的问题,尤其是像1TB这么大的日志,高峰时间段可能是平均的10倍,分桶可能非常的不平均;这样建议再降低维度使用秒,一天可最多得到86400个分桶。平均每个文件只有12M左右,高峰也不过才120M,所以选择秒是比较合适的分桶。
- 再将数据按秒划分到各个桶即可。
- 再将各个桶内的数据进行排序。
- 最后按桶的先后顺序,合并到一个文件中,就得到了1TB日志的排序结果。
代码我就不放在这里了。有兴趣可以去我在github看看
桶排序的实现
解决方案2:归并排序
再来说说另外的一种解决办法吧,归并排序,它的核心思想:分而治之,具本来说就是将大数据分解成一个一个小数据,将小数据进行排序,然后再将这些小数据合并排序成终最终有序的数据。
示例
还是以年龄数据为例来看排序的数据吧
现有一批年龄数据20,30,50,40,22,37,45,48,使用分治思想进行排序
详细排序过程
详细的来说明下过程:图中的序号与下面的过程进行对应。
1, 将一个原始数组中的数据拆分到3个数组中。
第一个小数组中的数据是:20、30、50;
第二个小数组中的数据是:40、22、37;
第三个小数组中的数据是:45、48。
2, 对每个小数组进行排序操作,使用快速排序。可得到如下结果
第一个小数组中的数据:20、30、50;
第二个小数组中的数据:22、37、40;
第三个小数组中的数据:45、48。
3, 开始进行小数组的合并操作。声明一个3(根据分拆的数组个数定义大小)个大小的合并数组,从每个小数组中取出首个元素。即可得到20、22、45,进行排序操作,结果为: 20、22、45,将20从合并数组中移出,输出到最终的数组中,最终数组现在仅一个数据20,这一个元素。
4, 从20这个所在的小数组中取出下一个元素,也就是图中的30这个数字,加入到合并数组中,可得到数据:22、45、30,进行排序,结果为:22、30、45。将22从合并数组中移出,将22输出到最终的数组中,现在最终数组中有20,22这两个元素。
5, 从22这个数字所在的小数组中取出下一个元素,也就是37这个数字,加入到合并数组中,可得到:30、45、37,进行排序,结果为:30、37、45,将30这个数字从合并数组中移出,输出到最终的数组中,现在最终的数组中有20、22、30这三个元素
6, 从30这个数字所在的小数组中取出下一个元素,也就是50这个数字,加入到合并数组中,可得到:37、45、50,进行排序,结果为:37、45、50,将37这个数字从合并数组中移出,输出到最终的数组中,现在最终的数组中有20、22、30、37这四个元素。
7, 从37这个数字所在的小数组中取出下一个元素,也就是40这个数字,加入到合并数组中,可得到:45、50、40,进行排序,结果为40、45、50,将40这个数字从合并数组中移出,输出到最终数组中,现在最终的数组中有20、22、30、37、40这五个元素。
8, 从40这个数字所在小数组中取下一个元素,但40这个数字是这个小数组中的最后一个,所以没有数据加入到合并数组中,将合并数组中的元素45、50,进行排序,结果为:45、50,将45这个数字从合并数组中移出,加入到最终数组中,现在最终的数组中有20、22、30、37、40、45,这六个元素。
9, 从45这个数字所在的小数组中取出下一个元素,也就是48这个数字。加入到合并数组中,可得到48、50,进行排序,结果为48、50,将48这个数字从合并数据中移出,加入到最终数组中,现在最终数据中有20、22、30、37、40、45、48,这七个元素。
10, 从48这个数字所在的小数组中取出下一个元素,但48是这个小数组中的最后一个,所以没有数据加入到合并数组中,现在合并数组中仅有一个数字,无需排序,直接将50加入到最终数组中,现在最终数组中有20、22、30、37、40、45、48、50这八个元素。排序完成。
再来说说归并排序的特点:
- 归并排序的时间复杂度是 O(nlogn),这个推导有点复杂,略过。而且即使是最坏情况下,时间复杂度依然是O(nlogn)
- 归并排序并非原地排序算法,它需要一个额外的存储空间,空间复杂变为O(n)
- 归并排序也适用于外部排序,相对于桶排序来说,它就没有苛刻的数据要求。通用性更强
解答问题
再来讲讲使用归并排序的思想来实现对1TB的数据进行排序的问题
- 将1TB的文件按固定大小进行切分,比如大小256M,读取时可按行读取,限制大小在256M以内,需保证完整的行,这样可以切出大约4096个文件。
- 将这4096个文件,使用快速排序算法对这每个文件进行排序操作,可得到4096个排序的文件
- 将这4096个文件进行合并输出,合并的逻辑就是从每个文件中依次获取数据,排序,输出,就像示例中的那样。就可以得到一个最终有序的文件。
代码我也不放在这里了。有兴趣可以去我在github看看
归并排序的实现
总结下
桶排序的性能更好,最好情况下能够在O(N)的时间复杂度内解决,但对于数据要求很苛刻,所以选择此算法,需要小心甄别数据的特点:能够很容易的划分出N个桶,最好能比较平均。
归并排序的通用性更好,几乎对数据无要求,但时间复杂度比桶排序高,它的场景是在比较通用的大文件排序中.