Java - удалить строку из текстового файла, переписав при чтении - PullRequest
8 голосов
/ 25 июня 2011

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

Но сначала, параметры: мои имена файлов являются числами, так что это будет 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 ]

20 голосов
/ 25 июня 2011

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

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();
}
2 голосов
/ 25 июня 2011

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

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

1 голос
/ 25 июня 2011

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

...