Diferencia entre revisiones de «Prog. Ficheros»

De MediaWiki
Ir a la navegación Ir a la búsqueda
Línea 453: Línea 453:
  
 
* Podéis comprobar en el enlace de la clase la cantidad de métodos útiles que tenemos para el manejo de ficheros y directorios.
 
* Podéis comprobar en el enlace de la clase la cantidad de métodos útiles que tenemos para el manejo de ficheros y directorios.
 +
: Operaciones:
 +
:* Borrar un archivo.
 +
:* Crear un archivo.
 +
:* Establecer fecha y hora de modificación del archivo.
 +
:* Crear un directorio.
 +
:* Listar el contenido de un directorio.
 +
....
 +
  
 
: Por ejemplo, podemos hacer uso del [https://docs.oracle.com/javase/7/docs/api/java/io/File.html#exists() método exists] para determinar si un fichero/directorio existe antes de hacer uso del mismo (hasta ahora hacíamos uso de la excepción FileNotFoundException).
 
: Por ejemplo, podemos hacer uso del [https://docs.oracle.com/javase/7/docs/api/java/io/File.html#exists() método exists] para determinar si un fichero/directorio existe antes de hacer uso del mismo (hasta ahora hacíamos uso de la excepción FileNotFoundException).

Revisión del 10:33 18 ene 2018

Entrada/Salida de información: Flujos

  • En java, para entender el sistema de entrada/salida de información (es decir, como leer o escribir información desde o hacia cualquier dispositivo), es necesario entender el concepto de flujo.
Podemos definir flujo como una 'abstracción' que proporciona Java y que identifica a una secuencia de bytes desde un dispositivo de entrada hacia un dispositivo de salida.


A través de dichos flujos vamos a poder realizar lecturas y escrituras sobre diferentes dispositivos sin que el programador necesita saber nada acerca de ellos, ya que será el núcleo de Java el que se entenderá con el S.O. para realizar las operaciones correspondientes.


  • En Java, dichos flujos están identificados por una jerarquía de clases que se encuentran en el paquete java.io:


Prog io 1jpg.jpg
Imagen obtenida de los apuntes de Jose Luis Comesaña


  • Podemos dividir estas clases en dos grupos, en función del tipo de dato que van a leer:
  • Clases que leen o escriben flujos de caracteres de 16 bits unicode. Estas son las clases abstractas Reader y Writer y todas las que deriven de ellas.
  • Clases que leen o escriben flujo de bytes de 8 bits. Estas son las clases abstractas InputStream y OutputStream y todas las que deriven de ellas.


  • Otra división de los flujos es de la dividirlos según vayamos a leer o a escribir datos:
  • Salida (...output...)
  • Entrada (...input...)


  • Quizás podemos entender más claramente el concepto de flujo si nos fijamos cuando escribimos por teclado y desde nuestro programa guardamos dicha información o cuando mandamos escribir por consola un texto determinado.
En Java, las letras (información) que estamos tecleando llegan a nuestro programa a través de un 'flujo' de entrada, denominado 'flujo de entrada estándar' y posteriormente enviamos información a la consola a través de un 'flujo de salida estándar'. En caso de encontrar algún error también enviará un mensaje a la consola (como la salida) por medio de otro flujo de datos.
Estos flujos en Java los tenemos implementados en los objetos System.in, System.out y System.err.
Si observáis la definición de dichos objetos (son final y de clase) veréis que pertenecen a alguna de las clases de la jerarquía anterior.


En la jerarquía anterior vemos que hay ciertas clases que implementan un buffer (BufferedReader, BufferedInputStream,...).
Recordar que un buffer es una zona de almacenamiento, que se utiliza de 'puente' entre un dispositivo de entrada y otro de salida, para aumentar la velocidad en las operaciones de lectura y escritura así como el rendimiento.
Si usamos sólo FileInputStream, FileOuputStream, FileReader o FileWriter, cada vez que hagamos una lectura o escritura, se hará físicamente en el disco duro.
Si escribimos o leemos pocos caracteres cada vez, el proceso se hace costoso y lento, con muchos accesos a disco duro.
Los BufferedReader, BufferedInputStream, BufferedWriter y BufferedOutputStream añaden un buffer intermedio.
Cuando leamos o escribamos, esta clase controlará los accesos a disco.
  • Si vamos escribiendo, se guardarán los datos hasta que tenga bastantes datos como para hacer la escritura eficiente.
  • Si queremos leer, la clase leerá muchos datos de golpe, aunque sólo nos dé los que hayamos pedido. En las siguientes lecturas nos dará lo que tiene almacenado, hasta que necesite leer otra vez.
Esta forma de trabajar hace los accesos a disco más eficientes y el programa correrá más rápido. La diferencia se notará más cuanto mayor sea el fichero que queremos leer o escribir.


  • Veamos un sencillo ejemplo de cómo leer un archivo de texto.
Para ello, debemos hacer uso de la clase FileReader, que como comentamos antes, es una clase que sirve para leer caracteres.
Incorpora por tanto el método read.
Para leer un archivo siempre vamos a tener que realizar las siguientes operaciones:
  • Abrir el archivo para iniciar la lectura.
  • Leer del archivo hasta llegar al final del mismo.
  • Cerrar el archivo.
En nuestro caso, debemos crear un objeto de la clase FileReader, en el que podemos observar como el constructor está sobrecargado, y en uno de ellos debemos enviar un String con la ruta y nombre del fichero a abrir.
  • Al llamar al constructor ya nos abre el fichero para iniciar la lectura.
  • Después debemos leer del fichero carácter a carácter utilizando el método read() hasta el final del mismo. Esto lo sabemos ya que el método read() devuelve el valor -1 cuando llega al final.
  • Finalmente debemos cerrar el fichero con el método close().
  • Puede suceder que tengamos excepciones debido a que el fichero no exista o no se pueda leer o que durante el proceso de lectura dejemos de tener acceso al mismo (FileNotFoundException y IOException).
 1         FileReader f = null;
 2         int c;
 3 
 4         try {
 5             f = new FileReader("/tmp/prueba.txt");
 6             while((c = f.read())!=-1) {
 7                 System.out.print((char)c);
 8             }
 9             
10             
11         } catch (FileNotFoundException ex) {
12             System.err.println("Fichero no encontrado");
13         } catch (IOException ex) {
14             System.err.println("Error al leer el contenido del fichero");
15         }
16         finally {
17             try {
18                 if (f != null)
19                     f.close();
20             } catch (IOException ex) {
21                 System.err.println("Error al leer cerrar fichero");
22             }
23         }
  • Antes de la versión Java 7 era necesario cerrar un fichero de forma explícita (es decir, teníamos que llamar al método close()).
La llamada a dicho método se debe realizar en la sección finally.


  • Indicar que a partir del JDK 7 podemos hacer uso de un nuevo recurso denominado try-with-resources.
Es una forma 'especial' de utilizar el try de tal forma que el cierre del flujo ya se realiza automaticamente tanto si todo se ejecuta de forma correcto como si hay una excepción y ya no tenemos que realizar una llamada a close(). Esto se puede hacer con cualquier objeto que implemente la interface AutoClosable.
 1         int c;
 2 
 3         try (FileReader f = new FileReader("/tmp/prueba.txt"){
 4             while((c = f.read())!=-1) {
 5                 System.out.print((char)c);
 6             }
 7             
 8             
 9         } catch (FileNotFoundException ex) {
10             System.err.println("Fichero no encontrado");
11         } catch (IOException ex) {
12             System.err.println("Error al leer el contenido del fichero");
13         }
  • Indicar que la definición del objeto debe ir dentro del try y entre paréntesis (en el ejemplo FileReader f...).
  • Si tenemos varios recursos, es mejor instanciarlos separadamente y separarlos con punto y coma, como por ejemplo:
1     String filePath = "/tmp/fichero.txt";
2     int bsize = 1024; /* tamaño del buffer */
3 
4     try (FileReader file = new FileReader(filePath);
5          BufferedReader br = new BufferedReader(file, bsize))
6     {
7         return read(br);
8     }


  • Necesario cerrar el fichero.
Llamar al método close del 'flujo' que estemos utilizando siempre es necesario por los siguientes motivos:
  • Si hemos abierto el fichero para leer, estamos haciendo uso de un recurso que está ocupando un espacio de memoria y que no quedará liberado hasta que se llame al método close().
  • Si hemos abierto el fichero para escritura, cuando llamamos al método close() no solamente liberamos la memoria como pasaba en la lectura, sino que además se suelen enviar los datos modificados que quedan guardados en un buffer (espacio para guardar temporalmente información y que cuando está lleno se envío a disco para su escritura definitiva, permitiendo aumentar el rendimiento).
Veamos un ejemplo de escritura de una cadena de texto a un archivo:
 1         String cadena = "Esto es una cadena";
 2         FileWriter fw=null;
 3         try {
 4             fw = new FileWriter("/tmp/salida.txt");
 5             fw.write(cadena);
 6         } catch (IOException ex) {
 7             System.out.printf("Error:%s",ex.getMessage());
 8         }
 9         finally{
10             if (fw!=null){
11                 try {
12                     fw.close();
13                 } catch (IOException ex) {
14                     System.out.printf("Error:%s",ex.getMessage());
15                 }
16             }
17         }
Si quitamos las líneas de la 9 a la 16, podemos comprobar como la cadena de texto no se llega a escribir a texto.




Clases para lectura de caracteres

  • Veamos ahora todas las clases de lectura de caracteres.
Como norma general debemos de tener en cuenta que los Reader van a permitir leer caracteres en base a alguna codificación y nos van a suministrar caracteres, mientras que los Stream van a leer bytes y nos suministran bytes.


  • En casi todas las clases de lectura de datos vamos a encontrar los siguientes métodos:
  • read(): Lee un solo byte.
  • read(byte[] b): Lee de una sola vez los bytes indicados por el tamaño del array y devuelve el número de bytes leídos.
  • read(byte[] b,int offset, int length): Lee de una sola vez el número de bytes indicado por length y los guarda en el array pero a partir de la posición indicada por offset. Hay que tener cuidado que la suma de length y offset no sobrepase el tamaño del array.



FileReader

  • Deriva de InputStreamReader.
  • Permite leer un fichero carácter a carácter o en bloque haciendo uso de un array.
  • Un ejemplo de uso:
 1         int leido = 0;  // Nos devuelve el carácter leído
 2         String texto = ""; // Texto del fichero
 3         try (FileReader fr = new FileReader("/tmp/prueba.txt")){    // Abrimos con recursos, se cierra automaticamente
 4 
 5             while (leido != -1){
 6                 leido = fr.read(); 
 7                 if (leido != -1) {
 8                     texto += (char)leido;
 9                 }
10             }
11         
12         } catch (FileNotFoundException ex) {
13            System.err.printf("%nError:%s",ex.getMessage());
14         } catch (IOException ex) {
15            System.err.printf("%nError:%s",ex.getMessage());
16         }
17         System.out.print(texto);
  • Línea 6: Al hacer un read() el método va a devolver el carácter leído o el valor -1 si llegó al final.
  • Línea 8: Necesitamos convertir el número a un char.
Prog io 3.jpg




  • Veamos un ejemplo de lectura haciendo uso de un array:
 1         char[] buf = new char[5];   
 2         int leidos = 0;  // Nos va a servir saber cuantos caracteres fueron leídos del fichero
 3         try (FileReader fr = new FileReader("/tmp/prueba.txt")){    // Abrimos con recursos, se cierra automáticamente
 4 
 5             while (leidos != -1){
 6                 leidos = fr.read(buf);  // Me indica cuantos caracteres se han leído 
 7                 System.out.println(buf);
 8             }
 9         
10         } catch (FileNotFoundException ex) {
11            System.err.printf("%nError:%s",ex.getMessage());
12         } catch (IOException ex) {
13            System.err.printf("%nError:%s",ex.getMessage());
14         }
En este caso el resultado será bloques de 5 caracteres. Usamos println para que en la salida se pueda apreciar como vamos leyendo de 5 en 5.
Prog io 2.jpg



BufferedReader

  • Permite leer texto sobre un flujo de texto pero haciendo uso de un buffer intermedio haciendo que el rendimiento aumente.
  • Normalmente las clases que implementan un buffer van a trabajar sobre otro flujo de entrada/salida, es como si envolvieran el flujo de entrada/salida original y añadieran una capa nueva con las funcionalidades del buffer.
  • En nuestro caso, vamos a hacer que el buffer trabaje sobre un flujo FileReader como el visto en el caso anterior.
  • Tendremos los siguientes métodos (a mayores de los visto en el FileReader):
  • readLine(): Devuelve una línea entera del fichero.
  • mark(): Permite establecer una marca en el fichero para en el momento que queramos, volver a situarnos en la posición marcada.
  • reset(): Nos volvemos a posicionar en una posición marcada previamente.
  • Veamos un ejemplo de su uso:
 1         String cadena = "";
 2         
 3         try (FileReader fr = new FileReader("/tmp/prueba.txt");
 4             BufferedReader bfr = new BufferedReader(fr)){
 5             
 6             while((cadena = bfr.readLine()) != null){
 7                 System.out.println(cadena);
 8             }
 9             
10             
11         } catch (FileNotFoundException ex) {
12            System.err.printf("%nError:%s",ex.getMessage());
13         } catch (IOException ex) {
14            System.err.printf("%nError:%s",ex.getMessage());
15         }
  • Líneas 3 y 4: Para utilizar el BufferedReader necesitamos un flujo y creamos previamente un FileReader
  • Línea 6: Llamamos al métod readLine() para leer una línea entera del fichero de texto.
  • Línea 7: Fijarse que para mostrar el contenido necesitamos un 'println' ya que la cadena no guarda el salto de línea.


  • Si quisiéramos guardar todo el contenido en una variable String tendríamos que poner algo parecido a esto (más adelante en este curso veremos el uso de la clase StringBuielder):
1             while((cadena = bfr.readLine()) != null){
2                 cadena += '\n';  // Salto de línea
3             }



Clase Scanner

  • No es una clase que represente un flujo sino que hace uso de las clases comentadas en el gráfico inicial.
  • La ponemos en este punto porque vamos a ver como podemos utilizar otros flujos de entrada que no sea el teclado y cómo nos va a resultar muy útil para analizar archivos de texto y des componerlos en partes...


  • Como primer ejemplo vamos a ver como leer un fichero línea a línea haciendo uso de la clase Scanner.
Haremos uso del método hasNextLine() para saber cuando se ha acabado de leer el fichero (podéis ver en el enlace de la clase que existen múltiples hasXXXXX disponibles).
Por lo tanto si necesitamos leer todos los números de una línea, por ejemplo, tendremos que preguntar por hasInt() dentro de un bucle.
 1         String salida="";
 2         
 3         try (FileReader fr = new FileReader("/tmp/prueba.txt");
 4             Scanner sc = new Scanner(fr)){
 5             
 6             while(sc.hasNextLine()){
 7                 salida +=sc.nextLine() + '\n'; 
 8             }
 9             
10             System.out.println(salida);
11             
12             
13         } catch (IOException ex) {
14            System.err.printf("%nError:%s",ex.getMessage());
15         }


  • En este segundo ejemplo vamos a ver como analizar una línea de las leídas en el paso anterior.
Supongamos que disponemos de un archivo '.csv' (archivo de texto separado cada campo por un símbolo y cada línea por un salto de línea) con los siguientes datos:
Angel:23:981111111
Pedro:45:234232314
En este caso necesitamos enviar al Scanner la línea leída para que la pueda analizar (va en el constructor).
También haremos uso del método useDelimiter, para indicar cual es el carácter que separa cada uno de los campos de cada línea.
 1         String linea="";
 2         
 3         try (FileReader fr = new FileReader("/tmp/datos.txt");
 4             Scanner sc = new Scanner(fr)){
 5             
 6             while(sc.hasNextLine()){
 7                 linea = sc.nextLine();
 8                 Scanner scLinea = new Scanner(linea);
 9                 scLinea.useDelimiter(":");  // Indicamos cual es el caracter utilizado para saparar los campos
10                 if (scLinea.hasNext()){ // Preguntamos por si no viene bien formado o sin datos
11                     String nombre = scLinea.next();
12                     int edad = scLinea.nextInt();
13                     String telefono = scLinea.next();
14                     
15                     System.out.printf("%nNombre:%s,edad:%d,telefeno:%s",nombre,edad,telefono);
16                 }
17             }
18             
19         } catch (IOException ex) {
20            System.err.printf("%nError:%s",ex.getMessage());
21         }


  • Esto mismo lo podemos realizar haciendo uso del método split de Java:
 1         String linea="";
 2         
 3         try (FileReader fr = new FileReader("/tmp/datos.txt");
 4             Scanner sc = new Scanner(fr)){
 5             
 6             while(sc.hasNextLine()){
 7                 linea = sc.nextLine();
 8                 String[] partes = linea.split(":");
 9                 if (partes.length == 3){
10                     System.out.printf("%nNombre:%s,edad:%s,telefeno:%s",partes[0],partes[1],partes[2]);
11                 }
12             }
13             
14             
15         } catch (IOException ex) {
16            System.err.printf("%nError:%s",ex.getMessage());
17         }
  • Línea 8: Descomponemos la línea en partes (obteniendo un array de cadena)
  • Línea 10: Fijarse como edad tiene que mostrarse con el %s ya que split devuelve todo en cadena. Sino daría un error de conversión.


Si quisiéramos convertir lo devuelto por split a un número deberemos tener cuidado y controlar la excepción java.lang.NumberFormatException de la forma:
1 try {
2    int edad = Integer.parseInt(partes[1]);
3 }
4 catch(java.lang.NumberFormatException ex){
5    System.err.println("Línea mal formada: ");
6 }



Comparativa entre utilizar Scanner o String.split.

CharacterArrayReader

  • Permite leer caracteres sobre un flujo de caracteres.
Ejemplo de uso.
¿ Y para qué puede servir esto ? Pues en el caso de que necesitéis pasar a un componente un Reader.


InputStreamReader

  • Permite leer un flujo de bytes convirtiéndolo a un flujo de caracteres, pudiendo especificar el juego de caracteres utilizado en la conversión.
  • Si observáis el constructor, necesitamos un InputStream por lo que tenemos que hacer uso de las clases que derivan de InputStream, por ejemplo, FileInputStream.
 1         try (FileInputStream fis = new FileInputStream("/tmp/prueba.txt");
 2              InputStreamReader isr = new InputStreamReader(fis,"ISO-8859-1")){
 3             char c=0;
 4             String cadena="";
 5             do {
 6                 c = (char) isr.read();
 7                 cadena +=c;
 8             }while (c!='\n');
 9             System.out.printf("%n%s",cadena);
10             
11         } catch (IOException ex) {
12             Logger.getLogger(Principal.class.getName()).log(Level.SEVERE, null, ex);
13         }
14 ::
  • En el ejemplo se está abriendo un archivo con el formato de caracteres ISO-8859-1.
  • Podríamos poner como InputStream la entrada estándar 'System.in'.

StringReader

  • Un flujo de caracteres cuyo origen es un String.

PipedReader

  • Permite una comunicación entre una 'tubería' de entrada y otra de salida. En Java se suele utilizar para comunicar dos threads dentro del mismo programa, en el que uno de ellos escribe en una tubería (PipedWriter) y otro lee el contenido de la misma (PipedReader) en forma de caracteres.
Sus equivalentes leyendo y escribiendo bytes son PipedInputStream y PipedOutputStream.
Podéis consultar un ejemplo de uso en este enlace.

PushbackReader

  • La clase FilterReader es abstracta y por tanto no se puede instanciar. La clase PushbackReader deriva de ella y es una clase que permite lectura de caracteres y además permite volver a 'meter' esos caracteres en el stream para volver a leerlos. Esto puede ser necesario si necesitamos leer un contenido de un fichero para determinar que tipo de contenido trae y después tratarlo en función de ese tipo de contenido. Para ello debemos de leer (método read) caracteres y después tendremos que volver a leerlos por lo que tendremos que llamar al método unread (buscar por unread en el enlace).

LineNumberReader


StreamTokenizer

  • Permite dividir un flujo de entrada en 'tokens' pudiendo reconocer palabras, números, identificadores, ciertos estilos de comentarios,...
Según la documentación será eliminada en el futuro, sólo se mantiene por compatibilidad y se recomienda hacer uso del método split de la clase String.



File

  • La clase File es una representación abstracta (no clase abstracta :) ) de un fichero o un directorio.
  • Podéis comprobar en el enlace de la clase la cantidad de métodos útiles que tenemos para el manejo de ficheros y directorios.
Operaciones:
  • Borrar un archivo.
  • Crear un archivo.
  • Establecer fecha y hora de modificación del archivo.
  • Crear un directorio.
  • Listar el contenido de un directorio.

....


Por ejemplo, podemos hacer uso del método exists para determinar si un fichero/directorio existe antes de hacer uso del mismo (hasta ahora hacíamos uso de la excepción FileNotFoundException).
  • Podemos enviarlo como un flujo de entrada de un FileInputStream, FileOutputStream, FileReader, Scanner...
  • Un ejemplo:
1         File fichero = new File("/tmp/fichero.txt");
2         if (!fichero.exists()){
3             System.err.println("El fichero no existe...");
4             try {
5                 fichero.createNewFile();
6             } catch (IOException ex) {
7                 System.err.println("El fichero no se puede crear:" + ex.getMessage());
8             }
9         }



Ejercicios propuestos

  • Crea una clase de nombre TratamientoFicherosTexto que conste de los siguientes métodos (no necesitas instanciar la clase para poder llamar a dichos métodos):
  • leerFichero(String nombre): devolverá un string con las líneas pares del fichero leído. Se debe comprobar que el fichero exista antes de leerlo.
  • obtenerSumaFichero(String nombre): devolverá la suma de los números que se encuentran en el fichero. Asumimos que el fichero puede tener líneas sin números y si en una línea hay varios números, estos están separados por una coma.
Ejemplo de fichero de datos: /tmp/datos.txt
1 30
2 20
3 
4 1,1,1
5 
6 10
Devolverá como resultado: 63
  • invertirFichero(String nombre): Muestra por pantalla el contenido invertido del fichero (no hace falta utilizar un array).
Haz una variación del ejercicio anterior
  • Crea un método de nombre cargarFicheroArray(String nombre) que lea el contenido y guarde cada línea del fichero en una posición de un array de String creado previamente (el array tendrá 100 elementos, si el fichero tiene más líneas que el tamaño del array dejaremos de leer el fichero).
Muestra el contenido del array en orden inverso.
  • contarPalabras(String nombre): método que devuelve el número de palabras del fichero.



Posibles soluciones ejercicios

  • obtenerSumaFichero(String nombre): devolverá la suma de los números que se encuentran en el fichero. Asumimos que el fichero puede tener líneas sin números y si en una línea hay varios números, estos están separados por una coma.
Ejemplo de fichero de datos: /tmp/datos.txt
1 30
2 20
3 
4 1,1,1
5 
6 10
Devolverá como resultado: 63
 1 package ficheros.caracteres;
 2 
 3 import java.io.File;
 4 import java.io.FileNotFoundException;
 5 import java.util.NoSuchElementException;
 6 import java.util.Scanner;
 7 import java.util.logging.Level;
 8 import java.util.logging.Logger;
 9 
10 public class TratamientoFicherosTexto {
11     
12     public static long obtenerSumaFichero(String nombre){
13         
14         File fichero = new File(nombre);
15         String linea = "";
16         int suma = 0;
17         
18         if (!fichero.exists()){ // Gestionamos nosotros el error. Podriamos no controlarlo y lanzar una excepción FileNotFoundException
19             return -1;
20         }
21         
22         try(Scanner sc = new Scanner(fichero)){
23             while((linea = sc.nextLine())!=null){
24                 Scanner scLinea = new Scanner(linea);
25                 scLinea.useDelimiter(",");
26                 while (scLinea.hasNextInt()){
27                     int numero = scLinea.nextInt();
28                     System.out.printf("%nNumero:%d",numero);
29                     suma += numero;
30                 }
31             }    
32         } catch (FileNotFoundException ex) {    // Obliga a ponerla pero no debería producirse
33             System.err.println("Fichero no encontrado");
34         }
35         finally{
36             return suma;
37         }
38     }
39     
40 }



Clases para escritura en ficheros de texto

Nueva línea

  • Para que un fichero de texto refleje una nueva línea debemos enviar la siguiente secuencia de caracteres:
  • En caso de Windows: "\r\n"
  • En caso de Linux: "\n"
  • Si queremos obtener la secuencia de caracteres por programación podemos poner: String nuevaLinea = System.getProperty("line.separator");

Formatear la información de salida

  • Puede ser que necesitemos darle un formato a la información que vamos a guardar en un fichero.
Dicho formato puede consistir en:
  • Modificar el ancho de las columnas.
  • Justificación del texto.
  • Alineación
  • Formato de fechas.


  • Todo lo anterior se puede conseguir haciendo uso de la clase Formatter de Java.
Parte de los formatos ya los hemos visto haciendo uso del método printf durante este curso.
  • Podéis consultar las diferentes opciones de formato en:


  • Veamos unos ejemplos de uso:
1         Formatter formato=new Formatter();
2         formato.format("%-10s-%-10s-%-10s-%1$-10s","casa","amigo","recuerdo");
3         System.out.println(formato);
Resultado:
casa      -amigo     -recuerdo  -casa      (acaba aquí)
  • Hasta ahora, poníamos algo parecido a esto en una sentencia printf (o llamando al método String.format): System.out.printf("%s-%s-%s-%s","casa","amigo","recuerdo","casa");
Como vemos, los caracteres '%s' son sustituidos por los datos "casa","amigo",....de izquierda a derecha.
Pero como vemos, existe un dato ("casa") que queremos repetir en otra parte de la cadena. format identifica cada dato de la forma 1$,2$,3$,...siendo 1$=>"casa",2$=>"amigo"...
Por tanto podemos cambiar lo anterior por la siguiente expresión: System.out.printf("%s-%s-%s-%1$s","casa","amigo","recuerdo");
De hecho podríamos ponerlo así: System.out.printf("%1$s-%2$s-%3$s-%1$s","casa","amigo","recuerdo");
Otra de las opciones que nos permite tener el formateo de cadenas es el de indicar que número de caracteres queremos que se impriman como mínimo en cada uno de los parámetros. Es el width.
En nuestro ejemplo queremos que todos los parámetros (los datos que enviamos imprimir) tengan un ancho mínimo de 10 caracteres: System.out.printf("%10s-%10s-%10s-%1$10s","casa","amigo","recuerdo");
Por defecto, la justificación de las cadenas de caracteres es hacia la derecha. En nuestro ejemplo queremos que se justifiquen a la izquierda, por lo que tenemos que añadir el signo menos (es uno de los flags posible): System.out.printlf("%-10s-%-10s-%-10s-%1$-10s","casa","amigo","recuerdo");
En el desarrollo del ejemplo he utilizado la sentencia printf, pero podemos aplicar los mismos conceptos de formato a la clase Formatter y al método String.format().


  • Otro ejemplo de uso:
1         Formatter formato=new Formatter();
2         formato.format("%+010d",10);
3         System.out.println(formato);
Resultado:
+000000012
Opciones empleadas:
  • +: Flag que indica que si el número es positivo lleve el símbolo +
  • 0: Como estamos indicando en la siguiente opción que tenga un tamaño de 10 caracteres, con el cero indicamos que el resto de caracteres se completen con el número 0 hasta llegar a 10.
  • 10: Indicamos que el tamaño total de la impresión del número sea de 10 caracteres.


  • Otro ejemplo de uso (obtenido del Enlace 1):
1         Formatter f3=new Formatter();
2         f3.format(Locale.FRENCH, "%1$te %1$tB, %1$tY",
3                 Calendar.getInstance());
4         System.out.println(f3);
5 
6         Formatter f4 = new Formatter();
7         f4.format(Locale.GERMANY, "%1$te %1$tB, %1$tY",
8                 Calendar.getInstance());
9         System.out.println(f4);
Resultado:
1 18 janvier, 2018
2 18 Januar, 2018
En este caso, estamos empleando otro formato del método format() de la clase Formatter.
Como primer parámetro indicamos la región.
El siguiente parámetro es el formato. Como vemos estamos indicando tres datos de tipo fecha con la opción %t, concretamente estamos utilizando el mismo parámetro tres veces: %1$t
Las letras 'e','B' e 'Y' indican que se muestre el día del mes en número, el mes en letra y el año con cuatro dígitos.
Códigos para mostrar fechas/horas.


  • Esta clase la podemos utilizar para escribir con un formato determinado los datos en un fichero de salida.



FileWriter

  • Su funcionamiento es igual al FileReader, pero en este caso sería para escribir datos en el fichero.
  • Como diferencia tenemos:
  • Podemos utilizar un constructor para indicar que vamos a añadir datos al fichero (por defecto borra todo)
  • Vamos a poder escribir datos (en vez de leer): método write() o método append().





Clases para lectura de bytes

  • Veamos ahora todas las clases de lectura de bytes.
Como norma general debemos de tener en cuenta que los Reader van a permitir leer caracteres en base a alguna codificación y nos van a suministrar caracteres, mientras que los Stream van a leer bytes que tendremos que convertir a caracteres.

FileInputStream

  • Obtiene bytes de un fichero del sistema operativo.
  • Si nos fijamos en el constructor, podemos utilizar una cadena para indicar el nombre del fichero a abrir, un descriptor o un objeto de la clase File.
  • Si utilizamos una cadena debemos de controlar la excepción de que el fichero no exista (FileNotFoundException).
Veremos después el uso de la clase File.
  • En casi todas las clases de lectura de datos vamos a encontrar los siguientes métodos:
  • read(): Lee un solo byte.
  • read(byte[] b): Lee de una sola vez los bytes indicados por el tamaño del array y devuelve el número de bytes leídos.
  • read(byte[] b,int offset, int length): Lee de una sola vez el número de bytes indicado por length y los guarda en el array pero a partir de la posición indicada por offset. Hay que tener cuidado que la suma de length y offset no sobrepase el tamaño del array.


  • Supongamos que tenemos creado un fichero /tmp/prueba.dat formado por números y queremos leerlo utilizando esta clase, dando como resultado la suma de todos ellos.
 1         int dato = 0,suma = 0;
 2         try (FileInputStream fis = new FileInputStream("/tmp/prueba.txt")){
 3             do {
 4                 dato = fis.read();
 5                 if (dato != -1){
 6                     suma += dato;
 7                 }
 8             }while(dato != -1);
 9             System.out.printf("La suma de todos los números es:%d",suma);
10 
11         } catch (FileNotFoundException ex) {
12             Logger.getLogger(Principal.class.getName()).log(Level.SEVERE, null, ex);
13         } catch (IOException ex) {
14             Logger.getLogger(Principal.class.getName()).log(Level.SEVERE, null, ex);
15         }
Debemos de fijarnos que esta clase va a leer bytes y debemos ser nosotros los que convirtamos al tipo de dato correcto.
  • Veamos ahora otro ejemplo utilizando un array:
 1         byte[] buf = new byte[5];
 2         int numBytesLeidos = 0, suma = 0;
 3         try (FileInputStream fis = new FileInputStream("/tmp/prueba.dat")){
 4             do {
 5                 numBytesLeidos = fis.read(buf);
 6                 if (dato != -1){
 7                     for(int cont=0;cont<numBytesLeidos;cont++){
 8                        suma += buf[cont];
 9                     }
10                 }
11             }while(dato != -1);
12             System.out.printf("La suma de todos los números es:%d",suma);
13 
14         } catch (FileNotFoundException ex) {
15             Logger.getLogger(Principal.class.getName()).log(Level.SEVERE, null, ex);
16         } catch (IOException ex) {
17             Logger.getLogger(Principal.class.getName()).log(Level.SEVERE, null, ex);
18         }




Entrada/Salida estándar

  • Utilizando los conceptos vistos de salida y entrada estándar podemos hacer una redirección de la salida estándar (podríamos hacerlo con la salida de errores o la entrada estándar) a un fichero.
Para ello debemos hacer uso del método setOut() de la clase System.
 1         FileReader f = null;
 2         int c;
 3         PrintStream pStreamSalida=null;
 4         
 5         try {
 6             pStreamSalida = new PrintStream("/tmp/salida.txt");
 7             System.setOut(pStreamSalida);
 8             f = new FileReader("/tmp/prueba.txt");
 9             while((c = f.read())!=-1) {
10                 System.out.print((char)c);
11             }
12             
13             
14         } catch (FileNotFoundException ex) {
15             System.err.println("Fichero no encontrado");
16         } catch (IOException ex) {
17             System.err.println("Error al leer el contenido del fichero");
18         }
19         finally {
20             try {
21                 if (f != null)
22                     f.close();
23             } catch (IOException ex) {
24                 System.err.println("Error al leer cerrar fichero");
25             }
26         }
Cualquier System.out que hagamos después de redirigir la salida estándar será enviado al fichero "/tmp/salida.txt"




Java NIO Files

  • Es una clase que se encuentra en java.nio.file.Files y permite la manipulación de ficheros y directorios de una forma mucho más sencilla a lo visto hasta ahora.


  • Tenéis ejemplos de uso en:




-- Ángel D. Fernández González -- (2017).