Java: InputStream слишком медленный для чтения огромных файлов

Мне нужно прочитать символ файла размером 53 МБ. Когда я делаю это на С++ с помощью ifstream, это выполняется за миллисекунды, но с использованием Java InputStream это занимает несколько минут. Это нормально, что Java такая медленная, или я что-то упустил?

Также мне нужно доделать программу на Java (в ней используются сервлеты, из которых я должен вызывать функции, обрабатывающие эти символы). Я подумал, может быть, написать часть обработки файлов на C или C++, а затем использовать Java Native Interface для взаимодействия этих функций с моими Java-программами... Как вам эта идея?

Может ли кто-нибудь дать мне какой-либо другой совет... Мне серьезно нужно прочитать файл быстрее. Я пытался использовать буферизованный ввод, но он все равно не дает производительности даже близкой к C++.

Отредактировано: мой код состоит из нескольких файлов, и он очень грязный, поэтому я даю краткий обзор

import java.io.*;

public class tmp {
    public static void main(String args[]) {
        try{
        InputStream file = new BufferedInputStream(new FileInputStream("1.2.fasta"));
        char ch;        
        while(file.available()!=0) {
            ch = (char)file.read();
                    /* Do processing */
            }
        System.out.println("DONE");
        file.close();
        }catch(Exception e){}
    }
}

person pflz    schedule 06.05.2012    source источник
comment
Покажи нам свой код. Мы не можем догадаться о вашей проблеме, не видя, как вы это делаете.   -  person Guillaume Polet    schedule 07.05.2012
comment
Вы используете BufferedInputStream? Вы должны использовать это поверх BufferedReader. Ваши шаблоны доступа таковы, что вы можете отображать части файла в память, используя java.nio? В частности, когда вы говорите char на char, достаточно ли вы знаете о кодировании, чтобы иметь дело с char, чьи последовательности байтов могут распространяться на несколько отображаемых в память сегментов?   -  person Mike Samuel    schedule 07.05.2012
comment
Невозможно просто прочитать эти 53M символов и ничего не делать, это может занять более пары секунд, будь то буферизация или отсутствие буферизации. Наверняка есть что-то еще.   -  person Marko Topolnik    schedule 07.05.2012
comment
Возможно, размер вашего буферного массива слишком мал или слишком велик   -  person Luiggi Mendoza    schedule 07.05.2012
comment
@MikeSamuel Да, я тоже использовал BufferedInputStream ..   -  person pflz    schedule 07.05.2012
comment
@MarkoTopolnik Я думал, что проблема в чем-то другом. Но я попытался прочитать файл, ничего не делая, используя InputStream, и все же это заняло 2 минуты.   -  person pflz    schedule 07.05.2012
comment
Не могли бы вы опубликовать свой код, как попросил @GuillaumePolet.   -  person Krrose27    schedule 07.05.2012
comment
Действительно, прямо сейчас я тестирую OS X, производительность составляет около МБ в секунду - минута для вашего файла. Это с необработанным FileInputStream. Но как только я добавляю BufferedInputStream, производительность взлетает до 183 МБ за 10 секунд — 20 МБ/с. Обратите внимание, что вы не можете преобразовать байт в такой символ, за исключением случаев, когда вы читаете чистый поток ASCII.   -  person Marko Topolnik    schedule 07.05.2012
comment
@MarkoTopolnik Мне нужно, чтобы он выполнялся намного быстрее, как в C ++. Нет ли другого выхода, кроме как написать программу на C++?   -  person pflz    schedule 07.05.2012
comment
Чтение символ за символом, вероятно, ваша проблема.   -  person Louis Wasserman    schedule 07.05.2012
comment
Да, это поток ASCII. Хорошо, я собираюсь попробовать BufferedInputStream.   -  person pflz    schedule 07.05.2012
comment
Вы уверены, что действительно читаете символы в своей программе на C++, или ваш компилятор стер бесполезный код?   -  person M Platvoet    schedule 07.05.2012
comment
Теперь я попытался использовать метод read(byte[]), используя массив из 1000 байт. Производительность составила 340 мс для 183 МБ, поэтому для вашего случая это будет около 100 мс.   -  person Marko Topolnik    schedule 07.05.2012
comment
@MarkoTopolnik В опубликованном коде я использовал объект BufferedInputStream ... это то же самое, что вы делаете для ускорения?   -  person pflz    schedule 07.05.2012
comment
Нет, использование оболочки BufferedInputStream или ее отсутствие дает вам совсем немного по сравнению с вызовом read(int) против read(byte[]).   -  person Marko Topolnik    schedule 07.05.2012
comment
Вы используете file.available() неправильно. Попробуйте это, while((ch = (char)file.read()) >= 0) и удалите ch = (char)file.read();   -  person user845279    schedule 07.05.2012
comment
К вашему сведению, в этом заголовке говорится о больших файлах. Если ваши файлы больше, чем Integer.MAX - 8 байт (~ 3,2 ГБ), вы получите целочисленное переполнение, что приведет к исключению NegativeArraySizeException bugs.openjdk.java.net/browse/JDK-7129312   -  person zdsbs    schedule 10.01.2014


Ответы (4)


Я запустил этот код с файлом размером 183 МБ. Он напечатал «Прошло 250 мс».

