通过Java向现有文件中插入文本

26

我想创建一个简单的程序(使用Java语言),它可以编辑文本文件,特别是能够在文本文件的随机位置插入任意文本。该功能是我目前正在编写的一个更大程序的一部分。

阅读有关java.util.RandomAccessFile的描述,似乎在文件中间进行任何写操作都会覆盖现有内容。这是一个副作用,我希望避免(如果可能的话)。

有没有简单的方法来实现这个功能呢?

提前感谢您的帮助。


4
请了解,这不仅仅是Java的限制,这几乎是所有输入输出操作的工作方式。这是底层操作系统提供的功能。 - Lou Franco
3
也许有一种文件系统实现支持诸如链表数据块之类的东西,这可能允许在文件中间快速插入数据。但是要实现这个功能,需要使用相当低级别的系统调用。 - Benedikt Waldvogel
1
我找不到确切的文章,但我读过一篇解释如何使用绳索作为驱动简单文本编辑器的数据结构的文章。http://en.wikipedia.org/wiki/Rope_%28computer_science%29 - Albert
8个回答

20

好的,这个问题很旧了,但FileChannels自Java 1.4以来就存在了,我不知道为什么在处理替换或插入文件内容的问题时没有人提到它们。 FileChannels速度快,请使用它们。

这里是一个示例(忽略异常和其他一些东西):

public void insert(String filename, long offset, byte[] content) {
  RandomAccessFile r = new RandomAccessFile(new File(filename), "rw");
  RandomAccessFile rtemp = new RandomAccessFile(new File(filename + "~"), "rw");
  long fileSize = r.length();
  FileChannel sourceChannel = r.getChannel();
  FileChannel targetChannel = rtemp.getChannel();
  sourceChannel.transferTo(offset, (fileSize - offset), targetChannel);
  sourceChannel.truncate(offset);
  r.seek(offset);
  r.write(content);
  long newOffset = r.getFilePointer();
  targetChannel.position(0L);
  sourceChannel.transferFrom(targetChannel, newOffset, (fileSize - offset));
  sourceChannel.close();
  targetChannel.close();
}

我可以使用FileChannel在图像上叠加文本吗? - Yohanim
一般来说,您可以使用FileChannels以任何您喜欢的方式修改任何类型的文件。但是问题在于:对于我能想象到的所有用例,您需要知道要写入文件的内容。 如果您的图像是.SVG,则当然可以使用FileChannel将必要的xml元素写入文件中。 但由于您的问题不是那么具体,所以答案是:“不行,因为您不知道自己在做什么。” 您可以查看java.awt.Image类,特别是BufferedImage类,并可能阅读有关它的教程并从那里开始。 - xor_eq
是的,在做了一些研究之后,我使用Graphic对象而不是FileChannel - Yohanim

11

嗯,我不认为有一种方式可以通过单个标准的Java IO API调用避免覆盖现有内容。

如果文件不太大,只需将整个文件读入ArrayList(每行一个条目),然后重写条目或插入新条目以获得新行。

然后使用新内容覆盖现有文件,或将现有文件移动到备份并编写新文件。

根据所需编辑的复杂程度,您的数据结构可能需要更改。

另一种方法是从现有文件中读取字符,同时向已编辑的文件中写入并在读取时编辑流。


4
你可以使用以下代码:
BufferedReader reader = null;
BufferedWriter writer = null;
ArrayList list = new ArrayList();

try {
    reader = new BufferedReader(new FileReader(fileName));
    String tmp;
    while ((tmp = reader.readLine()) != null)
        list.add(tmp);
    OUtil.closeReader(reader);

    list.add(0, "Start Text");
    list.add("End Text");

    writer = new BufferedWriter(new FileWriter(fileName));
    for (int i = 0; i < list.size(); i++)
        writer.write(list.get(i) + "\r\n");
} catch (Exception e) {
    e.printStackTrace();
} finally {
    OUtil.closeReader(reader);
    OUtil.closeWriter(writer);
}

4
如果Java有一种内存映射文件的方式,那么你可以扩展文件到其新长度,映射该文件,将所有字节移到末尾以创建空洞,并将新数据写入该空洞。在C中可以这样做,但我没有在Java中尝试过。
另一种通过随机文件访问实现相同功能的方法是:
- 定位到结尾处减去1 MB的位置 - 读取1 MB - 将其写入原始位置+间隙大小 - 对每个之前的1 MB重复上述步骤,直到达到所需的间隙位置 - 使用更大的缓冲区大小以提高性能。

是的,Java 中有一个用于此目的的工具:https://docs.oracle.com/javase/6/docs/api/java/nio/channels/FileChannel.html#map(java.nio.channels.FileChannel.MapMode,%20long,%20long) - Jerry Tian

2
我认为将文本插入现有文本文件的唯一方法是读取原始文件,并将内容与新文本插入临时文件中。然后擦除原始文件并将临时文件重命名为原始名称。
这个示例专注于向现有文件中插入单行,但仍可能对您有用。

2

我不确定除了以下方法外是否有更方便的方式:

  • 读取文件开头并将其写入目标文件
  • 将新文本写入目标文件
  • 读取文件剩余部分并将其写入目标文件。

关于目标文件:如果处理的文件不是太大,您可以在内存中构建新内容,然后覆盖旧文件的内容。或者,您可以将结果写入临时文件。

使用流可能是最容易的方法,RandomAccessFile 似乎不适用于在中间插入(据我所知)。如果需要,请查看 教程


1
如果它是一个文本文件,那么可以在StringBuffer中读取现有文件并将新内容附加到同一StringBuffer中,然后可以将StringBuffer写入文件。因此,现在该文件包含现有文本和新文本。

0

由于@xor_eq的编辑队列已满,在这里我提供一个新答案,它更详细并且稍微改进了他的版本:

public static void insert(String filename, long offset, byte[] content) throws IOException {
    File temp = Files.createTempFile("insertTempFile", ".temp").toFile(); // Create a temporary file to save content to
    try (RandomAccessFile r = new RandomAccessFile(new File(filename), "rw"); // Open file for read & write
            RandomAccessFile rtemp = new RandomAccessFile(temp, "rw"); // Open temporary file for read & write
            FileChannel sourceChannel = r.getChannel(); // Channel of file
            FileChannel targetChannel = rtemp.getChannel()) { // Channel of temporary file
        long fileSize = r.length();
        sourceChannel.transferTo(offset, (fileSize - offset), targetChannel); // Copy content after insert index to
                                                                                // temporary file
        sourceChannel.truncate(offset); // Remove content past insert index from file
        r.seek(offset); // Goto back of file (now insert index)
        r.write(content); // Write new content
        long newOffset = r.getFilePointer(); // The current offset
        targetChannel.position(0L); // Goto start of temporary file
        sourceChannel.transferFrom(targetChannel, newOffset, (fileSize - offset)); // Copy all content of temporary
                                                                                    // to end of file
    }
    Files.delete(temp.toPath()); // Delete the temporary file as not needed anymore
} 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接