1. Archivos La mayoría de los desarrolladores Java no requieren del manejo de archivos en sus labores diarias, sin embargo es esencial ya que muchas de las operaciones de I/O (Lectura y Escritura de información) que involucran archivos tienen mucho que ver con el rendimiento de algunas aplicaciones. Procesos de migración de aplicaciones, integraciones con sistemas legados, carga de archivos en aplicaciones web, procesos batch, procesos de facturación electrónica, etc. son algunas de las aplicaciones en las que se requiere del manejo de archivos. Java dentro de su paquete conocido como NIO provee un conjunto de clases para trabajar con archivos y procesos de I/O en general. Con el JDK 7 se incluyen un nuevo conjunto de clases en el paquete NIO2 cuyo enfoque es hacer más sencillo desde el punto de vista del desarrollo muchas de las operaciones de I/O, entre ellas la manipulación de archivos. Si bien Java desde su inicio se concibió de manera independiente de la plataforma, muchas de las operaciones que se realizan con con archivos si si son dependientes de la plataforma, operaciones sobre symbolic links o vínculos simbólicos existen solo en plataformas Unix es un ejemplo de esta e sta dependencia. Durante esta lectura revisaremos las clases necesarias para el manejo de archivos tanto con la versión inicial de Java NIO y NIO2
1.1. Clases para el manejo de Archivos 1. File: Es una representación abstracta de un archivo o directorio. Esta clase no se utiliza para escribir o leer datos, si no para trabajar a alto nivel; es decir p ara crear, buscar, eliminar archivos y para trabajar con directorios y rutas. 2. FileReader: Esta clase se usa para leer caracteres de un archivo. Tiene un método read() de bajo nivel, el cual se puede utilizar para leer un simple carácter, un flujo entero de caracteres, o un número específico de caracteres. 3. BufferedReader: Esta clase se utiliza para hacer más eficientes las operaciones de lectura cuando se trata de grandes cantidades de información, permite almacenar los datos en un buffer. De este modo cada vez que se necesite obtener la información, se obtendrá desde el buffer minimizando así los accesos a disco. 4. FileWriter: FileWriter: Esta clase se usa para escribir caracteres en un archivo. Posee el método write() que permite escribir caracteres o Strings a un archivo. 5. BufferedWriter: Se utiliza para hacer más eficiente la escritura de grandes cantidades de información a un archivo de una vez, minimizando el número de operaciones de escritura. También provee el método writeLine(), el cual añade los caracteres de salto de línea correspondientes a la plataforma en la que se ejecuta. 6. PrintWriter: Envuelve las clases FileWriter y BufferedWriter. Añade A ñade varios métodos como format(), printf() y append() que brindan mayor flexibilidad y poder.
1.2. Clases para el manejo de Archivos NIO2
1. Path: Se puede considerar de una actualización sobre la clase File. La clase Path es una referencia programática del mismo concepto relacionado dentro de un sistema de archivos, un Path se refiere a una ruta dentro un esquema jerárquico ya sea de un archivo o de un directorio. 2. Paths: Se trata de una clase utilitaria que permite obtener un Path a partir de un String o de una URI. 3. Files: Se trata de una clase utilitaria que permite realizar operaciones sobre archivos y directorios.
2. STREAMS La habilidad de escribir y leer datos de algún recurso o medio y de utilizarla para producir una salida es una característica muy común en las aplicaciones empresariales. A esta interacción que tiene un programa con el resto se la conoce como Input/Output (IO) Tomando en cuenta que un computador puede conectarse a diferentes tipos de dispositivos de entrada y salida, se hizo necesario definir una forma de abstraer los diferentes tipos de información que se intercambia entre el computador y los dispositivos, a este flujo de información se le conoce con el nombre de Streams.
Las clases utilizadas para realizar las diferentes operaciones de I/O se encuentran ubicadas en el paquete java.io.*, que contiene todas las clases Streams que soportan los algoritmos de escritura - lectura. Estas clases están dividas en dos grupos jerárquicos, basados en el tipo de dato y son las siguientes:
Character Streams Byte Streams
2.1 Character Streams Son especializados para lectura y escritura de caracteres de datos entendibles por el usuario (humano). Las clases principales o superclases para el manejo de caracteres son Reader y Writer , a partir de las cuales existen muchas generalizaciones. Es importante aclarar que los caracteres son almacenados en la computadora como valores Unicode de 16 bits, por lo cual la mayoría de programas deberían utilizar los character streams para leer o escribir información textual, ya que estos pueden manejar cualquier carácter unicode.
2.2 Byte Streams
Son especializados para lectura y escritura de datos en formato de m áquina, debido que son almacenados como 8 bytes. Las clases principales o superclases para el manejo de bytes son InputStream y OutputStream . Estos streams son especializados para leer o escribir datos binarios como sonidos o imágenes. Dentro de las generalizaciones de Byte Streams, se tiene a las clases ObjectInputStream y ObjectOutputStream que son usadas para la serialización de objetos. Cabe
destacar que los APIS utilizados para los Character Streams y Byte Streams son similares variando solamente el tipo de dato.
2.3. Clasificación Streams Entre los tipos de Streams de acuerdo al ámbito, entre los más comunes podemos mencionar los siguientes:
Ámbito
Streams
Descripción
Memoria
CharArrayReader, CharArrayWriter ByteArrayInputStream, ByteArrayOutputStream StringReader, StringWriter
Usados para leer y escribir a memoria ya sea utilizando caracteres o bytes como datos.
Archivos
FileReader, FileWriter FileInputStream, FileOutputStream
Usados para leer de un archivo o escribir a uno sobre un sistema de archivos nativo. (NTFS, EXT3).
Serialización Objetos
ObjectInputStream, ObjectOutputStream
Usado para serializar objetos
Filtrado
FilterReader, FilterWriter FIlterInputStream, FilterOutputStream
Usados para filtrar los datos de un stream ya sea para leer o escribir al mismo.
3. Manejo de Archivos - La clase Path Previa a la creación de un archivo, es necesario crear una referencia dentro del sistema de archivos del sistema operativo correspondiente, como se mencionó la clase File y la clase Path son las que permiten este manejo. Veamos a continuación como se lo realiza en código: Utilizando la clase File import java.io.File; public class ManejadorDeArchivos{ File file= new File("c:\\ejemplos\\archivo1.txt"); }
Utilizando la clase Path import java.nio.file.Path; import java.nio.file.Paths; public class ManejadorDeArchivos{ Path path = Paths.get("c:\\ejemplos\\archivo1.txt"); }
En ambos casos con las líneas de código indicadas no se ha creado aún el archivo "archivo1.txt", tanto la clase Path como la clase File están creando una referencia al archivo.
Ejercicio 1 Agrega un método main a la clase ManejadorDeArchivos y utilizando la clase Path, dentro del método main, crea una nueva instancia del objeto apuntando a una ruta que no exista dentro de tu máquina. Ha sido posible la ejecución del programa, ¿cómo se comporta la clase Path cuando se la trata de instanciar con una ruta a un archivo o directorio que no existe? Como se puede notar tanto en la definición de los objetos File y Path, el String que hace referencia al archivo dentro del sistema de archivos, es dependiente de la plataforma, en el caso de sistemas UNIX se pude definir de la siguiente manera Path path2 = Paths.get("/home/ejemplos/archivo1.txt");
Una solución para el manejo de la dependencia del separador en los directorios y archivos es utilizar una propiedad del sistema denominada path.separator System.getProperty("path.separator");
La clase Path permite realizar múltiples operaciones que involucran la manipulación de rutas dentro del sistema de archivos sin la necesidad de acceder al mismo o modificarlo, estas son operaciones lógicas que se las realizan en memoria. Por esta razón se dice que la clase Path trabaja a un nivel sintáctico. Debemos recordar que se puede definir Paths Absolutos y Paths relativos. Path absoluteFilePath = Paths.get("C:", "ejemplos", "prueba", "archivo1.txt"); Path relativeFilePath = Paths.get("/workspace/JavaAdvanced",
"src/resources","archivo1.txt");
Ejercicio 2 En los ejemplos de código anteriores se muestra como obtener la referencia a una ubicación en el sistema de archivos utilizando varios parámetros del tipo String, ¿cuáles son las maneras de utilizar el método get de la clase Path para obtener esta referencia? Se debe en este punto señalar que la clase Path no reemplaza al ciento por ciento a la clase File, por lo tanto ambas pueden coexistir en una aplicación sin ningún inconveniente. Como se mencionó al inicio la clase Path permite realizar varias operaciones lógicas, entre las cuales tenemos:
Manejar los diferentes niveles del Path como una estructura lineal tipo Arreglo utilizando el método getName(int posición)
System.out.format("getName(0): %s%n", path.getName(0));
Obtener el Path absoluto a partir de un Path relativo
System.out.println(relativeFilePath.toAbsolutePath());
Normalizar Paths (Cuando contienen escapes de un Path a otro utilizando puntos ..)
Path nomalizedFilePath =Paths.get("C:/workspace/ejemplos/src/resources/dumy/../archivo1.txt") .normalize();
Concatenación de Paths (Join)
Path path1 = Paths.get("C:\\ejemplos"); // Result is C:\\ejemplos\\archivo1.txt System.out.format("%s%n", path1.resolve("archivo1.txt"));
Comparación de Paths
Para esta comparación basta con utilizar el método equals de l a clase Path
4. Files La clase Files es quizá una de las más grandes características en NIO2, ya que soluciona muchos de los inconvenientes que presentaban cuando se trataba de manipular archivos. Operaciones al parecer tan simples como mover un archivo de un directorio a otro, que con la clase File (de NIO) no se podía realizar sino copiando el archivo origen al directorio destino y luego eliminando el archivo origen, se reducen a una invocación a la clase Files y su método move.
4.1 Creación de archivos Iniciemos entonces con la creación de un archivo utilizando File (NIO) En este caso se crea un archivo en la raíz del disco, en el caso de querer crearlo en un directorio, el directorio debe existir o sino crearlo file.mkdir(), donde file debe ser instanciado con la ruta del directorio try { File file = new File("C:\\archivo1.txt"); if (file.createNewFile()){ System.out.println("File is created!"); }else{ System.out.println("File already exists."); } } catch (IOException e) {
e.printStackTrace(); }
Revisemos ahora la creación con un Files (NIO2) En esta se verifica la existencia del directorio previo la creación del archivo, nótese que se usa el método getParent de la clase Path para obtener el directorio padre del archivo. Path newFile = Paths.get("C:\\ejemplos\\archivo1.txt"); try { //en primer lugar se verifica la existencia del directorio //ejemplos caso contrario lo crea if (Files.notExists(newFile.getParent())) { Files.createDirectory(newFile.getParent()); } newFile = Files.createFile(newFile); } catch (IOException e) { e.printStackTrace(); }
4.2 Lectura y Escritura de información de archivos Para la escritura de contenido sobre un archivo se utilizan los streams o flujos revisados con anterioridad en esta lectura. Analicemos la escritura y lectura de archivos pequeños
NIO public File escribirArchivo(File archivo) throwsIOException { BufferedReader entrada = new BufferedReader(new InputStreamReader(System.in)); String linea = entrada.readLine(); FileWriter salida = new FileWriter(archivo); salida.write(linea); salida.write(linea); salida.write(linea);
salida.close(); return archivo; }
NIO2 Charset charset = Charset.forName("UTF-8"); List
lines = new ArrayList<>(); lines.add(System.getProperty("line.separator")); lines.add("Nueva linea"); lines.add("Agregada al Archivo"); lines.add("usando el método write"); // After each line, this method appends the platform’s line separator
(line.separator system property). newFile = Files.write(newFile, lines, charset, StandardOpenOption.APPEND);
Ejercicio 3 El método write de la clase Files recibe un parámetro que hace referencia a las operaciones Estándar que se pueden realizar sobre un archivo, ¿cuáles son estas operaciones? Nota. Revisar las constantes de la clase StandardOpenOption
Lectura y escritura de archivos con buffer Charset charset = Charset.forName("UTF-8"); try (BufferedReader reader = Files.newBufferedReader(file, charset)) { String line = null; while ((line = reader.readLine()) != null) { System.out.println(line); } } catch (IOException x) { System.err.format("IOException: %s%n", x); }
Escritura con buffer Charset charset = Charset.forName("UTF-8"); String s = "Cadena de texto larga a escribir"; try (BufferedWriter writer = Files.newBufferedWriter(file, charset)) { writer.write(s, 0, s.length()); } catch (IOException x) { System.err.format("IOException: %s%n", x); }
Creando archivos temporales String sufix = ".txt"; String prefix="JVAD"; try { // pasando nulo en el prefijo y en sufijo toma el valor default ".tmp" Path tempFile = Files.createTempFile(null, null); Path tempFile1 = Files.createTempFile(prefix, sufix); Path baseTempPath = Paths.get("c://temp"); //se puede especificar el directorio de temporales Path tempFile2 = Files.createTempFile(baseTempPath, prefix, sufix); } catch (IOException e) { e.printStackTrace(); }
Moviendo un archivo import static java.nio.file.StandardCopyOption.*; Files.move(pathArchivoOrigen, directorioDestino, REPLACE_EXISTING);
Copiando un archivo import static java.nio.file.StandardCopyOption.*;
Files.copy(pathOrigen, pathDestino, REPLACE_EXISTING);
Eliminando un archivo try { Files.delete(path); } catch (NoSuchFileException x) { System.err.format("%s: no such" + " file or directory%n", path); } catch (DirectoryNotEmptyException x) { System.err.format("%s not empty%n", path); } catch (IOException x) { // File permission problems are caught here. System.err.println(x); }
5. Directorios Para el manejo de los contenidos y creación de directorios (carpetas) NIO2 proporciona un conjunto de métodos de uso sencillo. Para la copia y mover directorios se utiliza los mismos métodos que para mover un archivo ya que el parámetro es un Path y este puede referirse a un directorio.
5.1 Creando un directorio try { Path newDir = Paths.get("c://ejemplo"); if (Files.notExists(newDir)) { newDir = Files.createDirectory(newDir); } else { System.out.println("Dir: " + newDir.toString() + " is already exists."); } } catch (IOException e) { e.printStackTrace(); }
5.2 Creando un directorio temporal try { String prefijoDirectorio = "NIO2_"; Path tempDir = Files.createTempDirectory(null);
System.out.println("Temp dir sin prefijo: " + tempDir.toString()); tempDir = Files.createTempDirectory(prefijoDirectorio); System.out.println("Directorio temporal con prefijo: "+ tempDir.toString()); //para obtener la ubicación por default del directorio temporal System.out.println("El directorio temporal por default es: "+ System.getProperty("java.io.tmpdir")); } catch (IOException e) { e.printStackTrace(); }
5.3 Leyendo los contenidos de un directorio try { Path path = Paths.get("C:\\ejemplos"); try (DirectoryStream ds = Files.newDirectoryStream(path)) { for (Path file : ds) { System.out.println(file.getFileName()); } } } catch (IOException e) { e.printStackTrace(); }
5.4 Leyendo los contenidos de un directorio usado filtros try { Path path = Paths.get("C:\\ejemplos"); try (DirectoryStream ds = Files.newDirectoryStream(path, "*.{txt}")) { for (Path file : ds) { System.out.println(file.getFileName()); } } } catch (IOException e) { e.printStackTrace(); }
Ejercicio 5 Para el filtro de archivos se utilizar un concepto conocido como glob pattern, a que se refiere y como se usa el glob pattern