Java:查看目录以移动大文件

nit*_*ite 28 java directory watch

我一直在编写一个监视目录的程序,当在其中创建文件时,它会更改名称并将它们移动到新目录.在我的第一个实现中,我使用了Java的Watch Service API,当我测试1kb文件时,它运行良好.出现的问题是,实际上创建的文件是50-300mb.当发生这种情况时,观察者API会立即找到该文件,但由于它仍在被写入,因此无法移动它.我尝试将观察者放在循环中(生成异常,直到文件可以移动),但这看起来效率很低.

由于这不起作用,我尝试使用定时器,每隔10秒检查一次文件夹,然后尽可能移动文件.这是我最终选择的方法.

问题:无论如何在没有进行异常检查或不断比较大小的情况下发出文件写入信号?我喜欢为每个文件使用Watcher API一次,而不是使用计时器不断检查(并运行异常).

所有回复都非常感谢!

NT

Jas*_*man 20

我今天遇到了同样的问题.我在实际导入文件之前使用了一小段延迟并不是一个大问题,我仍然想使用NIO2 API.我选择的解决方案是等待文件在10秒内没有被修改,然后对其执行任何操作.

实施的重要部分如下.程序等待,直到等待时间到期或发生新事件.每次修改文件时都会重置到期时间.如果在等待时间到期之前删除文件,则会从列表中删除该文件.我使用poll方法的预期到期时间超时,即(lastmodified + waitTime)-currentTime

private final Map<Path, Long> expirationTimes = newHashMap();
private Long newFileWait = 10000L;

public void run() {
    for(;;) {
        //Retrieves and removes next watch key, waiting if none are present.
        WatchKey k = watchService.take();

        for(;;) {
            long currentTime = new DateTime().getMillis();

            if(k!=null)
                handleWatchEvents(k);

            handleExpiredWaitTimes(currentTime);

            // If there are no files left stop polling and block on .take()
            if(expirationTimes.isEmpty())
                break;

            long minExpiration = min(expirationTimes.values());
            long timeout = minExpiration-currentTime;
            logger.debug("timeout: "+timeout);
            k = watchService.poll(timeout, TimeUnit.MILLISECONDS);
        }
    }
}

private void handleExpiredWaitTimes(Long currentTime) {
    // Start import for files for which the expirationtime has passed
    for(Entry<Path, Long> entry : expirationTimes.entrySet()) {
        if(entry.getValue()<=currentTime) {
            logger.debug("expired "+entry);
            // do something with the file
            expirationTimes.remove(entry.getKey());
        }
    }
}

private void handleWatchEvents(WatchKey k) {
    List<WatchEvent<?>> events = k.pollEvents();
    for (WatchEvent<?> event : events) {
        handleWatchEvent(event, keys.get(k));
    }
    // reset watch key to allow the key to be reported again by the watch service
    k.reset();
}

private void handleWatchEvent(WatchEvent<?> event, Path dir) throws IOException {
    Kind<?> kind = event.kind();

    WatchEvent<Path> ev = cast(event);
        Path name = ev.context();
        Path child = dir.resolve(name);

    if (kind == ENTRY_MODIFY || kind == ENTRY_CREATE) {
        // Update modified time
        FileTime lastModified = Attributes.readBasicFileAttributes(child, NOFOLLOW_LINKS).lastModifiedTime();
        expirationTimes.put(name, lastModified.toMillis()+newFileWait);
    }

    if (kind == ENTRY_DELETE) {
        expirationTimes.remove(child);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这个主题的最佳答案 - 它是2013年,他们已经用Java修复了这个问题,还是仍然需要使用这样的代码? (5认同)
  • 方法 handleExpiredWaitTimes 正在迭代时删除条目,因此应使用迭代器。 (2认同)

sta*_*ker 11

写另一个文件作为原始文件完成的指示.如果完成创建文件'fileorg.done'并仅检查'fileorg.done',则Ig'fileorg.dat'正在增长.

通过巧妙的命名约定,您不应该遇到问题.


Sea*_*oyd 9

两种解决方案

第一个是堆叠器的答案略有不同:

对不完整的文件使用唯一的前缀.有点像myhugefile.zip.inc而不是myhugefile.zip.上传/创建完成后重命名文件.从手表中排除.inc文件.

第二种是在同一驱动器上使用不同的文件夹来创建/上传/写入文件,并在准备好后将它们移动到监视文件夹.如果它们位于同一个驱动器上,那么移动应该是一个原子操作(我想是依赖于文件系统).

无论哪种方式,创建文件的客户端都必须做一些额外的工作.


小智 5

我知道这是一个老问题,但也许它可以帮助别人。

我遇到了同样的问题,所以我做了以下事情:

if (kind == ENTRY_CREATE) {
            System.out.println("Creating file: " + child);

            boolean isGrowing = false;
            Long initialWeight = new Long(0);
            Long finalWeight = new Long(0);

            do {
                initialWeight = child.toFile().length();
                Thread.sleep(1000);
                finalWeight = child.toFile().length();
                isGrowing = initialWeight < finalWeight;

            } while(isGrowing);

            System.out.println("Finished creating file!");

        }
Run Code Online (Sandbox Code Playgroud)

当文件被创建时,它会变得越来越大。所以我所做的就是比较一秒之间的重量。应用程序将一直循环,直到两个权重相同。


小智 5

看起来 Apache Camel 通过尝试重命名文件 (java.io.File.renameTo) 来处理文件未完成上传问题。如果重命名失败,则没有读锁,但继续尝试。当重命名成功时,他们将其重命名,然后继续进行预期的处理。

请参阅下面的operations.renameFile。以下是 Apache Camel 源的链接: GenericFileRenameExclusiveReadLockStrategy.javaFileUtil.java

public boolean acquireExclusiveReadLock( ... ) throws Exception {
   LOG.trace("Waiting for exclusive read lock to file: {}", file);

   // the trick is to try to rename the file, if we can rename then we have exclusive read
   // since its a Generic file we cannot use java.nio to get a RW lock
   String newName = file.getFileName() + ".camelExclusiveReadLock";

   // make a copy as result and change its file name
   GenericFile<T> newFile = file.copyFrom(file);
   newFile.changeFileName(newName);
   StopWatch watch = new StopWatch();

   boolean exclusive = false;
   while (!exclusive) {
        // timeout check
        if (timeout > 0) {
            long delta = watch.taken();
            if (delta > timeout) {
                CamelLogger.log(LOG, readLockLoggingLevel,
                        "Cannot acquire read lock within " + timeout + " millis. Will skip the file: " + file);
                // we could not get the lock within the timeout period, so return false
                return false;
            }
        }

        exclusive = operations.renameFile(file.getAbsoluteFilePath(), newFile.getAbsoluteFilePath());
        if (exclusive) {
            LOG.trace("Acquired exclusive read lock to file: {}", file);
            // rename it back so we can read it
            operations.renameFile(newFile.getAbsoluteFilePath(), file.getAbsoluteFilePath());
        } else {
            boolean interrupted = sleep();
            if (interrupted) {
                // we were interrupted while sleeping, we are likely being shutdown so return false
                return false;
            }
        }
   }

   return true;
}
Run Code Online (Sandbox Code Playgroud)