在Java中找到用户的主目录的最佳方法是什么?

360

在Java中查找用户主目录的最佳方法是什么?

难点在于解决方案应该是跨平台的;它应该在Windows 2000、XP、Vista、OS X、Linux和其他Unix变体上工作。我正在寻找一段代码片段,可以为所有平台完成此操作,并且需要一种检测平台的方法。

根据Java bug 4787931,系统属性user.home在Windows XP上无法正常工作,因此使用此系统属性不是可接受的解决方案,因为它不是跨平台的。


1
你尝试过在错误报告中提到的解决方法了吗?有很多建议。 - Joachim Sauer
1
Java版本1.4.2及以下的Bug 4787931再次出现为Java 1.6的Bug 6519127。该问题并未消失,仍被列为低优先级。 - GregA100k
25
注意:Java 8中已标记修复了bug 4787391。 - Steven R. Loomis
有关Windows的讨论,请参见https://dev59.com/4nI_5IYBdhLWcg3wJfkM。 - koppor
9个回答

460

你提到的漏洞(bug 4787391)已经在Java 8中修复。即使您正在使用旧版本的Java,System.getProperty("user.home")方法可能仍然是最佳选择。在许多情况下,user.home方法似乎是有效的。在Windows上,100%完美的解决方案很难实现,因为Windows对“主目录”的定义是不断变化的。

如果user.home不能满足您的需求,我建议为Windows选择一个“主目录”定义,并使用System.getenv(String)获取适当的环境变量。


8
Apache有一个出色的包装器,可用于System.getProperty调用,我建议使用它。在该包装器下正确的调用应该是SystemUtils.getUserHome() - Varun Mathur

171

实际上,使用Java 8的正确方法是:

System.getProperty("user.home");

漏洞JDK-6519127已经修复,版本说明中的“JDK 8和JDK 7之间的不兼容性”部分说明:

领域:核心库 / java.lang

简介

用于在Windows上确定用户主目录的步骤已更改为遵循Microsoft推荐的方法。此更改可能在旧版Windows或设置了其他目录的注册表设置或环境变量的情况下可观察到。不兼容性质

behavioral RFE

6519127
尽管这个问题很旧,我把它留下来供以后参考。

41
System.getProperty("user.home");

请查看JavaDoc


13
不是正确的答案,这与上面的答案相同。是的,我不仅阅读了Java文档,还在提问之前在所有平台上进行了尝试!答案并不那么简单。 - Bruno Ranschaert
3
这可能会在Windows上出现严重问题,因为它只会获取桌面目录的父级目录,而该目录可能位于任何地方... - Chronial

32

在Windows中,HOME目录的概念似乎有点模糊。如果环境变量(HOMEDRIVE/HOMEPATH/USERPROFILE)不足够,您可能需要通过JNIJNA使用本机函数。SHGetFolderPath允许您检索特殊文件夹,例如我的文档(CSIDL_PERSONAL)或本地设置\应用程序数据(CSIDL_LOCAL_APPDATA)。

JNA代码示例:

public class PrintAppDataDir {

    public static void main(String[] args) {
        if (com.sun.jna.Platform.isWindows()) {
            HWND hwndOwner = null;
            int nFolder = Shell32.CSIDL_LOCAL_APPDATA;
            HANDLE hToken = null;
            int dwFlags = Shell32.SHGFP_TYPE_CURRENT;
            char[] pszPath = new char[Shell32.MAX_PATH];
            int hResult = Shell32.INSTANCE.SHGetFolderPath(hwndOwner, nFolder,
                    hToken, dwFlags, pszPath);
            if (Shell32.S_OK == hResult) {
                String path = new String(pszPath);
                int len = path.indexOf('\0');
                path = path.substring(0, len);
                System.out.println(path);
            } else {
                System.err.println("Error: " + hResult);
            }
        }
    }

    private static Map<String, Object> OPTIONS = new HashMap<String, Object>();
    static {
        OPTIONS.put(Library.OPTION_TYPE_MAPPER, W32APITypeMapper.UNICODE);
        OPTIONS.put(Library.OPTION_FUNCTION_MAPPER,
                W32APIFunctionMapper.UNICODE);
    }

    static class HANDLE extends PointerType implements NativeMapped {
    }

    static class HWND extends HANDLE {
    }

    static interface Shell32 extends Library {

        public static final int MAX_PATH = 260;
        public static final int CSIDL_LOCAL_APPDATA = 0x001c;
        public static final int SHGFP_TYPE_CURRENT = 0;
        public static final int SHGFP_TYPE_DEFAULT = 1;
        public static final int S_OK = 0;

        static Shell32 INSTANCE = (Shell32) Native.loadLibrary("shell32",
                Shell32.class, OPTIONS);

        /**
         * see http://msdn.microsoft.com/en-us/library/bb762181(VS.85).aspx
         * 
         * HRESULT SHGetFolderPath( HWND hwndOwner, int nFolder, HANDLE hToken,
         * DWORD dwFlags, LPTSTR pszPath);
         */
        public int SHGetFolderPath(HWND hwndOwner, int nFolder, HANDLE hToken,
                int dwFlags, char[] pszPath);

    }

}

请注意,对应于用户主目录的文件夹是CSIDL_PROFILE。请参阅http://msdn.microsoft.com/en-us/library/bb762494(VS.85).aspx。 - Matt Solnit
是的,这是一个针对Windows情况的详细版本。 - Bruno Ranschaert
2
在最近的 JNA 版本中(更准确地说是 jna-platform),有一个 Shell32Util 类,以非常好的方式封装了相应的 Windows API。特别是,使用 Shell32Util.getKnownFolderPath(...) 结合 KnownFolders 类中的一个常量应该是恰当的。自 Windows Vista 开始,旧的 getFolderPath API 函数已被弃用。 - Sebastian Marsching

18

虽然之前已经有人回答了这个问题,但是一个有用的程序可以打印出所有可用的属性:

for (Map.Entry<?,?> e : System.getProperties().entrySet()) {
    System.out.println(String.format("%s = %s", e.getKey(), e.getValue())); 
}

1
我不会依赖于这个,因为并非所有属性都是标准化的。相反,检查JavaDoc中的System.getProperties(),以找出保证存在的属性。 - Joachim Sauer
7
也许那是真的,但我认为对于新手来说仍然非常有用!我不确定它值得两个踩 :-( - oxbow_lakes

14

另一种选择是使用Apache CommonsIO FileUtils.getUserDirectory()而不是System.getProperty("user.home")。 它将为您提供相同的结果,并且在指定系统属性时没有引入输错的机会。

很有可能您已经在项目中使用了Apache CommonsIO库。 如果您计划仅用于获取用户主目录,请勿引入它。


1
File FileUtils.getUserDirectory() | String FileUtils.getUserDirectoryPath() - https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/FileUtils.htmlFile FileUtils.getUserDirectory() 返回当前用户的主目录作为文件对象。String FileUtils.getUserDirectoryPath() 返回当前用户的主目录路径作为字符串。 详见:https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/FileUtils.html - Pang

7

当我在寻找Scala版本时,我只能找到上面的McDowell的JNA代码。由于目前没有更合适的地方,我在这里提供我的Scala版本。

import com.sun.jna.platform.win32._
object jna {
    def getHome: java.io.File = {
        if (!com.sun.jna.Platform.isWindows()) {
            new java.io.File(System.getProperty("user.home"))
        }
        else {
            val pszPath: Array[Char] = new Array[Char](WinDef.MAX_PATH)
            new java.io.File(Shell32.INSTANCE.SHGetSpecialFolderPath(null, pszPath, ShlObj.CSIDL_MYDOCUMENTS, false) match {
                case true => new String(pszPath.takeWhile(c => c != '\0'))
                case _    => System.getProperty("user.home")
            })
        }
    }
}

与Java版本一样,您需要添加Java Native Access,包括两个jar文件,到您的引用库中。
很高兴看到JNA现在比原始代码发布时更容易实现这一点。

2
我会使用在Bug报告中详细介绍的算法,使用System.getenv(String)函数,并在没有环境变量指示有效现有目录的情况下回退到使用user.dir属性。这应该可以跨平台运行。
我认为,在Windows下,您真正需要的是用户的“文档”目录。

0
如果你想要在Windows上使用一个良好的软件包,那么有一个叫做WinFoldersJava的软件包,它封装了调用Windows上“特殊”目录的本地调用。我们经常使用它,而且它运行良好。

链接失效 - “404 | 这不是您要查找的网页” - Pang

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接