CodeGym /Blog Java /Random-ES /Archivos Java, Ruta
Autor
Andrey Gorkovenko
Frontend Engineer at NFON AG

Archivos Java, Ruta

Publicado en el grupo Random-ES
¡Hola! Hoy hablaremos sobre trabajar con archivos y directorios. Ya sabe cómo administrar el contenido de los archivos: hemos dedicado muchas lecciones a esto :) Creo que le resultará fácil recordar algunas clases utilizadas para estos fines. En la lección de hoy, hablaremos específicamente sobre la gestión de archivos: creación, cambio de nombre, etc. Antes de Java 7, todas estas operaciones se realizaban utilizando la clase File . Puedes leer sobre esto aquí . Pero en Java 7, los creadores del lenguaje decidieron cambiar la forma en que trabajamos con archivos y directorios. Esto sucedió porque la clase File tenía varios inconvenientes. Por ejemplo, no tenía el método copy() , que le permitiría copiar un archivo de una ubicación a otra (una habilidad aparentemente esencial). además, elLa clase de archivo tenía bastantes métodos que devolvían valores booleanos . Cuando hay un error, dicho método devuelve falso. No arroja una excepción, por lo que es muy difícil identificar errores y diagnosticar sus causas. En lugar de la única clase File , aparecieron 3 clases: Paths , Path y Files . Bueno, para ser precisos, Path es una interfaz, no una clase. Averigüemos en qué se diferencian entre sí y por qué necesitamos cada uno de ellos. Empecemos por lo más sencillo: Paths .

Caminos

Paths es una clase muy simple con un único método estático: get() . Fue creado únicamente para obtener un objeto Path de la cadena o URI pasada. No tiene otra funcionalidad. Aquí hay un ejemplo de esto en el trabajo:

import java.nio.file.Path;
import java.nio.file.Paths;

public class Main {

   public static void main(String[] args) {

       Path testFilePath = Paths.get("C:\\Users\\Username\\Desktop\\testFile.txt");
   }
}
No es la clase más compleja, ¿verdad? :) Bueno, también tenemos este tipo de ruta . Averigüemos qué es Path y por qué es necesario :)

Camino

Path , en general, es un análogo rediseñado de la clase File . Es mucho más fácil trabajar con File . En primer lugar , se eliminaron muchos métodos de utilidad (estáticos) y se trasladaron a la clase Archivos . En segundo lugar , se impuso orden en los valores de retorno de los métodos de la interfaz Path . En la clase File , los métodos devolvían un String , un booleano o un File . No fue fácil averiguarlo. Por ejemplo, había un método getParent() que devolvía una cadena que representaba la ruta principal del archivo actual. Pero también hubo unmétodo getParentFile() , que devolvió lo mismo pero en forma de unobjeto de archivo . Esto es claramente redundante. En consecuencia, en lainterfaz Path , el método getParent() y otros métodos para trabajar con archivos simplemente devuelven un objeto Path . Sin montones de opciones: todo es fácil y simple. ¿ Cuáles son algunos de los métodos útiles que tiene Path ? Estos son algunos de ellos y ejemplos de cómo funcionan:
  • getFileName() : devuelve el nombre del archivo de la ruta;

  • getParent() : devuelve el directorio "principal" de la ruta actual (en otras palabras, el directorio ubicado inmediatamente arriba en el árbol de directorios);

  • getRoot() : devuelve el directorio "raíz", es decir, el directorio en la parte superior del árbol de directorios;

  • empieza con() , termina con() : compruebe si la ruta comienza/termina con la ruta pasada:

    
    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    public class Main {
    
       public static void main(String[] args) {
    
           Path testFilePath = Paths.get("C:\\Users\\Username\\Desktop\\testFile.txt");
    
           Path fileName = testFilePath.getFileName();
           System.out.println(fileName);
    
           Path parent = testFilePath.getParent();
           System.out.println(parent);
    
           Path root = testFilePath.getRoot();
           System.out.println(root);
    
           boolean endWithTxt = testFilePath.endsWith("Desktop\\testFile.txt");
           System.out.println(endWithTxt);
    
           boolean startsWithLalala = testFilePath.startsWith("lalalala");
           System.out.println(startsWithLalala);
       }
    }
    

    Salida de la consola:

    
    testFile.txt
    C:\Users\Username\Desktop
    C:\
    true
    false
    

    Preste atención a cómo funciona el método extremosCon() . Comprueba si la ruta actual termina con la ruta pasada . Específicamente, si está en la ruta , no en la cadena pasada .

    Compare los resultados de estas dos llamadas:

    
    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    public class Main {
    
       public static void main(String[] args) {
    
           Path testFilePath = Paths.get("C:\\Users\\Username\\Desktop\\testFile.txt");
    
           System.out.println(testFilePath.endsWith("estFile.txt"));
           System.out.println(testFilePath.endsWith("Desktop\\testFile.txt"));
       }
    }
    

    Salida de la consola:

    
    false
    true
    

    Al método EndsWith() se le debe pasar una ruta genuina, no solo un conjunto de caracteres: de lo contrario, el resultado siempre será falso, incluso si la ruta actual realmente termina con esa secuencia de caracteres (como es el caso de "estFile.txt " en el ejemplo anterior).

    Además, Path tiene un grupo de métodos que simplifica el trabajo con rutas absolutas (completas) y relativas .