final InputStream in = new BufferedInputStream(new FileInputStream("file.txt"));
final long start = System.currentTimeMillis();
int cnt = 0;
final byte[] buf = new byte[1000];
while (in.read(buf) != -1) cnt++;
in.close();
System.out.println("Elapsed " + (System.currentTimeMillis() - start) + " ms");
person Marko Topolnik    schedule 06.05.2012
comment
Ницца. Кроме того, мне нужно обработать файл символ за символом. Поэтому вместо того, чтобы читать отдельные символы из файла, я буду извлекать его из буфера и, если он закончится, снова заполнить его. Большое спасибо :) - person pflz; 07.05.2012
comment
Да, я думаю, что Java увязает в диспетчеризации методов, тогда как C++, возможно, даже встраивает вызовы. Иногда после достаточного количества вызовов HotSpot также встраивает вызовы, но я не могу быть уверен в этом случае. - person Marko Topolnik; 07.05.2012
comment
@MarkoTopolnik Здесь нет никаких доказательств того, что Java «увязает» в чем-либо, кроме вызова InputStream. available () 53 миллиона раз, что составляет 53 миллиона избыточных системных вызовов. Поскольку он использует BufferedInputStream, количество системных вызовов для фактического чтения файла составляет 53/8192 миллиона, поэтому вызов available() требует огромных накладных расходов. - person user207421; 07.05.2012
comment
@EJP Я говорил о своих собственных тестах. Это в комментариях к вопросу. Изменение с BufferedInputStream.read() на read(byte[1000]) либо в необработанном, либо в буферизованном потоке дает 50-кратное увеличение скорости. - person Marko Topolnik; 07.05.2012
comment
@MarkoTopolnik Я получаю 676 мс с BufferedInputStream.read(byte[]); 2441 мс с FileInputStream.read(byte[]) и 610 мс с BufferedInputStream.read() (по одному байту за раз). Разные файлы со случайным содержимым (во избежание кеширования), все 183Мб, буфер 1000 байт. Java 6. Результаты довольно стабильны в течение нескольких прогонов. Никаких признаков 50-кратной проблемы. - person user207421; 07.05.2012
comment
@EJP ... а ваша ОС ...? Я думаю, что это ключевое здесь. - person Marko Topolnik; 07.05.2012
comment
@EJP Итак, мы можем говорить о дерьмовой реализации в OS X. После того, как я прочитал ваш результат, я перепроверил свой. Это реально. - person Marko Topolnik; 08.05.2012

я бы попробовал это

// create the file so we have something to read.
final String fileName = "1.2.fasta";
FileOutputStream fos = new FileOutputStream(fileName);
fos.write(new byte[54 * 1024 * 1024]);
fos.close();

// read the file in one hit.
long start = System.nanoTime();
FileChannel fc = new FileInputStream(fileName).getChannel();
ByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size());
while (bb.remaining() > 0)
    bb.getLong();
long time = System.nanoTime() - start;
System.out.printf("Took %.3f seconds to read %.1f MB%n", time / 1e9, fc.size() / 1e6);
fc.close();
((DirectBuffer) bb).cleaner().clean();

отпечатки

Took 0.016 seconds to read 56.6 MB
person Peter Lawrey    schedule 06.05.2012
comment
Символ DirectBuffer не найден. Поэтому я удалил последнюю строку, но при ее запуске возникло исключение java.nio.BufferUnderflowException. (файл 53,4 МБ) - person pflz; 07.05.2012
comment
Я читаю 8 байтов за раз, чтобы ускорить его, что нехорошо, если длина не кратна 8. Вместо этого вы можете использовать bb.get(). DirectBuffer находится в sun.nio.ch, что делает его API для внутреннего использования, который можно удалить. - person Peter Lawrey; 07.05.2012

Используйте BufferedInputStream:

InputStream buffy = new BufferedInputStream(inputStream);
person Bohemian♦    schedule 06.05.2012
comment
Я также использовал BufferedInputStream. InputStream fh = новый BufferedInputStream (новый FileInputStream (файл)); - person pflz; 07.05.2012

Как отмечалось выше, используйте BufferedInputStream. Вы также можете использовать пакет NIO. Обратите внимание, что для большинства файлов BufferedInputStream будет так же быстро читать, как и NIO. Однако для очень больших файлов NIO может работать лучше, потому что вы можете выполнять файловые операции с отображением памяти. Кроме того, пакет NIO выполняет прерываемый ввод-вывод, а пакет java.io — нет. Это означает, что если вы хотите отменить операцию из другого потока, вы должны использовать NIO, чтобы сделать ее надежной.

ByteBuffer buf = ByteBuffer.allocate(BUF_SIZE);
FileChannel fileChannel = fileInputStream.getChannel();
int readCount = 0;
while ( (readCount = fileChannel.read(buf)) > 0) {
  buf.flip();
  while (buf.hasRemaining()) {
    byte b = buf.get();
  }
  buf.clear();
}
person Matt    schedule 06.05.2012
comment
Я не думаю, что файлы с отображением памяти приносят пользу для последовательного чтения. - person Marko Topolnik; 07.05.2012
comment
@MarkoTopolnik Они дают не более 20% выигрыша во времени, но я не знаю, почему вы считаете последовательное чтение особым случаем. Это не так. Диски по-прежнему выполняют упреждающее чтение, как и при использовании потока или устройства чтения. - person user207421; 07.05.2012
comment
@EJP Да, но упреждающее чтение все равно выполняется на более низком уровне (даже внутри дисковой электроники, а также в реализации дискового кеша). - person Marko Topolnik; 07.05.2012
comment
@MarkoTopolnik Почему файлы MM не подходят для последовательного чтения? - person user207421; 07.05.2012
comment
Файлы @EJP MM в первую очередь удобны для чтения с произвольным доступом, поскольку они предоставляют простой API для доступа к файлу, как если бы он был массивом в ОЗУ. Если все, что вы делаете, это просматриваете файл MM сверху вниз, вы просто нагружаете диспетчер памяти и не получаете никакой пользы от парадигмы. - person Marko Topolnik; 07.05.2012
comment
@EJP Я все же прислушиваюсь к твоему убийственному аргументу. Надеюсь, с кодом, с которым можно поиграть! - person Marko Topolnik; 07.05.2012