假设您有一个外部进程将文件写入某个目录,并且您有一个单独的进程定期尝试从该目录读取文件。要避免的问题是读取另一个进程当前正在写出的文件,因此它是不完整的。目前,读取的进程使用最小文件年龄计时器检查,因此它会忽略所有文件,除非它们的最后修改日期超过 XX 秒。
我想知道是否有更清洁的方法来解决这个问题。如果文件类型未知(可能是多种不同的格式),是否有一些可靠的方法来检查文件头中应该在文件中的字节数,与文件中当前的字节数以确认它们匹配?
感谢您的任何想法或想法!
假设您有一个外部进程将文件写入某个目录,并且您有一个单独的进程定期尝试从该目录读取文件。要避免的问题是读取另一个进程当前正在写出的文件,因此它是不完整的。目前,读取的进程使用最小文件年龄计时器检查,因此它会忽略所有文件,除非它们的最后修改日期超过 XX 秒。
我想知道是否有更清洁的方法来解决这个问题。如果文件类型未知(可能是多种不同的格式),是否有一些可靠的方法来检查文件头中应该在文件中的字节数,与文件中当前的字节数以确认它们匹配?
感谢您的任何想法或想法!
我过去这样做的方式是写入文件的过程写入“临时”文件,然后在完成文件写入后将文件移动到读取位置。
所以写入过程将写入info.txt.tmp。完成后,它将文件重命名为info.txt。然后读取过程只需要检查info.txt的存在- 它知道如果存在,它已经被完整地写入。
或者,您可以让写入过程将info.txt写入不同的目录,然后如果您不喜欢使用奇怪的文件扩展名,则将其移动到读取目录。
您可以使用外部标记文件。写入过程可以在开始创建文件 XYZ 之前创建一个文件 XYZ.lock,并在 XYZ 完成后删除 XYZ.lock。然后,读者很容易知道,只有当相应的 .lock 文件不存在时,它才能认为文件是完整的。
我无法选择使用临时标记等,因为客户端通过密钥对 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 环境中使用的。
我过去在这种情况下使用 Windows 的一个简单解决方案是使用boolean File.renameTo(File)
并尝试将原始文件移动到单独的暂存文件夹:
boolean success = potentiallyIncompleteFile.renameTo(stagingAreaFile);
如果success
is false
,则potentiallyIncompleteFile
仍在写入。
这可以通过使用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);
即使字节数相等,文件的内容也可能不同。
所以我认为,您必须逐字节匹配旧文件和新文件。
似乎可以解决此问题的 2 个选项:
首先,当复制到 Samba 共享时,为什么 OS X 不像 Windows 那样锁定文件?但这是你已经在做的事情的变化。
就读取任意文件和查找大小而言,有些文件具有该信息,有些则没有,但即使是那些没有任何通用表示方式的文件。您将需要每种格式的特定信息,并独立管理它们。
如果您绝对必须在文件完成后“立即”采取行动,那么您的写作过程将需要发送某种通知。否则,您几乎无法轮询文件,并且与从随机文件中读取随机块相比,读取目录在 I/O 方面非常便宜。
测试文件是否已完全写入的另一种方法:
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);
}
}
}
如果您使用 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);
}