Я пытаюсь обрабатывать файлы по одному, которые хранятся в сети. Чтение файлов происходит быстро, потому что буферизация не является проблемой. Проблема, с которой я столкнулся, - это просто список каталогов в папке. У меня есть как минимум 10k файлов в папке во многих папках.
Производительность очень медленная, поскольку File.list () возвращает массив вместо итерируемого. Java отключается, собирает все имена в папке и упаковывает их в массив перед возвратом.
Запись об ошибке для этого - http://bugs.sun.com/view_bug.do;jsessionid=db7fcf25bcce13541c4289edeb4?bug_id=4285834, и ее нельзя обойти. Они просто говорят, что это было исправлено для JDK7.
Несколько вопросов:
Я понимаю, что это очень старый вопрос, но для всех, кто использует последнюю версию JDK7, эта функция теперь доступна через Files.newDirectoryStream() API в пакете java.nio.file.
@AdamRosenfield Спасибо! :)
Кстати, мы смогли значительно повысить производительность, широко используя подкаталоги, чтобы ограничить количество файлов / каталогов в любом отдельном каталоге. Определенные файловые операции, такие как проверка файлов и запись, стали намного быстрее, но некоторые вещи, такие как du и rm -rf, занимали больше времени. Это компромисс, в зависимости от того, что более важно для вашего варианта использования.




