JavaRush /Blog Java /Random-ES /Archivos Java, ruta

Archivos Java, ruta

Publicado en el grupo Random-ES
¡Hola! Hoy hablaremos sobre cómo trabajar con archivos y directorios. Ya sabes cómo administrar el contenido de los archivos: tuvimos muchas clases dedicadas a esto :) Creo que puedes recordar fácilmente varias clases que son necesarias para estos propósitos. En la conferencia 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 File. Puedes leer sobre su trabajo aquí . Pero en Java 7, los creadores del lenguaje decidieron cambiar la forma en que trabajan con archivos y directorios. Esto se debió al hecho de que la clase Filetenía una serie de desventajas. Por ejemplo, no tenía un método copy()que le permitiera copiar un archivo de una ubicación a otra (una característica aparentemente claramente necesaria). Además, la clase Filetenía bastantes métodos que devolvían booleanvalores. Si se produce un error, dicho método devuelve falso en lugar de generar una excepción, lo que dificulta mucho el diagnóstico de errores y la determinación de sus causas. En lugar de una sola clase, Fileaparecieron hasta 3 clases: Paths, Pathy Files. Bueno, para ser precisos, Pathesta es una interfaz, no una clase. Averigüemos en qué se diferencian entre sí y por qué es necesario cada uno de ellos. Comencemos con lo más fácil: Paths.

Caminos

PathsEs una clase muy simple con un único método estático get(). Fue creado únicamente para obtener un objeto de tipo de la cadena o URI pasada Path. No tiene otra funcionalidad. Aquí un ejemplo de su 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 difícil, ¿verdad? :) Bueno, ya que tenemos un objeto de tipo Path, averigüemos qué es Pathy por qué es necesario :)

Camino

