1.1. Gestión de archivos#
Conocer cómo gestionar archivos con Java nos permite conocer, dentro de una aplicación, cómo se encuentran los archivos dentro del sistema de archivos y crear nuevos y borrar ya existentes. Por otro lado, estas tareas tienen una fuerte dependencia del sistema operativo sobre el que corra la aplicación (p.ej. en UNIX el carácter separador es la barra «/» mientras que en Windows es la barra invertida «\») y uno de nuestros objetivos al crear una aplicación de Java es que ésta sea independiente de esta circunstancia. Por tanto, tenemos que realizar estas tareas intentando evitar estas dependencias.
Prudencia
Java tiene dos API distintas para llevar a cabo estas tareas:
La original que se basa en la clase java.io.File.
La surgida a partir de Java7 basada en Path (interfaz), y Files (clase).
Analizaremos la segunda.
Todo esto, sin embargo, requiere conocer dos aspectos previos:
1.1.1. Variables de entorno#
Las variables de entorno son variables definidas en el sistema operativo que heredan las aplicaciones que se ejecutan dentro de él, y que, cuando quieren realizarse tareas relacionadas con el propio sistema, es necesario consultar en tiempo de programación. Por ejemplo, si quisiéramos acceder a un archivo que sabemos situado dentro del directorio personal del usuario, necesitamos conocer cuál es esta ruta, para lo cual podemos consultar la variable de entorno correspondiente.
Para acceder a las variables de entorno, Java dispone del método estático[1] System.getenv()
con o sin argumento.
System.getenv(String nombre)
devuelve el valor de la variable de entorno o
null
si ésta no existe:System.getenv("HOME"); // /home/usuario en mi sistema.
System.getenv()
devuelve un mapa cada una de cuyas entradas es una variable y su valor correspondiente.
Map<String, String> entorno = System.getenv(); entorno.get("HOME"); // /home/usuario, de nuevo.
El problema crucial de consultar las variables de entorno es el de que el nombre cambia entre los distintos sistemas operativos[2] y, por tanto, si nos limitamos a usarlas tal como acabamos de ver, nuestra aplicación será dependiente del sistema en que se ejecute.
Una alternativa es consultar las propiedades de nuestro entorno de Java, algunas de las cuales recogen los valores de las variables de entorno:
System.getProperty(String nombre_propiedad)
devuelve el valor de la propiedad que se indica en su argumento:
System.getProperty("user.home"); // /home/usuario en mi sistema.
Este segundo código es independiente del sistema operativo y siempre devolverá la ruta al directorio personal del usuario. Si nuestra intención es saber cuáles son las propiedades definidas puede ejecutarse la siguiente orden:
$ java -XshowSettings:properties -version
Si se ejecuta la orden, se verá que apenas hay variables de entorno disponibles a través de propiedades, con lo que a veces no quedará más remedio que usarlas. En ese caso, tendremos que crear un código que compruebe cuál es el sistema operativo y, en función de ello, que obtenga la variable.
Ver también
Por ejemplo, este artículo expone cómo detectar el tipo de sistema operativo.
1.1.2. Rutas#
Antes de entrar de lleno a gestionar archivos, hay que aprender cómo referirnos a ellos, esto es, cómo construir rutas, para lo cual usaremos Path. El modo habitual de constituir una es con:
Path.of(String path)
que crea una ruta a partir de la cadena que se le proporcione:
Path dir = Path.of("/home/usuario/.config");
Esta, sin embargo, no es una buena estrategia, ya que la ruta que hemos definido es una ruta de sistemas UNIX y hará dependiente nuestro código del sistema sobre el que se ejecuta. Así que habría sido mucho mejor en este caso hacer lo siguiente:
String home = System.getProperty("user.home"); Path dir = Path.of(home, ".config");
esto es, utilizar una ruta ya hecha que nos proporciona el propio Java y corregirla añadiendo los subdirectorios necesarios mediante la adición de argumentos (tantos como subdirectorios requiramos).
Truco
Con el mismo fin también puede usarse el método estático
get()
de Paths:String home = System.getProperty("user.home"); Path dir = Paths.get(home, ".config");
Una vez que tenemos un objeto que implementa la interfaz Path, podemos usar los métodos que nos brinda para alterarla:
isAbsolute()
que comprueba si la ruta es absoluta.
getFileName()
que devuelve un objeto con el nombre del archivo:
dir.getFileName(); // .config (no es un String).
getParent()
que devuelve un objeto con la misma ruta a la que se le ha eliminado el nombre del archivo.
dir.getParent(); // /home/usuario
getRoot()
que devuelve el directorio raíz del sistema:
dir.getRoot(); // /, ya que es un sistema UNIX. En Windows, devolvería C:\ posiblemente.
resolve(String otro)
yresolve(Path otro)
que genera la ruta resultante de añadir a la que se tiene la proporcionada en el argumento:
Path appdir = dir.resolve("MyApp"); // /home/usuario/.config/MyApp Path config = dir.getFileName(); Path path = dir.getParent(); dir.equals(path.resolve(config)); // true
resolveSibling(String otro)
yresolveSibling(Path otro)
que es como los anteriores métodos, pero la ruta se construye sobre el directorio padre:
dir.resolveSibling(".cache"); // /home/usuario/.cache
toAbsolutePath()
ytoRealPath()
que devuelve la ruta absoluta. El segundo método, resuelve, además, los enlaces simbólicos en caso de que existiera alguno:
Path.of(".").toAbsolutePath(); // /home/usuario/.
normalize()
que elimina elementos redundantes en la ruta:
Path.of(".").toAbsolutePath().normalize(); // /home/usuario/ (sin el punto)
relativize(Path parent)
que construye una ruta relativa tomando como base la ruta que se proporciona en el argumento:
dir.relative(dir.getParent()); // .config
getNameCount()
que cuenta el número de elementos de la ruta:
dir.getNameCount(); // 3
getName(int idx)
que devuelve como ruta el elemento de la ruta original que se indique como argumento:
dir.getName(1); // usuario
subPath(int idx1, int idx2)
que devuelve la subruta que se forma tomando desde el elemento indicado con el primer argumento hasta el indicado con el segundo (sin incluir).
toUri()
que convierte la ruta en una URI (o sea, comenzará por
file:/
), lo que nos permitirá tratar la lectura del archivo del mismo modo que si fuera un enlace:URI uri = dir.toUri(); InputStream st = uri.toURL().openStream(); // etc.
Ver también
Más adelante trataremos la lectura de archivos.
Truco
Si tenemos una URI que refiere un archivo local, podremos obtener su
Path
conPath.of
:Path archivo = Path.of(uri);
Por último, Path
implemente la interfaz Iterable, así que
podemos hacer construcciones como esta:
for(Path p: dir) {
System.out.println(p);
}
Atención
Con estas herramientas sólo construimos rutas, así que tales rutas no tienen por qué existir.
1.1.3. Gestión#
Discutido lo anterior, ahora sí podemos centrarnos en gestionar archivos de disco. Para ello utilizaremos java.nio.file.Files y todos los métodos estáticos que trae definidos. Téngase en cuenta que para expresar las rutas no acepta cadenas, sino rutas de tipo Path:
1.1.3.1. Consulta#
Para comprobar la existencia de una ruta:
Path home = Path.of(System.getProperty("user.home"));
Files.exists(home); // Obviamente, true
Files.exists(home.resolve(".bashrc")); // true, uso bash.
Para consultar su tamaño:
Files.size(home.resolve(".bashrc")); // Tamaño en bytes.
Para comprobar la naturaleza del archivo hay varios métodos distintos:
Files.isDirectory(home); // true
Files.isRegularFile(home); // false
Y para comprobar el tipo MIME:
Files.probeContentType("/path/a/settings.json"); // "application/json"
Para comprobar permisos:
Files.isReadable(home); // true
Files.isWritable(home); // true
Files.isWritable(home.resolve("..")); // false
Para consultar el propietario:
Files.getOwner(home); // usuario
Para comprobar si dos archivos son el mismo:
// Estoy trabajando en mi directorio personal
home.equals("."); // false, porque home es ruta absoluta.
Files.isSameFile(home, Path.of(".")); // true.
Y varios otros métodos para consultar fechas, si es ejecutable, si está oculto, etc. También es posible listar directorios sin recursividad:
Stream<Path> archivos = Files.list(home);
archivos.forEach(System.out::println);
o con ella:
Stream<Path> archivos = Files.walk(home, 3, FileVisitOption.FOLLOW_LINKS);
archivos.forEach(System.out::println);
En este caso, se devuelven todos los archivos dentro del directorio personal, hasta una profundidad de 3 y, si un enlace simbólico es un directorio, se entra dentro de él. Los dos últimos argumentos son opcionales.
1.1.3.2. Manipulación#
Por manipulación entendemos, simplemente, la copia y traslado de archivos o el cambio de sus propiedades (permisos, propietarios, etc) y no la alteración del contenido, que reservamos para el próximo epígrafe sobre manipulación.
Para crear un archivo:
Files.createFile(home.resolve("caca.txt"));
lo que creará el archivo vacío caca.txt
dentro de nuestro directorio
natural con los permisos que determine la máscara del sistema. Podríamos haber
definido otros permisos, pero para ello tendríamos que haber incluido argumentos
adicionales (véase FileAttribute), pero no
entraremos en tanto detalle. También existe Files.createDirectory(Path dir)
para crear un directorio.
Podemos también copiar, mover o borrar archivos:
Path tmp = Path.of(System.getProperty("java.io.tmpdir"));
Path caca = home.resolve("caca.txt");
Files.copy(caca, tmp.resolve("caca.txt")); // Copia en /tmp/kk.txt
Files.move(caca, tmp.resolve("caca.txt")); // Error, el destino ya existe.
Files.move(caca, tmp.resolve("caca.txt"), StandardCopyOption.REPLACE_EXISTING);
Files.delete(caca);
Truco
También se pueden copiar flujos (InputStream) a archivo.
Para cambiar el usuario o los atributos del archivo también existen métodos.
Los métodos que nos queda por revisar son aquellos que permiten leer y escribir contenido, pero los dejamos para el siguiente epígrafe.
Prudencia
A diferencia de lo que ocurre al usar en una terminal las órdenes cp o mv, no basta con poner el directorio de destino, cuando queremos conservar el nombre original.
Advertencia
Por supuesto, estas acciones pueden generar excepciones (p.ej. al intentar crear un archivo dentro de un directorio en el que no tenemos permisos), así que en la programación deberemos estar atentos a tratarlas.
Notas al pie