soluciones solucionar rendimiento propuestas posibles para niños nivel mejorar escolar con como bajo alternativas academico java performance directory-walk

solucionar - ¿Hay alguna solución para el bajo rendimiento de Java al recorrer enormes directorios?



propuestas para mejorar el nivel academico (10)

Dudo que el problema esté relacionado con el informe de error al que hizo referencia. El problema es "solo" el uso de memoria, pero no necesariamente la velocidad. Si tienes suficiente memoria, el error no es relevante para tu problema.

Debe medir si su problema está relacionado con la memoria o no. Encienda su registro de Garbage Collector y use, por ejemplo, gcviewer para analizar su uso de memoria.

Sospecho que tiene que ver con el protocolo SMB que causa el problema. Puede intentar escribir una prueba en otro idioma y ver si es más rápido, o puede tratar de obtener la lista de nombres de archivo a través de otro método, como se describe aquí en otra publicación.

Estoy tratando de procesar archivos de uno en uno que están almacenados en una red. Leer los archivos es rápido debido al almacenamiento en búfer no es el problema. El problema que tengo es solo enumerar los directorios en una carpeta. Tengo al menos 10k archivos por carpeta en muchas carpetas.

El rendimiento es super lento ya que File.list () devuelve una matriz en lugar de una iterable. Java se apaga y recoge todos los nombres en una carpeta y los empaqueta en una matriz antes de volver.

La entrada del error para esto es http://bugs.sun.com/view_bug.do;jsessionid=db7fcf25bcce13541c4289edeb4?bug_id=4285834 y no tiene una solución alternativa. Solo dicen que esto ha sido arreglado para JDK7.

Unas cuantas preguntas:

  1. ¿Alguien tiene una solución alternativa a este cuello de botella de rendimiento?
  2. ¿Estoy tratando de lograr lo imposible? ¿El rendimiento seguirá siendo pobre incluso si solo itera sobre los directorios?
  3. ¿Podría usar las compilaciones beta JDK7 que tienen esta funcionalidad sin tener que construir todo mi proyecto en ella?

Me pregunto por qué hay 10k archivos en un directorio. Algunos sistemas de archivos no funcionan bien con tantos archivos. Existen limitaciones específicas para los sistemas de archivos, como la cantidad máxima de archivos por directorio y la cantidad máxima de niveles de subdirectorio.

Resuelvo un problema similar con una solución de iterador.

Necesitaba caminar a través de enormes directores y varios niveles de árbol de directorios recursivamente.

Intento FileUtils.iterateFiles () de Apache commons io. Pero implementa el iterador al agregar todos los archivos en una lista y luego devolver List.iterator (). Es muy malo para la memoria.

Entonces prefiero escribir algo como esto:

private static class SequentialIterator implements Iterator<File> { private DirectoryStack dir = null; private File current = null; private long limit; private FileFilter filter = null; public SequentialIterator(String path, long limit, FileFilter ff) { current = new File(path); this.limit = limit; filter = ff; dir = DirectoryStack.getNewStack(current); } public boolean hasNext() { while(walkOver()); return isMore && (limit > count || limit < 0) && dir.getCurrent() != null; } private long count = 0; public File next() { File aux = dir.getCurrent(); dir.advancePostition(); count++; return aux; } private boolean walkOver() { if (dir.isOutOfDirListRange()) { if (dir.isCantGoParent()) { isMore = false; return false; } else { dir.goToParent(); dir.advancePostition(); return true; } } else { if (dir.isCurrentDirectory()) { if (dir.isDirectoryEmpty()) { dir.advancePostition(); } else { dir.goIntoDir(); } return true; } else { if (filter.accept(dir.getCurrent())) { return false; } else { dir.advancePostition(); return true; } } } } private boolean isMore = true; public void remove() { throw new UnsupportedOperationException(); } }

Tenga en cuenta que el iterador se detiene en una cantidad de archivos iterados y también tiene un FileFilter.

Y DirectoryStack es:

public class DirectoryStack { private class Element{ private File files[] = null; private int currentPointer; public Element(File current) { currentPointer = 0; if (current.exists()) { if(current.isDirectory()){ files = current.listFiles(); Set<File> set = new TreeSet<File>(); for (int i = 0; i < files.length; i++) { File file = files[i]; set.add(file); } set.toArray(files); }else{ throw new IllegalArgumentException("File current must be directory"); } } else { throw new IllegalArgumentException("File current not exist"); } } public String toString(){ return "current="+getCurrent().toString(); } public int getCurrentPointer() { return currentPointer; } public void setCurrentPointer(int currentPointer) { this.currentPointer = currentPointer; } public File[] getFiles() { return files; } public File getCurrent(){ File ret = null; try{ ret = getFiles()[getCurrentPointer()]; }catch (Exception e){ } return ret; } public boolean isDirectoryEmpty(){ return !(getFiles().length>0); } public Element advancePointer(){ setCurrentPointer(getCurrentPointer()+1); return this; } } private DirectoryStack(File first){ getStack().push(new Element(first)); } public static DirectoryStack getNewStack(File first){ return new DirectoryStack(first); } public String toString(){ String ret = "stack:/n"; int i = 0; for (Element elem : stack) { ret += "nivel " + i++ + elem.toString()+"/n"; } return ret; } private Stack<Element> stack=null; private Stack<Element> getStack(){ if(stack==null){ stack = new Stack<Element>(); } return stack; } public File getCurrent(){ return getStack().peek().getCurrent(); } public boolean isDirectoryEmpty(){ return getStack().peek().isDirectoryEmpty(); } public DirectoryStack downLevel(){ getStack().pop(); return this; } public DirectoryStack goToParent(){ return downLevel(); } public DirectoryStack goIntoDir(){ return upLevel(); } public DirectoryStack upLevel(){ if(isCurrentNotNull()) getStack().push(new Element(getCurrent())); return this; } public DirectoryStack advancePostition(){ getStack().peek().advancePointer(); return this; } public File[] peekDirectory(){ return getStack().peek().getFiles(); } public boolean isLastFileOfDirectory(){ return getStack().peek().getFiles().length <= getStack().peek().getCurrentPointer(); } public boolean gotMoreLevels() { return getStack().size()>0; } public boolean gotMoreInCurrentLevel() { return getStack().peek().getFiles().length > getStack().peek().getCurrentPointer()+1; } public boolean isRoot() { return !(getStack().size()>1); } public boolean isCurrentNotNull() { if(!getStack().isEmpty()){ int currentPointer = getStack().peek().getCurrentPointer(); int maxFiles = getStack().peek().getFiles().length; return currentPointer < maxFiles; }else{ return false; } } public boolean isCurrentDirectory() { return getStack().peek().getCurrent().isDirectory(); } public boolean isLastFromDirList() { return getStack().peek().getCurrentPointer() == (getStack().peek().getFiles().length-1); } public boolean isCantGoParent() { return !(getStack().size()>1); } public boolean isOutOfDirListRange() { return getStack().peek().getFiles().length <= getStack().peek().getCurrentPointer(); } }


Si necesita procesar finalmente todos los archivos, tener Iterable sobre String [] no le dará ninguna ventaja, ya que de todos modos tendrá que ir a buscar toda la lista de archivos.


Una alternativa es tener los archivos servidos sobre un protocolo diferente. Como entiendo, estás usando SMB para eso y java solo intenta listarlos como un archivo normal.

El problema aquí podría no ser solo java (¿cómo se comporta cuando abre ese directorio con Microsoft Explorer x: / shared) En mi experiencia también lleva un tiempo considerable.

Puede cambiar el protocolo a algo como HTTP, solo para recuperar los nombres de los archivos. De esta forma puede recuperar la lista de archivos a través de http (10k líneas no deberían ser demasiado) y dejar que el servidor se ocupe de la lista de archivos. Esto sería muy rápido, ya que se ejecutará con recursos locales (los que están en el servidor)

Luego, cuando tenga la lista, puede procesarla exactamente de la manera en que lo hace en este momento.

El punto clave es tener un mecanismo de ayuda en el otro lado del nodo.

¿Es esto factible?

Hoy:

File [] content = new File("X://remote//dir").listFiles(); for ( File f : content ) { process( f ); }

Propuesto:

String [] content = fetchViaHttpTheListNameOf("x://remote//dir"); for ( String fileName : content ) { process( new File( fileName ) ); }

El servidor http podría ser un archivo pequeño y simple muy pequeño.

Si esta es la forma en que lo tiene ahora, lo que está haciendo es buscar toda la información de los archivos de 10k en su equipo cliente (no sé cuánto de esa información) cuando solo necesita el nombre del archivo para su posterior procesamiento .

Si el procesamiento es muy rápido en este momento, puede ralentizarse un poco. Esto se debe a que la información extraída previamente ya no está disponible.

Darle una oportunidad.


Una solución no portátil sería hacer llamadas nativas al sistema operativo y transmitir los resultados.

Para Linux

Puedes ver algo como readdir . Puede recorrer la estructura del directorio como una lista vinculada y devolver resultados en lotes o individualmente.

Para ventanas

En Windows, el comportamiento sería bastante similar con las API FindFirstFile y FindNextFile .


¿Estás seguro de que se debe a Java, no solo a un problema general con tener 10k entradas en un directorio, particularmente a través de la red?

