2010-07-30 2 views
27

Я пишу программу, которая следит за каталогом и когда в нем создаются файлы, она изменяет имя и перемещает их в новый каталог. В моей первой реализации я использовал Java Watch Service API, который отлично работал, когда я тестировал 1kb-файлы. Проблема, которая возникла, заключается в том, что на самом деле создаваемые файлы находятся где угодно от 50-300 мб. Когда это произошло, API-интерфейс наблюдателя сразу найдет файл, но не сможет его переместить, поскольку он все еще записывается. Я попытался поставить наблюдателя в цикл (который генерировал исключения, пока файл не мог быть перемещен), но это показалось довольно неэффективным.Java: просмотр каталога для перемещения больших файлов

Поскольку это не сработало, я попытался использовать таймер, который проверяет папку каждые 10 секунд, а затем перемещает файлы, когда это возможно. Это тот метод, в который я попал.

Вопрос: Есть ли какие-либо сигналы, когда файл записывается без проверки исключения или постоянного сравнения размера? Мне нравится идея использовать API Watcher только один раз для каждого файла, а не постоянно проверять таймер (и работать с исключениями).

Все отзывы приветствуются!

нт

+1

'Я пытался помещать наблюдателя в цикл (который генерировал исключения, пока файл не мог быть перемещен), но это казалось довольно неэффективным.« Да, это ужасное решение. Исключения не предназначены для управления потоком управления. –

+1

Печально @ntmp, из того, что я тестировал до сих пор, поиск исключений был лучшим способом сказать, что ОС все еще «писала» или «копировала» файл. Но я согласен с @Sean Patrick Floyd, что это ужасный способ заставить его работать. Лично мне хотелось бы, чтобы чек был частью API java.io.File. Не уверен, почему это не так. Осталось бы ребятам JVM реализовать и упростить для нас разработчиков .... –

+3

Подход «проверка на исключение» даже не работает в UNIX, поскольку файловые системы UNIX не блокируют файлы, которые записываются. В UNIX java с радостью переместит частично написанный файл, что приведет к повреждению данных. – Raman

ответ

11

Написать другой файл, как признак того, что исходный файл будет завершен. I.g 'fileorg.dat' растет, если сделано, создайте файл 'fileorg.done' и отметьте только для 'fileorg.done'.

С умными соглашениями об именах у вас не должно быть проблем.

9

Два решения:

Первый представляет собой небольшое изменение the answer by stacker:

Используйте уникальный префикс для неполных файлов. Что-то вроде myhugefile.zip.inc вместо myhugefile.zip. Переименуйте файлы при завершении загрузки/создания. Исключить файлы .inc из часов.

Вторая - использовать другую папку на одном диске для создания/выгрузки/записи файлов и перемещения их в просматриваемую папку, как только они будут готовы. Перемещение должно быть атомарным действием, если они находятся на одном диске (зависит от файловой системы, я думаю).

В любом случае клиентам, создающим файлы, придется выполнять дополнительную работу.

+0

Проблема в том, что у меня очень мало контроля над клиентом, создающим файлы. Я не могу добавить уникальный префикс. Я могу указать папку, в которой написаны файлы, но я не могу сказать клиенту переместить их в другую папку, когда они будут записаны. – nite

+1

@ntmp Вы получили какое-то решение по этой проблеме, пожалуйста, поделитесь со мной, так как я тоже сталкиваюсь с тем же вопросом. –

-1

Я предполагаю, что java.io.File.canWrite() сообщит вам, когда файл был записан.

+0

Я пробовал быстрый тест с одним потоком, записывающим в файл, в то время как другой поток проверяет метод canWrite() но он всегда возвращает true. – Serxipc

+0

На самом деле я считаю, что он просто проверяет ОС, чтобы узнать, есть ли у вас разрешение на запись. У вас может быть разрешение с точки зрения безопасности, но не с точки зрения ожидания его завершения записи. –