Непереносимым решением было бы выполнение собственных вызовов операционной системы и потоковая передача результатов.
Для Linux
Вы можете посмотреть что-то вроде Readdir. Вы можете просматривать структуру каталогов как связанный список и возвращать результаты группами или по отдельности.
Для Windows
В Windows поведение будет примерно таким же, используя API FindFirstFile и FindNextFile.
пока согласен с тем, что вы можете захотеть обернуть FFF / FNF, как предлагается здесь.
Имейте в виду, что преодоление естественного барьера само по себе повлияет на производительность. Если вы воспользуетесь этим подходом, вы можете рассмотреть возможность пакетирования нескольких результатов FNF для каждого вызова JNI (или, возможно, использовать DirectByteBuffer для передачи данных)
+1 за предложение FFF / FNF. Ниже я опубликовал рабочий пример.
Альтернативой является обслуживание файлов по другому протоколу. Насколько я понимаю, вы используете для этого SMB, а java просто пытается перечислить их как обычный файл.
Проблема здесь может быть не только в java (как она себя ведет, когда вы открываете этот каталог с помощью Microsoft Explorer x: \ shared). По моему опыту, это также занимает значительное количество времени.
Вы можете изменить протокол на что-то вроде HTTP, только чтобы получить имена файлов. Таким образом, вы можете получить список файлов через http (10k строк не должно быть слишком много) и позволить серверу обрабатывать список файлов. Это будет очень быстро, так как он будет работать с локальными ресурсами (теми, что на сервере).
Затем, когда у вас есть список, вы можете обрабатывать их по одному точно так же, как вы делаете сейчас.
Ключевым моментом является наличие вспомогательного механизма на другой стороне узла.
Возможно ли это?
Сегодня:
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 на ваш клиентский компьютер (я не знаю, сколько из этой информации), когда вам нужно только имя файла для последующей обработки. .
Если сейчас обработка идет очень быстро, она может немного замедлиться. Это связано с тем, что предварительно загруженная информация больше недоступна.
Попробуйте.
это подразумевает множество вещей через: веб-сервер, реализацию какой-то безопасности, которая уже есть в SMB и т. д., и так далее.
Не совсем. Разве у нас нет веб-сайта в локальной сети? Думаю, это уже решенный вопрос. Хотя теперь, когда вы упомянули об этом, я понятия не имею, как настроить веб-сайт, который будет виден только в локальной сети. Я почти уверен, что файл .exe, который файлы сервера и использует проверку подлинности Windows, уже существует.
Разве IIS не поддерживает такую функциональность? ... Я думаю, что он может очень легко аутентифицировать пользователей окна. Но я согласен, это непрозрачно. Однако я считаю, что это намного проще / быстрее, чем переносить собственный код через JNI и по-прежнему использовать SMB в качестве протокола.
Хотя это некрасиво, я однажды решил эту проблему, направив вывод dir / ls в файл перед запуском моего приложения и передав имя файла.
Если вам нужно сделать это в приложении, вы можете просто использовать system.exec (), но это создаст некоторую неприятность.
Ты спросил. Первая форма будет невероятно быстрой, вторая тоже должна быть довольно быстрой.
Обязательно укажите один элемент в строке (пустой, без украшения, без графики), полный путь и параметры рекурсии выбранной вами команды.
Обновлено:
30 минут только на то, чтобы получить список каталогов, ничего себе.
Меня просто поразило, что если вы используете exec (), вы можете перенаправить его stdout в канал вместо того, чтобы записывать его в файл.
Если вы это сделали, вы должны немедленно начать получать файлы и иметь возможность начать обработку до завершения команды.
Взаимодействие может на самом деле замедлить процесс, но, возможно, и нет - вы можете попробовать.
Вау, я просто пошел, чтобы найти для вас синтаксис команды .exec, и наткнулся на это, возможно, именно то, что вы хотите (он перечисляет каталог с использованием exec и "ls" и передает результат в вашу программу для обработки): хорошая ссылка на обратном пути ( Йорг предоставил в комментарии замену Вот этот от sun, который сломал Oracle)
В любом случае идея проста, но правильный код раздражает. Я пойду украду коды из интернета и взломаю их - брб
/**
* Note: Only use this as a last resort! It's specific to windows and even
* at that it's 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's 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
это хорошее решение для статических деревьев или деревьев, которые не часто меняются, но вам нужно часто их пересекать
Да, я использовал его для случаев, когда это была программа с однократным запуском. Если бы вы делали это часто, я не уверен, что использование exec () для воссоздания этого файла не сработает, но, несмотря на это, это неэлегантное решение, подверженное ошибкам.
Я просто попробовал это, и «dir / B / S ...» занял около 30 минут, чтобы собрать более 200 МБ путей, в то время как для пересечения того же дерева с помощью File.list () потребовалось около 3,5 часов.
Я собираюсь принять это решение, так как оно, вероятно, решит проблему в большинстве случаев.
Билл: Упомяните, что вы должны использовать флаг, который сообщает dir / ls нет о сортировке результатов (так что начинайте получать имена немедленно): «ls -U». Похоже, такой опции для "dir" нет: /
@BillK: ссылка в вашем ответе мертва. В любом случае вы можете добавить какие-то детали в ответ?
Хорошо, теперь я официально ненавижу Oracle.
@BillK есть резервная копия вашей неработающей ссылки, доступная на машине обратного пути здесь: web.archive.org/web/20080912133319/http://java.sun.com/…
@AaronDigulla dir /b делает свое дело (хотя в Windows 7 в 2014 году)
Я сомневаюсь, что проблема связана с сообщением об ошибке, на которое вы ссылались. Проблема заключается в использовании «только» памяти, но не обязательно в скорости. Если у вас достаточно памяти, ошибка не имеет отношения к вашей проблеме.
Вы должны определить, связана ли ваша проблема с памятью или нет. Включите журнал сборщика мусора и используйте, например, gcviewer для анализа использования памяти.
Я подозреваю, что проблема связана с протоколом SMB. Вы можете попробовать написать тест на другом языке и посмотреть, будет ли он быстрее, или вы можете попытаться получить список имен файлов каким-либо другим способом, например, описанным здесь в другом сообщении.
Интересно, почему в каталоге 10к файлов. Некоторые файловые системы не работают с таким большим количеством файлов. Существуют специфические ограничения для файловых систем, такие как максимальное количество файлов в каталоге и максимальное количество уровней подкаталога.
Решаю аналогичную проблему итератором.
Мне нужно было рекурсивно пройтись по огромным каталогам и нескольким уровням дерева каталогов.
Я пробую 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();
}
}
? В любом случае вы используете listFiles (), что приведет к такой же производительности. Ваш код ничего не делает. listFiles (). iterator () был бы таким же.
Если вы хотите рекурсивно пройти через 5 уровней подкаталогов с 10.000 файлами в каталоге listFiles.iterator (), это убьет вашу память. Для нескольких файлов в одном каталоге это то же самое.
Если вам нужно в конечном итоге обработать все файлы, то использование Iterable вместо String [] не даст вам никаких преимуществ, так как вам все равно придется пойти и получить весь список файлов.
Использование Iterable не означает, что файлы будут переданы вам в потоковом режиме. На самом деле обычно все наоборот. Таким образом, массив обычно быстрее, чем Iterable.
Вы уверены, что это из-за Java, а не просто из-за общей проблемы с наличием 10 тыс. Записей в одном каталоге, особенно в сети?
Пробовали ли вы написать программу проверки концепции, чтобы сделать то же самое на C, используя функции win32 findfirst / findnext, чтобы увидеть, будет ли она быстрее?
Я не разбираюсь в тонкостях SMB, но сильно подозреваю, что для каждого файла в списке требуется круговой обход, что не будет быстрым, особенно в сети с умеренной задержкой.
Наличие 10k строк в массиве звучит как нечто, что не должно слишком сильно обременять современную виртуальную машину Java.
Если вы используете Java 1.5 или 1.6, использование команд "dir" и анализ стандартного потока вывода в Windows - вполне приемлемый подход. Я использовал этот подход в прошлом для обработки сетевых дисков, и обычно это было намного быстрее, чем ожидание возврата собственного метода 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 делает под капотом, но если вам нужны дополнительные атрибуты файла, кроме пути к файлу (например, размер, дата модификации и т.д.), я обнаружил, что следующая функция JNI намного быстрее, чем создание экземпляра java .io.Файловый объект в сети путь.
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».
Как насчет использования метода File.list (фильтр FilenameFilter) и реализации FilenameFilter.accept (File dir, String name) для обработки каждого файла и возврата false.
Я запустил это на Linux vm для каталога с более чем 10 КБ файлов, и это заняло <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));
}
}
в окнах, перечисление формы удаленной папки> 20 секунд перешло к 500 мс ... +1
Какая у вас целевая архитектура? Вам нужно поддерживать кроссплатформенное исполнение?