¿Has intentado escribir un programa de prueba de concepto para hacer lo mismo en C usando las funciones win32 findfirst / findnext para ver si es más rápido?

No conozco los pormenores de SMB, pero sospecho fuertemente que necesita un viaje redondo para cada archivo de la lista, lo que no será rápido, especialmente en una red con latencia moderada.

Tener cadenas de 10k en una matriz suena como algo que tampoco debería gravar demasiado la VM de Java moderna.


El uso de un Iterable no implica que los archivos se transmitirán a usted. De hecho, generalmente es todo lo contrario. Entonces una matriz es típicamente más rápida que una Iterable.


Si tiene Java 1.5 o 1.6, desgranar los comandos "dir" y analizar el flujo de salida estándar en Windows es un enfoque perfectamente aceptable. He utilizado este enfoque en el pasado para procesar unidades de red y, en general, ha sido mucho más rápido que esperar a que vuelva el método nativo java.io.File listFiles ().

Por supuesto, una llamada JNI debería ser más rápida y potencialmente más segura que desgranar comandos "dir". El siguiente código JNI se puede usar para recuperar una lista de archivos / directorios utilizando la API de Windows. Esta función se puede refactorizar fácilmente en una nueva clase para que la persona que llama pueda recuperar rutas de archivos de forma incremental (es decir, obtener una ruta a la vez). Por ejemplo, puede refactorizar el código para llamar a FindFirstFileW en un constructor y tener un método separado para llamar a FindNextFileW.