0

Это очень обсуждение, так как это, конечно же, случай использования хлеба и масла: дождитесь создания нового файла, а затем каким-то образом отреагируйте на файл. Условие гонки здесь интересно, так как, конечно, требование высокого уровня здесь состоит в том, чтобы получить событие, а затем фактически получить (по крайней мере) блокировку чтения в файле. С большими файлами или просто с большим количеством созданий файлов для этого может потребоваться целый пул рабочих потоков, которые просто периодически пытаются получить блокировки на вновь созданных файлах и, когда они будут успешными, на самом деле выполняют эту работу. Но, как я уверен, NT понимает, нужно было бы сделать это осторожно, чтобы сделать его масштабируемым, поскольку это в конечном счете подход к опросу, а масштабируемость и опрос - это не два слова, которые хорошо сочетаются.

19

Сегодня я столкнулся с той же проблемой. Я очень немного задерживаю, прежде чем файл импортируется, и это не является большой проблемой, и я все еще хотел использовать API NIO2.Решение, которое я выбрал, состояло в том, чтобы ждать, пока файл не будет изменен на 10 секунд, прежде чем выполнять какие-либо операции над ним.

Важная часть реализации заключается в следующем. Программа ждет, пока не истечет время ожидания или не произойдет новое событие. Время истечения срока действия сбрасывается при каждом изменении файла. Если файл удаляется до истечения времени ожидания, он удаляется из списка. Я использую метод опроса с таймаутом ожидаемого ExpirationTime, то есть (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); 
    } 
} 
+4

Лучший ответ в этой теме - это 2013 год, и они уже исправили это на Java, или все еще необходимо использовать такой код? – gregn3

+1

Определенно самый полезный ответ в этой теме ... –

0

мне пришлось иметь дело с подобной ситуацией, когда я реализовал систему Бодрствующего файла для передачи загруженных файлов. Решение, которое я реализовал для решения этой проблемы, состоит из следующего:

1- Прежде всего, сохраните карту необработанного файла (до тех пор, пока файл все еще копируется, файловая система создает Modify_Event, поэтому вы можете игнорировать если флаг фальшивый).

2- В вашем файлеПроцессор вы выбираете файл из списка и проверяете, заблокирован ли он файловой системой, если да, вы получите исключение, просто поймайте это исключение и поставьте свой поток в состояние ожидания (т.е. 10 секунд), а затем повторите попытку до тех пор, пока замок не будет освобожден. После обработки файла вы можете либо изменить флаг на true, либо удалить его с карты.

Это решение не будет эффективным, если многие версии одного и того же файла будут переданы во время временного интервала ожидания.

Приветствия, Рамзи

3

Хотя это не возможно быть notificated по API Watcher службы, когда SO закончить копирование, все опции, как представляется, «обойти» (в том числе и этот!).

Как отметил выше,

1) Перемещение или копирование не вариант на UNIX;

2) File.canWrite всегда возвращает true, если у вас есть разрешение на запись, даже если файл все еще копируется;

3) Ожидает, что время ожидания или возникновение нового события будет вариантом, но что, если система перегружена, но копия не была закончена? если время ожидания является большим значением, программа будет ждать так долго.

4) Запись другого файла в «флаг», который завершена копией, не является вариантом, если вы просто потребляете файл, а не создаете.

Альтернативой является использование кода ниже:

boolean locked = true; 

while (locked) { 
    RandomAccessFile raf = null; 
    try { 
      raf = new RandomAccessFile(file, "r"); // it will throw FileNotFoundException. It's not needed to use 'rw' because if the file is delete while copying, 'w' option will create an empty file. 
      raf.seek(file.length()); // just to make sure everything was copied, goes to the last byte 
      locked = false; 
     } catch (IOException e) { 
      locked = file.exists(); 
      if (locked) { 
       System.out.println("File locked: '" + file.getAbsolutePath() + "'"); 
       Thread.sleep(1000); // waits some time 
      } else { 
       System.out.println("File was deleted while copying: '" + file.getAbsolutePath() + "'"); 
      } 
    } finally { 
      if (raf!=null) { 
       raf.close();  
      } 
     } 
} 
0

