OutOfMemoryError читает текстовый файл размером 174 Мб с большими строками - PullRequest
5 голосов
/ 27 марта 2019

У меня есть CSV-файл с 12000 строк. В каждой строке есть несколько полей, заключенных в двойные кавычки и разделенных запятой.Одним из этих полей является документ XML, поэтому строка может быть очень длинной .Размер файла составляет 174 МБ.

Вот пример файла:

"100000","field1","field30","<root><data>Hello I have a
line break</data></root>","field31"
"100001","field1","field30","<root><data>Hello I have multiple
line 
break</data></root>","field31"

Проблема с этим файлом находится внутри поля xml, которое может иметь один или несколько разрывов строк и, таким образом,может сломать разбор.Цель здесь - прочитать весь файл и применить регулярное выражение, которое заменит все разрывы строк в двойных кавычках пустой строкой.

Следующий код дает мне OutOfMemoryError:

    String path = "path/to/file.csv";

    try {
        byte[] content = Files.readAllBytes(Paths.get(path));
    }
    catch (Exception e) {
        e.printStackTrace();
        System.exit(1);
    }

Я также пытался прочитать файл, используя BufferedReader и StringBuilder, получил OutOfMemoryError в строке 5000:

String path = "path/to/file.csv";

    try {
        StringBuilder sb = new StringBuilder();
        BufferedReader br = new BufferedReader(new FileReader(path));
        String line;
        int count = 0;
        while ((line = br.readLine()) != null) {
            sb.append(line);
            System.out.println("Read " + count++);
        }
    }
    catch (Exception e) {
        e.printStackTrace();
        System.exit(1);
    }

Я пытался запустить обе вышеупомянутые программы с разными значениями кучи Java, например -Xmx1024m,-Xmx4096m, -Xmx8092m.Во всех случаях я получил OutOfMemoryError.Почему это происходит, учитывая, что размер файла составляет 174 МБ?

Ответы [ 3 ]

3 голосов
/ 27 марта 2019

Вам необходимо использовать двойные буферы для анализа вашей специальной структуры данных и построчной обработки.Чтение всего документа - не лучшая идея.

Создайте собственный BufferedReader, который читает строки с внутренним BufferedReader вашего CSV-файла.После прочтения строки попробуйте определить, нужно ли читать больше строк, чтобы завершить одну строку в CSV (например, если вы знаете, что ваш XML начинается с <root> и заканчивается </root>, проверьте наличие этих строк и прочитайтеи добавляйте, пока не дойдете до заключительного токена - это будет последняя строка для вашей строки CSV).

Второй уровень будет вашей обработкой CSV, основанной на строке CSV, которую вы получите с первого шага.Разобрать, сохранить, обработать, затем выброситьТогда он не будет занимать больше памяти, Java Garbage Collector освободит его.

Это единственный способ работать с большими файлами.Это также называется «потоковой моделью», поскольку вы пропускаете только небольшие порции данных, поэтому фактическое потребление памяти низкое.

2 голосов
/ 27 марта 2019

Оберните ваш InputStream фильтрующим:

class QuotedNewLineFilterInputStream extends FilterInputStream {

    private boolean insideQuotes;

    public QuotedNewLineFilterInputStream(InputStream in) {
        super(in);
    }

    @Override
    public int read() throws IOException {
        int c = super.read();
        if (c == '\"') {
            insideQuotes = !insideQuotes;
        }
        if (insideQuotes && (c == '\n' || c == '\r')) {
            c = read();
        }
        return c;
    }
}

Это удаляет LF и CR внутри двойных кавычек. Поскольку все они являются ASCII, а XML вероятен в UTF-8, можно работать на байтовом уровне (InputStream).

Кстати, замена на \t может лучше сохранить макет (c = \ t 'i.o. c = read()).

Не очень умный, но простое решение.

0 голосов
/ 27 марта 2019

Если чтение файла 174 МБ с Files.readAllBytes(Paths.get(path)); вызывает OutOfMemoryError, то вам не удалось увеличить предел памяти с -Xmx8g.При использовании кучи памяти объемом 8 ГБ не должно возникнуть проблем с выделением 174 МБ непрерывной памяти для byte[]

. Проверьте, как вы прошли флаг -Xmx.Вы можете проверить параметры времени выполнения JVM, подключившись к работающему JVM-серверу с помощью JConsole, JVisualVM или другого инструмента.Взгляните на Использование JConsole , в котором показано, как проверить параметры времени выполнения JVM, например, вкладку Память.

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