2010-07-30 14 views
27

He estado escribiendo un programa que mira un directorio y cuando se crean archivos en él, cambia el nombre y los mueve a un nuevo directorio. En mi primera implementación utilicé la API de Watch Service de Java, que funcionaba bien cuando estaba probando archivos de 1kb. El problema que surgió es que, en realidad, los archivos que se crean están en cualquier parte de 50-300mb. Cuando esto sucedió, la API de Watcher encontraría el archivo de inmediato, pero no pudo moverlo porque aún se estaba escribiendo. Traté de poner al observador en un bucle (lo que generó excepciones hasta que el archivo se pudiera mover) pero parecía bastante ineficiente.Java: viendo un directorio para mover archivos grandes

Como eso no funcionó, intenté usar un temporizador que verifica la carpeta cada 10 segundos y luego mueve los archivos cuando puede. Este es el método por el que terminé yendo.

Pregunta: ¿Hay alguna manera de indicar cuándo se está escribiendo un archivo sin hacer una comprobación de excepción o comparando continuamente el tamaño? Me gusta la idea de usar Watcher API solo una vez para cada archivo en lugar de verificar continuamente con un temporizador (y encontrar excepciones).

¡Todas las respuestas son muy apreciadas!

nt

+1

'Intenté poner al observador en un bucle (lo que generó excepciones hasta que el archivo se pudiera mover) pero esto parecía bastante ineficiente. Sí, esta es una solución horrible. Las excepciones no están hechas para administrar el flujo de control. –

+1

Tristemente @ntmp, por lo que he probado hasta ahora, buscar excepciones fue la mejor manera de decir que el sistema operativo aún estaba "escribiendo" o "copiando" el archivo. Pero estoy de acuerdo con @Sean Patrick Floyd en que es una manera terrible de hacerlo funcionar. Personalmente, me gustaría que el cheque formara parte de la API java.io.File. No estoy seguro de por qué no fue así. Quedaría en manos de los chicos de JVM implementarlo y hacerlo más fácil para nosotros los desarrolladores ... –

+3

El enfoque de "verificar excepciones" ni siquiera funcionará en UNIX, ya que los sistemas de archivos UNIX no bloquean los archivos que se están escribiendo. En UNIX, java moverá felizmente el archivo parcialmente escrito, lo que dará como resultado datos dañados. – Raman

Respuesta

11

escribir otro archivo como una indicación de que el archivo original se ha completado. I.g 'fileorg.dat' está creciendo si está hecho, cree un archivo 'fileorg.done' y marque solo para 'fileorg.done'.

Con convenciones de nomenclatura inteligentes no debería tener problemas.

9

dos soluciones:

La primera es una ligera variación del the answer by stacker:

utilizar un prefijo único para los archivos incompletos. Algo así como myhugefile.zip.inc en lugar de myhugefile.zip. Cambie el nombre de los archivos cuando termine la carga/creación. Excluya archivos .inc del reloj.

La segunda es utilizar una carpeta diferente en la misma unidad para crear/cargar/escribir los archivos y moverlos a la carpeta observada una vez que estén listos. Moverse debería ser una acción atómica si están en la misma unidad (depende del sistema de archivos, supongo).

De cualquier forma, los clientes que crean los archivos tendrán que hacer un trabajo extra.

+0

El problema es que tengo muy poco control sobre el cliente que crea los archivos. No puedo agregar un prefijo único. Puedo especificar la carpeta en la que están escritos los archivos, pero no puedo decirle al cliente que los mueva a otra carpeta cuando terminen de escribir. – nite

+1

@ntmp Obtuviste alguna solución con respecto a este problema, por favor comparte conmigo ya que también estoy enfrentando el mismo tipo de problema –

-1

Supongo que java.io.File.canWrite() le dirá cuándo se ha terminado de escribir un archivo.

+0

He intentado una prueba rápida con un hilo escribiendo en el archivo mientras que otro hilo comprueba el método canWrite() pero siempre vuelve verdadero. – Serxipc

+0

realmente creo que solo verifica el sistema operativo para ver si tiene permiso para escribir. Puede tener permiso desde el punto de vista de la seguridad, pero no desde el punto de vista de esperar a que se termine de escribir. –

0

Esta es una discusión muy interesante de , como ciertamente este es un caso de uso de pan y mantequilla: espere a que se cree un nuevo archivo y luego reaccione al archivo de alguna manera. La condición de carrera aquí es interesante, ya que ciertamente el requisito de alto nivel aquí es obtener un evento y luego obtener (al menos) un bloqueo de lectura en el archivo. Con archivos grandes o simplemente muchas creaciones de archivos, esto podría requerir un conjunto completo de subprocesos de trabajo que solo periódicamente intentan obtener bloqueos en archivos recién creados y, cuando tienen éxito, realmente hacen el trabajo. Pero como estoy seguro de que NT se da cuenta, uno tendría que hacer esto cuidadosamente para hacerlo escala, ya que es, en última instancia, un método de votación, y la escalabilidad y la votación no son dos palabras que van bien juntas.

19

