鉴于120GB的硬盘驱动器,其中100个填充了长度为256和2 GB的字符串,如何最有效地对Java中的字符串进行排序? 需要多长时间?
答案 0 :(得分:22)
A1。您可能希望实现某种形式的合并排序。
A2:比你的机器上有256GB RAM的时间要长。
编辑:受到批评的刺激,我引用了维基百科关于合并排序的文章:
合并排序本质上是顺序的,使用慢速磁带驱动器作为输入和输出设备来运行它是切实可行的。它非常需要 内存很少,所需的内存不依赖于数量 数据元素。
出于同样的原因,它对于磁盘上的数据排序也很有用 太大而不能完全适合主存储器。在磁带驱动器上可以 向后和向前运行,合并传递可以在两者中运行 方向,避免倒带时间。
答案 1 :(得分:18)
我是这样做的:
阶段1是将100Gb拆分为50个2Gb分区,将50个分区中的每个分区读入内存,使用快速排序进行排序,然后写出。您希望排序的分区位于光盘的顶端。
第2阶段是合并50个已排序的分区。这是棘手的一点,因为光盘上没有足够的空间来存储分区和最终排序的输出。所以...
进行50路合并以填充光盘底端的第一个20Gb。
将50个分区中的剩余数据滑动到顶部,使另一个20Gb的可用空间与第一个20Gb的末尾相邻。
重复步骤1.和2.直到完成。
这会占用大量的光盘IO,但您可以利用2Gb的内存在复制和合并步骤中进行缓冲,以通过最小化光盘搜索次数来获取数据吞吐量,并进行大量数据传输。
编辑 - @meriton提出了一种减少复制的聪明方法。他没有滑动,而是建议将分区按顺序排序,并在合并阶段向后读取。这将允许算法通过简单地截断分区文件来释放分区使用的磁盘空间(阶段2,步骤2)。
潜在的缺点是磁盘碎片增加,并且由于向后读取分区而导致性能下降。 (在后一点上,在Linux / UNIX上向后读取文件需要更多的系统调用,并且FS实现可能无法反向执行“预读”。)
最后,我想指出,这个算法(和其他人)花费的时间的理论预测在很大程度上是猜测。这些算法在真正的JVM +真实操作系统+真实光盘上的行为对于“回到包络”计算来说太复杂了,无法给出可靠的答案。适当的处理需要实际实施,调整和基准测试。
答案 2 :(得分:17)
我基本上在重复Krystian's answer,但是要详细说明:
是的,您需要或多或少地执行此操作,因为您可用的RAM很少。但是天真的就地分类在这里只会因为移动字符串的成本而成为灾难。
不是实际移动字符串,而是跟踪哪些字符串应与其他字符串交换并实际移动它们,最后一次移动到最终位置。也就是说,如果您有1000个字符串,请创建一个1000个整数的数组。 array [i]是字符串i应该结束的位置。如果最后是array [17] == 133,则意味着字符串17应该在字符串133的最后位置.array [i] == i表示所有i的开始。那么,交换字符串只需要交换两个整数。
然后,像quicksort这样的任何就地算法效果都很好。
运行时间肯定取决于琴弦的最终移动。假设每一个都移动,你就会在合理大小的写入中移动大约100GB的数据。我可能会认为驱动器/控制器/操作系统可以为您移动大约100MB /秒。那么,1000秒左右? 20分钟?
但是它适合记忆吗?你有100GB的字符串,每个字符串是256个字节。多少串? 100 * 2 ^ 30/2 ^ 8,或约419M字符串。您需要419M整数,每个是4个字节,或大约1.7GB。瞧,适合你的2GB。
答案 3 :(得分:6)
听起来像是一个需要External sorting方法的任务。 “计算机程序设计艺术”第3卷包含一个对外部排序方法进行广泛讨论的部分。
答案 4 :(得分:5)
我认为你应该使用BogoSort。您可能需要稍微修改算法以允许就地排序,但这不应该太难。 :)
答案 5 :(得分:1)
你应该使用trie(又名:前缀树):构建一个树状结构,通过比较它们的前缀,你可以通过有序的方式轻松地遍历你的字符串。实际上,您不需要将其存储在内存中。您可以将trie构建为文件系统上的目录树(显然,不是数据来自的目录树)。
答案 6 :(得分:0)
AFAIK,merge-sort需要尽可能多的可用空间。这可能是任何避免随机访问的外部排序的要求,但我不确定这一点。