提问人:coolcomputery 提问时间:11/15/2021 最后编辑:coolcomputery 更新时间:11/16/2021 访问量:511
在 Java 中存储一组非常大的数字
Storing a very large set of numbers in Java
问:
我正在尝试存储一组范围从 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 秒。 我的问题是:有没有一种更快的方法来访问和修改任意索引的非常大的文件?有没有一种完全不同的方法来存储大型整数集,比我目前的方法更快?
答:
Boolean
数组是一种非常低效的信息存储方式,因为每个布尔值占用 8 位。您应该改用 a。但 BitSet 也有 20 亿的限制,因为它使用原始 int 值作为参数(并限制内部长数组的大小)。BitSet
Integer.MAX_VALUE
跨越超过 20 亿个条目的节省空间的内存中替代方法是创建您自己的 BitSet 包装器,该包装器将数据拆分为子集并为您执行索引:
public class LongBitSet {
// TODO: Initialize array and add error checking.
private final BitSet bitSets = new BitSet[64];
public void set(long index) {
bitSets[(int) (index / Integer.MAX_VALUE)]
.set((int) (index % Integer.MAX_VALUE));
}
}
但也有其他选择。如果数据非常密集,则使用运行长度编码是增加内存容量的廉价方法。但这可能涉及 B 树结构,以使其访问效率更高。这些只是指针。很多正确答案完全取决于你实际使用数据结构的方式。
评论
事实证明,最简单的解决方案是使用 64 位 JVM 并通过在终端中运行我的 Java 程序来增加 Java 堆空间,并带有类似 .然后,我可以简单地使用一个 s 数组来隐式存储整个集合。-Xmx10g
long
评论