8

I'm trying to write an API to replace all the lines containing a certain substring with a different string in a text file.

I’m using Java 8 stream to filter the lines which contains the given pattern. I’m having problem with the file write part.

Files.lines(targetFile).filter(line -> line.contains(plainTextPattern)).parallel()
      .map(line-> line.replaceAll(plainTextPattern, replaceWith)).parallel();

Above code reads the file line-wise, filters the lines that match the pattern and replaces with the give string and gives back a stream of strings which has only the replaced lines.

We need to write these lines back to file. Since we lose the stream once the pipeline ends, I appended the following to the pipeline:

.forEach(line -> {
try {
            Files.write(targetFile, line.toString().getBytes());
} catch (IOException e) {
  e.printStackTrace();
}

I was hoping it would write to the file only the modified (since it is in the pipeline) line and keep the other lines untouched.

But it seems to truncate the file for each line in the file and keep only the last processed line and deletes all the lines that were not matched in the pipeline.

Is there something I’m missing about handling files using streams?

Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
A.R.K.S
  • 1,692
  • 5
  • 18
  • 40
  • 3
    From [documentation](https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html#write-java.nio.file.Path-byte:A-java.nio.file.OpenOption...-): "By default the method creates a new file or overwrites an existing file.". For solution read rest of documentation. Also remember that you can't simply alter old text file. You need to create new one with proper changes and then replace it. – Pshemo Feb 05 '16 at 22:24
  • Thanks! I thought of using StandardOpenOption.APPEND. But I felt it doesn't make sense because I don't want it to get appended to end of file. It has to replace the existing line. Am I missing something again? – A.R.K.S Feb 05 '16 at 22:27
  • I just tried append. It appends all the content (including the lines that didn't match) to end of file.. At least this tells me the unmatched lines are not totally lost. But I need to figure out how to rewrite the file with the new content. – A.R.K.S Feb 05 '16 at 22:29
  • 3
    @AshwiniR Trying to just replace the lines of the file you want -- especially when the file is text and has newlines, instead of just replacing bytes -- is going to be a huge mess if it's possible at all. Just do the most straightforward thing: read the whole file into lines, replace the appropriate places, write the whole thing back. – Louis Wasserman Feb 05 '16 at 22:31

3 Answers3

26

Using filter eliminates anything that doesn't match the filter from the stream. (Additionally, for what it's worth, a) you only need to use parallel once, b) parallel isn't that effective on streams coming from I/O sources, c) it's almost never a good idea to use parallel until you've actually tried it non-parallel and found it too slow.)

That said: there's no need to filter out the lines that match the pattern if you're going to do a replaceAll. Your code should look like this:

try (Stream<String> lines = Files.lines(targetFile)) {
   List<String> replaced = lines
       .map(line-> line.replaceAll(plainTextPattern, replaceWith))
       .collect(Collectors.toList());
   Files.write(targetFile, replaced);
}
A.R.K.S
  • 1,692
  • 5
  • 18
  • 40
Louis Wasserman
  • 191,574
  • 25
  • 345
  • 413
7

So sorry to tell you that this is not how files work. If you want to write to the middle of a file, you need to have RandomAccess; Get a FilePointer, seek, that pointer, and write from there.

This holds if the size of data you want write is equal to the size of data you want to overwrite. If this is not the case, you have to copy the tail of the file to a temp buffer and append it to the text you wish to write.

And btw, parallelStreams on IO bound tasks is often a bad idea.

Sleiman Jneidi
  • 22,907
  • 14
  • 56
  • 77
0

You might want to implement a stream, like Jenkov has done it here: http://tutorials.jenkov.com/java-howto/replace-strings-in-streams-arrays-files.html

This simple one is specifically replacing tokens in the form of ${tokenName}.

There are more general algorithms.

Ondra Žižka
  • 43,948
  • 41
  • 217
  • 277