8

I have a .txt file, which I want to process in Java. I want to delete its last line.

I need ideas on how to achieve this without having to copy the entire content into another file and ignoring the last line. Any suggestions?

weltraumpirat
  • 22,414
  • 5
  • 39
  • 54
Sergiu
  • 2,340
  • 5
  • 31
  • 54

2 Answers2

9

You could find the beginning of the last line by scanning the file and then truncate it using FileChannel.truncate or RandomAccessFile.setLength.

MForster
  • 8,450
  • 5
  • 27
  • 31
  • This does only work on byte basis, wouldn't you have to do the encoding related size calculations by yourself? – Hauke Ingmar Schmidt Feb 05 '12 at 14:02
  • 1
    Obviously, this depends on the encoding. Byte-wise searching for a new-line character (\0x0a) in ASCII or Latin-1 encoding (or other encodings with 1 byte per character) is safe. I believe that doing this for UTF-8 is safe, too, because all multi-byte sequences have the high bit set. It would fail for, e.g., UTF-16. – MForster Feb 05 '12 at 14:16
  • You are right, for typical encodings including UTF-8 this should not be a problem (as long as you can define "line ending", that is ;-)). – Hauke Ingmar Schmidt Feb 05 '12 at 14:23
1

By taking RandomAccessFile you can:

  • use method seek(long) to jump forward and read those lines. But you won't know exactly how big the jump should be.
  • to delete last lines you need the position of begin of last line so before reading each line store their file pointer position (method getFilePointer()). Deleting to that position you use setLength(long).

Code would be something like this:

    LinkedList<String> lines=null;
    int howMuch = 1; // one line to read and delete
    try{
        RandomAccessFile raf = new RandomAccessFile(inputFileName, "rw");

        System.out.println("File Length="+raf.length());

        long step = 20;  // here I advice to write average length of line
        long jump = raf.length()<step*howMuch?
                0:
                    raf.length()-step*howMuch; 
        raf.seek(jump);
        lines = new LinkedList<String>();
        LinkedList<Long> pos = new LinkedList<Long>();

        Entry<LinkedList<String>,LinkedList<Long>> rLinesRead = getRemainingLines(raf,
                new AbstractMap.SimpleEntry<LinkedList<String>,LinkedList<Long>> (lines,pos));
        while(rLinesRead.getKey().size()<howMuch){
            if(jump<step)
                if(jump<=0)
                    break;
                else{
                    jump=0;
                    raf.seek(jump);
                    rLinesRead=getRemainingLines(raf,rLinesRead);
                    break;
                }
            else
                jump=jump-step;
            raf.seek(jump);
            rLinesRead=getRemainingLines(raf,rLinesRead);
        }
        int originalSize=rLinesRead.getKey().size(); 
        lines=rLinesRead.getKey();
        pos=rLinesRead.getValue();
        for (int i=0;i<originalSize-howMuch;++i){
            lines.removeFirst();
            pos.removeFirst();
        }
        if(!pos.isEmpty())
            raf.setLength(pos.getFirst()); // before last(from EOF) returned fully read lines in file          
    }catch(Exception ex){
        ex.printStackTrace();
    } finally{
        try {               raf.close();            } catch (Exception e) {             e.printStackTrace();            }
    }
//returns line to EOF with their begin file pointer positions
private Entry<LinkedList<String>,LinkedList<Long>> getRemainingLines(RandomAccessFile raf,
        Entry<LinkedList<String>,LinkedList<Long>> linesAlreadyLoadedFromEnd) throws IOException{
    LinkedList<String> pLines = linesAlreadyLoadedFromEnd.getKey();
    LinkedList<Long> pPos = linesAlreadyLoadedFromEnd.getValue();

    long init=raf.getFilePointer();
    String str = raf.readLine();
    if(pPos.size()>0?pPos.getFirst()==0:false || str==null)
        return linesAlreadyLoadedFromEnd;

    LinkedList<String> lines = new LinkedList<String>();
    LinkedList<Long> pos = new LinkedList<Long>();
    if(init==0L ){              
        lines.add(str);
        pos.add(0L);
    }
    Long tmpPos = raf.getFilePointer();
    while ((str = raf.readLine())!=null && !pPos.contains(tmpPos)){
        lines.add(str);
        pos.add(tmpPos);
        tmpPos = raf.getFilePointer();
    }
    pLines.addAll(0,lines);
    pPos.addAll(0,pos);
    return new AbstractMap.SimpleEntry<LinkedList<String>,LinkedList<Long>> (pLines,pPos);
}
Waldemar Wosiński
  • 1,437
  • 17
  • 28