Я должен иметь дело с каталогом приблизительно 2 миллионов xml's, которые будут обработаны.
Я уже решил обработку, распределяющую работу между машинами и очередями использования потоков, и все идет право.
Но теперь большой проблемой является узкое место чтения каталога с 2 миллионами файлов для заполнения очередей инкрементно.
Я попытался использовать File.listFiles()
метод, но это дает мне Java out of memory: heap space
исключение. Какие-либо идеи?
Во-первых, есть ли у вас возможность использовать 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))
Если имена файлов соответствуют определенным правилам, вы можете использовать File.list (фильтр)
вместо File.listFiles
, чтобы получить управляемые части списка файлов. .
Почему вы все равно храните 2 миллиона файлов в одном каталоге? Я могу представить, что это ужасно тормозит доступ уже на уровне ОС.
Я бы определенно хотел разделить их на подкаталоги (например, по дате / времени создания) еще до обработки. Но если по каким-то причинам это невозможно, можно ли это сделать во время обработки? Например. переместить 1000 файлов, поставленных в очередь для Process1, в Directory1, еще 1000 файлов для Process2 в Directory2 и т. д. Затем каждый процесс / поток видит только (ограниченное количество) файлов, выделенных для него.
Сначала вы можете попытаться увеличить память вашей JVM, передав -Xmx1024m, например.
Попробуй, мне это подходит, но у меня не так много документов ...
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];
}
Используйте File.list ()
вместо File.listFiles ()
- объекты String
, которые он возвращает, потребляют меньше памяти, чем File
, и (что более важно, в зависимости от расположения каталога) они не содержат полного имени пути.
Затем создайте объекты File
по мере необходимости при обработке результата.
Однако это не сработает и для произвольно больших каталогов. В целом лучше организовать файлы в иерархии каталогов, чтобы ни один каталог не содержал более нескольких тысяч записей.
Если 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
Пожалуйста, опубликуйте полную трассировку стека исключения OOM, чтобы определить, где находится узкое место, а также короткую, полную программу на Java, демонстрирующую поведение, которое вы видите.
Скорее всего, это происходит потому, что вы собираете все два миллиона записей в памяти, и они не помещаются. Можете ли вы увеличить место в куче?
.