Есть ли решение для плохой работы Java при ходьбе огромных каталогов?

Я пытаюсь обрабатывать файлы по одному, которые хранятся в сети. Чтение файлов происходит быстро из-за буферизации, это не проблема. Проблема, которую я имею, - это просто перечисление каталогов в папке. У меня есть по меньшей мере 10 тыс. Файлов в папке по многим папкам.

Производительность супер медленна, так как File.list() возвращает массив вместо итерабельного. Java уходит и собирает все имена в папке и отправляет их в массив перед возвратом.

Запись об ошибке для этого http://bugs.sun.com/view_bug.do;jsessionid=db7fcf25bcce13541c4289edeb4?bug_id=4285834 и не работает. Они просто говорят, что это исправлено для JDK7.

Несколько вопросов:

  • Есть ли у кого-нибудь проблемы с этим узким местом производительности?
  • Я пытаюсь добиться невозможного? Является ли производительность еще плохой, даже если она просто выполняет итерации по каталогам?
  • Могу ли я использовать бета-версии JDK7, которые имеют эту функциональность, без необходимости создавать на ней весь проект?

Ответ 1

Хотя это не очень, я решил эту проблему однажды, отправив вывод dir/ls в файл перед запуском своего приложения и передав имя файла.

Если вам нужно сделать это в приложении, вы можете просто использовать system.exec(), но это создаст некоторую гадость.

Ты спросил. Первая форма будет невероятно быстрой, вторая тоже будет довольно быстрой.

Обязательно сделайте один элемент в строке (голый, без декорации, без графики), полный путь и параметры рекурсии выбранной вами команды.

ИЗМЕНИТЬ:

30 минут, чтобы получить список каталогов, ничего себе.

Мне просто показалось, что если вы используете exec(), вы можете заставить его stdout перенаправляться в канал вместо того, чтобы записывать его в файл.

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

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

Ничего себе, я просто нашел синтаксис команды .exec для вас и наткнулся на это, возможно, именно то, что вам нужно (он перечисляет каталог с помощью exec и "ls" и передает результат в вашу программу для обработки): хорошая ссылка в обратном пути (Jörg предоставил комментарий для замены этот от солнца, который Oracle сломал)

В любом случае, идея проста, но правильный код - это раздражает. Я пойду украсть некоторые коды из интернетов и взломать их - brb

