Existe uma solução para o mau desempenho do Java em andar enormes diretórios?

votos
16

Eu estou tentando processar arquivos de um de cada vez que são armazenados em uma rede. Ler os arquivos é rápido devido ao buffer não é a questão. O problema que tenho é apenas listando os diretórios em uma pasta. Tenho pelo menos 10k de arquivos por pasta ao longo de muitas pastas.

Desempenho é super lento desde File.list () retorna uma matriz em vez de um iterable. Java apaga-se e recolhe todos os nomes em uma pasta e embala-lo em uma matriz antes de retornar.

A entrada de bug para isso é http://bugs.sun.com/view_bug.do;jsessionid=db7fcf25bcce13541c4289edeb4?bug_id=4285834 e não tem um trabalho em torno. Eles apenas dizem isso foi corrigido para JDK7.

Algumas questões:

  1. Alguém tem uma solução para este gargalo de desempenho?
  2. Estou tentando conseguir o impossível? É desempenho ainda vai ser pobre, mesmo que apenas itera sobre os diretórios?
  3. Eu poderia usar o beta JDK7 compilações têm essa funcionalidade sem ter que construir todo o meu projeto sobre ele?
Publicado 10/12/2008 em 01:14
fonte usuário
Em outras línguas...                            


10 respostas

votos
7

Embora não seja bonita, eu resolvi esse tipo de problema de uma vez por canalizando a saída de dir / ls para um arquivo antes de iniciar meu aplicativo, e passando no nome do arquivo.

Se for necessário para fazê-lo dentro do aplicativo, você pode simplesmente usar system.exec (), mas seria criar alguma maldade.

Você perguntou. A primeira forma vai ser incrivelmente rápido, o segundo deve ser bastante rápido também.

Certifique-se de fazer o um item por linha (nua, sem decoração, sem gráficos), caminho completo e opções recurse do seu comando selecionado.

EDITAR:

30 minutos apenas para obter uma listagem de diretório, wow.

Ele apenas me ocorreu que se você usar o exec (), você pode obter é stdout redirecionada para um tubo em vez de escrevê-lo em um arquivo.

Se você fizer isso, você deve começar a receber os arquivos imediatamente e ser capaz de iniciar o processamento antes que o comando foi concluído.

A interação pode coisas realmente lento, mas talvez não - você pode experimentá-lo.

Uau, eu só fui para encontrar a sintaxe do comando .exec para você e me deparei com isso, possivelmente, exatamente o que você quer (ele lista um diretório usando exec e "ls" e tubulações o resultado em seu programa de processamento): bom link em wayback (Jörg fornecido em um comentário para substituir este de sol que a Oracle quebrou)

De qualquer forma, a idéia é simples, mas recebendo o código certo é irritante. Eu vou roubar alguns códigos das internets e cortar-los - BRB


/ **
 * Nota: Só use isso como um último recurso! É específico para janelas e até mesmo
 * Em que não é uma boa solução, mas deve ser rápido.
 * 
 * Usá-lo, estender FileProcessor e chamar ProcessFiles ( "...") com uma lista
 * De opções se quiser como o / s ... Eu recomendo / b
 * 
 * Substituir processFile e será chamado uma vez para cada linha de saída.
 * /
import java.io. *;

FileProcessor classe abstrata pública
{
   ProcessFiles public void (dirOptions String)
   {
      Processar theprocess = nulo;
      BufferedReader streamDeEntrada = nulo;

      // chamar a classe Olá
      experimentar
      {
          . Theprocess = Runtime.getRuntime () exec ( "CMD / c dir" + dirOptions);
      }
      prendedor (IOException e)
      {
         System.err.println ( "Erro no método exec ()");
         e.printStackTrace ();  
      }

      // lê a partir de fluxo de saída padrão a chamada do programa
      experimentar
      {
         streamDeEntrada = BufferedReader novo (
                                nova InputStreamReader (theProcess.getInputStream ()));  
         processFile (inStream.readLine ());
      }
      prendedor (IOException e)
      {
         System.err.println ( "Erro na inStream.readLine ()");
         e.printStackTrace ();  
      }

   } // método extremidade
   / ** Substitua esse método - ele será chamado uma vez para cada arquivo * /
   public abstract vazio processFile (filename String);


} // fim da classe

