Самый быстрый способ записать массив целых чисел в файл в Java? - PullRequest
12 голосов
/ 05 декабря 2010

Как видно из названия, я ищу самый быстрый способ записи целочисленных массивов в файлы. Массивы будут различаться по размеру и реально будут содержать от 2500 до 25 000 000 дюймов.

Вот код, который я сейчас использую:

DataOutputStream writer = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(filename)));

for (int d : data)
  writer.writeInt(d);

Учитывая, что DataOutputStream имеет метод для записи массивов байтов, я попытался преобразовать массив int в байтовый массив следующим образом:

private static byte[] integersToBytes(int[] values) throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    DataOutputStream dos = new DataOutputStream(baos);
    for (int i = 0; i < values.length; ++i) {
        dos.writeInt(values[i]);
    }

    return baos.toByteArray();
}

и вот так:

private static byte[] integersToBytes2(int[] src) {
    int srcLength = src.length;
    byte[] dst = new byte[srcLength << 2];

    for (int i = 0; i < srcLength; i++) {
        int x = src[i];
        int j = i << 2;
        dst[j++] = (byte) ((x >>> 0) & 0xff);
        dst[j++] = (byte) ((x >>> 8) & 0xff);
        dst[j++] = (byte) ((x >>> 16) & 0xff);
        dst[j++] = (byte) ((x >>> 24) & 0xff);
    }
    return dst;
}

Кажется, что оба дают небольшое увеличение скорости, около 5%. Я не достаточно тщательно их протестировал, чтобы подтвердить это.

Существуют ли какие-либо методы, ускоряющие эту операцию записи в файл, или соответствующие руководства, которые помогут повысить производительность записи Java IO?

Ответы [ 6 ]

23 голосов
/ 05 декабря 2010

Я рассмотрел три варианта:

  1. Использование DataOutputStream;
  2. Использование ObjectOutputStream (для Serializable объектов, которые int[] есть);и
  3. Использование FileChannel.

Результаты:

DataOutputStream wrote 1,000,000 ints in 3,159.716 ms
ObjectOutputStream wrote 1,000,000 ints in 295.602 ms
FileChannel wrote 1,000,000 ints in 110.094 ms

Так что версия NIO самая быстрая.Он также имеет то преимущество, что позволяет редактировать, что означает, что вы можете легко изменить один int, тогда как для ObjectOutputStream потребуется прочитать весь массив, изменить его и записать в файл.1023 *

6 голосов
/ 05 декабря 2010

Я бы использовал FileChannel из пакета nio и ByteBuffer.Такой подход (на моем компьютере) дает от 2 до 4 раз лучшей производительности записи :

Вывод из программы:

normal time: 2555
faster time: 765

Это программа:

public class Test {

    public static void main(String[] args) throws IOException {

        // create a test buffer
        ByteBuffer buffer = createBuffer();

        long start = System.currentTimeMillis();
        {
            // do the first test (the normal way of writing files)
            normalToFile(new File("first"), buffer.asIntBuffer());
        }
        long middle = System.currentTimeMillis(); 
        {
            // use the faster nio stuff
            fasterToFile(new File("second"), buffer);
        }
        long done = System.currentTimeMillis();

        // print the result
        System.out.println("normal time: " + (middle - start));
        System.out.println("faster time: " + (done - middle));
    }

    private static void fasterToFile(File file, ByteBuffer buffer) 
    throws IOException {

        FileChannel fc = null;

        try {

            fc = new FileOutputStream(file).getChannel();
            fc.write(buffer);

        } finally {

            if (fc != null)
                fc.close();

            buffer.rewind();
        }
    }

    private static void normalToFile(File file, IntBuffer buffer) 
    throws IOException {

        DataOutputStream writer = null;

        try {
            writer = 
                new DataOutputStream(new BufferedOutputStream(
                        new FileOutputStream(file)));

            while (buffer.hasRemaining())
                writer.writeInt(buffer.get());

        } finally {
            if (writer != null)
                writer.close();

            buffer.rewind();
        }
    }

