为什么Groovy eachDir()每次都给予我相同的排序?

vnzz0bqm  于 2022-11-01  发布在  其他
关注(0)|答案(1)|浏览(114)

我正在创建一个包含子目录列表的文件

task createNotes {
  doLast {
    def myFile = new File("my-notes.txt")
    def file = new File("src/test/")
    println myFile.exists()
    myFile.delete()
    println myFile.exists()
    println myFile.absolutePath
    println file.absolutePath
    myFile.withWriter {
      out ->
        file.eachDir { dir ->
          out.println dir.getName()
        }
    }
  }
}

显然,排序顺序无法保证,但每次运行它时,都得到相同的排序顺序:

soft
java
calc
conc
caab
pres

如果我将“soft”dir更改为“sofp”,则输出为:

java
sofp
calc
conc
caab
pres

当我把名字改回来时,它就会回到原来的顺序。
它似乎没有按任何特定的顺序排序--这与文档中所说的顺序不能得到保证相匹配,但如果是这样,为什么它每次都给我相同的排序?

t9eec4r0

t9eec4r01#

让我们先来看看eachDir Groovy扩展方法的实现:

public static void eachDir(File self, @ClosureParams(value = SimpleType.class, options = "java.io.File") Closure closure) throws FileNotFoundException, IllegalArgumentException {
    eachFile(self, FileType.DIRECTORIES, closure);
}

eachFile的功能是什么?

public static void eachFile(final File self, final FileType fileType, @ClosureParams(value = SimpleType.class, options = "java.io.File") final Closure closure)
        throws FileNotFoundException, IllegalArgumentException {
    checkDir(self);
    final File[] files = self.listFiles();
    // null check because of http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4803836
    if (files == null) return;
    for (File file : files) {
        if (fileType == FileType.ANY ||
                (fileType != FileType.FILES && file.isDirectory()) ||
                (fileType != FileType.DIRECTORIES && file.isFile())) {
            closure.call(file);
        }
    }
}

好的,Groovy只是在幕后调用Java的File#listFiles方法,然后在不干扰现有顺序的情况下迭代结果。
转到OpenJDK实现,我们可以看到Files#listFiles通过normalizedList方法使用FileSystem#list
FileSystem#list是抽象的。继续讨论两个最流行的实现,结果是UnixFileSystem#listWin32FileSystem#list都有一个native实现:

@Override
public native String[] list(File f);

窗口

深入了解Windows实现:

JNIEXPORT jobjectArray JNICALL
Java_java_io_WinNTFileSystem_list(JNIEnv *env, jobject this, jobject file)
{
    WCHAR *search_path;
    HANDLE handle;
    WIN32_FIND_DATAW find_data;
    int len, maxlen;
    jobjectArray rv, old;
    DWORD fattr;
    jstring name;
    jclass str_class;
    WCHAR *pathbuf;
    DWORD err;

    str_class = JNU_ClassString(env);
    CHECK_NULL_RETURN(str_class, NULL);

    pathbuf = fileToNTPath(env, file, ids.path);
    if (pathbuf == NULL)
        return NULL;
    search_path = (WCHAR*)malloc(2*wcslen(pathbuf) + 6);
    if (search_path == 0) {
        free (pathbuf);
        errno = ENOMEM;
        JNU_ThrowOutOfMemoryError(env, "native memory allocation failed");
        return NULL;
    }
    wcscpy(search_path, pathbuf);
    free(pathbuf);
    fattr = GetFileAttributesW(search_path);
    if (fattr == INVALID_FILE_ATTRIBUTES) {
        free(search_path);
        return NULL;
    } else if ((fattr & FILE_ATTRIBUTE_DIRECTORY) == 0) {
        free(search_path);
        return NULL;
    }

    /* Remove trailing space chars from directory name */
    len = (int)wcslen(search_path);
    while (search_path[len-1] == L' ') {
        len--;
    }
    search_path[len] = 0;

    /* Append "*", or possibly "\\*", to path */
    if ((search_path[0] == L'\\' && search_path[1] == L'\0') ||
        (search_path[1] == L':'
        && (search_path[2] == L'\0'
        || (search_path[2] == L'\\' && search_path[3] == L'\0')))) {
        /* No '\\' needed for cases like "\" or "Z:" or "Z:\" */
        wcscat(search_path, L"*");
    } else {
        wcscat(search_path, L"\\*");
    }

    /* Open handle to the first file */
    handle = FindFirstFileW(search_path, &find_data);
    free(search_path);
    if (handle == INVALID_HANDLE_VALUE) {
        if (GetLastError() != ERROR_FILE_NOT_FOUND) {
            // error
            return NULL;
        } else {
            // No files found - return an empty array
            rv = (*env)->NewObjectArray(env, 0, str_class, NULL);
            return rv;
        }
    }

    /* Allocate an initial String array */
    len = 0;
    maxlen = 16;
    rv = (*env)->NewObjectArray(env, maxlen, str_class, NULL);
    if (rv == NULL) { // Couldn't allocate an array
        FindClose(handle);
        return NULL;
    }
    /* Scan the directory */
    do {
        if (!wcscmp(find_data.cFileName, L".")
                                || !wcscmp(find_data.cFileName, L".."))
           continue;
        name = (*env)->NewString(env, find_data.cFileName,
                                 (jsize)wcslen(find_data.cFileName));
        if (name == NULL) {
            FindClose(handle);
            return NULL; // error
        }
        if (len == maxlen) {
            old = rv;
            rv = (*env)->NewObjectArray(env, maxlen <<= 1, str_class, NULL);
            if (rv == NULL || JNU_CopyObjectArray(env, rv, old, len) < 0) {
                FindClose(handle);
                return NULL; // error
            }
            (*env)->DeleteLocalRef(env, old);
        }
        (*env)->SetObjectArrayElement(env, rv, len++, name);
        (*env)->DeleteLocalRef(env, name);

    } while (FindNextFileW(handle, &find_data));

    err = GetLastError();
    FindClose(handle);
    if (err != ERROR_NO_MORE_FILES) {
        return NULL; // error
    }

    if (len < maxlen) {
        /* Copy the final results into an appropriately-sized array */
        old = rv;
        rv = (*env)->NewObjectArray(env, len, str_class, NULL);
        if (rv == NULL)
            return NULL; /* error */
        if (JNU_CopyObjectArray(env, rv, old, len) < 0)
            return NULL; /* error */
    }
    return rv;
}