Veamos estos métodos:
  • boolean isAbsolute() devuelve verdadero si la ruta actual es absoluta:

    
    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    public class Main {
    
       public static void main(String[] args) {
    
           Path testFilePath = Paths.get("C:\\Users\\Username\\Desktop\\testFile.txt");
    
           System.out.println(testFilePath.isAbsolute());
       }
    }
    

    Salida de la consola:

    
    true
    
  • Path normalize() : "normaliza" la ruta actual, eliminando elementos innecesarios de ella. Es posible que sepa que en los sistemas operativos populares los símbolos "." (directorio actual) y ".." (directorio principal) se utilizan a menudo para designar rutas. Por ejemplo, " ./Imágenes/perro.jpg " significa que el directorio actual tiene una carpeta "Imágenes", que a su vez contiene un archivo "perro.jpg".

    Mira aquí. Si una ruta usando "." o ".." aparece en su programa, el método normalize() los eliminará y producirá una ruta que no los contiene:

    
    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    public class Main {
    
       public static void main(String[] args) {
    
          
           Path path5 = Paths.get("C:\\Users\\Java\\.\\examples");
          
           System.out.println(path5.normalize());
          
           Path path6 = Paths.get("C:\\Users\\Java\\..\\examples");
           System.out.println(path6.normalize());
       }
    }
    

    Salida de la consola:

    
    C:\Users\Java\examples
    C:\Users\examples
    
  • Path relativize() : calcula la ruta relativa entre la ruta actual y la pasada.

    Por ejemplo:

    
    import java.nio.file.Path;
    import java.nio.file.Paths;
    
    public class Main {
    
       public static void main(String[] args) {
    
           Path testFilePath1 = Paths.get("C:\\Users\\Users\\Users\\Users");
           Path testFilePath2 = Paths.get("C:\\Users\\Users\\Users\\Users\\Username\\Desktop\\testFile.txt");
    
           System.out.println(testFilePath1.relativize(testFilePath2));
       }
    }
    

    Salida de la consola:

    
    Username\Desktop\testFile.txt
    

La lista completa de métodos Path es bastante larga. Puede encontrarlos todos en la documentación de Oracle . Ahora pasaremos a considerar Files .

archivos

Archivos es una clase de utilidad que contiene los métodos estáticos extraídos de la clase Archivo . Files es comparable a Arrays o Collections . La diferencia es que funciona con archivos, no con arreglos o colecciones :) Se enfoca en administrar archivos y directorios. Usando los métodos estáticos de la clase Files , podemos crear, eliminar y mover archivos y directorios. Estas operaciones se realizan utilizando losmétodos createFile() (para directorios, createDirectory() ), move() y delete() . He aquí cómo usarlos:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

