2009-12-18 34 views
8

Mi aplicación escanea parte de un sistema de archivos, y mis usuarios informaron que era muy lento cuando estaban escaneando una unidad de red. Al probar mi código, identifiqué el cuello de botella: los métodos File.isFile(), File.isDirectory() y File.isHidden(), que están llamando al fs.getBooleanAttributes(File f). Este método parece ser muy lento en las unidades de red de Windows. ¿Cómo puedo mejorar el rendimiento? ¿Puedo evitar llamar a este método de alguna manera?¿Acelerar el acceso al sistema de archivos?

Respuesta

6

¿Cómo construye esta lista de archivos? A menos que visualice cada archivo en el sistema al mismo tiempo, debe tener algunas opciones ...

  1. Solo procese esta información cuando el usuario lo solicite. p.ej. Hacen clic en la carpeta "Windows", en ese momento puede procesar los archivos dentro de Windows.
  2. Procese esta información en un hilo de fondo, dando la ilusión de un mejor tiempo de respuesta.

Quizás si muestra el código que está utilizando para compilar la lista, podríamos encontrar otras áreas de mejora. (¿Por qué no puede inferir el tipo basándose en el método utilizado para recopilar la información? Si llama a un método como GetFiles() ¿no sabe que todo lo que devuelve es un archivo?)

+0

+1 para una solución simple. – wheaties

+0

+2 :) .......... – OscarRyz

+0

Tienes razón en que podría ser flojo y solo cargar carpetas cuando el usuario las solicite, pero luego habría una pausa cada vez que el usuario intentara abrirlas una nueva carpeta, que puede hacer que todo el árbol se sienta lento. Es una compensación entre tomar un largo tiempo al principio y ser lento todo el tiempo ... pero tal vez no sería tan malo. Tendré que probarlo. Gracias por las ideas! –

10

El código defensivo a menudo llama a esos isXYZ() métodos, y en general es una buena práctica. Sin embargo, a veces el rendimiento es pobre, como has descubierto.

Un enfoque alternativo es suponer que el archivo es un archivo, existe, es visible, legible, etc., y simplemente intente y léalo. Si no son esas cosas, obtendrá una excepción, que puede detectar, y luego realizará los controles para descubrir exactamente qué salió mal. De esta forma, está optimizando para el caso común (es decir, todo está bien) y solo realiza las operaciones lentas cuando las cosas van mal.

+0

Huh. Esa es una forma muy interesante de ver el problema, pero desafortunadamente no es muy aplicable en mi caso. Lo que estoy haciendo es construir un árbol que refleje la estructura del archivo del usuario, así que tengo que averiguar si un archivo es un archivo o un directorio. Aunque, supongo que podría usar listFiles() para hacer esa distinción ... ¡Gracias, me has dado algo en qué pensar! –

+0

+1 Me gusta el objetivo de optimizar el 'caso común'. Todavía puede mantener las comprobaciones defensivas en su lugar para manejar cuando algo no es un archivo. – mpeterson

+0

+1 Acabo de modificar un código de procesamiento de árbol de directorios recursivo para usar listFiles para determinar si el "archivo" es un directorio; es una aceleración menor para las unidades locales y LAN conectadas. No puedo probar las unidades remotas VPN hasta que llegue a casa. También es un código mucho más compacto. –

3

I enfrentó exactamente el mismo problema

La solución para nuestro caso fue bastante simple: dado que nuestra estructura de directorios seguía un estándar (no había ningún directorio que tuviera el carácter '.' en su nombre), seguí el estándar y aplicó una heurística muy simple: "en nuestro caso, directorios no tiene el '.' personaje en su nombre ". Esta heurística simple redujo drásticamente el número de veces que nuestra aplicación tuvo que llamar a la función isDirectory() de la clase java.io.File.

Quizás este es tu caso. Tal vez en su estructura de directorio pueda saber si un archivo es un directorio solo por sus convenciones de nombres.

+2

Personalmente, soy muy reacio a escribir código que se base en "cómo deberían ser las cosas". No decir que esto nunca es una buena solución, pero es peligroso. Si alguien no conoce el estándar o no lo sigue por el motivo que sea, su código arroja resultados incorrectos. Una cosa es dar un mensaje de error sobre datos incorrectos, y otra simplemente fallar. – Jay

+0

Sería bueno si tuviera ese tipo de estándar en el que confiar, pero desafortunadamente no lo tengo. : o) –

