Java-удалить строку из текстового файла путем перезаписи при чтении

Я пытаюсь удалить строку текста из текстового файла без копирования во временный файл. Я пытаюсь сделать это, используя Printwriter и сканер и заставляя их одновременно пересекать файл, писатель пишет то, что читает сканер, и перезаписывает каждую строку одним и тем же, пока не доберется до строки, которую я хочу удалить. Затем я продвигаю сканер, но не писатель, и продолжаю, как и раньше. Вот код:

но сначала параметры: мой файл имена-это числа, так что это будет 1.txt или 2.txt и т. д., и поэтому f указывает имя файла. Я преобразую его в строку в конструкторе для файла. Int n-индекс строки, которую я хочу удалить.

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();
 }
}

Это дает мне странные ошибки. В нем говорится "NoSuchElementException" и "строка не найдена" в трассировке стека. Он указывает на разные линии; кажется, что любой из вызовов nextLine() может это сделать. Можно ли удалить строку таким образом? Если да, то что я делаю не так? Если нет, почему? (Кстати, на всякий случай, если вам это нужно, текстовый файл составляет около 500 строк. Я не знаю, считается ли это большим или даже имеет значение.)

3 ответов


как указывали другие, вам может быть лучше использовать временный файл, если есть малейший риск того, что ваша программа вылетит в середине пути:

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);

}

(остерегайтесь небрежной обработки кодировок, символов новой строки и обработки исключений.)


тем не менее, я не люблю отвечать на вопросы с "Я не скажу вам, как, потому что вы все равно не должны этого делать.". (В какой-то другой ситуации, например, вы можете работать с файлом, который больше половины вашего жесткого диска!) Так вот:

вам нужно использовать RandomAccessFile. Используя этот класс, вы можете читать и записывать в файл, используя один и тот же объект:

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();
}

вы не можете сделать это таким образом. FileWriter может только добавлять к файлу, а не писать в середине его - вам нужен RandomAccessFile, если вы хотите писать в середине. То, что вы делаете сейчас - вы переопределяете файл при первой записи в него (и он становится пустым - вот почему вы получаете исключение). Вы можете создать FileWriter с флагом append, установленным в true, но таким образом вы добавите файл, а не напишете в его середине.

Я бы очень рекомендовал написать новому файл, а затем переименовать его в конце.


@shelley: вы не можете делать то, что вы пытаетесь сделать, и более того, вы не должны. Вы должны прочитать файл и записать его во временный файл по нескольким причинам: Во-первых, это можно сделать таким образом (в отличие от того, что вы пытаетесь сделать), а во-вторых, если процесс будет поврежден, вы можете сбросить без потери исходного файла. Теперь вы можете обновить определенное местоположение файла с помощью RandomAccessFile, но это обычно делается (по моему опыту), когда вы имеете дело с фиксированный размер записи, а не типичные текстовые файлы.