我正在尝试一次处理一个通过网络存储的文件。由于缓冲不是问题,因此读取文件很快。我遇到的问题只是列出文件夹中的目录。在多个文件夹中,每个文件夹至少有10k个文件。
由于File.list()返回一个数组而不是一个可迭代的数据,因此性能非常慢。 Java关闭并收集文件夹中的所有名称,并在返回之前将其打包成数组。
此错误条目为http://bugs.sun.com/view_bug.do;jsessionid=db7fcf25bcce13541c4289edeb4?bug_id=4285834,但没有解决方法。他们只是说这已经为JDK7修复了。
几个问题:
答案 0 :(得分:7)
虽然它不漂亮,但我在启动应用程序之前将dir / ls的输出传递给文件并传入文件名,解决了这种问题。
如果您需要在应用程序中执行此操作,您可以使用system.exec(),但这会产生一些肮脏。
你问道。第一种形式将非常快,第二种形式也应该非常快。确保每行执行一项(裸露,无装饰,无图形),完整路径并递归所选命令的选项。
编辑:
30分钟才获得目录列表,哇。
让我感到震惊的是,如果使用exec(),你可以将它的stdout重定向到管道而不是将其写入文件。
如果你这样做,你应该立即开始获取文件,并能够在命令完成之前开始处理。
这种互动实际上可能会让事情变慢,但也许不会 - 你可能会尝试一下。
哇,我刚刚为你找到.exec命令的语法并遇到了这个,可能正是你想要的(它使用exec和“ls”列出一个目录,并将结果通过管道传输到你的程序中) :good link in wayback(Jörg在评论中提供了将this one替换为Oracle破坏的sun)
无论如何,这个想法很简单,但正确的代码是令人讨厌的。我将从互联网中窃取一些代码并将其破解 - brb
/** * 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
答案 1 :(得分:5)
如何使用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));
}
}
答案 2 :(得分:4)
另一种方法是让文件通过不同的协议提供服务。据我所知你正在使用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文件信息提取到你的客户端机器上(我不知道有多少信息)供以后处理。
如果现在处理速度非常快,可能会慢一点。这是因为预取的信息不再可用。
试一试。
答案 3 :(得分:3)
我怀疑问题与您引用的错误报告有关。 问题是“仅”内存使用,但不一定是速度。 如果您有足够的内存,则该错误与您的问题无关。
您应该衡量您的问题是否与内存有关。打开垃圾收集器日志并使用例如gcviewer来分析您的内存使用情况。
我怀疑它与导致问题的SMB协议有关。 您可以尝试用另一种语言编写测试并查看它是否更快,或者您可以尝试通过其他方法获取文件名列表,如此处另一篇文章中所述。
答案 4 :(得分:2)
非便携式解决方案是对操作系统进行本机调用并传输结果。
适用于Linux
您可以查看readdir之类的内容。您可以像链接列表一样遍历目录结构,并分批或单独返回结果。
适用于Windows
在Windows中,使用FindFirstFile和FindNextFile apis的行为非常相似。
答案 5 :(得分:1)
如果你需要最终处理所有文件,那么通过String []进行Iterable将不会给你带来任何好处,因为你仍然需要去获取整个文件列表。
答案 6 :(得分:1)
如果您使用的是Java 1.5或1.6,那么在Windows上解析“dir”命令并解析标准输出流是一种完全可以接受的方法。我过去曾使用这种方法处理网络驱动器,它通常比等待本机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.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;
}
您可以在javaxt-core库中找到这个基于JNI的方法的完整工作示例。在我使用Java 1.6.0_38和Windows主机命中Windows共享的测试中,我发现这个JNI方法比调用java.io.File listFiles()或炮轰“dir”命令快大约10倍。
答案 7 :(得分:0)
我想知道为什么目录中有10k个文件。某些文件系统无法与这么多文件一起使用。文件系统存在特定限制,例如每个目录的最大文件数量和子目录的最大级别数。
我用迭代器解决方案解决了类似的问题。
我需要递归地遍历巨大的directorys和几级目录树。
我尝试Apache commons io的FileUtils.iterateFiles()。但它通过添加List中的所有文件然后返回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();
}
}
答案 8 :(得分:0)
使用Iterable并不意味着文件将流式传输给您。实际上它通常是相反的。因此,数组通常比Iterable快。
答案 9 :(得分:0)
您确定这是由于Java,而不仅仅是在一个目录中有10k条目的一般问题,特别是在网络上?
您是否尝试使用win32 findfirst / findnext函数编写概念验证程序以在C中执行相同操作以查看它是否更快?
我不知道SMB的来龙去脉,但我强烈怀疑它需要对列表中的每个文件进行往返 - 这不会很快,特别是在中等延迟的网络上。
在一个数组中有10k个字符串听起来像是不应该过多地对现代Java VM征税的东西。