Java - delete line from text file by overwriting while reading it

44,465

Solution 1

As others have pointed out, you might be better off using a temporary file, if there's a slightest risk that your program crashes mid way:

public static void removeNthLine(String f, int toRemove) throws IOException {

    File tmp = File.createTempFile("tmp", "");

    BufferedReader br = new BufferedReader(new FileReader(f));
    BufferedWriter bw = new BufferedWriter(new FileWriter(tmp));

    for (int i = 0; i < toRemove; i++)
        bw.write(String.format("%s%n", br.readLine()));

    br.readLine();

    String l;
    while (null != (l = br.readLine()))
        bw.write(String.format("%s%n", l));

    br.close();
    bw.close();

    File oldFile = new File(f);
    if (oldFile.delete())
        tmp.renameTo(oldFile);

}

(Beware of the sloppy treatment of encodings, new-line characters and exception handling.)


However, I don't like answering questions with "I won't tell you how, because you shouldn't do it anyway.". (In some other situation for instance, you may be working with a file that's larger than half your hard drive!) So here goes:

You need to use a RandomAccessFile instead. Using this class you can both read and write to the file using the same object:

public static void removeNthLine(String f, int toRemove) throws IOException {
    RandomAccessFile raf = new RandomAccessFile(f, "rw");

    // Leave the n first lines unchanged.
    for (int i = 0; i < toRemove; i++)
        raf.readLine();

    // Shift remaining lines upwards.
    long writePos = raf.getFilePointer();
    raf.readLine();
    long readPos = raf.getFilePointer();

    byte[] buf = new byte[1024];
    int n;
    while (-1 != (n = raf.read(buf))) {
        raf.seek(writePos);
        raf.write(buf, 0, n);
        readPos += n;
        writePos += n;
        raf.seek(readPos);
    }

    raf.setLength(writePos);
    raf.close();
}

Solution 2

You cannot do it this way. FileWriter can only append to a file, rather than write in the middle of it - You need RandomAccessFile if you want to write in the middle. What you do now - you override the file the first time you write to it (and it gets empty - that's why you get the exception). You can create FileWriter with append flag set to true - but this way you would append to a file rather than write in the middle of it.

I'd really recommend to write to a new file and then rename it at the end.

Solution 3

@shelley: you can't do what you are trying to do and what's more, you shouldn't. You should read the file and write to a temporary file for several reasons, for one, it's possible to do it this way (as opposed to what you're trying to do) and for another, if the process gets corrupted, you could bale out without loss of the original file. Now you could update a specific location of a file using a RandomAccessFile, but this is usually done (in my experience) when you are dealing with fixed sized records rather than typical text files.

Share:
44,465

Related videos on Youtube

Shelley
Author by

Shelley

Updated on August 09, 2020

Comments

  • Shelley
    Shelley almost 4 years

    I'm trying to delete a line of text from a text file without copying to a temporary file. I am trying to do this by using a Printwriter and a Scanner and having them traverse the file at the same time, the writer writing what the Scanner reads and overwriting each line with the same thing, until it gets to the line that I wish to delete. Then, I advance the Scanner but not the writer, and continue as before. Here is the code:

    But first, the parameters: My file names are numbers, so this would read 1.txt or 2.txt, etc, and so f specifies the file name. I convert it to a String in the constructor for a file. Int n is the index of the line that I want to delete.

    public void deleteLine(int f, int n){
     try{
     Scanner reader = new Scanner(new File(f+".txt")); 
     PrintWriter writer = new PrintWriter(new FileWriter(new File(f+".txt")),false); 
     for(int w=0; w<n; w++)
       writer.write(reader.nextLine()); 
     reader.nextLine(); 
     while(reader.hasNextLine())
       writer.write(reader.nextLine());
     } catch(Exception e){
       System.err.println("Enjoy the stack trace!");
       e.printStackTrace();
     }
    }
    

    It gives me strange errors. It says "NoSuchElementException" and "no line found" in the stack trace. It points to different lines; it seems that any of the nextLine() calls can do this. Is it possible to delete a line this way? If so, what am I doing wrong? If not, why? (BTW, just in case you'd want this, the text file is about 500 lines. I don't know if that counts as large or even matters, though.)

  • Shelley
    Shelley about 13 years
    That is a good point. Your advice is taken. One more question: to create a temporary file, would I use createTempFile or just the constructor with a different file name? (Sorry if it seems a silly question; I'm a novice with I/O with files.)
  • Hovercraft Full Of Eels
    Hovercraft Full Of Eels about 13 years
    @aioobe: but why, what's the advantage, when his/her main rationale for doing it the original way was due to ignorance rather than need? And what if he/she wants to insert text in later that is larger than text deleted?
  • aioobe
    aioobe about 13 years
    The most obvious advantage is that it doesn't require twice the hard-drive space. If the file is really large, it may not be possible to have two copies even temporarily.
  • Hovercraft Full Of Eels
    Hovercraft Full Of Eels about 13 years
    @aioobe: does 500 lines of text qualify as really large? The reason I'm asking is I'm afraid that your answer will mislead someone who is quite new to Java in thinking that this is the best answer for her situation. I believe that it isn't.
  • aioobe
    aioobe about 13 years
    Obviously depends on the lengths of the lines. Doesn't it ;-) Updated the answer anyway.
  • Hovercraft Full Of Eels
    Hovercraft Full Of Eels about 13 years
    @Shelley: have you gone through the Java tutorials, the I/O section? If not, I advise you to have a look: Basic I/O
  • Hovercraft Full Of Eels
    Hovercraft Full Of Eels about 13 years
    Yep, 1+ for recommending to write to a new file and rename it.
  • Shelley
    Shelley about 13 years
    @aioobe I appreciate it ;) @Hovercraft Full Of Eels Well, my rationale was the efficiency, but I admit I'm somewhat new to Java. I'm going to do it with the temporary file, the way it should be done. Still... Could you explain a little more fully why doing it the way I was attempting is wrong if it is possible and takes up less space? An explanation would help me greatly in my quest for Java knowledge :-)
  • Hovercraft Full Of Eels
    Hovercraft Full Of Eels about 13 years
    @Shelley: I don't know of any relative speed advantages of one technique over the other, but as aioobe, a RAF has some size advantages while on the other hand there are definite safety advantages of leaving the original file intact until the process is complete, and even then perhaps renaming the original file so it can be saved a a backup before giving the temp file the original name.
  • Hovercraft Full Of Eels
    Hovercraft Full Of Eels about 13 years
    @aioobe: thanks for the edit and agree. Changed -1 vote to +1.

Related