E obrigado doador código na IBM

Respondeu 10/12/2008 em 01:57
fonte usuário

votos
4

Uma alternativa é ter os arquivos servidos sobre um protocolo diferente. Como eu entendo que você está usando SMB para isso e java está apenas tentando incluí-los como um arquivo regular.

O problema aqui pode não ser java sozinho (como é que ele se comporta quando você abrir esse diretório com o Microsoft Explorer X: \ compartilhada) Na minha experiência, também levar uma quantidade considerável de tempo.

Você pode alterar o protocolo para algo como HTTP, apenas para buscar os nomes de arquivo. Desta forma, você pode recuperar a lista de arquivos através de HTTP (10k linhas should't ser demais) e deixar o negócio servidor com lista de arquivos. Isso seria muito rápido, uma vez que será executado com recursos locais (aqueles no servidor)

Então, quando você tem a lista, você pode processá-los um por exatamente do jeito que você está fazendo agora.

O ponto-chave é ter um mecanismo de ajuda no outro lado do nó.

É isso possível?

Hoje:

File [] content = new File("X:\\remote\\dir").listFiles();

for ( File f : content ) {
    process( f );
}

proposta:

String [] content = fetchViaHttpTheListNameOf("x:\\remote\\dir");

for ( String fileName : content ) {
    process( new File( fileName ) );
}

O servidor http poderia ser um arquivo muito pequeno pequeno e simples.

Se esta é a maneira que você tê-lo agora, o que você está fazendo é buscar todas as informações arquivos 10k para sua máquina cliente (Eu não sei o quanto essa informação) quando você só precisa do nome do arquivo para processamento posterior .

Se o processamento é muito rápido agora pode ser abrandado um pouco. Isso ocorre porque as informações prefetched não está mais disponível.

De uma chance.

Respondeu 10/12/2008 em 01:36
fonte usuário

votos
3

Que tal usar método File.list (filtro FilenameFilter) e implementação FilenameFilter.accept (dir arquivo, nome String) para processar cada arquivo e retornar falso.

Eu corri isso em Linux vm para o diretório com 10K arquivos + e levou <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));
    }  
}
Respondeu 22/10/2013 em 15:20
fonte usuário

votos
3

Eu duvido que o problema é se relacionam com o relatório de bug você referenciado. A questão não é "apenas" o uso de memória, mas não necessariamente acelerar. Se você tem memória suficiente o erro não é relevante para o seu problema.

Você deve medir se o seu problema é a memória relacionada ou não. Ligue o log do coletor de lixo e usar por exemplo gcviewer para analisar o seu uso de memória.

Eu suspeito que isso tem a ver com o protocolo SMB causando o problema. Você pode tentar escrever um teste em outra língua e ver se é mais rápido, ou você pode tentar obter a lista de nomes de arquivos através de algum outro método, como descrito aqui em outro post.

Respondeu 10/12/2008 em 09:49
fonte usuário

votos
2

A solução não-portátil seria fazer chamadas nativas para o sistema operacional e transmitir os resultados.

para Linux

Você pode olhar para algo como readdir . Você pode andar a estrutura de diretórios como uma lista ligada e retornar resultados em lotes ou individualmente.

Para Windows

No Windows, o comportamento seria bastante similar usando FindFirstFile e FindNextFile apis.

Respondeu 10/12/2008 em 01:22
fonte usuário

votos
1

