web-dev-qa-db-ja.com

Java-テキストファイルの読み取り中に上書きして、テキストファイルから行を削除します

一時ファイルにコピーせずに、テキストファイルから1行のテキストを削除しようとしています。プリントライターとスキャナーを使用して同時にファイルをトラバースすることでこれを実行しようとしています。ライターはスキャナーが読み取ったものを書き込み、各行を同じもので上書きして、目的の行に到達します。削除します。次に、ライターではなくスキャナーを進め、前と同じように続行します。コードは次のとおりです。

しかし、最初に、パラメータ:私のファイル名は数字なので、これは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行です。それが大きいと見なされるのか、それとも重要なのかはわかりません。)

8
Shelley

他の人が指摘しているように、プログラムが途中でクラッシュするリスクがわずかにある場合は、一時ファイルを使用する方がよい場合があります。

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

この方法ではできません。 FileWriterは、ファイルの途中に書き込むのではなく、ファイルに追加することしかできません。途中に書き込む場合は、RandomAccessFileが必要です。あなたが今していること-あなたは最初にファイルに書き込むときにファイルを上書きします(そしてそれは空になります-それがあなたが例外を得る理由です)。追加フラグをtrueに設定してFileWriterを作成できますが、この方法では、ファイルの途中に書き込むのではなく、ファイルに追加します。

新しいファイルに書き込んで、最後に名前を変更することを強くお勧めします。

2
Jarek Potiuk

@shelley:やろうとしていることはできませんし、それ以上にすべきではありません。ファイルを読み取って一時ファイルに書き込む必要がある理由はいくつかあります。1つは、(実行しようとしていることとは対照的に)この方法で実行できること、もう1つは、プロセスが破損した場合にベールを発生させることです。元のファイルを失うことなく出力します。これで、RandomAccessFileを使用してファイルの特定の場所を更新できますが、これは通常、(私の経験では)通常のテキストファイルではなく固定サイズのレコードを処理するときに行われます。