Быстрое чтение байтов в файле - PullRequest
7 голосов
/ 22 февраля 2011

Мне нужно прочитать двоичный файл, состоящий из 4-х байтовых целых чисел (little-endian), в двумерный массив для моего приложения 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 для чтения этого файла в массив, но в данный момент я его не вижу. Есть идеи, как это ускорить?

Ответы [ 3 ]

12 голосов
/ 22 февраля 2011

Почему бы не прочитать 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 голосов
/ 05 апреля 2012

Если вы работаете на платформе, которая поддерживает отображаемые в память файлы, рассмотрите 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 самостоятельно и получить представление IntBuffer с прямым порядком байтов для данных. Затем в цикле сбрасывайте позиции буфера в 0, используйте DataInputStream.readFully для одновременного считывания больших областей в ByteBuffer и извлечения значений int из IntBuffer.

3 голосов
/ 22 февраля 2011

Прежде всего, ваш '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);
}
...