Path, en general, es un análogo rediseñado de File. Es mucho más fácil trabajar con él que con File. En primer lugar , se eliminaron muchos métodos de utilidad (estáticos) y se trasladaron a la clase Files. En segundo lugar , Pathse ordenaron los valores de retorno de los métodos. En la clase, Filelos métodos devolvían esto String, aquello boolean, aquello File; no fue fácil de entender. Por ejemplo, había un método getParent()que devolvía la ruta principal del archivo actual como una cadena. Pero al mismo tiempo había un método getParentFile()que devolvía lo mismo, ¡pero en forma de objeto File! Esto es claramente redundante. Por lo tanto, en la interfaz, Pathel método getParent()y otros métodos para trabajar con archivos simplemente devuelven un objeto Path. No hay muchas opciones: todo es fácil y sencillo. ¿ Qué métodos útiles tiene Path? Éstos son algunos de ellos y ejemplos de su trabajo:
  • getFileName()— devuelve el nombre del archivo de la ruta;

  • getParent()— devuelve el directorio "principal" en relación con la ruta actual (es decir, el directorio que está más arriba en el árbol de directorios);

  • getRoot()— devuelve el directorio “raíz”; es decir, el que está en la parte superior del árbol de directorios;

  • startsWith(), endsWith()— comprobar 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 consola:

    testFile.txt
    C:\Users\Username\Desktop
    C:\
    verdadero
    falso

    Preste atención a cómo funciona el método endsWith(). Comprueba si la ruta actual termina con la ruta pasada . Está en el camino , y no en el conjunto de personajes .

    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 consola:

    falso
    verdadero

    Debe pasar la ruta completa al método endsWith(), y no solo un conjunto de caracteres: de lo contrario, el resultado siempre será falso , incluso si la ruta actual realmente termina con dicha secuencia de caracteres (como en el caso de "estFile.txt ”en el ejemplo anterior).

    Además, existe Pathun 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 consola:

    verdadero

  • Path normalize()— “normaliza” la ruta actual, eliminando de ella elementos innecesarios. Quizás sepa que los sistemas operativos populares suelen utilizar caracteres "." para indicar rutas. (“directorio actual”) y “..” (directorio principal). Por ejemplo: “ ./Pictures/dog.jpg ” significa que en el directorio en el que nos encontramos ahora hay una carpeta Imágenes y en ella hay un archivo “dog.jpg”

    Asi que aqui esta. Si su programa tiene una ruta que usa "." o “..”, el método normalize()los eliminará y obtendrá una ruta que no los contendrá:

    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 consola:

    C:\Usuarios\Java\ejemplos
    C:\Usuarios\ejemplos

  • 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 consola:

    Nombre de usuario\Escritorio\testFile.txt

La lista completa de métodos Pathes bastante grande. Puede encontrarlos todos en la documentación de Oracle . Pasaremos a revisar Files.

Archivos

Files- esta es una clase de utilidad donde se movieron los métodos estáticos de la clase File. Files- Esto es aproximadamente lo mismo que Arrayso Collections, solo que funciona con archivos, no con matrices y colecciones :) Está enfocado a administrar archivos y directorios. Usando métodos estáticos Files, podemos crear, eliminar y mover archivos y directorios. Para estas operaciones se utilizan los métodos createFile()(para directorios - createDirectory()), move()y delete(). A continuación se explica cómo utilizarlos:
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 {

       // creación de archivos
       Path testFile1 = Files.createFile(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt"));
       System.out.println("¿Se creó correctamente el archivo?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));

       // crear directorio
       Path testDirectory = Files.createDirectory(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory"));
       System.out.println("¿Se creó correctamente el directorio?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory")));

       //mover archivo del escritorio a testDirectory. ¡Tienes que moverte con el nombre del archivo en la carpeta!
       testFile1 = Files.move(testFile1, Paths.get("C:\\Users\\Username\\Desktop\\testDirectory\\testFile111.txt"), REPLACE_EXISTING);

       System.out.println("¿Queda nuestro archivo en el escritorio?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));

       System.out.println("¿Se ha movido nuestro archivo a testDirectory?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory\\testFile111.txt")));

       //Remover archivo
       Files.delete(testFile1);
       System.out.println("¿El archivo todavía existe?");
       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 allí (método Files.createDirectory()). Después de eso, movemos el archivo (método Files.move()) desde el escritorio a esta nueva carpeta, y al final eliminamos el archivo (método Files.delete()). Salida de la consola: ¿Se creó el archivo correctamente? true ¿Se creó correctamente el directorio? true ¿Nuestro archivo todavía está en el escritorio? false ¿Nuestro archivo se ha movido a testDirectory? true ¿El archivo todavía existe? FALSO Prestar atención:Al igual que los métodos de interfaz Path, muchos métodos Filesdevuelven un objetoPath . La mayoría de los métodos de clase Filestambién aceptan Path. Aquí un método se convertirá en su fiel asistente Paths.get(): utilícelo activamente. ¿ Qué más es interesante Files? Lo que realmente le faltaba a la antigua clase era el Filemétodo copy()! Hablamos de él al principio de la conferencia, ¡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 {

       // creación de archivos
       Path testFile1 = Files.createFile(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt"));
       System.out.println("¿Se creó correctamente el archivo?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));

       // crear directorio
       Path testDirectory2 = Files.createDirectory(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2"));
       System.out.println("¿Se creó correctamente el directorio?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2")));

       //copiar el archivo del escritorio al directorio testDirectory2.
       testFile1 = Files.copy(testFile1, Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2\\testFile111.txt"), REPLACE_EXISTING);

       System.out.println("¿Queda nuestro archivo en el escritorio?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testFile111.txt")));

       System.out.println("¿Se ha copiado nuestro archivo en testDirectory?");
       System.out.println(Files.exists(Paths.get("C:\\Users\\Username\\Desktop\\testDirectory2\\testFile111.txt")));
   }
}
Salida de la consola: ¿Se creó el archivo correctamente? true ¿Se creó correctamente el directorio? true ¿Nuestro archivo todavía está en el escritorio? true ¿Se copió nuestro archivo en testDirectory? verdadero ¡Ahora puedes copiar archivos mediante programación! :) Pero la clase Fileste permite no sólo gestionar los archivos en sí, sino también trabajar con su contenido. Para escribir datos en un archivo, tiene un método write(), y para leer, hasta 3:, read()y readAllBytes()nos readAllLines() detendremos en este último en detalle. ¿Por qué en eso? Porque tiene un tipo de retorno muy interesante - List<String>! Es decir, nos devuelve una lista de líneas del archivo. Por supuesto, esto hace que trabajar con el contenido sea muy conveniente, porque el archivo completo, línea por línea, puede, por ejemplo, enviarse a la consola en 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 consola: Recuerdo un momento maravilloso: Apareciste ante mí, Como una visión fugaz, Como un genio de pura belleza. ¡Muy cómodamente! :) Esta característica apareció en Java 7. En Java 8, apareció Stream API , que agregó algunos elementos de programación funcional a Java. Incluyendo capacidades más ricas para trabajar con archivos. Imaginemos que tenemos una tarea: encontrar todas las líneas en un archivo que comiencen con la palabra "Cómo", convertirlas a MAYÚSCULAS y enviarlas a la consola. ¿ Cómo sería una solución que utilizara una clase Filesen 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("Cómo")) {
               String upper = s.toUpperCase();
               result.add(upper);
           }
       }

       for (String s: result) {
           System.out.println(s);
       }
   }
}
Salida de consola: COMO UNA VISIÓN EN AYUNO, COMO UN GENIO DE PURA BELLEZA. Parece que lo hemos hecho, pero ¿no crees que para una tarea tan sencilla nuestro código resultó un poco... detallado? Usando Java 8 Stream API la solución parece 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("Cómo"))
               .map(String::toUpperCase)
               .collect(Collectors.toList());
       result.forEach(System.out::println);
   }
}
¡Obtuvimos el mismo resultado, pero con mucho menos código! Además, no se puede decir que hayamos perdido en “legibilidad”. Creo que puedes comentar fácilmente lo que hace este código, incluso si no estás familiarizado con la API Stream. Pero en resumen, un Stream es una secuencia de elementos sobre los cuales puedes realizar diferentes funciones. Obtenemos el objeto Stream del método Files.lines()y luego le aplicamos 3 funciones:
  1. Usando el método, filter()seleccionamos solo aquellas líneas del archivo que comienzan con "Cómo".

  2. Revisamos todas las filas seleccionadas usando el método map()y reducimos cada una de ellas a MAYÚSCULAS.

  3. Combinamos todas las líneas resultantes para Listusar el archivo collect().

