我有一个有趣的问题。我想在Java中交叉两组Longs,每组有1B成员 - 每组4GB。这不适合我需要在服务器上运行的内存。
我想知道有什么有趣的方法来解决这个问题。
到目前为止,我想到的是从磁盘读取每个原始集的子集,这些子集足够小以适合内存,然后交叉每个子集,并临时写入磁盘。最后,我可以通过并交叉这些子集。我觉得这可能会变成地图缩减工作。
也许你会有更好的想法:)我怀疑我是第一个提出这个问题的人。
答案 0 :(得分:8)
分别对A
和B
两个集进行排序。
从集合A中取出并删除第一个元素,从集合B中删除第一个元素
如果它们相等,请添加到结果集。
如果一组中的项目较大,请从第二组中取下一项。
只要您没有达到任何一组的结尾,请转到2.
这种方法的优点是你永远不会在内存中保留2个以上的长度(排序除外)。可以在磁盘上有效地进行排序(合并排序)。
答案 1 :(得分:2)
对两个集合执行基于磁盘的合并排序。
完成后,您只需按顺序浏览已排序的文件,并将交叉点记录在新文件中。
答案 2 :(得分:2)
这是我认为可以做到的。显然是放在磁盘上。你必须对它们进行排序。
比较,
if a. length < 1 or b.length < 1
exit
else if a[0] == b[0]
addToIntersectionSet(a[0])
remove a[0] from a
remove b[0] from b
else if a[0] < b[0]
remove a[0]
else
remove b[0]
答案 3 :(得分:2)
在内存中初始化2个大位映射到零(位图1和位图2)(如果可用)或磁盘。 对于set1中的每个值,将value-th bitmap1位置设置为1。 对于set2中的每个值,读取bitmap1位中的第th个位置,如果为1,则将第bit位置的bitmap2设置为1。 对于bitmap2中的每个设置位值,该位置的输出值。
编辑:Jessop在回复中指出了这个缺陷:它是Java 64位(8字节)长整数,而不是32位架构C编译器4字节长整数。 64位长的解决方案是不切实际的。
答案 4 :(得分:2)
然后排序的一种可能更有效的方法是使用散列,并将数据拆分成几个箱 - 并在每个箱上进行交叉。 想法是将问题分解为适合内存的子问题 - 然后你可以在RAM上有效地进行交集。
假设你想要找到R,S的交集:
for each element in R:
write element in bucket_R[hash(element) % NUM_BUCKETS]
for each element in S:
write element in bucket_S[hash(element) % NUM_BUCKETS]
//assuming each bucket from bucket_S or bucket_R now fits memory - proceed.
//if it doesn't, you can repeat the previous step with a new hash function.
for each i from 0 to NUM_BUCKETS:
find bucket_S INTERSECTION bucket_R
重要:强>
bucket_S,bucket_R或DISK而不是RAM。
磁盘访问次数:
使用此方法的磁盘读取总数为3 * (|R|+|S|)
虽然任何基于排序的算法最有可能需要多于1次读取+ 1次写入(以及对数据的额外遍历) - 这将产生更多,然后3 * (|R|+|S|)
P.S。我正在学习文件系统考试(将在星期一进行),讲座说明这是大多数数据库系统中首选的解决方案,假设你有一个磁盘。
答案 5 :(得分:1)
这确实感觉就像地图减少作业,但您必须非常小心您选择的子集。如果希望交叉点起作用,则必须在相同点处剪切原始集合中的子集。例如,假设您有集合
A = {1 3 7 9}
B = {2 7 8 9}
你将它们分成两个子集:
A1 = {1 3} A2 = {7 9}
B1 = {2 7} B2 = {8 9}
然后你相交:
C1 = A1 inter B1 = {}
C2 = A2 inter B2 = {9}
然后你想:
C = A inter B = C1 union C2 = {9}
这显然是错误的。为了使map reduce工作,你必须使用一些常量值来剪切集合,例如A1和B1将包含值&lt; 5和A2和B2值&gt; = 5.
您还可以从磁盘中获取集合A和B的常规部分,然后以智能方式将它们相交,这意味着逐步查看已排序的元素,并在到达两个子集之一时停止。那时,你获取了一个额外的子集部分。
答案 6 :(得分:1)
一个显而易见的事情是从磁盘上的排序集开始。然后,您可以同时从两个文件中读取,找到匹配并将其写出。假设您从一个文件中读取204,您将从另一个文件读取,直到第一个数字> = 204.此时您知道此特定数字是否属于交叉点。
答案 7 :(得分:1)
你的第一步可能是对每一组进行排序;排序较小的块&amp;合并排序以构建排序文件。
排序后,您可以浏览两组。
答案 8 :(得分:1)
您可以轻松地打开512个文件,因此您可以将两个集合预先浏览到磁盘上的256个块,最多16M项目,每个大小为64 MB。您可以根据每个Long的最重要字节(从set.A.00到set.B.ff
)执行此操作然后你可以加载每对相应的块(set.A.42
包含Longs,从0x42开始对应set.B.42
)并使用它们初始化一个16M Byte数组 - 你将它初始化为全0,当你从第一个块读取值i
,你增加索引i-th)。然后你读入第二个块,但这次增加2。
完成后,您将对阵列进行扫描; 0表示该值不存在于任一块中,1表示它仅存在于第一组中,2表示仅存在于第二组中,3表示两者中。并将结果写入结果文件。
您不需要排序,即使结果文件将被排序(因为您将按顺序检查块,并按顺序执行最终扫描)。
这一切都在O(n)中运行(所有步骤都以线性时间运行)并且最多需要16M的RAM。
如果512个打开的文件太多,您可以使用前7个最高位,并使用256个打开文件和32M RAM。或者128个打开文件和64M RAM,依此类推。
也可以(并且可能更高效,如果文件系统缓存不太好)保留一系列256个“桶”,每个大小为16384 Longs
(因此它再次为16M)。当存储桶接近满时,您在磁盘上打开相应的块文件,转储到目前为止找到的16384 Long
,然后关闭该文件。然后为集合B执行相同操作。最终会有512个文件,其中包含0(不太可能)到1600万Long
s,从未一次打开两个以上的文件。