public class Main {

   public static void main(String[] args) throws IOException {

       // Create a file
       Path testFile1 = Files.createFile(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt"));
       System.out.println("Was the file created successfully?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));

       // Create a directory
       Path testDirectory = Files.createDirectory(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory"));
       System.out.println("Was the directory created successfully?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory")));

       // Move the file from the desktop to the testDirectory directory. When you move a folder, you need to indicate its name in the folder!
       testFile1 = Files.move(testFile1, Paths.get("C:\\Users\\Username\\Desktop\\testDirectory\\testFile111.txt"), REPLACE_EXISTING);

       System.out.println("Did our file remain on the desktop?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));

       System.out.println("Has our file been moved to testDirectory?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory\\testFile111.txt")));

       // Delete a file
       Files.delete(testFile1);
       System.out.println("Does the file still exist?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory\\testFile111.txt")));
   }
}
Aquí primero creamos un archivo ( método Files.createFile() ) en el escritorio. Luego creamos una carpeta en la misma ubicación ( método Files.createDirectory() ). Después de eso, movemos el archivo ( método Files.move() ) desde el escritorio a esta nueva carpeta, y finalmente eliminamos el archivo ( método Files.delete() ). Salida de la consola:

Was the file created successfully? 
true 
Was the directory created successfully? 
true
Did our file remain on the desktop? 
false 
Has our file been moved to testDirectory? 
true 
Does the file still exist? 
false
Nota:al igual que los métodos de la Pathinterfaz, muchos métodos de la Filesclase devuelven unPath objeto. La mayoría de los métodos de la Filesclase también toman Pathobjetos como entradas. Aquí el Paths.get()método será su fiel ayudante: utilícelo bien. ¿ Qué más es interesante en Files? File¡Lo que realmente le faltaba a la vieja clase es un copy()método! Hablamos de ello al principio de esta lección. ¡Ahora es el momento de conocerlo!

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

public class Main {

   public static void main(String[] args) throws IOException {

       // Create a file
       Path testFile1 = Files.createFile(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt"));
       System.out.println("Was the file created successfully?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));

       // Create a directory
       Path testDirectory2 = Files.createDirectory(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2"));
       System.out.println("Was the directory created successfully?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2")));

       // Copy the file from the desktop to the testDirectory2 directory.
       testFile1 = Files.copy(testFile1, Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2\\testFile111.txt"), REPLACE_EXISTING);

       System.out.println("Did our file remain on the desktop?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));

       System.out.println("Was our file copied to testDirectory?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2\\testFile111.txt")));
   }
}
Salida de la consola:

Was the file created successfully? 
true 
Was the directory created successfully? 
true 
Did our file remain on the desktop? 
true 
Was our file copied to testDirectory? 
true
¡Ahora sabe cómo copiar archivos mediante programación! :) Por supuesto, la Filesclase le permite no solo administrar un archivo en sí mismo, sino también trabajar con su contenido. Tiene el write()método para escribir datos en un archivo, y los 3 métodos para leer datos: read(), readAllBytes()y readAllLines() Nos detendremos en detalle en el último. ¿Por qué ese? Porque tiene un tipo de retorno muy interesante: List<String>! Es decir, nos devuelve una lista de todas las líneas del archivo. Por supuesto, esto hace que sea muy conveniente trabajar con el contenido del archivo, porque el archivo completo, línea por línea, puede, por ejemplo, mostrarse en la consola usando un bucle normal for:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;

import static java.nio.charset.StandardCharsets.UTF_8;

public class Main {

   public static void main(String[] args) throws IOException {

       List<String> lines = Files.readAllLines(Paths.get("C:\\Users\\Username\\Desktop\\pushkin.txt"), UTF_8);

       for (String s: lines) {
           System.out.println(s);
       }
   }
}
Salida de la consola:

I still recall the wondrous moment: 
When you appeared before my sight, 
As though a brief and fleeting omen, 
Pure phantom in enchanting light.
¡Súper conveniente! :) Esta habilidad apareció en Java 7. La API Stream apareció en Java 8. Agrega algunos elementos de programación funcional a Java. Incluyendo capacidades de manejo de archivos más ricas. Imagina que tenemos la siguiente tarea: encontrar todas las líneas que comienzan con la palabra "As", convertirlas a MAYÚSCULAS y mostrarlas en la consola. ¿ Cómo sería una solución usando la Filesclase en Java 7? Algo como esto:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;

import static java.nio.charset.StandardCharsets.UTF_8;

public class Main {

   public static void main(String[] args) throws IOException {

       List<String> lines = Files.readAllLines(Paths.get("C:\\Users\\Username\\Desktop\\pushkin.txt"), UTF_8);

       List<String> result = new ArrayList<>();

       for (String s: lines) {
           if (s.startsWith("As")) {
               String upper = s.toUpperCase();
               result.add(upper);
           }
       }

       for (String s: result) {
           System.out.println(s);
       }
   }
}
Salida de la consola:

AS THOUGH A BRIEF AND FLEETING OMEN, 
PURE PHANTOM IN ENCHANTING LIGHT.
Misión cumplida, pero ¿no crees que para una tarea tan simple nuestro código resultó ser un poco... prolijo? Usando la API Stream de Java 8, la solución se ve mucho más elegante:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Main {

   public static void main(String[] args) throws IOException {

       Stream<String> stream = Files.lines(Paths.get("C:\\Users\\Username\\Desktop\\pushkin.txt"));

       List<String> result  = stream
               .filter(line -> line.startsWith("As"))
               .map(String::toUpperCase)
               .collect(Collectors.toList());
       result.forEach(System.out::println);
   }
}
Logramos el mismo resultado, ¡pero con mucho menos código! Es más, nadie puede decir que hemos perdido "legibilidad". Creo que puede comentar fácilmente lo que hace este código, incluso sin estar familiarizado con Stream API. En resumen, un Stream es una secuencia de elementos, sobre los cuales se pueden realizar diversas operaciones. Obtenemos un objeto Stream del Files.lines()método y luego le aplicamos 3 funciones:
  1. Usamos el filter()método para seleccionar solo aquellas líneas del archivo que comienzan con "As".

  2. Recorremos todas las líneas seleccionadas usando el map()método y convertimos cada una de ellas a MAYÚSCULAS.

  3. Usamos el collect()método para reunir todas las líneas recibidas en un archivo List.

Obtenemos la misma salida:

AS THOUGH A BRIEF AND FLEETING OMEN, 
PURE PHANTOM IN ENCHANTING LIGHT.
Ahora volvamos a nuestro pan y mantequilla, es decir, archivos :) La última capacidad que consideraremos hoy es caminar a través de un árbol de archivos . En los sistemas operativos modernos, la estructura de archivos suele parecerse a un árbol: tiene una raíz y ramas, que pueden tener otras ramas, etc. La raíz y las ramas son directorios. Por ejemplo, el directorio " С:// " puede ser la raíz. Incluye dos ramas: " C://Descargas " y " C://Usuarios ". Cada una de estas ramas tiene dos ramas: " C://Descargas/Imágenes ", " C://Descargas/Video ", " C://Usuarios/JohnSmith ", " C://Usuarios/Pudge2005". Y estas ramas a su vez tienen otras ramas, etc. y es por eso que lo llamamos árbol. En Linux, la estructura es similar, pero el directorio / es la raíz. Ahora imaginaArchivos, Ruta - 2 que necesitamos comenzar en el directorio raíz , recorra todas sus carpetas y subcarpetas y busque archivos que tengan algún contenido en particular. Buscaremos archivos que contengan la línea "¡Este es el archivo que necesitamos!" Tomaremos la carpeta "testFolder", que está en el escritorio, como directorio raíz. Aquí está su contenido: Archivos, Ruta - 3Las carpetas level1-a y level1-b también contienen carpetas: Archivos, Ruta - 4Archivos, Ruta - 5No hay carpetas en estas "carpetas de segundo nivel", solo archivos individuales: Archivos, Ruta - 6Archivos, Ruta - 7Los 3 archivos con los contenidos que necesitamos reciben deliberadamente nombres explicativos: FileWeNeed1.txt, FileWeNeed2.txt, FileWeNeed3.txt. Estos son precisamente los archivos que necesitamos encontrar usando Java. Cómo hacemos esto? Un método muy poderoso para atravesar un árbol de archivos viene en nuestra ayuda: Files.walkFileTree (). Esto es lo que tenemos que hacer. Primero, necesitamos un FileVisitor. FileVisitores una interfaz especial, en la que se describen los métodos para recorrer un árbol de archivos. En particular, ahí es donde colocaremos la lógica para leer el contenido de un archivo y verificar si contiene el texto que necesitamos. Así es como FileVisitorse ve nuestro:

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;

