разделить очень большой текстовый файл на максимальное количество строк

Я хочу разбить огромный файл, содержащий строки, на набор новых (меньших) файлов и попытался использовать nio2.

Я не хочу загружать весь файл в память, поэтому попробовал с помощью BufferedReader.

Текстовые файлы меньшего размера должны быть ограничены количеством текстовых строк.

Решение работает, однако я хочу спросить, знает ли кто-нибудь решение с более высокой производительностью с помощью usion java 8 (может быть, lamdas с stream () - api?) и nio2:

public void splitTextFiles(Path bigFile, int maxRows) throws IOException{

        int i = 1;
        try(BufferedReader reader = Files.newBufferedReader(bigFile)){
            String line = null;
            int lineNum = 1;

            Path splitFile = Paths.get(i + "split.txt");
            BufferedWriter writer = Files.newBufferedWriter(splitFile, StandardOpenOption.CREATE);

            while ((line = reader.readLine()) != null) {

                if(lineNum > maxRows){
                    writer.close();
                    lineNum = 1;
                    i++;
                    splitFile = Paths.get(i + "split.txt");
                    writer = Files.newBufferedWriter(splitFile, StandardOpenOption.CREATE);
                }

                writer.append(line);
                writer.newLine();
                lineNum++;
            }

            writer.close();
        }
}

person nimo23    schedule 28.08.2014    source источник
comment
Поскольку вы читаете файл только один раз и последовательно, я не думаю, что какой-либо API может дать вам значительно лучшую производительность. Лямбда-выражения могут улучшить внешний вид кода, но поскольку ваш процесс сильно привязан к вводу-выводу, они вообще не повлияют на производительность.   -  person biziclop    schedule 28.08.2014
comment
Спасибо. В stackoverflow.com/questions/25546750/ nio2 использовался с FileChannel, который работает лучше, чем читатель на основе char, однако, я думаю, в этом случае нет возможности использовать FileChannel, как мне нужно доступ к фактической строке файла.   -  person nimo23    schedule 28.08.2014
comment
Хорошая мысль, да, это тоже часть. Если вам нужны фрагменты фиксированного размера (например, каждый файл составляет ровно 1 МБ), вы определенно можете сэкономить на преобразовании байтов в символы.   -  person biziclop    schedule 28.08.2014


Ответы (2)


Остерегайтесь разницы между прямым использованием _1 _ / OutputStreamWriter и их подклассы и _3 _ / _ 4_ фабричные методы _5 _ . Если в первом случае используется системная кодировка по умолчанию, когда не указана явная кодировка, то во втором всегда по умолчанию используется UTF-8. Поэтому я настоятельно рекомендую всегда указывать желаемую кодировку, даже если это Charset.defaultCharset() или StandardCharsets.UTF_8, чтобы задокументировать ваше намерение и избежать сюрпризов, если вы переключаетесь между различными способами создания Reader или Writer.


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

Если вы готовы пожертвовать переносимостью, вы можете попробовать несколько оптимизаций. Если вы знаете, что кодировка кодировки будет однозначно отображать '\n' в (byte)'\n', как это имеет место для большинства однобайтовых кодировок, а также для UTF-8, вы можете сканировать разрывы строк на уровне байтов, чтобы получить позиции файлов для разделения и избежать любой передачи данных из вашего приложения в систему ввода-вывода.

public void splitTextFiles(Path bigFile, int maxRows) throws IOException {
    MappedByteBuffer bb;
    try(FileChannel in = FileChannel.open(bigFile, READ)) {
        bb=in.map(FileChannel.MapMode.READ_ONLY, 0, in.size());
    }
    for(int start=0, pos=0, end=bb.remaining(), i=1, lineNum=1; pos<end; lineNum++) {
        while(pos<end && bb.get(pos++)!='\n');
        if(lineNum < maxRows && pos<end) continue;
        Path splitFile = Paths.get(i++ + "split.txt");
        // if you want to overwrite existing files use CREATE, TRUNCATE_EXISTING
        try(FileChannel out = FileChannel.open(splitFile, CREATE_NEW, WRITE)) {
            bb.position(start).limit(pos);
            while(bb.hasRemaining()) out.write(bb);
            bb.clear();
            start=pos;
            lineNum = 0;
        }
    }
}

Недостатком является то, что он не работает с кодировками типа UTF-16 или EBCDIC и, в отличие от BufferedReader.readLine(), не поддерживает одиночный '\r' в качестве терминатора строки, как в старой MacOS9.

Кроме того, он поддерживает только файлы размером менее 2 ГБ; предел, вероятно, еще меньше на 32-битных JVM из-за ограниченного виртуального адресного пространства. Для файлов, размер которых превышает установленный предел, необходимо будет перебирать фрагменты исходного файла и map их один за другим.

Эти проблемы можно исправить, но это повысит сложность этого подхода. Учитывая тот факт, что прирост скорости на моей машине составляет всего около 15% (я не ожидал большего, поскольку здесь преобладает ввод-вывод) и будет даже меньше, когда возрастет сложность, я не думаю, что оно того стоит.


Суть в том, что для этой задачи подход _20 _ / _ 21_ достаточно, но вы должны позаботиться о Charset, используемом для операции.

person Holger    schedule 29.08.2014

Я внес небольшие изменения в код @ nimo23, учитывая возможность добавления верхнего и нижнего колонтитула для каждого из разделенных файлов, а также выводит файлы в каталог с тем же именем, что и исходный файл, с добавленным к нему _split . код ниже:

public static void splitTextFiles(String fileName, int maxRows, String header, String footer) throws IOException
    {
        File bigFile = new File(fileName);
        int i = 1;
        String ext = fileName.substring(fileName.lastIndexOf("."));

        String fileNoExt = bigFile.getName().replace(ext, "");
        File newDir = new File(bigFile.getParent() + "\\" + fileNoExt + "_split");
        newDir.mkdirs();
        try (BufferedReader reader = Files.newBufferedReader(Paths.get(fileName)))
        {
            String line = null;
            int lineNum = 1;
            Path splitFile = Paths.get(newDir.getPath() + "\\" +  fileNoExt + "_" + String.format("%03d", i) + ext);
            BufferedWriter writer = Files.newBufferedWriter(splitFile, StandardOpenOption.CREATE);
            while ((line = reader.readLine()) != null)
            {
                if(lineNum == 1)
                {
                    writer.append(header);
                    writer.newLine();
                }
                writer.append(line);
                writer.newLine();
                lineNum++;
                if (lineNum > maxRows)
                {
                    writer.append(footer);
                    writer.close();
                    lineNum = 1;
                    i++;
                    splitFile = Paths.get(newDir.getPath() + "\\" + fileNoExt + "_" + String.format("%03d", i) + ext);
                    writer = Files.newBufferedWriter(splitFile, StandardOpenOption.CREATE);
                }
            }
            if(lineNum <= maxRows) // early exit
            {
                writer.append(footer);
            }
            writer.close();
        }

        System.out.println("file '" + bigFile.getName() + "' split into " + i + " files");
    }
person amralieg    schedule 16.04.2017