JNIEXPORT jstring JNICALL Java_javaxt_io_File_GetFiles(JNIEnv *env, jclass, jstring directory) { HANDLE hFind; try { //Convert jstring to wstring const jchar *_directory = env->GetStringChars(directory, 0); jsize x = env->GetStringLength(directory); wstring path; //L"C://temp//*"; path.assign(_directory, _directory + x); env->ReleaseStringChars(directory, _directory); if (x<2){ jclass exceptionClass = env->FindClass("java/lang/Exception"); env->ThrowNew(exceptionClass, "Invalid path, less than 2 characters long."); } wstringstream ss; BOOL bContinue = TRUE; WIN32_FIND_DATAW data; hFind = FindFirstFileW(path.c_str(), &data); if (INVALID_HANDLE_VALUE == hFind){ jclass exceptionClass = env->FindClass("java/lang/Exception"); env->ThrowNew(exceptionClass, "FindFirstFileW returned invalid handle."); } //HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE); //DWORD dwBytesWritten; // If we have no error, loop thru the files in this dir while (hFind && bContinue){ /* //Debug Print Statment. DO NOT DELETE! cout and wcout do not print unicode correctly. WriteConsole(hStdOut, data.cFileName, (DWORD)_tcslen(data.cFileName), &dwBytesWritten, NULL); WriteConsole(hStdOut, L"/n", 1, &dwBytesWritten, NULL); */ //Check if this entry is a directory if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY){ // Make sure this dir is not . or .. if (wstring(data.cFileName) != L"." && wstring(data.cFileName) != L"..") { ss << wstring(data.cFileName) << L"//" << L"/n"; } } else{ ss << wstring(data.cFileName) << L"/n"; } bContinue = FindNextFileW(hFind, &data); } FindClose(hFind); // Free the dir structure wstring cstr = ss.str(); int len = cstr.size(); //WriteConsole(hStdOut, cstr.c_str(), len, &dwBytesWritten, NULL); //WriteConsole(hStdOut, L"/n", 1, &dwBytesWritten, NULL); jchar* raw = new jchar[len]; memcpy(raw, cstr.c_str(), len*sizeof(wchar_t)); jstring result = env->NewString(raw, len); delete[] raw; return result; } catch(...){ FindClose(hFind); jclass exceptionClass = env->FindClass("java/lang/Exception"); env->ThrowNew(exceptionClass, "Exception occured."); } return NULL; }

Crédito: https://sites.google.com/site/jozsefbekes/Home/windows-programming/miscellaneous-functions

Incluso con este enfoque, aún hay eficiencias que ganar. Si serializa la ruta a java.io.File, hay un enorme impacto en el rendimiento, especialmente si la ruta representa un archivo en una unidad de red. No tengo idea de qué hace Sun / Oracle bajo el capó, pero si necesita atributos de archivo adicionales a la ruta del archivo (por ejemplo, tamaño, fecha de modificación, etc.), he descubierto que la siguiente función JNI es mucho más rápida que crear un java .io.Archivo objeto en una red la ruta.

JNIEXPORT jlongArray JNICALL Java_javaxt_io_File_GetFileAttributesEx(JNIEnv *env, jclass, jstring filename) { //Convert jstring to wstring const jchar *_filename = env->GetStringChars(filename, 0); jsize len = env->GetStringLength(filename); wstring path; path.assign(_filename, _filename + len); env->ReleaseStringChars(filename, _filename); //Get attributes WIN32_FILE_ATTRIBUTE_DATA fileAttrs; BOOL result = GetFileAttributesExW(path.c_str(), GetFileExInfoStandard, &fileAttrs); if (!result) { jclass exceptionClass = env->FindClass("java/lang/Exception"); env->ThrowNew(exceptionClass, "Exception Occurred"); } //Create an array to store the WIN32_FILE_ATTRIBUTE_DATA jlong buffer[6]; buffer[0] = fileAttrs.dwFileAttributes; buffer[1] = date2int(fileAttrs.ftCreationTime); buffer[2] = date2int(fileAttrs.ftLastAccessTime); buffer[3] = date2int(fileAttrs.ftLastWriteTime); buffer[4] = fileAttrs.nFileSizeHigh; buffer[5] = fileAttrs.nFileSizeLow; jlongArray jLongArray = env->NewLongArray(6); env->SetLongArrayRegion(jLongArray, 0, 6, buffer); return jLongArray; }

Puede encontrar un ejemplo completo de trabajo de este enfoque basado en JNI en la biblioteca javaxt-core . En mis pruebas usando Java 1.6.0_38 con un host de Windows que golpea un recurso compartido de Windows, he encontrado que este enfoque JNI es aproximadamente 10 veces más rápido que llamar java.io.File listFiles () o descascarar comandos "dir".


¿Qué tal si usamos el método File.list (FilenameFilter filter) e implementamos FilenameFilter.accept (File dir, String name) para procesar cada archivo y devolver false.

Ejecuté esto en Linux vm para el directorio con 10K + archivos y tardó <10 segundos.

import java.io.File; import java.io.FilenameFilter; public class Temp { private static void processFile(File dir, String name) { File file = new File(dir, name); System.out.println("processing file " + file.getName()); } private static void forEachFile(File dir) { String [] ignore = dir.list(new FilenameFilter() { public boolean accept(File dir, String name) { processFile(dir, name); return false; } }); } public static void main(String[] args) { long before, after; File dot = new File("."); before = System.currentTimeMillis(); forEachFile(dot); after = System.currentTimeMillis(); System.out.println("after call, delta is " + (after - before)); } }


Aunque no es bonito, resolví este tipo de problema una vez conectando el resultado de dir / ls a un archivo antes de iniciar mi aplicación y pasando el nombre del archivo.

Si necesita hacerlo dentro de la aplicación, puede usar system.exec (), pero crearía algo desagradable.

Tu preguntaste. La primera forma será increíblemente rápida, la segunda también será bastante rápida.

Asegúrese de hacer un artículo por línea (pelado, sin decoración, sin gráficos), ruta completa y opciones recurrentes del comando seleccionado.

EDITAR:

30 minutos solo para obtener una lista de directorio, guau.

Me acabo de dar cuenta de que si usa exec (), puede hacer que se redireccione en una tubería en vez de escribirla en un archivo.

Si lo hizo, debería comenzar a obtener los archivos inmediatamente y poder comenzar a procesar antes de que el comando se haya completado.

La interacción en realidad puede ralentizar las cosas, pero tal vez no, podrías intentarlo.

Vaya, fui a buscar la sintaxis del comando .exec para ti y encontré esto, posiblemente exactamente lo que quieres (enumera un directorio usando exec y "ls" y canaliza el resultado en tu programa para su procesamiento): buen enlace en wayback (Jörg proporcionó en un comentario para reemplazar este del sol que Oracle rompió)

De todos modos, la idea es clara, pero obtener el código correcto es molesto. Iré a robar algunos códigos de los internets y los piratearé - brb

/** * Note: Only use this as a last resort! It''s specific to windows and even * at that it''s not a good solution, but it should be fast. * * to use it, extend FileProcessor and call processFiles("...") with a list * of options if you want them like /s... I highly recommend /b * * override processFile and it will be called once for each line of output. */ import java.io.*; public abstract class FileProcessor { public void processFiles(String dirOptions) { Process theProcess = null; BufferedReader inStream = null; // call the Hello class try { theProcess = Runtime.getRuntime().exec("cmd /c dir " + dirOptions); } catch(IOException e) { System.err.println("Error on exec() method"); e.printStackTrace(); } // read from the called program''s standard output stream try { inStream = new BufferedReader( new InputStreamReader( theProcess.getInputStream() )); processFile(inStream.readLine()); } catch(IOException e) { System.err.println("Error on inStream.readLine()"); e.printStackTrace(); } } // end method /** Override this method--it will be called once for each file */ public abstract void processFile(String filename); } // end class

Y gracias código donador en IBM