Se você estiver em Java 1.5 ou 1.6, descascar para fora comandos "dir" e analisar o fluxo de saída padrão no Windows é uma abordagem perfeitamente aceitável. Eu usei essa abordagem no passado para unidades de rede de processamento e geralmente tem sido muito mais rápido do que espera para o método nativo java.io.File ListFiles () para retornar.

Claro, uma chamada JNI deve ser mais rápido e potencialmente mais seguro do que gastar dinheiro com comandos "dir". O seguinte código JNI pode ser usado para recuperar uma lista de arquivos / diretórios usando a API do Windows. Esta função pode ser facilmente reformulado em uma nova classe para o chamador pode recuperar caminhos de arquivo de forma incremental (ou seja, obter um caminho de cada vez). Por exemplo, você pode refatorar o código para que FindFirstFileW é chamado em um construtor e tem um método separado para chamar 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

Mesmo com esta abordagem, ainda existem eficiências de ser adquirida. Se você serializar o caminho para um java.io.File, há um enorme impacto na performance - especialmente se o caminho representa um arquivo em uma unidade de rede. Eu não tenho idéia o que a Sun / Oracle está fazendo sob o capô, mas se você precisar de arquivo atributos adicionais que não seja o caminho do arquivo (por exemplo, tamanho, data de modificação, etc), eu descobri que a seguinte função JNI é muito mais rápido do que instanciar um java .io.File objeto em uma rede do caminho.

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;
}

Você pode encontrar um exemplo de trabalho completo desta abordagem baseada em JNI na javaxt-core biblioteca. Em meus testes usando Java 1.6.0_38 com um host Windows bater um compartilhamento do Windows, tenho encontrado esta abordagem JNI cerca de 10 vezes mais rápido, em seguida, chamando java.io.File ListFiles () ou descascar para fora comandos "dir".

Respondeu 27/01/2013 em 18:30
fonte usuário

votos
1

Se você precisar, eventualmente processar todos os arquivos, em seguida, ter Iterable sobre String [] não lhe dará qualquer vantagem, como você ainda vai ter que ir buscar toda a lista de arquivos.

Respondeu 10/12/2008 em 13:26
fonte usuário

votos
0

Tem certeza que é devido ao Java, não apenas um problema geral com ter 10k entradas em um diretório, particularmente através da rede?

você já tentou escrever um programa de prova de conceito para fazer a mesma coisa em C usando o win32 FindFirst / funções FindNext para ver se é mais rápido?

Eu não sei os prós e contras de SMB, mas eu suspeito fortemente que ele precisa de uma ida e volta para cada arquivo na lista - o que não vai ser rápido, particularmente através de uma rede com latência moderado.

Tendo 10k cordas em uma matriz soa como algo que não deve tributar o moderno Java VM muito também.

Respondeu 01/03/2009 em 09:06
fonte usuário

votos
0

Usando um Iterable não implica que os arquivos serão transmitidos para você. Na verdade sua geralmente o oposto. Assim, uma matriz é normalmente mais rápido do que um Iterable.

Respondeu 01/03/2009 em 08:56
fonte usuário

votos
0

Eu me pergunto por que há arquivos 10k em um diretório. Alguns sistemas de arquivos não funcionam bem com tantos arquivos. Há especificas limitações para sistemas de arquivos como máximo quantidade de arquivos por diretório e quantidade máxima de níveis de subdiretório.

I resolver um problema semelhante com uma solução iterador.

Eu precisava atravessar enormes directorys e vários níveis de árvore de diretórios de forma recursiva.

Tento FileUtils.iterateFiles () do Apache commons io. Mas implementar o iterador adicionando todos os arquivos em uma lista e, em seguida, retornar List.iterator (). É muito ruim para a memória.

Então eu prefiro escrever algo como isto:

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();
    }

}

Note-se que a paragem iterador por uma quantidade de arquivos iterateds e tem um FileFilter também.

E DirectoryStack é:

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();
    }

}
Respondeu 10/12/2008 em 13:18
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more