【问题标题】:Fast reading of little endian integers from file从文件中快速读取小端整数
【发布时间】:2011-02-22 12:29:32
【问题描述】:

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

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 将该文件读入数组,但我目前没有看到它。关于如何加快速度的任何想法?

【问题讨论】:

  • 真的需要同时读取所有数据吗?您是否经常访问许多条目?如果没有,您可以避免将整个数组“解析”为整数。只需读取或包装整个文件,并通过计算其与 x y 坐标的偏移量来提供所需的条目。
  • @Luzifer 一开始我至少需要一次所有数据。

标签: java android performance file-io endianness


【解决方案1】:

为什么不读入一个 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 读取了所有四个字节,但如果不是,您应该检查情况。这样你就不会在读取过程中创建任何对象(所以垃圾收集器没有压力),你不调用任何东西,你只使用按位操作。

【讨论】:

  • 谢谢,这个版本大约是我原来版本的 5 倍,现在只需要 5 秒。我不习惯直接摆弄这些位。
  • 这是我发现的将原始字节转换为无符号整数的唯一工作方法。谢谢!
  • 你为什么用FF做位&?字节不是写在8位上吗?如果是这样,该操作将不会做任何事情......我错过了什么?
  • @Sushi271 因为如果你只是将一个字节转换为 int,你可能会在字节被签名时得到负数。例如,如果一个字节包含0b11111111,它将变为-1,而不是255。
【解决方案2】:

如果您在支持内存映射文件的平台上,请考虑 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,并从 IntBuffer 中拉出 int 值。

【讨论】:

    【解决方案3】:

    首先,您的“inp.read(buffer)”是不安全的,因为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);
    }
    

    【讨论】:

      【解决方案4】:

      我认为没有必要重新发明轮子并再次为字节顺序执行字节重新排序。这很容易出错,并且存在像 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();
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2018-10-04
        • 1970-01-01
        • 2011-11-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多