Подтвердить что ты не робот

Как перечислить каталог из 2 миллионов файлов в java без исключения "из памяти"

Мне нужно иметь дело с каталогом около 2 миллионов xml для обработки.

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

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

Я пробовал использовать метод File.listFiles(), но он дает мне исключение java out of memory: heap space. Любые идеи?

4b9b3361

Ответ 1

Прежде всего, есть ли у вас возможность использовать Java 7? Там у вас есть FileVisitor и Files.walkFileTree, которые, вероятно, должны работать в пределах ваших ограничений памяти.

В противном случае единственный способ, которым я могу думать, - использовать File.listFiles(FileFilter filter) с фильтром, который всегда возвращает false (гарантируя, что полный массив файлов никогда не хранится в памяти), но это ловит файлы для обработки по пути и, возможно, помещает их в очередь производителей/потребителей или записывает имена файлов на диск для последующего обхода.

В качестве альтернативы, если вы управляете именами файлов или если они называются каким-то образом, вы можете обрабатывать файлы в кусках, используя фильтр, который принимает имена файлов в форме file0000000 - filefile0001000, затем file0001000 - filefile0002000 и т.д.

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


Обновление: Вздох. Наверное, не получится. Просто посмотрел на реализацию listFiles:

public File[] listFiles(FilenameFilter filter) {
    String ss[] = list();
    if (ss == null) return null;
    ArrayList v = new ArrayList();
    for (int i = 0 ; i < ss.length ; i++) {
        if ((filter == null) || filter.accept(this, ss[i])) {
            v.add(new File(ss[i], this));
        }
    }
    return (File[])(v.toArray(new File[v.size()]));
}

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

Btw, не могли бы вы привести пример имени файла? Являются ли они "допустимыми"? Как

for (int i = 0; i < 100000; i++)
    tryToOpen(String.format("file%05d", i))

Ответ 2

Если Java 7 не является опцией, этот хак будет работать (для UNIX):

Process process = Runtime.getRuntime().exec(new String[]{"ls", "-f", "/path"});
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while (null != (line = reader.readLine())) {
    if (line.startsWith("."))
        continue;
    System.out.println(line);
}

Параметр -f ускорит его (от man ls):

-f     do not sort, enable -aU, disable -lst

Ответ 3

Используйте File.list() вместо File.listFiles() - объекты String, которые он возвращает, потребляют меньше памяти, чем объекты File, и (что более важно, в зависимости от местоположения каталога), они не содержат полного имени пути.

Затем создайте объекты File по мере необходимости при обработке результата.

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

Ответ 4

Если вы можете использовать Java 7, это можно сделать таким образом, и у вас не будет проблем с памятью.

Path path = FileSystems.getDefault().getPath("C:\\path\\with\\lots\\of\\files");
        Files.walkFileTree(path, new FileVisitor<Path>() {
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                // here you have the files to process
                System.out.println(file);
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
               return FileVisitResult.TERMINATE;
            }

            @Override
            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
              return FileVisitResult.CONTINUE;
            }
        });

Ответ 5

Вы можете сделать это с помощью библиотеки Apache FileUtils. Нет проблемы с памятью. Я проверил с visualvm.

  Iterator<File> it = FileUtils.iterateFiles(folder, null, true);
  while (it.hasNext())
  {
     File fileEntry = (File) it.next();
  }

Надеюсь, что это поможет. свидания

Ответ 6

Почему вы все равно сохраняете 2 миллиона файлов в одном каталоге? Я могу себе представить, что он значительно замедляет доступ на уровне ОС.

Я бы определенно хотел, чтобы они были разделены на подкаталоги (например, по дате/времени создания) уже перед обработкой. Но если это невозможно по какой-то причине, можно ли это сделать во время обработки? Например. переместить 1000 файлов в очередь для Process1 в Directory1, еще 1000 файлов для Process2 в Directory2 и т.д. Затем каждый процесс/поток видит только (ограниченное число) файлов, порции для него.

Ответ 7

Поскольку вы работаете в Windows, кажется, что вы просто должны использовать ProcessBuilder для запуска чего-то вроде "cmd/k dir/b target_directory", захватить вывод этого и перенаправить его в файл. Затем вы можете обрабатывать этот файл по одной строке за раз, считывая имена файлов и обрабатывая их.

Лучше поздно, чем никогда?;)

Ответ 8

В кулаке вы можете попытаться увеличить объем памяти вашего JVM с передачей -Xmx1024m, например.

Ответ 9

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

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

Ответ 10

Если имена файлов соответствуют определенным правилам, вы можете использовать File.list(filter) вместо File.listFiles, чтобы получить управляемые части списка файлов.

Ответ 11

В качестве первого подхода вы можете попробовать настроить некоторые параметры памяти JVM, например. увеличить размер кучи, как было предложено, или даже использовать параметр AggressiveHeap. Принимая во внимание большое количество файлов, это может не помочь, тогда я бы предложил решить эту проблему. Создайте несколько файлов с именами файлов в каждом, скажем, 500 тыс. Имен файлов на файл и прочитайте их.

Ответ 12

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

подробнее о командной оболочке здесь: http://adbshell.com/commands/adb-shell-ls

        Process process = Runtime.getRuntime().exec("ls -R /");
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));

        //TODO: Read the stream to get a list of file path.

Ответ 13

Для этого также требуется Java 7, но это проще, чем ответ Files.walkFileTree, если вы просто хотите перечислить содержимое каталога и не ходить по всему дереву:

Path dir = Paths.get("/some/directory");
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
    for (Path path : stream) {
        handleFile(path.toFile());
    }
} catch (IOException e) {
    handleException(e);
}

Реализация DirectoryStream специфична для платформы и никогда не вызывает File.list или что-то в этом роде, вместо этого использует системные вызовы Unix или Windows, которые перебирают по каталогу одну запись за раз.

Ответ 14

Вы можете использовать listFiles со специальным FilenameFilter. В первый раз, когда FilenameFilter отправляется в listFiles, он принимает первые 1000 файлов, а затем сохраняет их как посещенные.

В следующий раз, когда FilenameFilter отправляется в listFiles, он игнорирует первые 1000 посещенных файлов и возвращает следующие 1000 и т.д. до завершения.

Ответ 15

Попробуй, это работает для меня, но у меня было не так много документов...

File dir = new File("directory");
String[] children = dir.list();
if (children == null) {
   //Either dir does not exist or is not a  directory
  System.out.print("Directory doesn't  exist\n");
}
else {
  for (int i=0; i<children.length; i++) {   
    // Get filename of file or directory   
    String filename = children[i];  
}