1

I have a .txt file that will be accessed by many users, possibly at the same time (or close to that) and because of that I need a way modify that txt file without creating a temporary file and I haven't found answer or solution to this. So far, I only found this approach ->

Take existing file -> modify something -> write it to a new file (temp file) -> delete the old file.

But his approach is not good to me, I need something like: Take existing file -> modify it -> save it.

Is this possible? I'm really sorry if this question already exists, I tried searching Stack-overflow and I read thru Oracle Docs but I haven't found solution that suits my needs.

EDIT:

After modification, file would stay the same size as before. For example imagine list of students, each student can have value 1 or 0 (passed or failed the exam)

So in this case I would just need to update one character per row in a file (that is per, student). Example:

Lee Jackson 0 -> Lee Jackson 0

Bob White 0 -> would become -> Bob White 1

Jessica Woo 1 -> Jessica Woo 1

In the example above we have a file with 3 records one below other and I need to update 2nd record while 1st and 3rd would became the same and all that without creating a new file.

JustQuest
  • 249
  • 4
  • 15
  • if you modify it by reading it entirely into memory you can simply overwrite the original file with your modifications. If not and you read the file in chunks and write only chunks then writing a temp file is a lot safer because other users might get to see potential incomplete files – zapl Sep 29 '22 at 19:32
  • I have to read entire file (worst case scenario) I need to search for a record line by line in the file until I find the record I'm searching for). But after I find that record, I want to alter it (I edited my question with an example). So basically I would replace '1' with '0' on that row and that's it, that's all I need. – JustQuest Sep 29 '22 at 19:43
  • how large of a file is that kilobytes, gigabytes? If it's just a few kilobytes it fits in an in memory `String` what can be written back as new version of the file. Byte editing text files gets messy (e.g. https://stackoverflow.com/q/58686657/995891) – zapl Sep 29 '22 at 19:45
  • File is about ~140 MB – JustQuest Sep 29 '22 at 19:47
  • 1
    *So in this case I would just need to update one character per row in a file* - you can use a RandomAccessFile to read/write from random location. I would guess you first need to read through the file to find the student you want to update while tracking the offset of each student. Once you find the student you would then update the character. – camickr Sep 29 '22 at 20:03
  • 1
    Use sqlite or javadb instead of a file. – Cheng Thao Sep 29 '22 at 20:04
  • ChengThao I have to use file in this case, it's a requirement. @camickr That's right, workflow is next: open file -> search for a particular student -> if student is found, replace 0 with 1 -> save the file. For this scenario, RandomAccessFile can help, right? – JustQuest Sep 29 '22 at 20:07
  • Finding those offsets is the big challenge, The 7th character in an UTF-8 file/string isn't necessarily the 7th byte and `RandomAccessFile` is just a giant byte array in the end. And even going back to the last character after a `readline()` means you need to carefully check for cr/lf line breaks. Technically possible but I would in practice just write a new file and swap it out for the old version afterwards or change to sqlite or similar – zapl Sep 29 '22 at 20:10

2 Answers2

2

Here's a potential approach using RandomAccessFile. The idea would be to use readline to read it in strings but to remember the position in the file so you can go back there and write a new line. It's still risky in case anything in the text encoding would change byte lenght, because that could overwrite the line break for example.

void modifyFile(String file) throws IOException {
    try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
        long beforeLine = raf.getFilePointer();
        String line;
        while ((line = raf.readLine()) != null) {
            // edit the line while keeping its length identical
            if (line.endsWith("0")) {
                line = line.substring(0, line.length() - 1) + "1";
            }
            // go back to the beginning of the line
            raf.seek(beforeLine);
            // overwrite the bytes of that line
            raf.write(line.getBytes());
            // advance past the line break
            String ignored = raf.readLine();
            // and remember that position again
            beforeLine = raf.getFilePointer();
        }
    }
}

Handling correct String encoding is tricky in this case. If the file isn't in the encoding used by readline() and getBytes(), you could workaround that by doing

// file is in "iso-1234" encoding which is made up.
// reinterpret the byte as the correct encoding first
line = new String(line.getBytes("ISO-8859-1"), "iso-1234");
... modify line
// when writing use the expected encoding
raf.write(line.getBytes("iso-1234"));

See How to read UTF8 encoded file using RandomAccessFile?

zapl
  • 63,179
  • 10
  • 123
  • 154
0

Try storing the changes you want to make to a file in the RAM (string or linked list of strings). If you read in the file to a linked list of strings (per line of the file) and write a function to merge the string you want to insert into that linked list of lines from the file and then rewrite the file entirely by putting down every line from the linked list it should give you what you want. Heres what I mean in psudocode the order is important here. By reading in the file and setting after input we minimize interference with other users.

String lineYouWantToWrite = yourInput
LinkedList<String> list = new LinkedList<String>()
while (file has another line)
    list.add(file's next line)

add your string to whatever index of list you want
write list to file line by line, file's first line = list[1]...
Drew
  • 26
  • 3