从文件中快速读取小端整数

时间:2011-02-22 12:29:33

标签: java android performance file-io endianness

我需要将一个由4字节整数(小端)组成的二进制文件读入我的Android应用程序的2D数组中。我目前的解决方案如下:

DataInputStream inp = null;
try {
    inp = new DataInputStream(new BufferedInputStream(new FileInputStream(procData), 32768));
}
catch (FileNotFoundException e) {
    Log.e(TAG, "File not found");
}

int[][] test_data = new int[SIZE_X][SIZE_Y];
byte[] buffer = new byte[4];
ByteBuffer byteBuffer = ByteBuffer.allocate(4);
for (int i=0; i < SIZE_Y; i++) {
    for (int j=0; j < SIZE_X; j++) {
        inp.read(buffer);
        byteBuffer = ByteBuffer.wrap(buffer);
        test_data[j][SIZE_Y - i - 1] = byteBuffer.order(ByteOrder.LITTLE_ENDIAN).getInt();
    }
}

这对于2k * 2k阵列来说非常慢,大约需要25秒。我可以在DDMS中看到垃圾收集器正在加班,所以这可能是缓慢的原因之一。

必须有一种更有效的方法来使用ByteBuffer将该文件读入数组,但我目前还没有看到它。关于如何提高速度的任何想法?

4 个答案:

答案 0 :(得分:12)

为什么不读入4字节缓冲区然后手动重新排列字节?它看起来像这样:

for (int i=0; i < SIZE_Y; i++) {
    for (int j=0; j < SIZE_X; j++) {
        inp.read(buffer);
        int nextInt = (buffer[0] & 0xFF) | (buffer[1] & 0xFF) << 8 | (buffer[2] & 0xFF) << 16 | (buffer[3] & 0xFF) << 24;
        test_data[j][SIZE_Y - i - 1] = nextInt;
    }
}

当然,假设read读取所有四个字节,但是你应该检查它的情况。这样你就不会在阅读过程中创建任何对象(因此垃圾收集器没有任何压力),你不会调用任何东西,只需使用按位操作。

答案 1 :(得分:5)

如果您使用的是支持内存映射文件的平台,请考虑使用MappedByteBuffer和来自java.nio的朋友

FileChannel channel = new RandomAccessFile(procData, "r").getChannel();
MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_ONLY, 0, 4 * SIZE_X * SIZE_Y);
map.order(ByteOrder.LITTLE_ENDIAN);
IntBuffer buffer = map.asIntBuffer();

int[][] test_data = new int[SIZE_X][SIZE_Y];
for (int i=0; i < SIZE_Y; i++) {
    for (int j=0; j < SIZE_X; j++) {
        test_data[j][SIZE_Y - i - 1] = buffer.get();
    }
}

如果您需要跨平台支持或您的平台缺少内存映射缓冲区,您可能仍希望避免使用IntBuffer自行执行转换。考虑删除BufferedInputStream,自己分配更大的ByteBuffer并获取数据的little-endian IntBuffer视图。然后在循环中将缓冲区位置重置为0,使用DataInputStream.readFully立即将大区域读入ByteBuffer,并将int值从IntBuffer中拉出来。

答案 2 :(得分:3)

首先,你的'inp.read(缓冲区)'是不安全的,因为read契约并不能保证它会读取所有4个字节。

除此之外,为了快速转换,请使用DataInputStream.readInt

中的算法

我已经适应了4字节字节数组的情况:

int little2big(byte[ ] b) {
    return (b[3]&0xff)<<24)+((b[2]&0xff)<<16)+((b[1]&0xff)<<8)+(b[0]&0xff);
}

答案 3 :(得分:0)

我认为没有必要重新发明轮子并再次为字节序执行字节重新排序。这容易出错,并且存在诸如ByteBuffer之类的类的原因。

可以在浪费对象的意义上优化您的代码。当byte[]ByteBuffer包装时,缓冲区将添加一个视图,但是原始数组保持不变。可以直接修改/读取原始数组,也可以使用ByteBuffer实例。

因此,您只需要初始化ByteBuffer的一个实例,也只需设置一次ByteOrder

要重新开始,只需使用rewind()将计数器重新设置为缓冲区的开头即可。

我已将您的代码修改为所需的代码。请注意,如果输入的剩余字节数不足,它不会检查错误。我建议使用inp.readFully,因为如果找不到足够的字节来填充缓冲区,则会抛出EOFException

int[][] test_data = new int[SIZE_X][SIZE_Y];
ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[4]).order(ByteOrder.LITTLE_ENDIAN);
for (int i=0; i < SIZE_Y; i++) {
    for (int j=0; j < SIZE_X; j++) {
        inp.read(byteBuffer.array());
        byteBuffer.rewind();
        test_data[j][SIZE_Y - i - 1] = byteBuffer.getInt();
    }
}