【发布时间】:2014-10-05 11:44:19
【问题描述】:
我有一个大文件,我只需要删除其中的几行 有没有办法在不打开新文件并复制整个文本的情况下做到这一点?
编辑: 主要问题是当它在一个以上的线程中运行时,带有大 txt 文件的程序会失败
【问题讨论】:
-
为此编写一个脚本并使用 java 调用它
-
我知道这个选项,我正在寻找一种无需处理整个文本的方法(我知道行号)
我有一个大文件,我只需要删除其中的几行 有没有办法在不打开新文件并复制整个文本的情况下做到这一点?
编辑: 主要问题是当它在一个以上的线程中运行时,带有大 txt 文件的程序会失败
【问题讨论】:
有没有办法在不打开新文件并复制整个文本的情况下做到这一点?
不,没有。当然,如果您想安全地这样做,则没有。
RandomAccessFile 也不会真正帮助您。它允许您用相同数量的字节替换文件中的字节序列,但这并不等于删除一行。
您可以像这样使用 RAF:
给定初始状态
L1L2L3...LN将L2L3...LN替换为L3...LN
或者您可以使用 RAF 根据@halfbit 的回答一次“滑动”一行。
但是:
在最坏的情况下,您会复制整个文件内容,而一般情况下涉及读取和写入 O(N) 行的字节。
执行此操作的简单方法需要在内存中保存O(N) 行。
“滑动”方法需要O(N) I/O 操作(即系统调用)。
最重要的是:通过就地文件更新删除行是有风险的。如果应用程序在进程中间中断(例如电源故障),那么您最终会得到一个损坏的文件。
FWIW:这不是 Java 的限制本身。相反,它是现代操作系统表示/模型文件方式的限制。
【讨论】:
查看Random Access Files,以便将文件指针定位在所需位置并移动文本。
【讨论】:
这是一些使用RandomAccessFile 删除行而不打开新文件 的独立示例代码,这似乎对我有用。 (不过需要就地复制。)
public static void main(String[] args) {
try {
// prepare test file
String path = "/tmp/test.txt";
writeTestLines(path, 999999);
// mode "rws": read + write synchronous
RandomAccessFile raf = new RandomAccessFile(path, "rws");
int bufSize = 1 << 20; // 1 MiB
Scanner s = new Scanner(new BufferedInputStream(new FileInputStream(raf.getFD()), bufSize));
PrintWriter pw = new PrintWriter(new BufferedOutputStream(new FileOutputStream(raf.getFD()), bufSize));
long writeOffset = 0;
for (int nr = 1;; nr++) {
if (!s.hasNextLine())
break;
String line = s.nextLine();
if (nr != 2 && !line.contains("00")) {
// switch to writing: save read offset, seek write offset
long readOffset = raf.getFilePointer();
raf.seek(writeOffset);
pw.println(line);
// switch to reading: save write offset, seek read offset
writeOffset = raf.getFilePointer();
raf.seek(readOffset);
}
}
// write buffered output and truncate file
raf.seek(writeOffset);
pw.flush();
raf.setLength(raf.getFilePointer());
pw.close();
s.close();
raf.close();
} catch (Exception ex) {
ex.printStackTrace(System.err);
}
}
public static void writeTestLines(String path, int n) throws IOException {
PrintWriter pw = new PrintWriter(path);
for (int i = 1; i <= n; i++) pw.println("line " + i);
pw.close();
}
请注意,此代码假定 Scanner 读取的行尾与 PrintWriter 产生的行尾相同(例如,在 Windows 上不仅仅是一个 LineFeed)。
请注意,上面的代码可以优化为不重写任何未更改的文件头 - 例如只需先跟踪写入偏移量,然后切换到“普通”PrintWriter。
【讨论】: