Как перечислить 2 миллиона каталогов файлов в Java, не имея “из памяти” исключение

Я должен иметь дело с каталогом приблизительно 2 миллионов xml's, которые будут обработаны.

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

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

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

21
задан Fgblanch 6 April 2016 в 10:28
поделиться

8 ответов

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

В противном случае, единственный способ, который я могу придумать, - это использовать File.listFiles (фильтр FileFilter) с фильтром, который всегда возвращает 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()]));
}

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

Кстати, не могли бы вы привести пример имени файла? Они «угадываемы»? Типа

for (int i = 0; i < 100000; i++)
    tryToOpen(String.format("file%05d", i))
11
ответ дан 29 November 2019 в 20:55
поделиться

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

0
ответ дан 29 November 2019 в 20:55
поделиться

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

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

1
ответ дан 29 November 2019 в 20:55
поделиться

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

0
ответ дан 29 November 2019 в 20:55
поделиться

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

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];  
}
-3
ответ дан 29 November 2019 в 20:55
поделиться

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

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

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

8
ответ дан 29 November 2019 в 20:55
поделиться

Если 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
9
ответ дан 29 November 2019 в 20:55
поделиться

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

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

.
0
ответ дан 29 November 2019 в 20:55
поделиться
Другие вопросы по тегам:

Похожие вопросы: