¿Cuál es la mejor manera de encontrar el directorio de inicio de los usuarios en Java?

280

La dificultad es que debe ser multiplataforma. Windows 2000, XP, Vista, OSX, Linux, otras variantes de Unix. Estoy buscando un fragmento de código que pueda lograr esto para todas las plataformas, y una forma de detectar la plataforma.

Ahora, debe tener en cuenta el error 4787931 que user.homeno funciona correctamente, así que no me proporcione las respuestas de los libros de texto, puedo encontrarlas en los manuales.

Bruno Ranschaert
fuente
1
¿Intentó las soluciones alternativas mencionadas en el error? Hay muchas sugerencias
Joachim Sauer
1
El error 4787931 para las versiones de Java hasta la 1.4.2 se muestra nuevamente como error 6519127 para Java 1.6. El problema no desaparece y sigue figurando como de baja prioridad.
GregA100k
16
Nota: el error 4787391 está marcado como solucionado en Java 8
Steven R. Loomis

Respuestas:

366

El error al que hace referencia (error 4787391) se ha corregido en Java 8. Incluso si está utilizando una versión anterior de Java, el System.getProperty("user.home")enfoque probablemente sea el mejor. El user.homeenfoque parece funcionar en una gran cantidad de casos. Una solución 100% a prueba de balas en Windows es difícil, porque Windows tiene un concepto cambiante de lo que significa el directorio de inicio.

Si user.homeno es lo suficientemente bueno para que yo sugeriría que la elección de una definición de home directorypara las ventanas y su uso, consiguiendo la variable de entorno apropiado con System.getenv(String).

DJClayworth
fuente
136

En realidad, con Java 8, la forma correcta es usar:

System.getProperty("user.home");

El error JDK-6519127 se ha solucionado y la sección "Incompatibilidades entre JDK 8 y JDK 7" de las notas de la versión dice:

Área: Core Libs / java.lang

Sinopsis

Los pasos utilizados para determinar el directorio de inicio del usuario en Windows han cambiado para seguir el enfoque recomendado por Microsoft. Este cambio puede ser observable en ediciones anteriores de Windows o donde la configuración del registro o las variables de entorno están configuradas en otros directorios. Naturaleza de la incompatibilidad

behavioral RFE

6519127

A pesar de que la pregunta es antigua, dejo esto para referencia futura.

Paulo Fidalgo
fuente
35
System.getProperty("user.home");

Ver el JavaDoc .

Joachim Sauer
fuente
11
No, no es una respuesta correcta, esta es la misma que la anterior. Sí, no solo leí JavaDocs, sino que también lo probé en todas las plataformas antes de hacer esta pregunta. La respuesta no es tan simple.
Bruno Ranschaert 03 de
3
Esto podría salir terriblemente mal en Windows, donde solo llevará al padre del directorio de escritorio, que podría estar en cualquier lugar ...
Crónica
29

El concepto de un directorio HOME parece ser un poco vago cuando se trata de Windows. Si las variables de entorno (HOMEDRIVE / HOMEPATH / USERPROFILE) no son suficientes, es posible que deba recurrir al uso de funciones nativas a través de JNI o JNA . SHGetFolderPath le permite recuperar carpetas especiales, como Mis documentos (CSIDL_PERSONAL) o Configuración local \ Datos de aplicación (CSIDL_LOCAL_APPDATA).

Código JNA de muestra:

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);

    }

}
McDowell
fuente
Para su información, la carpeta que corresponde al directorio de inicio del usuario es CSIDL_PROFILE. Ver msdn.microsoft.com/en-us/library/bb762494(VS.85).aspx .
Matt Solnit
Sí, esta es una versión elaborada para el caso de Windows.
Bruno Ranschaert
2
En versiones recientes de JNA (más precisamente jna-platform), hay una clase Shell32Util que encapsula la API de Windows correspondiente de una manera muy agradable. En particular, usar Shell32Util.getKnownFolderPath (...) en combinación con una de las constantes de la clase KnownFolders debería ser apropiado. La antigua función de la API getFolderPath está en desuso desde Windows Vista.
Sebastian Marsching
17

Otros han respondido la pregunta antes que yo, pero un programa útil para imprimir todas las propiedades disponibles es:

for (Map.Entry<?,?> e : System.getProperties().entrySet()) {
    System.out.println(String.format("%s = %s", e.getKey(), e.getValue())); 
}
oxbow_lakes
fuente
No dependería de esto, porque no todas las propiedades están estandarizadas. En su lugar, consulte JavaDoc para System.getProperties () para averiguar qué propiedades se garantiza que existen.
Joachim Sauer
66
Eso puede ser cierto, ¡pero creo que es bastante útil para un novato! No estoy seguro de que merezca 2
votos a favor
6

Mientras buscaba la versión de Scala, todo lo que pude encontrar fue el código JNA de McDowell arriba. Incluyo mi puerto Scala aquí, ya que actualmente no hay ningún lugar más apropiado.

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")
            })
        }
    }
}

Al igual que con la versión de Java, deberá agregar Java Native Access , incluidos ambos archivos jar, a sus bibliotecas referenciadas.

Es agradable ver que JNA ahora hace esto mucho más fácil que cuando se publicó el código original.

Peter
fuente
2

Usaría el algoritmo detallado en el informe de error usando System.getenv (String), y recurriría al uso de la propiedad user.dir si ninguna de las variables de entorno indicara un directorio existente válido. Esto debería funcionar multiplataforma.

Creo que, en Windows, lo que realmente busca es el directorio de "documentos" nocionales del usuario.

Lawrence Dol
fuente
2

La alternativa sería usar Apache CommonsIO en FileUtils.getUserDirectory()lugar de System.getProperty("user.home"). Obtendrá el mismo resultado y no hay posibilidad de introducir un error tipográfico al especificar la propiedad del sistema.

Hay una gran posibilidad de que ya tenga la biblioteca Apache CommonsIO en su proyecto. No lo presente si planea usarlo solo para obtener el directorio de inicio del usuario.

mladzo
fuente
0

Si desea algo que funcione bien en Windows, hay un paquete llamado WinFoldersJava que envuelve la llamada nativa para obtener los directorios 'especiales' en Windows. Lo usamos con frecuencia y funciona bien.

Neil Benn
fuente