33

假设您有一个外部进程将文件写入某个目录,并且您有一个单独的进程定期尝试从该目录读取文件。要避免的问题是读取另一个进程当前正在写出的文件,因此它是不完整的。目前,读取的进程使用最小文件年龄计时器检查,因此它会忽略所有文件,除非它们的最后修改日期超过 XX 秒。

我想知道是否有更清洁的方法来解决这个问题。如果文件类型未知(可能是多种不同的格式),是否有一些可靠的方法来检查文件头中应该在文件中的字节数,与文件中当前的字节数以确认它们匹配?

感谢您的任何想法或想法!

4

10 回答 10

15

我过去这样做的方式是写入文件的过程写入“临时”文件,然后在完成文件写入后将文件移动到读取位置。

所以写入过程将写入info.txt.tmp。完成后,它将文件重命名为info.txt。然后读取过程只需要检查info.txt的存在- 它知道如果存在,它已经被完整地写入。

或者,您可以让写入过程将info.txt写入不同的目录,然后如果您不喜欢使用奇怪的文件扩展名,则将其移动到读取目录。

于 2012-04-05T13:37:07.820 回答
14

您可以使用外部标记文件。写入过程可以在开始创建文件 XYZ 之前创建一个文件 XYZ.lock,并在 XYZ 完成后删除 XYZ.lock。然后,读者很容易知道,只有当相应的 .lock 文件不存在时,它才能认为文件是完整的。

于 2012-04-05T13:33:02.430 回答
8

我无法选择使用临时标记等,因为客户端通过密钥对 SFTP 上传文件。它们的尺寸可能非常大。

它非常hacky,但我比较了睡几秒钟之前和之后的文件大小。

锁定线程显然不理想,但在我们的例子中,它只是作为后台系统进程运行,所以似乎工作正常

private boolean isCompletelyWritten(File file) throws InterruptedException{
    Long fileSizeBefore = file.length();
    Thread.sleep(3000);
    Long fileSizeAfter = file.length();

    System.out.println("comparing file size " + fileSizeBefore + " with " + fileSizeAfter);

    if (fileSizeBefore.equals(fileSizeAfter)) {
        return true;
    }
    return false;
}

注意:如下所述,这可能不适用于 Windows。这是在 Linux 环境中使用的。

于 2016-07-11T04:20:29.587 回答
6

我过去在这种情况下使用 Windows 的一个简单解决方案是使用boolean File.renameTo(File) 并尝试将原始文件移动到单独的暂存文件夹:

boolean success = potentiallyIncompleteFile.renameTo(stagingAreaFile);

如果successis false,则potentiallyIncompleteFile仍在写入。

于 2014-02-26T21:37:22.937 回答
3

这可以通过使用Apache Commons IO maven 库 FileUtils.copyFile() 方法来实现。如果您尝试复制文件并获得 IOException,则意味着该文件未完全保存。

例子:

public static void copyAndDeleteFile(File file, String destinationFile) {

    try {
        FileUtils.copyFile(file, new File(fileDirectory));
    } catch (IOException e) {
        e.printStackTrace();
        copyAndDeleteFile(file, fileDirectory, delayThreadPeriod);
    }

或定期检查包含此文件的文件夹的一些延迟大小:

FileUtils.sizeOfDirectory(folder);
于 2017-10-09T10:48:13.687 回答
2

即使字节数相等,文件的内容也可能不同。

所以我认为,您必须逐字节匹配旧文件和新文件。

于 2012-04-05T13:25:30.770 回答
2

似乎可以解决此问题的 2 个选项:

  1. 最好的选择——写进程以某种方式通知读进程写已经完成。
  2. 将文件写入 {id}.tmp,而不是在完成时将其重命名为 {id}.java,并且读取过程仅在 *.java 文件上运行。重命名花费的时间要少得多,这两个过程一起工作的机会也会减少。
于 2012-04-05T13:33:43.807 回答
2

首先,当复制到 Samba 共享时,为什么 OS X 不像 Windows 那样锁定文件?但这是你已经在做的事情的变化。

就读取任意文件和查找大小而言,有些文件具有该信息,有些则没有,但即使是那些没有任何通用表示方式的文件。您将需要每种格式的特定信息,并独立管理它们。

如果您绝对必须在文件完成后“立即”采取行动,那么您的写作过程将需要发送某种通知。否则,您几乎无法轮询文件,并且与从随机文件中读取随机块相比,读取目录在 I/O 方面非常便宜。

于 2012-04-05T13:35:27.297 回答
1

测试文件是否已完全写入的另一种方法:

private void waitUntilIsReadable(File file) throws InterruptedException {
    boolean isReadable = false;
    int loopsNumber = 1;
    while (!isReadable && loopsNumber <= MAX_NUM_OF_WAITING_60) {
        try (InputStream in = new BufferedInputStream(new FileInputStream(file))) {
            log.trace("InputStream readable. Available: {}. File: '{}'",
                    in.available(), file.getAbsolutePath());
            isReadable = true;
        } catch (Exception e) {
            log.trace("InputStream is not readable yet. File: '{}'", file.getAbsolutePath());
            loopsNumber++;
            TimeUnit.MILLISECONDS.sleep(1000);
        }
    }
}
于 2020-09-26T10:14:40.393 回答
0

如果您使用 FTP 或 Winscp 传输文件,请将此用于 Unix:

public static void isFileReady(File entry) throws Exception {
        long realFileSize = entry.length();
        long currentFileSize = 0;
        do {
            try (FileInputStream fis = new FileInputStream(entry);) {
                currentFileSize = 0;
                while (fis.available() > 0) {
                    byte[] b = new byte[1024];
                    int nResult = fis.read(b);
                    currentFileSize += nResult;
                    if (nResult == -1)
                        break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("currentFileSize=" + currentFileSize + ", realFileSize=" + realFileSize);
        } while (currentFileSize != realFileSize);
    }
于 2020-08-12T15:45:14.410 回答