Java - динамическая замена строки в потоке Reader - PullRequest
0 голосов
/ 11 июня 2018

У меня есть (текстовый) файл на диске, который мне нужно прочитать в библиотеку, которая принимает объект Reader.

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

Мое текущее решение - прочитать весь файл в память как одну строку, выполнить замену строки, а затем создать StringReader для этой строки и передать ее обратно в библиотеку в качестве читателя.

Это работает, однако с большими файлами (особенно работающими в нескольких потоках) производительность является проблемой.

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

Есть ли лучший способ решить эту задачу?

Я использую Java7

Ниже приведен пример моего текущего решения - чтение из «файла», замена всех «а» на «б» и последующая передача потока потребителю.

public void loadFile(final File file) throws Exception
{
    final Pattern regexPattern = Pattern.compile("a");
    final String replacementString = "b";

    try (BufferedReader cleanedBufferedReader = new BufferedReader(new StringReader(replaceInBufferedReader(new BufferedReader(new FileReader(file)),
            regexPattern, replacementString))))
    {
        new StreamSource(cleanedBufferedReader).doSomething();
    }
}

private static String replaceInBufferedReader(final BufferedReader reader, final Pattern pattern, final String replacement) throws IOException
{
    final StringBuilder builder = new StringBuilder();
    String str;

    while ((str = reader.readLine()) != null)
    {
        builder.append(str).append(System.lineSeparator());
    }

    return pattern.matcher(builder.toString()).replaceAll(replacement);
}

Ответы [ 3 ]

0 голосов
/ 11 июня 2018

Вы просто хотите создать подкласс BufferedReader.

class MyBufferedReader extends BufferedReader {

    MyBufferedReader(Reader r) {
        super(r);
    }

    @Override
    String readLine() {
        String line = super.readLine();
        // perform replacement here
        return line;
    }

}

Откройте ваш файл как обычно, но вместо того, чтобы оборачивать его в BufferedReader, оберните его в свой подкласс.

try ( Reader r = ...;
          BufferedReader br = new MyBufferedReader(r)) {
     String line;
     while ((line = br.readLine()) != null) {
         // use returned line
     }
}

Обновление

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

Внутри исходный поток обернут в BufferedReader и читает по одной строке за раз.Любое желаемое преобразование может быть выполнено на прочитанных строках.Преобразованная линия затем превращается в StringReader.Когда пользователь потока вызывает какую-либо из операций read(...), запрос направляется в буферизованный StringReader для удовлетворения.Если в StringReader заканчиваются символы, следующая строка BufferedReader загружается и преобразуется для продолжения ввода для read(...).

abstract public class TranslatingReader extends Reader {

    private BufferedReader input;
    private StringReader output;

    public TranslatingReader(Reader in) {
        input = new BufferedReader(in);
        output = new StringReader("");
    }

    abstract public String translate(String line);

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        int read = 0;

        while (len > 0) {
            int nchars = output.read(cbuf, off, len);
            if (nchars == -1) {
                String line = input.readLine();
                if (line == null) {
                    break;
                }

                line = tranlate(line);

                line += "\n"; // Add the newline which was removed by readLine()
                output = new StringReader(line);
            } else {
                read += nchars;
                off += nchars;
                len -= nchars;
            }
        }

        if (read == 0)
            read = -1;

        return read;
    }

    @Override
    public void close() throws IOException {
        input.close();
        output.close();
    }
}
0 голосов
/ 11 июня 2018

Другой идеей без дополнительного переопределения было бы использование Scanner с вашим шаблоном в качестве пользовательского разделителя.Это не будет читать весь файл сразу, а только часть до заданного шаблона на каждой итерации.Очень эффективная память.Может быть что-то в этом роде (вы можете улучшить его в соответствии со своими потребностями):

PS о #performance: Я думаю, что этот подход может быть даже более эффективным, чем слепое чтение построчно!Например, некоторые случаи:

  • В нескольких строках нет субтитров, и они все еще читаются в них!
  • Текстовый файл (как ни странно) был сохранен в виде одной большой строки!(Без \n s. Это возможно при плохом экспорте в файл или при поиске информации)

Не стесняйтесь взглянуть на это альтернативное решение ↓

    private static String replaceInBufferedReader(String pathToFile){

    File some = new File("some.txt");
    StringBuilder sb = new StringBuilder();
    String replacementString = "b";
    String delimiter = "x";    // you can use pattern or regex

    try {
        // set Scanner's delimiter to the pattern you wanna replace 
        Scanner sc = new Scanner(some).useDelimiter(delimiter);        

        while (sc.hasNext()) {
            sb.append(sc.next()).append(replacementString);
        }
        sc.close();
    }
    catch (FileNotFoundException e) {
        e.printStackTrace();
    }
    return sb.toString();  // or maybe save to new file
}

Я проверил это с текстовым файлом 8 МБ, и это было легко.Я использовал Writer, чтобы сохранить его как новый файл, вместо этого возвращая sb.toString ()

...
try {
    Files.write(Paths.get("some2.txt"),
            sb.toString().getBytes(),
            StandardOpenOption.CREATE);
    }
    catch (IOException e) {
        e.printStackTrace();
    }
0 голосов
/ 11 июня 2018

[edit] OP отредактировал вопрос, так что он больше не актуален

Я ожидаю, что ваш файл не является монолитным, так как вы используете символьное средство чтения Reader,Если данные не являются монолитными, они должны иметь несколько разделителей, которые разбивают файл на записи.Обычно эти разделители являются символами новой строки и / или возврата каретки для формирования записей «строки текста».

Разделите ваши данные на записи в соответствии с разделителями и пропустите каждую запись через регулярное выражение.В случае текстовых строк вы можете использовать BufferedReader.readLine()

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