public class MyFileVisitor extends SimpleFileVisitor<Path> {

   @Override
   public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {

       List<String> lines = Files.readAllLines(file);
       for (String s: lines) {
           if (s.contains("This is the file we need")) {
               System.out.println("We found a file we need!");
               System.out.println(file.toAbsolutePath());
               break;
           }
       }

       return FileVisitResult.CONTINUE;
   }
}
En este caso, nuestra clase hereda SimpleFileVisitor. Esta es una clase que implementa FileVisitor, en la que necesitamos anular solo un método: visitFile(). Aquí definimos lo que se debe hacer con cada archivo en cada directorio. Si necesita una lógica más compleja para atravesar la estructura de archivos, debe escribir su propia implementación de FileVisitor. Necesitaría implementar 3 métodos más en esa clase:
  • preVisitDirectory(): la lógica a ejecutar antes de ingresar a una carpeta;

  • visitFileFailed(): la lógica a ejecutar si un archivo no puede ser visitado (sin acceso, o por otras razones);

  • postVisitDirectory(): la lógica a ejecutar después de ingresar a una carpeta.

No necesitamos que se ejecute ninguna lógica de este tipo, por lo que estamos bien con SimpleFileVisitor. La lógica dentro del visitFile()método es bastante simple: lea todas las líneas del archivo, verifique si contienen el contenido que necesitamos y, de ser así, imprima la ruta absoluta en la consola. La única línea que podría causarle dificultades es esta:

return FileVisitResult.CONTINUE;
En realidad, esto es muy simple. Aquí simplemente describimos lo que debe hacer el programa después de que se visita el archivo y se han realizado todas las operaciones necesarias. En nuestro caso, queremos seguir recorriendo el árbol, por lo que elegimos la CONTINUEopción. Pero, alternativamente, podríamos tener un objetivo diferente: en lugar de encontrar todos los archivos que contengan "Este es el archivo que necesitamos", busque solo uno de esos archivos . Después de eso, el programa debería terminar. En este caso, nuestro código se vería exactamente igual, pero en lugar de romper habría:

return FileVisitResult.TERMINATE;
Bueno, ejecutemos nuestro código y veamos si funciona.

import java.io.IOException;
import java.nio.file.*;

public class Main {

   public static void main(String[] args) throws IOException {

       Files.walkFileTree(Paths.get("C:\\Users\\Username\\Desktop\\testFolder"), new MyFileVisitor());
   }
}
Salida de la consola:

We found a file we need! 
C:\Users\Username\Desktop\testFolder\FileWeNeed1.txt 
We found a file we need! 
C:\Users\Username\Desktop\testFolder\level1-a\level2-a-a\FileWeNeed2.txt 
We found a file we need! 
C:\Users\Username\Desktop\testFolder\level1-b\level2-b-b\FileWeNeed3.txt
¡Excelente! ¡Funcionó! :) También podría aceptar este pequeño desafío: reemplácelo SimpleFileVisitorcon un ordinario FileVisitor, anule los 4 métodos y proponga su propio propósito para el programa. Por ejemplo, podría escribir un programa que registre todas sus acciones: muestre el nombre del archivo o carpeta antes o después de ingresarlos. Eso es todo por ahora. ¡Nos vemos pronto! :)
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION