52 關於System.setProperty("user.dir", newUserDir);

前言

之前曾經看過這樣一篇文章 [討論] java啓動目錄(user.dir)的問題 , 裏面出現了一個奇怪的問題 

更新了 user.dir 之後, 創建 File 對象, 獲取路徑(getAbsolutePath) 可以看到獲取到的是 更新之後的路徑結果 

但是當讀取改 File 上面的一些 屬性的時候, 發現 與預期不符(exists), 這裏獲取到的結果是 更新之前的路徑結果 

呵呵 之前也整理了一些東西, 這裏就把這些東西 直接搬過來就好了 

 

以下代碼, 截圖 基於 jdk8 

 

 

測試用例

首先依然是測試用例 

package com.hx.test03;

import java.io.File;

/**
 * UpdateUserDir
 *
 * @author Jerry.X.He <[email protected]>
 * @version 1.0
 * @date 2019-12-29 10:42
 */
public class Test04UpdateUserDir {

  // Test04UpdateUserDir
  public static void main(String[] args) throws Exception {

    System.out.println(System.getProperty("user.dir"));
    File pomFile = new File("pom.xml");
    System.out.println(pomFile.exists() + " - " + pomFile.getAbsolutePath());

    String newUserDir = "/Users/jerry/IdeaProjects/HelloWorld/src/main/java/com/hx/test03";
    System.setProperty("user.dir", newUserDir);
    File thisJavaFile = new File("Test04UpdateUserDir.java");
    System.out.println(thisJavaFile.exists() + " - " + thisJavaFile.getAbsolutePath());

    // 實際的工作空間是固定的, 但是顯示層面上存在問題
    File currentFolder = new File(".");
    System.out.println(currentFolder.isDirectory());
    for(File file : currentFolder.listFiles()) {
      System.out.println(file.getAbsolutePath());
    }

  }

}

 測試用例結果如下 

我們發現結果 似乎是和我們預期的結果不一樣, 但是 和上面 "前言" 裏面分析的結果卻是一致的 

getAbsolutePath 是以 user.dir 更新之後的環境的結果, exists, listFiles 是以更新之前的環境取文件作爲結果 

.. 那麼 這麼神奇 是爲什麼呢? 

R大在文章中 也有回覆, 但是 關於 R大的 "然則File.exists()在Windows上的實現在JDK的C代碼裏,這塊代碼會有current working directory做緩存,於是後面怎麼改"user.dir"都不影響它了。", 我表示沒有驗證出來 可能是 查看的 版本不一致吧, 又或者 水平不夠, stat 傳入的就是 一個相對路徑嘛(這個測試用例), 沒有找到 cwd 的相關處理(os的代碼?)

 

 

getAbsolutePath() 的分析

File. getAbsolutePath

    public String getAbsolutePath() {
        return fs.resolve(this);
    }
UnixFileSystem.resolve 
    public String resolve(File f) {
        if (isAbsolute(f)) return f.getPath();
        return resolve(System.getProperty("user.dir"), f.getPath());
    }

這個問題 比較簡單, 大致就是這樣, 是獲取的 user.dir 拼接上相對路徑的 

 

 

exists() 的分析

呵呵 這個就稍微複雜一點了, 可能需要 c 相關的 api 的瞭解 

File.exists() 

    public boolean exists() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkRead(path);
        }
        if (isInvalid()) {
            return false;
        }
        return ((fs.getBooleanAttributes(this) & FileSystem.BA_EXISTS) != 0);
    }
UnixFileSystem. getBooleanAttributes0
    public native int getBooleanAttributes0(File f);

    public int getBooleanAttributes(File f) {
        int rv = getBooleanAttributes0(f);
        String name = f.getName();
        boolean hidden = (name.length() > 0) && (name.charAt(0) == '.');
        return rv | (hidden ? BA_HIDDEN : 0);
    }

 

UnixFileSystem_md.c 裏面的 Java_java_io_UnixFileSystem_getBooleanAttributes0

JNIEXPORT jint JNICALL
Java_java_io_UnixFileSystem_getBooleanAttributes0(JNIEnv *env, jobject this,
                                                  jobject file)
{
    jint rv = 0;

    WITH_FIELD_PLATFORM_STRING(env, file, ids.path, path) {
        int mode;
        if (statMode(path, &mode)) {
            int fmt = mode & S_IFMT;
            rv = (jint) (java_io_FileSystem_BA_EXISTS
                  | ((fmt == S_IFREG) ? java_io_FileSystem_BA_REGULAR : 0)
                  | ((fmt == S_IFDIR) ? java_io_FileSystem_BA_DIRECTORY : 0));
        }
    } END_PLATFORM_STRING(env, path);
    return rv;
}

 

依賴的 io_util.c 相關宏定義如下 