В зависимости от того, насколько срочно вам нужно переместить файл, как только это будет сделано записью, вы можете также проверить наличие стабильного последнего изменения метки времени и только переместите файл, который он оставил в покое. Количество времени, в течение которого вы должны быть стабильным, может быть зависимым от реализации, но я бы предположил, что что-то с последней измененной меткой времени, которая не изменилась за 15 секунд, должна быть достаточно стабильной, чтобы ее можно было перемещать.

4

Я знаю, что это старый вопрос, но, возможно, он может помочь кому-то.

Я была такая же проблема, так что я сделал следующее:

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!"); 

     } 

Когда файл создается, он будет все больше и больше. Так что я сделал, чтобы сравнить вес, разделенный секундой. Приложение будет находиться в цикле, пока оба веса не будут одинаковыми.

+0

Это помогло мне - большое спасибо :) +1 – lulu88

+1

Не уверен, что это будет работать на Win7, потому что при копировании файла Win7 выделяет все необходимое пространство в жестком диск, а затем «заполняет» его байтами файла. –

+0

не работает над моей Win 7 – Tianhai

3

Похоже, что Apache Camel обрабатывает проблему с загрузкой файла, не загружая его, пытаясь переименовать файл (java.io.File.renameTo). Если переименование завершилось неудачей, не заблокируйте чтение, но продолжайте попытки. Когда переименование удастся, они переименуют его обратно, затем перейдут к намеченной обработке.

См. operations.renameFile ниже. Вот ссылки на источник Apache Camel: GenericFileRenameExclusiveReadLockStrategy.java и FileUtil.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; 
} 
0

Для больших файлов в Linux, файлы получают скопированный с расширением .filepart. Вам просто нужно проверить расширение с помощью commons api и зарегистрировать событие ENTRY_CREATE. Я проверил это с моим .csv файлов (1 Гб) и добавить его работал

public void run() 
{ 
    try 
    { 
     WatchKey key = myWatcher.take(); 
     while (key != null) 
     { 
      for (WatchEvent event : key.pollEvents()) 
      { 
       if (FilenameUtils.isExtension(event.context().toString(), "filepart")) 
       { 
        System.out.println("Inside the PartFile " + event.context().toString()); 
       } else 
       { 
        System.out.println("Full file Copied " + event.context().toString()); 
        //Do what ever you want to do with this files. 
       } 
      } 
      key.reset(); 
      key = myWatcher.take(); 
     } 
    } catch (InterruptedException e) 
    { 
     e.printStackTrace(); 
    } 
} 
0

Если вы не имеете контроля над процессом записи, регистрировать все ENTRY_CREATED события и наблюдать, если есть модели.

В моем случае, файлы создаются с помощью WebDAV (Apache) и много временных файлов создаются, но и дваENTRY_CREATED события инициируются для того же файла. Второе событие ENTRY_CREATED указывает, что процесс копирования завершен.

Это мой пример ENTRY_CREATED событий. Абсолютный путь к файлу печатается (ваш журнал может отличаться, в зависимости от приложения, которое записывает файл):

[info] application - /var/www/webdav/.davfs.tmp39dee1 was created 
[info] application - /var/www/webdav/document.docx was created 
[info] application - /var/www/webdav/.davfs.tmp054fe9 was created 
[info] application - /var/www/webdav/document.docx was created 
[info] application - /var/www/webdav/.DAV/__db.document.docx was created 

Как вы видите, я получаю два ENTRY_CREATED события для document.docx. После второго события я знаю, что файл завершен. Временные файлы, очевидно, игнорируются в моем случае.

Смежные вопросы