我们可以看到FindFirstFileWFindNextFileWFindClose WinAPI函数的组合用于迭代文件。
搜索返回文件的顺序(如字母顺序)并不保证,它取决于文件系统。
(...)
此函数返回文件名的顺序取决于文件系统类型。对于NTFS文件系统和CDFS文件系统,通常按字母顺序返回文件名。对于FAT文件系统,通常按文件写入磁盘的顺序返回文件名,可能按字母顺序,也可能不按字母顺序。但是,如前所述,这些行为并不保证。
因此,在给定操作系统和文件系统类型约束的情况下,实现以一种最优化的方式列出文件。

* 无

那么 *nix systems呢?代码如下:

JNIEXPORT jobjectArray JNICALL
Java_java_io_UnixFileSystem_list(JNIEnv *env, jobject this,
                                 jobject file)
{
    DIR *dir = NULL;
    struct dirent *ptr;
    int len, maxlen;
    jobjectArray rv, old;
    jclass str_class;

    str_class = JNU_ClassString(env);
    CHECK_NULL_RETURN(str_class, NULL);

    WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
        dir = opendir(path);
    } END_PLATFORM_STRING(env, path);
    if (dir == NULL) return NULL;

    /* Allocate an initial String array */
    len = 0;
    maxlen = 16;
    rv = (*env)->NewObjectArray(env, maxlen, str_class, NULL);
    if (rv == NULL) goto error;

    /* Scan the directory */
    while ((ptr = readdir(dir)) != NULL) {
        jstring name;
        if (!strcmp(ptr->d_name, ".") || !strcmp(ptr->d_name, ".."))
            continue;
        if (len == maxlen) {
            old = rv;
            rv = (*env)->NewObjectArray(env, maxlen <<= 1, str_class, NULL);
            if (rv == NULL) goto error;
            if (JNU_CopyObjectArray(env, rv, old, len) < 0) goto error;
            (*env)->DeleteLocalRef(env, old);
        }

# ifdef MACOSX

        name = newStringPlatform(env, ptr->d_name);

# else

        name = JNU_NewStringPlatform(env, ptr->d_name);

# endif

        if (name == NULL) goto error;
        (*env)->SetObjectArrayElement(env, rv, len++, name);
        (*env)->DeleteLocalRef(env, name);
    }
    closedir(dir);

    /* Copy the final results into an appropriately-sized array */
    if (len < maxlen) {
        old = rv;
        rv = (*env)->NewObjectArray(env, len, str_class, NULL);
        if (rv == NULL) {
            return NULL;
        }
        if (JNU_CopyObjectArray(env, rv, old, len) < 0) {
            return NULL;
        }
    }
    return rv;

 error:
    closedir(dir);
    return NULL;
}

opendir/readdir/closedir三人组支持这种时间迭代,POSIX documentation of readdir只提到了排序:
在头中定义的类型DIR<dirent.h>表示目录流,该目录流是特定目录中的所有目录条目的有序序列。
Linux documentation还有更多的内容要说:
对readdir()的连续调用读取文件名的顺序取决于文件系统实现;不太可能以任何方式对姓名进行排序。
离Windows足够近的地方,除了有一些秩序之外,没有任何秩序保证。

结论

“不保证”意味着一个特定的特性是一个实现细节,您不应该依赖它。与“保证”特性相反,由于向后兼容性,它保证了对某些对这些特性的更改称为breaking changes,通常在release notes和迁移指南中有详细的说明(例如,参见Vue 3 migration guide)。它们也会在最终确定和发布之前很久就被宣布-参见deprecation(通常会给开发人员留下一些时间来调整他们的代码,即使在它们上线之后)。
另一方面,“不保证”功能的行为可能因给定的产品/库版本、环境(例如JVM实作、操作系统、产品版本)或什至是特定的呼叫。它们有时候会显示出一些可预测的特性,但不应依赖它们。您需要确保处理好您期望代码片段拥有的保证,并自行实现它们。

总结您的问题:如果您希望文件有任何特定的顺序,请先对它们进行排序-即使这意味着顺序将保持不变。

相关问题