#define WITH_PLATFORM_STRING(env, strexp, var)
    \
    if (1) {
    \
        const char *var;
    \
        jstring _##var##str = (strexp);
    \
        if (_##var##str == NULL) {
    \
            JNU_ThrowNullPointerException((env), NULL);
    \
            goto _##var##end;
    \
        }
    \
        var = JNU_GetStringPlatformChars((env), _##var##str, NULL);
    \
        if (var == NULL) goto _##var##end;



#define WITH_FIELD_PLATFORM_STRING(env, object, id, var)
    \
    WITH_PLATFORM_STRING(env,
    \
                         ((object == NULL)
    \
                          ? NULL
    \
                          : (*(env))->GetObjectField((env), (object), (id))),
    \
                         var)



#define END_PLATFORM_STRING(env, var)
    \
        JNU_ReleaseStringPlatformChars(env, _##var##str, var);
    \
    _##var##end: ;
    \
    } else ((void)NULL)


 

Java_java_io_UnixFileSystem_getBooleanAttributes0 宏定義替換之後的結果如下 

JNIEXPORT jint JNICALL
Java_java_io_UnixFileSystem_getBooleanAttributes0(JNIEnv *env, jobject this,
 jobject file) 
{

    jint rv = 0;


    if(1) {
        const char *path;
        jstring _pathstr = ( (object == NULL) ? NULL : (*(env))->GetObjectField((env), (object), (ids.path)) );
        if (_pathstr == NULL) {
            JNU_ThrowNullPointerException((env), NULL);
            goto _pathend;


        }
        path = JNU_GetStringPlatformChars((env), _pathstr, NULL);
        if (path == NULL) goto _pathend;



        {

            int mode;

            if (statMode(path, &mode)) {

                int fmt = mode & S_IFMT;

                rv = (jint) (java_io_FileSystem_BA_EXISTS

                | ((fmt == S_IFREG) ? java_io_FileSystem_BA_REGULAR : 0)

                | ((fmt == S_IFDIR) ? java_io_FileSystem_BA_DIRECTORY : 0));

            }

        }

        JNU_ReleaseStringPlatformChars(env, _pathstr, path);
        _pathend: ;
    } else ((void)NULL)

    return rv;

}

 

ids.path 相關定義, 初始化如下 

/* -- Field IDs -- */

static struct {
    jfieldID path;
} ids;


JNIEXPORT void JNICALL
Java_java_io_UnixFileSystem_initIDs(JNIEnv *env, jclass cls)
{
    jclass fileClass = (*env)->FindClass(env, "java/io/File");
    if (!fileClass) return;
    ids.path = (*env)->GetFieldID(env, fileClass,
                                  "path", "Ljava/lang/String;");
}

 

UnixFileSystem_md.c 裏面的 statMode 

#define stat64 stat

#define statvfs64 statvfs

static jboolean
 statMode(const char *path, int *mode)
{

    struct stat64 sb;

    if (stat64(path, &sb) == 0) {

        *mode = sb.st_mode;

        return JNI_TRUE;

    }

    return JNI_FALSE;

}

整理一下 其實就是獲取到 給定的文件的 path(構造函數析之後的絕對路徑或者相對路徑)

然後使用 stat 函數來處理給定的路徑, 獲取文件的狀態信息(java層面的 user.dir 已經發生了變化, 但是c層面的這個 cwd 是沒有變化的), 因此就造成了 測試結果的奇怪現象 

 

 

stat, getcwd, chdir 的使用

呵呵 相關的 api 的一些基礎的使用, 我之前整理了一下 

stat 的使用 

//
// Created by Jerry.X.He on 2019-12-29.
//

#include <iostream>
#include <sys/stat.h>
#include <unistd.h>

using namespace std;

int main() {

    struct stat buf;
//    stat("/etc/hosts", &buf);
    stat("out.txt", &buf);
    printf("file size = %d \n", (int) buf.st_size);

    return 0;

}

 

測試結果如下 

 

getcwd 的使用 

//
// Created by Jerry.X.He on 2019-12-29.
//

#include <stdio.h>
#include <unistd.h>

int main() {

    char buf[80];
    getcwd(buf,sizeof(buf));
    printf("current working directory: %s\n", buf);

    return 0;

}

測試結果如下 

 

 

chdir 的使用

//
// Created by Jerry.X.He on 2019-12-29.
//

#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>

int main() {

    // update cwd
    chdir("/Users/jerry/ClionProjects/HelloWorld");

    // print cwd
    char buf[80];
    getcwd(buf,sizeof(buf));
    printf("current working directory: %s\n", buf);

    // use cwd 
    struct stat stats;
    stat("Test06UpdatePwd.cpp", &stats);
    printf("file size = %d \n", (int) stats.st_size);

    return 0;

}

 

 

完 

 

 

參考 

[討論] java啓動目錄(user.dir)的問題

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章