2

Aquí hay un ejemplo de código antes y después para usar listFiles y usar isDirectory para recorrer un árbol de directorios (mi código usa una devolución de llamada genérica para hacer algo con cada directorio y archivo; si codificaba C# sería un delegado) .

Como puede ver, el enfoque de ListFiles es más compacto y fácil de entender, además de ser marginalmente más rápido en un disco local (950 ms frente a 1000 ms) y LAN (26 segundos, frente a 28 segundos), ambos para 23 mil archivos

Es muy posible que para una unidad conectada remotamente la aceleración pueda ser considerable, pero no puedo probar eso del trabajo. Sorprendentemente, la velocidad sigue siendo de solo un 10% en una VPN RAS de Windows a una unidad de red.

Nuevo Código

static public int processDirectory(File dir, Callback cbk, FileSelector sel) { 
    dir=dir.getAbsoluteFile(); 
    return _processDirectory(dir.getParentFile(),dir,new Callback.WithParams(cbk,2),sel); 
    } 

static private int _processDirectory(File par, File fil, Callback.WithParams cbk, FileSelector sel) { 
    File[]        ents=(sel==null ? fil.listFiles() : fil.listFiles(sel)); // listFiles returns null if fil is not a directory 
    int         cnt=1; 

    if(ents!=null) { 
     cbk.invoke(fil,null); 
     for(int xa=0; xa<ents.length; xa++) { cnt+=_processDirectory(fil,ents[xa],cbk,sel); } 
     } 
    else { 
     cbk.invoke(par,fil);             // par can never be null 
     } 
    return cnt; 
    } 

Código antiguo

static public int oldProcessDirectory(File dir, Callback cbk, FileSelector sel) { 
    dir=dir.getAbsoluteFile(); 
    return _processDirectory(dir,new Callback.WithParams(cbk,2),sel); 
    } 

static private int _processDirectory(File dir, Callback.WithParams cbk, FileSelector sel) { 
    File[]        ents=(sel==null ? dir.listFiles() : dir.listFiles(sel)); 
    int         cnt=1; 

    cbk.invoke(dir,null); 

    if(ents!=null) { 
     for(int xa=0; xa<ents.length; xa++) { 
      File      ent=ents[xa]; 

      if(!ent.isDirectory()) { 
       cbk.invoke(dir,ent); 
       ents[xa]=null; 
       cnt++; 
       } 
      } 
     for(int xa=0; xa<ents.length; xa++) { 
      File      ent=ents[xa]; 

      if(ent!=null) { 
       cnt+=_processDirectory(ent,cbk,sel); 
       } 
      } 
     } 
    return cnt; 
    } 
0

En caso de que no se ha probado todavía, llamando getBooleanAttributes sí mismo y realizar el enmascaramiento necesaria será considerablemente más rápido si está realizando múltiples comprobaciones en el mismo archivo. Aunque no es una solución perfecta (y una que comienza a impulsar su código para que sea específico de la plataforma), podría mejorar el rendimiento en un factor de 3 o 4. Eso es un impulso de rendimiento bastante significativo, aunque no es tan rápido como debiera ser.

La funcionalidad JDK7 java.nio.file.Path debería ayudar bastante a este tipo de cosas.

Finalmente, si tiene algún tipo de control en el entorno del usuario final, sugiera a los usuarios que configuren su software antivirus para que no analice las unidades de red. Muchas de las soluciones AV grandes (no estoy seguro exactamente de lo que están resolviendo) tienen activado esto de manera predeterminada. No sé qué impacto puede tener esto en los diversos métodos de archivos, pero descubrimos que el virus anit configurado incorrectamente puede causar problemas de latencia masiva en casi todo tipo de acceso a archivos en recursos de red.

+0

De hecho, sería más rápido si pudiera llamar a getBooleanAttributes() yo mismo, pero desafortunadamente, estoy obligado a Java 1.5 y java.io.FileSystem está protegido por paquetes. Barrera de abstracción tonta, interponiéndose todo el tiempo. : o) –

+0

Debería poder acceder al método utilizando la reflexión (consulte el Método.setAccessible()) Otro truco que podría funcionar (no estoy seguro si el jvm rechazará las clases en el pacakge de java.io si no están firmadas por Sun) sería crear su propia clase que está en la misma carpeta (simplemente colóquelo en una carpeta java.io en su jar). –

Cuestiones relacionadas