我需要一种在内存和时间方面有效处理大型二进制数据文件的方法。
我正在研究一个多线程Java应用程序,它处理由许多多维数据点组成的大型二进制数据文件。数据点具有相同的尺寸,每个点约为100 kb。数据点的数量大约为10,000到100,000。测试阶段的数据文件是几千兆字节,但未来它们将是几千兆字节。
客户在运行应用程序时遇到内存问题,因此我正在处理数据列表,这些数据点会减少处理所需的内存,同时仍能提供良好的性能。
Java是项目的要求,我们受客户端系统上当前内存的限制。客户端系统有许多内核,但它是一个共享系统,内存现在是限制因素。
在我们的应用程序中重复使用这组数据点。有时这些点是按顺序处理的。在其他时间,处理点的子集,包括两个不同点的所有组合。在子集内,可以按任何顺序处理这些点,但是子集中的点可以任意相隔很远。数据文件是我通过解析文本数据文件并将值写入二进制文件而生成的简单二进制文件。目前,数据是双精度的,因此我将数据点连续写为二进制数据文件的一系列双精度数。 (我解析每个文本文件数据点并立即将其写入二进制文件,而不是将它们全部保存在内存中。)将来我们可以处理float,int等数据。
我搜索了SO和其他互联网网站。到目前为止,我已经尝试了几种方法,包括根据需要从二进制文件中读取点,但与将所有数据点的列表同时保存在内存中相比,性能一直很差。这适用于较小尺寸点数较少的测试,但这些测试的数据集比实际数据集小几个数量级。到目前为止,我尝试过的方法比保留内存中的所有点慢几百或几千倍。
我已尝试使用直接ByteBuffers和MappedByteBuffers。最好的方法是我已经提取了下面相关部分的类。它将二进制数据读入MappedByteBuffers数组。然后,当通过下面的get(int index)方法请求数据点时,该方法加载相关缓冲区,将相关字节读入字节数组,将字节转换为double数组,并创建DataPoint对象。我使用了一个MappedByteBuffers数组,因为整个数据文件无法适应物理内存。我使用了一个字节数组数组,这样线程就会有单独的字节数组来读取数据。然后我只对实际访问MappedByteBuffer进行同步,以最大限度地减少阻塞。据我所知,Java类库中的Buffer不是线程安全的,尽管我最近读了一篇声称MappedByteBuffers不需要同步的帖子。
欢迎任何反馈。特别是,我很好奇MappedByteBuffers的同步。
final static private int DOUBLE_BYTE_SIZE = Double.SIZE / Byte.SIZE;
public enum DataType {
CHAR,
DOUBLE,
FLOAT,
INT,
LONG,
SHORT;
}
final static private int numberOfBuffers = 8;
private MappedByteBuffer[] buffers = null;
private int bufferSize = -1;
private byte[][] readArray = null;
private DataType dataType;
private int sizeOfVector;
private int byteSizeOfVector;
private File binFile;
private int size = -1;
private int makeList(File binaryFile, DataType argDataType, int numberOfComponents) {
FileInputStream fis = null;
FileChannel fc = null;
try {
dataType = argDataType;
sizeOfVector = numberOfComponents;
fis = new FileInputStream(binaryFile);
fc = fis.getChannel();
long fileSize = fc.size();
switch (dataType) {
case DOUBLE:
byteSizeOfVector = DOUBLE_BYTE_SIZE * sizeOfVector;
break;
default:
break;
}
size = (int) fileSize / byteSizeOfVector;
bufferSize = size / numberOfBuffers;
buffers = new MappedByteBuffer[numberOfBuffers];
long remaining = fileSize;
long position = 0;
int bufferNumber = 0;
while(remaining > 0) {
long length = Math.min(remaining, bufferSize * byteSizeOfVector);
buffers[bufferNumber] = fc.map(MapMode.READ_ONLY, position, length);
position += length;
remaining -= length;
bufferNumber++;
}
readArray = new byte[numberOfBuffers][byteSizeOfVector];
} catch (IOException ex) {
return -1;
} finally {
try {
if(fis != null) {
fis.close();
}
if(fc != null) {
fc.close();
}
} catch (IOException exClose) {
return -1;
}
}
return 0;
}
private static long makeLong(byte[] data) {
if (data == null || data.length != 8) return 0x0;
return (long)(
(long) (0xFF & data[0]) << 56 |
(long) (0xFF & data[1]) << 48 |
(long) (0xFF & data[2]) << 40 |
(long) (0xFF & data[3]) << 32 |
(long) (0xFF & data[4]) << 24 |
(long) (0xFF & data[5]) << 16 |
(long) (0xFF & data[6]) << 8 |
(long) (0xFF & data[7]) << 0
);
}
private static double makeDouble(byte[] data) {
if (data == null || data.length != 8) return 0x0;
return Double.longBitsToDouble(makeLong(data));
}
private static double[] makeDoubleArray(byte[] data) {
if (data == null) return null;
if (data.length % 8 != 0) return null;
double[] doubleArray = new double[data.length / 8];
for (int index = 0; index < dbls.length; index++) {
doubleArray[index] = makeDouble(new byte[] {
data[(index*8)],
data[(index*8)+1],
data[(index*8)+2],
data[(index*8)+3],
data[(index*8)+4],
data[(index*8)+5],
data[(index*8)+6],
data[(index*8)+7],
}
);
}
return doubleArray;
}
@Override
public DataPoint get(int index) {
if(index > size() - 1) {
throw new IndexOutOfBoundsException("Index exceeds length of list.");
} else if(index < 0) {
throw new IndexOutOfBoundsException("Index is less than zero.");
}
int bufferNumber = index / bufferSize;
int bufferPosition = index % bufferSize;
MappedByteBuffer buffer = buffers[bufferNumber];
synchronized (buffer) {
buffer.load();
buffer.position(bufferPosition * sizeOfVector);
buffer.get(readArray[bufferNumber]);
}
switch(dataType) {
case DOUBLE:
return new DoublePoint(makeDoubleArray(readArray[bufferNumber]));
default:
return null;
}
}