/**
 * Note: Only use this as a last resort!  It specific to windows and even
 * at that it 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 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

И спасибо вам, донор кода, IBM

Ответ 2

Альтернативой является использование файлов в другом протоколе. Насколько я понимаю, вы используете SMB для этого, а java просто пытается перечислить их как обычный файл.

Проблема здесь может быть не только java (как она себя ведет при открытии этого каталога с помощью Microsoft Explorer x:\shared). По моему опыту это также занимает значительное количество времени.

Вы можете изменить протокол на что-то вроде HTTP, только для получения имен файлов. Таким образом, вы можете получить список файлов по http (10k строк не должно быть слишком много) и позволить серверу иметь дело с списком файлов. Это будет очень быстро, так как он будет работать с локальными ресурсами (на сервере)

Затем, когда у вас есть список, вы можете обрабатывать их один точно так, как вы делаете прямо сейчас.

Ключевой точкой является наличие механизма помощи на другой стороне node.

Возможно ли это?

Сегодня:

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

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

Предлагаемые

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

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

http-сервер может быть очень маленьким и простым файлом.

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

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

Попробуйте.

Ответ 3

Я сомневаюсь, что проблема связана с сообщением об ошибке, на который вы ссылаетесь. Проблема там "только" использования памяти, но не обязательно скорость. Если у вас достаточно памяти, ошибка не имеет отношения к вашей проблеме.

Вы должны измерить, связана ли ваша проблема с памятью или нет. Включите журнал регистрации мусора и используйте, например, gcviewer, чтобы проанализировать использование вашей памяти.

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

Ответ 4

Как насчет использования фильтра File.list(FilenameFilter filter) и реализации FilenameFilter.accept(File dir, String name) для обработки каждого файла и возврата false.

Я запустил это в Linux vm для каталога с файлами 10K +, и потребовалось < 10 секунд.

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

Ответ 5

Непереносимым решением будет создание собственных вызовов в операционную систему и поток результатов.

Для Linux

Вы можете посмотреть что-то вроде readdir. Вы можете перемещаться по структуре каталогов, как связанный список, и возвращать результаты пакетами или индивидуально.

Для Windows

В окнах поведение было бы довольно похоже, используя FindFirstFile и FindNextFile apis.

Ответ 6

Если вам нужно в конечном итоге обработать все файлы, то использование Iterable над String [] не даст вам никаких преимуществ, так как вам все равно придется идти и получать весь список файлов.

Ответ 7

Если вы используете Java 1.5 или 1.6, отлаживание команд "dir" и разбор стандартного потока вывода на Windows - вполне приемлемый подход. Я использовал этот подход в прошлом для обработки сетевых дисков, и он, как правило, был намного быстрее, чем ждать возврата метода native java.io.File listFiles().

Конечно, вызов JNI должен быть более быстрым и потенциально безопасным, чем отключение команд "dir" . Следующий код JNI можно использовать для извлечения списка файлов/каталогов с помощью Windows API. Эта функция может быть легко реорганизована в новый класс, чтобы вызывающий пользователь мог получить пути к файлам пошагово (т.е. Получить один путь за раз). Например, вы можете реорганизовать код так, чтобы FindFirstFileW вызывался в конструкторе и имел отдельный метод для вызова 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;
}

Кредит:  https://sites.google.com/site/jozsefbekes/Home/windows-programming/miscellaneous-functions

Даже при таком подходе все еще есть эффективность. Если вы сериализуете путь к java.io.File, есть огромный успех - особенно если этот путь представляет файл на сетевом диске. Я не знаю, что делает Sun/Oracle под капотом, но если вам нужны дополнительные атрибуты файлов, отличные от пути к файлу (например, размер, дата mod и т.д.), Я обнаружил, что следующая функция JNI намного быстрее, чем создание экземпляра java.io.File объект в сети путь.

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

Вы можете найти полный рабочий пример этого подхода, основанного на JNI, в библиотеке javaxt-core. В моих тестах с использованием Java 1.6.0_38 с хостом Windows, поражающим общий ресурс Windows, я нашел этот подход JNI примерно в 10 раз быстрее, чем вызов java.io.File listFiles() или обрезание команд "dir" .

Ответ 8

Интересно, почему в каталоге есть 10k файлов. Некоторые файловые системы не работают с таким количеством файлов. Существуют ограничения для файловых систем, таких как максимальное количество файлов в каталоге и максимальное количество уровней подкаталога.

Я решаю аналогичную проблему с решением итератора.

Мне нужно было рекурсивно пройти через огромные каталоги и несколько уровней дерева каталогов.

Я пытаюсь FileUtils.iterateFiles() из Apache commons io. Но он реализует итератор, добавляя все файлы в список и затем возвращая List.iterator(). Это очень плохо для памяти.

Поэтому я предпочитаю писать что-то вроде этого:

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

}

Обратите внимание, что итератор останавливается на количество файлов, итерации также имеют FileFilter.

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

}

Ответ 9

Использование Iterable не означает, что файлы будут переданы вам. На самом деле это обычно наоборот. Таким образом, массив обычно быстрее, чем Iterable.

Ответ 10

Вы уверены, что это связано с Java, а не только с общей проблемой с наличием 10k записей в одном каталоге, особенно в сети?

Пробовали ли вы писать концептуальную программу, чтобы сделать то же самое в C, используя функции win32 findfirst/findnext, чтобы увидеть, быстрее ли это?

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

Наличие строк 10k в массиве звучит как нечто, что тоже не должно облагать современной виртуальной машиной Java.