【发布时间】:2021-12-26 09:30:00
【问题描述】:
我正在尝试存储一组范围从 0 到 ~600 亿的数字,其中该集合开始为空并逐渐变得更密集,直到它包含该范围内的每个数字。该集合不必能够删除数字。目前我的方法是将集合表示为一个非常长的布尔数组并将该数组存储在文本文件中。我为此创建了一个类,并测试了 RandomAccessFile 和 FileChannel,其数字范围限制在 0 到 20 亿之间,但在这两种情况下,该类在添加和查询数字方面都比使用常规布尔数组慢得多。 这是我班级的当前状态:
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.*;
public class FileSet {
private static final int BLOCK=10_000_000;
private final long U;
private final String fname;
private final FileChannel file;
public FileSet(long u, String fn) throws IOException {
U=u;
fname=fn;
BufferedOutputStream out=new BufferedOutputStream(new FileOutputStream(fname));
long n=u/8+1;
for (long rep=0; rep<n/BLOCK; rep++) out.write(new byte[BLOCK]);
out.write(new byte[(int)(n%BLOCK)]);
out.close();
file=new RandomAccessFile(fn,"rw").getChannel();
}
public void add(long v) throws IOException {
if (v<0||v>=U) throw new RuntimeException(v+" out of range [0,"+U+")");
file.position(v/8);
ByteBuffer b=ByteBuffer.allocate(1); file.read(b);
file.position(v/8);
file.write(ByteBuffer.wrap(new byte[] {(byte)(b.get(0)|(1<<(v%8)))}));
}
public boolean has(long v) throws IOException {
if (v<0||v>=U) return false;
file.position(v/8);
ByteBuffer b=ByteBuffer.allocate(1); file.read(b);
return ((b.get(0)>>(v%8))&1)!=0;
}
public static void main(String[] args) throws IOException {
long U=2000_000_000;
SplittableRandom rnd=new SplittableRandom(1);
List<long[]> actions=new ArrayList<>();
for (int i=0; i<1000000; i++) actions.add(new long[] {rnd.nextInt(2),rnd.nextLong(U)});
StringBuilder ret=new StringBuilder(); {
System.out.println("boolean[]:");
long st=System.currentTimeMillis();
boolean[] b=new boolean[(int)U];
System.out.println("init time="+(System.currentTimeMillis()-st));
st=System.currentTimeMillis();
for (long[] act:actions)
if (act[0]==0) b[(int)act[1]]=true;
else ret.append(b[(int)act[1]]?"1":"0");
System.out.println("query time="+(System.currentTimeMillis()-st));
}
StringBuilder ret2=new StringBuilder(); {
System.out.println("FileSet:");
long st=System.currentTimeMillis();
FileSet fs=new FileSet(U,"FileSet/"+U+"div8.txt");
System.out.println("init time="+(System.currentTimeMillis()-st));
st=System.currentTimeMillis();
for (long[] act:actions) {
if (act[0]==0) fs.add(act[1]);
else ret2.append(fs.has(act[1])?"1":"0");
}
System.out.println("query time="+(System.currentTimeMillis()-st));
fs.file.close();
}
if (!ret.toString().equals(ret2.toString())) System.out.println("MISMATCH");
}
}
和输出:
boolean[]:
init time=1248
query time=148
FileSet:
init time=269
query time=3014
此外,当范围从 20 亿增加到 100 亿时,查询的总运行时间会有很大的跳跃,尽管理论上总运行时间应该大致保持不变。当我单独使用该类时(因为布尔数组不再适用于这么大的范围),查询时间从 ~3 秒到 ~50 秒。当我将范围增加到 600 亿时,时间增加到 ~240 秒。 我的问题是:是否有更快的方法来访问和修改任意索引处的超大文件?是否有一种完全不同的方法来存储比我目前的方法更快的大型整数集?
【问题讨论】:
-
是一组键值对吗?
-
如果你的设置超过了物理内存的大小,那么虚拟内存被用来存储映射的一部分,当访问一个位时它可能需要一个磁盘加载。除了购买更多内存或更快的磁盘驱动器之外,没有什么可做的了。 600 亿对于随机访问来说是一个巨大的数字,而您已经处于机器的极限。
-
是的,你可以存储非常多的集合数据。
-
@markspace 600 亿字节是 60 GB,可以放入内存。 600 亿位是除以 8,即 7.5 GB,现在可以轻松放入内存中。
-
您可能希望将en.wikipedia.org/wiki/Bloom_filter 作为检查集合成员资格的第一阶段...