    private static ByteBuffer createBuffer() {
        ByteBuffer buffer = ByteBuffer.allocate(4 * 25000000);
        Random r = new Random(1);

        while (buffer.hasRemaining()) 
            buffer.putInt(r.nextInt());

        buffer.rewind();

        return buffer;
    }
}
3 голосов
/ 05 декабря 2010

Главное улучшение, которое вы можете получить для написания int [], это либо:

  • увеличить размер буфера. Размер подходит для большинства потоков, но доступ к файлам может быть быстрее с большим буфером. Это может дать улучшение на 10-20%.

  • Используйте NIO и прямой буфер. Это позволяет записывать 32-битные значения без преобразования в байты. Это может дать улучшение на 5%.

Кстати: вы должны иметь возможность записывать не менее 10 миллионов значений типа int в секунду. С кэшированием диска вы увеличиваете это до 200 миллионов в секунду.

3 голосов
/ 05 декабря 2010

Я думаю, вам следует рассмотреть возможность использования файловых каналов (библиотека java.nio) вместо простых потоков (java.io).Хорошей отправной точкой является это интересное обсуждение: JavaC NI FileChannel против FileOutputstream производительность / полезность

и соответствующие комментарии ниже.

1 голос
/ 25 января 2019

Тесты должны повторяться время от времени, не так ли? :) После исправления некоторых ошибок и добавления моего собственного варианта написания, вот результаты, которые я получаю при запуске теста на ASUS ZenBook UX305 под управлением Windows 10 (время указано в секундах):

Running tests... 0 1 2
Buffered DataOutputStream           8,14      8,46      8,30
FileChannel alt2                    1,55      1,18      1,12
ObjectOutputStream                  9,60     10,41     11,68
FileChannel                         1,49      1,20      1,21
FileChannel alt                     5,49      4,58      4,66

А вот результаты, запущенные на том же компьютере, но с Arch Linux и порядок переключения методов записи:

Running tests... 0 1 2
Buffered DataOutputStream          31,16      6,29      7,26
FileChannel                         1,07      0,83      0,82
FileChannel alt2                    1,25      1,71      1,42
ObjectOutputStream                  3,47      5,39      4,40
FileChannel alt                     2,70      3,27      3,46

Каждый тест записывал файл по 800 Мб. Небуферизованный DataOutputStream взял долго, поэтому я исключил его из теста.

Как видно, запись с использованием файлового канала по-прежнему бьет все дерьмо другие методы, но очень важно, является ли байтовый буфер отображается в памяти или нет. Без отображения памяти запись файла канала заняло 3-5 секунд:

var bb = ByteBuffer.allocate(4 * ints.length);
for (int i : ints)
    bb.putInt(i);
bb.flip();
try (var fc = new FileOutputStream("fcalt.out").getChannel()) {
    fc.write(bb);
}

С отображением памяти время было сокращено до 0,8-1,5. секунд:

try (var fc = new RandomAccessFile("fcalt2.out", "rw").getChannel()) {
    var bb = fc.map(READ_WRITE, 0, 4 * ints.length);
    bb.asIntBuffer().put(ints);
}

Но учтите, что результаты зависят от порядка. Особенно так далее Linux. Похоже, что отображенные в память методы не записывают данные в полном объеме, а скорее выгружает запрос задания в ОС и возвращает до его завершения. Желательно ли такое поведение или нет зависит от ситуации.

Отображение памяти также может привести к проблемам OutOfMemory, поэтому оно не всегда правильный инструмент для использовать. Предотвратить OutOfMemory при использовании java.nio.MappedByteBuffer .

Вот моя версия кода теста: https://gist.github.com/bjourne/53b7eabc6edea27ffb042e7816b7830b

0 голосов
/ 05 декабря 2010

Массив Serializable - вы не можете просто использовать writer.writeObject(data);? Это определенно будет быстрее, чем отдельные writeInt звонки.

Если у вас есть другие требования к формату выходных данных, чем поиск в int[], это другой вопрос.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...