Me encontré con el mismo problema hoy. Yo uso un pequeño retraso antes de que el archivo sea realmente importado no fue un gran problema y aún quería usar la API de NIO2.La solución que elegí fue esperar hasta que un archivo no se haya modificado durante 10 segundos antes de realizar cualquier operación en él.

La parte importante de la implementación es la siguiente. El programa espera hasta que el tiempo de espera expire o ocurra un nuevo evento. El tiempo de caducidad se restablece cada vez que se modifica un archivo. Si se elimina un archivo antes de que expire el tiempo de espera, se elimina de la lista. Yo uso el método de sondeo con un tiempo de espera de la ExpirationTime esperado, es decir (+ 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

La mejor respuesta en este hilo: es 2013 y ¿ya arreglaron esto en Java, o aún es necesario usar un código como este? – gregn3

+1

Definitivamente la respuesta más útil en este hilo ... –

0

que tenía que hacer frente a una situación similar cuando he implementado un vigilante de sistema de archivos para transferir archivos cargados. La solución que implementé para resolver este problema consiste en lo siguiente:

1- Primero, mantenga un Mapa del archivo sin procesar (Mientras el archivo se esté copiando, el sistema de archivos genera Modify_Event, por lo que puede ignorar ellos si la bandera es falsa).

2- En su fileProcessor, recoge un archivo de la lista y comprueba si está bloqueado por el sistema de archivos; si es así, obtendrá una excepción, simplemente tome esta excepción y ponga su hilo en estado de espera (es decir, 10 segundos) y luego vuelva a intentarlo hasta que se suelte la cerradura. Después de procesar el archivo, puede cambiar el indicador a verdadero o eliminarlo del mapa.

Esta solución no será eficiente si las muchas versiones del mismo archivo se transfieren durante el intervalo de espera.

Saludos, Ramzi

3

Si bien no es posible ser notificated por la API del servicio de vigilante cuando el SO termine de copiar, todas las opciones parece ser 'evitar' (incluida ésta!).

Como se ha comentado anteriormente,

1) mover o copiar no es una opción en UNIX;

2) File.canWrite siempre devuelve verdadero si tiene permiso para escribir, incluso si el archivo todavía se está copiando;

3) Espera hasta que ocurra el tiempo de espera o un nuevo evento sería una opción, pero ¿qué pasa si el sistema está sobrecargado pero la copia no se terminó? si el tiempo fuera es un gran valor, el programa esperaría tanto tiempo.

4) Escribir otro archivo para 'marcar' que la copia finalizada no es una opción si solo está consumiendo el archivo, no creando.

Una alternativa es usar el código de abajo:

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

En función de la urgencia con que necesita para mover el archivo una vez que se hace siendo escrita, también se puede comprobar si hay un establo de última modificación de marca de tiempo y sólo mueve el archivo uno está inmovilizado. La cantidad de tiempo que necesita para que sea estable puede depender de la implementación, pero supongo que algo con una marca de tiempo de última modificación que no ha cambiado durante 15 segundos debe ser lo suficientemente estable como para moverse.

4

Sé que es una vieja pregunta, pero tal vez pueda ayudar a alguien.

que tenían el mismo problema, así que lo que hice fue lo siguiente:

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

     } 

Cuando se crea el archivo, será cada vez más grande y más grande. Entonces, lo que hice fue comparar el peso separado por un segundo. La aplicación estará en el ciclo hasta que ambos pesos sean iguales.

+0

Esto me ayudó, muchas gracias :) +1 – lulu88

+1

No estoy seguro si esto funcionará en Win7 porque, al copiar un archivo, Win7 asigna todo el espacio necesario en el disco duro disco y luego "lo llena" con los bytes del archivo. –

+0

no funciona en mi Win 7 – Tianhai

3

Parece que Apache Camel maneja el problema de archivo-no-hecho-cargando tratando de cambiar el nombre del archivo (java.io.File.renameTo). Si el cambio de nombre falla, no hay bloqueo de lectura, pero sigue intentándolo. Cuando el cambio de nombre tiene éxito, cambian el nombre y luego continúan con el procesamiento previsto.

Ver operations.renameFile a continuación. Aquí están los enlaces a la fuente de Apache Camel: GenericFileRenameExclusiveReadLockStrategy.java y 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

Para archivo de gran tamaño en Linux, los archivos se copia con una extensión de .filepart. Solo necesita verificar la extensión usando la API común y registrar el evento ENTRY_CREATE. He probado esto con mis archivos .csv (1 GB) y añadirlo trabajado

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

Si usted no tiene control sobre el proceso de escritura, registrar todos los eventos ENTRY_CREATED y observar si hay patrones .

En mi caso, los archivos se crean a través de WebDAV (Apache) y una gran cantidad de archivos temporales se crean sino también dosENTRY_CREATED eventos son provocados para el mismo archivo. El segundo evento ENTRY_CREATED indica que el proceso de copia está completo.

Estos son mis eventos de ejemplo ENTRY_CREATED. Se imprime la ruta de archivo absoluta (su registro puede ser diferente, dependiendo de la aplicación que escribe el archivo):

[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 

Como se puede ver, me sale dos ENTRY_CREATED eventos para document.docx. Después del segundo evento, sé que el archivo está completo. Los archivos temporales obviamente son ignorados en mi caso.

Cuestiones relacionadas