Java чтение длинного текстового файла очень медленно

У меня есть текстовый файл (XML, созданный с помощью XStream), длина которого составляет 63000 строк (3,5 МБ). Я пытаюсь прочитать его с помощью буферизованного считывателя:

                BufferedReader br = new BufferedReader(new FileReader(file));
                try {
                    String s = "";
                    String tempString;
                    int i = 0;
                    while ((tempString = br.readLine()) != null) {
                        s = s.concat(tempString);
//                        s=s+tempString;
                        i = i + 1;
                        if (i % 1000 == 0) {
                            System.out.println(Integer.toString(i));
                        }
                    }
                    br.close();

Здесь вы можете увидеть мои попытки измерения скорости чтения. И это очень низко. Для считывания 1000 строк после 10000 строк требуется несколько секунд. Я явно делаю что-то неправильно, но не могу понять, что. Заранее благодарим за помощь.

Ответ 1

@PaulGrime является правильным. Вы копируете строку каждый раз, когда цикл читает строку. Как только строка становится большой (скажем, 10 000 строк), она делает много работы, чтобы сделать это копирование.

Попробуйте следующее:

StringBuilder sb = new StringBuilder();
while (...reading lines..){ 
   ....
   sb.append(tempString);  //should add newline
   ...
}

s = sb.toString();

Примечание: прочитайте ниже ответ на вопрос о том, почему удаление новых строк делает этот файл неправильным для чтения в файле. Кроме того, как упоминалось в комментариях к вопросу, XStream предоставляет способ для чтения файла, и даже если бы он этого не сделал, IOUtils.toString(reader) был бы более безопасным способом чтения файла.

Ответ 2

Некоторые немедленные улучшения, которые вы можете сделать:

  • Используйте StringBuilder вместо concat и +. Использование + и concat может действительно повлиять на производительность, особенно при использовании в цикле.
  • Уменьшить доступ к диску. Вы можете сделать это, используя большой буфер:

    BufferedReader br = new BufferedReader (новый FileReader ( "someFile.txt" ), SIZE);

Ответ 3

Вы должны использовать StringBuilder, так как String конкатенация чрезвычайно медленна даже для небольших строк.

Кроме того, попробуйте использовать NIO, а не BufferedReader.

public static void main(String[] args) throws IOException {
    final File file = //some file
    try (final FileChannel fileChannel = new RandomAccessFile(file, "r").getChannel()) {
        final StringBuilder stringBuilder = new StringBuilder();
        final ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        final CharsetDecoder charsetDecoder = Charset.forName("UTF-8").newDecoder();
        while (fileChannel.read(byteBuffer) > 0) {
            byteBuffer.flip();
            stringBuilder.append(charsetDecoder.decode(byteBuffer));
            byteBuffer.clear();
        }
    }
}

Вы можете настроить размер буфера, если он все еще слишком медленный - он сильно зависит от системы, какой размер буфера работает лучше. Для меня очень мало разница, если буфер 1K или 4K, но в других системах, которые я знаю, это изменение для увеличения скорости на порядок.

Ответ 4

В дополнение к тому, что уже было сказано, в зависимости от вашего использования XML ваш код потенциально неверен, поскольку он отбрасывает окончания строк. Например, этот код:

package temp.stackoverflow.q15849706;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;

import com.thoughtworks.xstream.XStream;

public class ReadXmlLines {
    public String read1(BufferedReader br) throws IOException {
        try {
            String s = "";
            String tempString;
            int i = 0;
            while ((tempString = br.readLine()) != null) {
                s = s.concat(tempString);
                // s=s+tempString;
                i = i + 1;
                if (i % 1000 == 0) {
                    System.out.println(Integer.toString(i));
                }
            }
            return s;
        } finally {
            br.close();
        }
    }

    public static void main(String[] args) throws IOException {
        ReadXmlLines r = new ReadXmlLines();

        URL url = ReadXmlLines.class.getResource("xml.xml");
        String xmlStr = r.read1(new BufferedReader(new InputStreamReader(url
                .openStream())));

        Object ob = null;

        XStream xs = new XStream();
        xs.alias("root", Root.class);

        // This is incorrectly read/parsed, as the line endings are not
        // preserved.
        System.out.println("----------1");
        System.out.println(xmlStr);
        ob = xs.fromXML(xmlStr);
        System.out.println(ob);

        // This is correctly read/parsed, when passing in the URL directly
        ob = xs.fromXML(url);
        System.out.println("----------2");
        System.out.println(ob);

        // This is correctly read/parsed, when passing in the InputStream
        // directly
        ob = xs.fromXML(url.openStream());
        System.out.println("----------3");
        System.out.println(ob);
    }

    public static class Root {
        public String script;

        public String toString() {
            return script;
        }
    }
}

и этот файл xml.xml в пути к классам (в том же пакете, что и класс):

<root>
    <script>
<![CDATA[
// taken from http://www.w3schools.com/xml/xml_cdata.asp
function matchwo(a,b)
{
if (a < b && a < 0) then
  {
  return 1;
  }
else
  {
  return 0;
  }
}
]]>
    </script>
</root>

выводит следующий результат. Первые две строки показывают, что окончание строк было удалено, и поэтому Javascript в разделе CDATA недействителен (поскольку первый комментарий JS теперь комментирует всю JS, поскольку линии JS были объединены).

----------1
<root>    <script><![CDATA[// taken from http://www.w3schools.com/xml/xml_cdata.aspfunction matchwo(a,b){if (a < b && a < 0) then  {  return 1;  }else  {  return 0;  }}]]>    </script></root>
// taken from http://www.w3schools.com/xml/xml_cdata.aspfunction matchwo(a,b){if (a < b && a < 0) then  {  return 1;  }else  {  return 0;  }}    
----------2


// taken from http://www.w3schools.com/xml/xml_cdata.asp
function matchwo(a,b)
{
if (a < b && a < 0) then
  {
  return 1;
  }
else
  {
  return 0;
  }
}
...