Как сохранить новые строки при чтении файла с помощью stream-java 8

      try (Stream<String> lines = Files.lines(targetFile)) {  
     List<String> replacedContent = lines.map(line ->  
                                       StringUtils.replaceEach(line,keys, values))
                                       .parallel()
                                       .collect(Collectors.toList());
    Files.write(targetFile, replacedContent);
}

Я пытаюсь заменить несколько текстовых шаблонов в каждой строке файла. Но я наблюдаю, что"rn "(байтовый эквивалент 10 и 13) заменяется только"r " (только 10), и мои тесты сравнения терпят неудачу.

Я хочу сохранить новые строки, как они есть во входном файле, и не хочу, чтобы java касалась их. Может ли кто-нибудь предложить, есть ли способ сделать это без использования отдельной замены по умолчанию для "rn".

2 ответов


проблема в том, что Files.lines() реализован поверх BufferedReader.readLine(), который читает строку до конца строки и выбрасывает ее. Затем, когда вы пишете строки с чем-то вроде Files.write(), это обеспечивает системный Терминатор строки после каждой строки, который может отличаться от Терминатора строки, который был прочитан.

если вы действительно хотите сохранить Терминаторы линии точно так же, как они есть, даже если они представляют собой смесь разных Терминаторов линии, вы можете использовать regex и Scanner для этого.

сначала определите шаблон, который соответствует строке, включая допустимые Терминаторы строк или EOF:

Pattern pat = Pattern.compile(".*\R|.+\z");

на \R это специальный linebreak matcher, который соответствует обычным Терминаторам строк плюс несколько Терминаторов строк Unicode, о которых я никогда не слышал. :- ) Вы можете использовать что-то вроде (\r\n|\r|\n) если вы хотите просто обычный CRLF, CR или LF Терминаторы.

вы должны включить .+\z для того, чтобы соответствовать потенциальной последней "строке" в файле, который не имеет Терминатора строки. Убедитесь, что регулярное выражение всегда соответствует хотя бы одному символу, чтобы совпадение не было найдено, Когда сканер достигнет конца файла.

затем прочитайте строки с помощью Scanner пока он не вернется null:

try (Scanner in = new Scanner(Paths.get(INFILE), "UTF-8")) {
    String line;
    while ((line = in.findWithinHorizon(pat, 0)) != null) {
        // Process the line, then write the output using something like
        // FileWriter.write(String) that doesn't add another line terminator.
    }
}

строки в потоке не содержат символов новой строки.

было бы неплохо, если бы документация метода для Files.lines() об этом говорил. Однако, если вы будете следить за реализацией, это в конечном итоге приведет к BufferedReader.readLine(). Этот метод документирован для возврата содержимого строки,не включая любые символы завершения строки.

вы можете добавить символ новой строки, Когда вы пишете их.

зависящий от системы разделитель строк используется Files.write() метод, который вы вызываете, как задокументировано в своем брате. Вы также можете получить этот системный разделитель строк с помощью System.lineSeparator().

если вы хотите другой разделитель строк, и знаю, что это, вы можете задать его. Например:

    try ( PrintStream out = new PrintStream( Files.newOutputStream( targetFile ))) 
    {
        lines.forEach( line -> out.print( line + "\r\n") );
    }

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

  • чтение разделителя первой строки и предположение, что он согласован по всему файлу. Это позволяет вам продолжать использовать Files.lines() читать строки.
  • используйте API, который позволяет получать строки с их разделителями.
  • читать Символ за символом, а не по строкам, так что вы можете получить разделители строк.

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