关于一道著名的面试题,如何在 2G 内存限制下排序一个 10G 文件?
答案理所当然是用归并的原理先拆分,再各自排序,最后归并到一个文件中。
真正写过会发现没那么简单。
1,拆分大文件时,不能简单等分。比如原文件中数字用「,」分割,直接等分会把数字拆散,所以需要顺延到逗号后的一个数字。于是又有个问题,每次需要把 byte 数组转成 string 再 parseInt 成数字才行,这样的效率很低。
2,合并排好序的文件时,会发现现实没有那么美好。如果只每次读一个数字的话,10G 的文件得处理到地老天荒。
所以要做优化,大致优化的方法是。
1,用多线程去排序拆分后的文件。但问题来了,可能会出现 OOM 的问题,或者慢到要死,因为不停地在 GC。因为拆分是可能按照 2G 左右内存拆分的。于是还得回去改拆分的 Size,再限制线程池的数量。
2,归并排序后的文件时,做缓冲池。比如之前是每次读一个数字,改成每次读一千个数字。这样代码的复杂度会直线上升,需要维护每个文件的索引和状态,但可以提高效率。这样做的意义不是减少读盘,因为本身读文件就是 buffered,每次读下一个字节并不是直接读盘。而是把排序的过程从 O(n²) 降到 O(nlogn)。
3,减少归并后追加到最终文件的次数。
即使这样效率还是低下的,瓶颈大概是在 byte 转数字的操作,以及最终单线程读取排好序的文件,并单线程排序以及输出。
再进一步的话就是多线程读取排好序的文件,用信号量或者倒数计数器。这样或许代码的复杂度又要继续加重。
如果能做完所有优化,将一定量级内文件排序时间把控在可以接受的时间,保证代码的健壮性,最后如果还相对优雅可读。我想,这大概可以真正算是入门了计算机。
答案理所当然是用归并的原理先拆分,再各自排序,最后归并到一个文件中。
真正写过会发现没那么简单。
1,拆分大文件时,不能简单等分。比如原文件中数字用「,」分割,直接等分会把数字拆散,所以需要顺延到逗号后的一个数字。于是又有个问题,每次需要把 byte 数组转成 string 再 parseInt 成数字才行,这样的效率很低。
2,合并排好序的文件时,会发现现实没有那么美好。如果只每次读一个数字的话,10G 的文件得处理到地老天荒。
所以要做优化,大致优化的方法是。
1,用多线程去排序拆分后的文件。但问题来了,可能会出现 OOM 的问题,或者慢到要死,因为不停地在 GC。因为拆分是可能按照 2G 左右内存拆分的。于是还得回去改拆分的 Size,再限制线程池的数量。
2,归并排序后的文件时,做缓冲池。比如之前是每次读一个数字,改成每次读一千个数字。这样代码的复杂度会直线上升,需要维护每个文件的索引和状态,但可以提高效率。这样做的意义不是减少读盘,因为本身读文件就是 buffered,每次读下一个字节并不是直接读盘。而是把排序的过程从 O(n²) 降到 O(nlogn)。
3,减少归并后追加到最终文件的次数。
即使这样效率还是低下的,瓶颈大概是在 byte 转数字的操作,以及最终单线程读取排好序的文件,并单线程排序以及输出。
再进一步的话就是多线程读取排好序的文件,用信号量或者倒数计数器。这样或许代码的复杂度又要继续加重。
如果能做完所有优化,将一定量级内文件排序时间把控在可以接受的时间,保证代码的健壮性,最后如果还相对优雅可读。我想,这大概可以真正算是入门了计算机。
展开
评论
点赞
![[流泪]](http://lf-web-assets.juejin.cn/obj/juejin-web/xitu_juejin_web/img/jj_emoji_6.dde0d83.png)