A la salida obtenemos el mismo resultado: COMO UNA VISIÓN EN AYUNO, COMO UN GENIO DE PURA BELLEZA. Si está interesado en conocer más sobre las capacidades de esta biblioteca, le recomendamos leer este artículo . Volveremos a nuestras ovejas, es decir, a los archivos :) La última posibilidad que consideraremos hoy es caminar por el árbol de archivos . La estructura de archivos en los sistemas operativos modernos suele adoptar la forma de un árbol: tiene una raíz y hay ramas de las que se pueden separar otras ramas, etc. Los directorios desempeñan el papel de raíz y ramas. Por ejemplo, el directorio “ C:// ” puede actuar como raíz . De él se bifurcan dos ramas: " C://Descargas " y " C://Usuarios ". De cada una de estas ramas hay 2 ramas más: " C://Downloads/Pictures ", " C://Downloads/Video ", " C://Users/JohnSmith ", " C://Users/Pudge2005 " . De estas ramas se ramifican otras ramas, etc. - Así resulta un árbol. En Linux se ve más o menos igual, solo que allí el directorio actúa como raíz / Archivos, ruta - 2 Ahora imaginemos que tenemos una tarea: conociendo el directorio raíz, debemos revisarlo, buscar carpetas de todos los niveles y encontrar archivos con el contenido en ellas. nosotros necesitamos. Buscaremos archivos que contengan la línea "¡Este es el archivo que necesitamos!" Nuestro directorio raíz será la carpeta "testFolder", que se encuentra en el escritorio. En su interior tiene el siguiente contenido: Archivos, Ruta - 3Dentro de las carpetas nivel1-a y nivel1-b también hay carpetas: Archivos, Ruta - 4Archivos, Ruta - 5Dentro de estas “carpetas de segundo nivel” no hay más carpetas, solo archivos individuales: Archivos, Ruta - 6Archivos, Ruta - 7Designaremos especialmente 3 archivos con el contenido que Necesito con nombres claros: FileWeNeed1.txt, FileWeNeed2.txt, FileWeNeed3.txt Estos son los que necesitamos encontrar por contenido usando Java. ¿Cómo podemos hacer esto? Un método muy poderoso para recorrer un árbol de archivos viene al rescate: Files.walkFileTree(). Esto es lo que debemos hacer. Primero, necesitamos FileVisitor. FileVisitores una interfaz especial que describe todos los métodos para recorrer el árbol de archivos. Específicamente, pondremos lógica allí para leer el contenido del archivo y verificar si contiene el texto que necesitamos. Así será el nuestro FileVisitor:
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("¡Archivo requerido encontrado!");
               System.out.println(file.toAbsolutePath());
               break;
           }
       }

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

  • visitFileFailed()— qué hacer si es imposible acceder al archivo (falta de acceso u otros motivos);

  • postVisitDirectory()— la lógica que se debe ejecutar después de ingresar a la carpeta.

No tenemos esa lógica, así que es suficiente para nosotros SimpleFileVisitor. La lógica dentro del método visitFile()es bastante simple: lee todas las líneas del archivo, verifica si contienen el contenido que necesitamos y, de ser así, imprime la ruta absoluta a la consola. La única línea que podría causarte problemas es ésta:
return FileVisitResult.CONTINUE;
De hecho, todo es sencillo. Aquí simplemente describimos lo que debe hacer el programa después de que se haya ingresado el archivo y se hayan completado todas las operaciones necesarias. En nuestro caso, necesitamos continuar recorriendo el árbol, así que elegimos la opción CONTINUE. Pero nosotros, por ejemplo, podríamos tener otra tarea: encontrar no todos los archivos que contienen "Este es el archivo que necesitamos", sino sólo uno de esos archivos . Después de esto, se debe finalizar el programa. En este caso, nuestro código se vería exactamente igual, pero en lugar de romperse; harí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: ¡Se ha encontrado el archivo requerido! C:\Users\Username\Desktop\testFolder\FileWeNeed1.txt ¡Se encontró el archivo requerido! C:\Users\Username\Desktop\testFolder\level1-a\level2-aa\FileWeNeed2.txt ¡Se encontró el archivo requerido! C:\Users\Username\Desktop\testFolder\level1-b\level2-bb\FileWeNeed3.txt ¡ Genial, lo logramos! :) Si quieres saber más al respecto walkFileTree(), te recomiendo este artículo . También puede completar una pequeña tarea: reemplazarla SimpleFileVisitorpor una normal FileVisitor, implementar los 4 métodos y encontrar un propósito para este programa. Por ejemplo, puede escribir un programa que registre todas sus acciones: mostrar el nombre de un archivo o carpeta en la consola antes/después de ingresarlas. Eso es todo, ¡hasta luego! :)
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION