Prog. Ficheros

De MediaWiki
Saltar a: navegación, buscar

Contenido

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 / escritura de texto

  • Vamos a ver cuales son las clases que utiliza Java para la lectura y escritura de caracteres.
  • Indicar que todas tienen como parte de su nombre XXXXReader o XXXXWriter ya que derivan de las clases abstractas Reader y Writer.
Siempre que veamos una clase con parte de su nombre Reader o Writer sabremos que es una clase para lectura / escritura de caracteres.



Clases para lectura de ficheros de texto

  • 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 caracter (devuelve un int representa el código del carácter).
  • read(char[] b): Lee de una sola vez los caracteres indicados por el tamaño del array y devuelve el número de caracteres leídos.
Siempre que se pueda mejor utilizar esta opción que ir leyendo carácter a carácter aunque va a depender de las necesidades de nuestro programa. Si por ejemplo tengo un fichero de datos en los que tengo que cada dato se que va a tener 100 caracteres de tamaño, podré leer de 100 en 100 los datos del fichero.
  • read(char[] b,int offset, int length): Lee de una sola vez el número de caracteres 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.


  • Siempre que se pueda, es mejor especificar el juego de caracteres a utilizar en la lectura (que debe corresponder con el juego de caracteres del archivo de texto).
Si utilizamos clases para leer el contenido de un archivo y no especificamos el juego de caracteres, este utilizará el de la plataforma Java instalada en el ordenador, que normalmente debería ser UTF-8.



FileReader

  • Deriva de InputStreamReader.
  • Permite leer un fichero carácter a carácter o en bloque haciendo uso de un array.
  • No permite indicar el tipo de codificación que tiene que utilizar para leer el archivo, por lo que si este se encuentra codificado de forma diferente al que tiene la plataforma Java por defecto, no lo leerá correctamente.
En caso de necesitar indicar el tipo de codificación podremos hacer uso de la clase InputStreamReader como veremos más adelante.
  • 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=fr.read(buf)) != -1){
  6.                 System.out.println(new String(buf,0,leidos));
  7.             }
  8.        
  9.         } catch (FileNotFoundException ex) {
  10.            System.err.printf("%nError:%s",ex.getMessage());
  11.         } catch (IOException ex) {
  12.            System.err.printf("%nError:%s",ex.getMessage());
  13.         }
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.
  • Línea 6: Fijarse que necesitamos indicar cuantos caracteres del array conforman la cadena ya que el la última lectura puede suceder que se lean menos de 5 (porque quedan menos de 5 caracteres para acabar el fichero).
Prog io 2.jpg



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.
  • Esta clase permite indicar el tipo de codificación del archivo.
  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.             System.err.printf("Error en la lectura:%s",ex.getMessage());
  13.         }
  14. ::
  • En el ejemplo se está abriendo un archivo con el formato de caracteres ISO-8859-1.
  • Podemos averiguar cual es el código de caracteres empleado por el fichero llamando al método getEncoding().


  • Podríamos poner como InputStream en el constructor de la clase FileInputStream:
  • La entrada estándar 'System.in' (lo veremos más adelante)
  • Un flujo de bytes como la clase FileInputStream (lo veremos más adelante).


  • Veamos un ejemplo en el que la clase BufferedReader va a 'envolver' a la clase InputStreamReader para permitirnos leer línea a línea el fichero (que tiene un juego de caracteres diferente al que tiene el entorno de Java por defecto).
  1.         String cadena = "";
  2.         try (FileInputStream fis = new FileInputStream("/tmp/prueba.txt");
  3.              InputStreamReader isr = new InputStreamReader(fis,"ISO-8859-1");
  4.              BufferedReader bfr = new BufferedReader(isr)
  5.             ){
  6.             while((cadena=bfr.readLine()) != null)
  7.             System.out.printf("%s%n",cadena);
  8.            
  9.         } catch (IOException ex) {
  10.             System.err.printf("Error en la lectura:%s",ex.getMessage());
  11.         }
  12. ::



BufferedReader

  • Permite leer texto sobre un flujo de texto pero haciendo uso de un buffer intermedio haciendo que el rendimiento aumente.
El tamaño del buffer por defecto es de 8192 bytes (8KB), pero puede ser indicado en el constructor.
  • 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.             }


  • También podríamos envolver a un objeto de la clase InputStreamReader de la forma:
  1. FileInputStream fis = new FileInputStream("/tmp/prueba2.txt");
  2. InputStreamReader isr = new InputStreamReader(fis,"UTF-8");
  3. BufferedReader bfr = new BufferedReader(isr);



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.



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.         }



Filtrando ficheros

  • Nos puede ser útil obtener el nombre de ciertos ficheros en base a algún criterio de búsqueda.
Esto lo conseguimos haciendo uso de la Interface FileNameFilter.
  • Esta interface debe ser implementado en una clase definida por nosotros.
Al hacerlo, tendremos que implementar el método accept() el cual devolverá true/false en base a si queremos que un determinado fichero sea incluido en la lista.
Este método debe de recibir como argumentos un objeto de la clase File que representa el directorio donde se encuentra el fichero y un objeto de la clase String, que indica el nombre del fichero.
Nosotros debemos de implementar en base a los criterios que queramos (nombre del fichero, extensión, tamaño, ....) si el fichero indicado por el nombre se va a incluir en la lista de ficheros (devolveremos true o false).
  • Por ejemplo. la siguiente clase implementa un filtro:
  1. public class MiFiltroFicheros implements FilenameFilter{
  2.     private static final long  MEGABYTE = 1024L * 1024L;
  3.    
  4.     @Override
  5.     public boolean accept(File dir, String name) {
  6.        
  7.         File fichero = new File(dir+name);
  8.         if (name.endsWith(".pdf") &&
  9.             ((fichero.length()/MEGABYTE)<2)
  10.            ){
  11.             return true;
  12.         }
  13.         return false;
  14.        
  15.     }
  16. }
  • Línea 8: Comprobamos que el archivo tenga extensión pdf (fijarse que indicamos extensión, el contenido del archivo no tendría por qué ser pdf. Para estar seguros de que un archivo es de un tipo determinado tendríamos que verificar su 'mime type', haciendo uso del método Files.probeContent por ejemplo).
  • Línea 9: Comprobamos que tenga un tamaño inferior a 2MB.


  • Ahora llega la segunda parte. Tenemos que aplicar este filtro a una lista de ficheros que se encuentren en un directorio.
Esto lo haremos con el método list() de la clase File.
Dicho método va a devolver un conjunto de nombres de ficheros en forma de array de String con los que cumplan los criterios establecidos en el método accept() de la interface.
Espera recibir como argumento un objeto de una clase que implemente la interface 'FileNamedFilter'.
Veamos un ejemplo:
  1.         File directorio = new File("/tmp");
  2.        
  3.         String[] ficherosEncontrados = directorio.list(new MiFiltroFicheros());
  4.        
  5.         if (ficherosEncontrados.length==0){
  6.             System.out.println("Ningún fichero encontrado con esos criterios...");
  7.         }
  8.         else {
  9.             System.out.println("Ficheros encontrados.");
  10.             for (int cont=0;cont<ficherosEncontrados.length;cont++){
  11.                 System.out.printf("Fichero:%s",ficherosEncontrados[cont]);
  12.             }
  13.         }
  • Línea 3: Aplicamos el filtro a los ficheros que se encuentren en el directorio '/tmp/'.



Ejercicios propuestos flujos de lectura de caracteres

  • 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.
Haz una variante del ejercicio anterior (contarPalabrasB(String nombre)) y haz que devuelva un array de 3 elementos con:
  • El primer elemento del array debe contar el número de caracteres del fichero.
  • El segundo elemento del array debe contar el número de palabras del fichero.
  • El tercer elemento del array debe contar el número de líneas del fichero.


  • contarPalabras(String directorio, String extension): Método que buscará todos los ficheros que se encuentren en el directorio indicado con la extensión indicada y contará las palabras de todos ellos. Devolverá el número de palabras total de todos los ficheros. Hará uso del método anterior contarPalabras.


  • Disponemos de un fichero de entrada de nombre 'datos.txt' con el siguiente formato por línea:
tipo-nombre-nota1-nota2-nota3
Ejemplo:
A-Angel-5.3-7.6-4
A-Pepe-6.2-7.6-4
P-Juan
......
Se quiere hacer un programa que lea el contenido del fichero y guarde en un array de objetos de una clase Alumno creada previamente la siguiente información: Nombre, nota1ª, nota2ª, nota3ª, media
También se debe guardar en un array los profesores encontrados.
Implementa los métodos que lean el contenido de los ficheros en una interface.
La clase que guarda el array de Alumnos y Profesores se debe llamar Gestion.
Crea un método en Gestion que muestre el contenido del los arrays, indicando el número de estudiantes/profesores encontrados en el fichero.
Nota: Como no sabemos hacer arrays dinámicos, leeréis dos veces el fichero para saber primero el número de alumnos/profesores y poder reservar el espacio en el array.


  • Crea un método de nombre contarLineas(String fichero) que devuelve el número de líneas del fichero.
En caso de no existir el fichero devolverá -1.
Haz uso de la clase LineNumberReader.



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
30
20

1,1,1

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(sc.hasNextLine()){
  24.                 linea = sc.nextLine();
  25.                 Scanner scLinea = new Scanner(linea);
  26.                 scLinea.useDelimiter(",");
  27.                 while (scLinea.hasNextInt()){
  28.                     int numero = scLinea.nextInt();
  29.                     System.out.printf("%nNumero:%d",numero);
  30.                     suma += numero;
  31.                 }
  32.             }    
  33.         } catch (FileNotFoundException ex) {    // Obliga a ponerla pero no debería producirse
  34.             System.err.println("Fichero no encontrado");
  35.         }
  36.         finally{
  37.             return suma;
  38.         }
  39.     }
  40.    
  41. }



Clases para escritura en ficheros de texto

  • En todas ellas tendremos:
  • El método write() para escribir un dato al fichero.
  • El método flush() para vaciar el flujo y escribir los datos del mismo al fichero. Esto se hace automáticamente al cerrar el fichero.


  • Siempre (a no ser que se especifique lo contrario) debemos indicar el juego de caracteres (charset) que debe emplear el fichero.
Esto es así ya que si no lo indicamos, va a depender de la configuración de la plataforma Java instalada en cada ordenador, dando lugar a que si el mismo programa se ejecuta en ordenadores diferentes, podemos tener juegos de caracteres distintos.



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");



Separador de ficheros

  • La barra que separa los directorios en el sistema de ficheros depende del S.O. empleado.
Así:
  • Windows: '\'
  • Linux: '/'
  • Si quisiéramos indicar una ruta de un archivo de Windows, tendríamos que escribir: String ruta = "C:\\misarchivos\\nombre.txt";
Necesitamos poner doble barra para que escape el carácter e interprete la siguiente barra como el carácter que es.
  • Si quisiéramos indicar una ruta de un archivo en Linux, tendríamos que escribir: String ruta = "/misarchivos/nombre.txt";
  • En Java disponemos de dos formas para saber cual es el carácter empleado por el S.O. para separar las rutas:
  • String separador = File.separator;
  • String separador = System.getProperty("file.separator");


  • Por lo tanto, cuando empleemos rutas de acceso a ficheros deberemos de utilizar el separador obtenido de una de las dos formas anteriores.
  • Una forma de hacerlo 'automático' es utilizar una de los dos separadores por defecto (imaginemos que escogemos el de Linux) e implementar en una clase (por ejemplo, una clase de nombre Ficheros) el siguiente código:


  1.     public static String getRutaSO(String rutaYFichero){
  2.         // Supongo que en nuestro código estoy utilizando el separador / por defecto
  3.        
  4.         return rutaYFichero.replaceAll("/", File.separator);
  5.        
  6.     }




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:


  • Esta clase la vamos a poder aplicar sobre cualquiera de las clases Writer que vamos a ver a continuación.


  • 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",12);
  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.


  • Si utilizamos la salida estándard (la pantalla) para mostrar la información, debemos de tener encuenta que en este caso, el método format va 'acumulando' las diferentes salidas. Será necesario crear una nueva instancia si queremos 'inicializarlo':
  1. Formatter f = new Formatter();
  2.  
  3. f.format("Una línea %n");
  4. f.format("Segunda línea %n");
  5. f.format("Tercera línea %n");
  6.  
  7. System.out.println(f);  // Muestra las 3 líneas


  • Esta clase la podemos utilizar para escribir con un formato determinado los datos en un fichero de salida como veremos a continuación.



FileWriter

  • Su funcionamiento es igual al FileReader, pero en este caso 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 al abrirlo)
  • Vamos a poder escribir datos (en vez de leer): método write() o método append().


  • Deriva de la clase OutputStreamWriter, la cual se puede utilizar en caso de necesitar especificar el juego de caracteres a utilizar en el fichero de destino.


  • Un ejemplo:
  1.         try (java.io.FileWriter fw = new FileWriter("/tmp/salida.txt")){    // Abrimos con recursos, se cierra automaticamente
  2.  
  3.             fw.write("Una línea de texto\n");
  4.             fw.write("Otra línea de texto\n");
  5.            
  6.            
  7.         } catch (IOException ex) {
  8.             System.err.printf("Erro en escritura:%s",ex.getMessage());
  9.         }


  • Otro ejemplo haciendo uso de la clase Formatter y comprobando si el fichero existe para añadir información:
  1.         boolean añadir=false;
  2.         File fichero = new File("/tmp/salida.txt");
  3.         if (fichero.exists()){
  4.             añadir = true;
  5.         }
  6.         try (java.io.FileWriter fw = new FileWriter("/tmp/salida.txt",añadir);
  7.             Formatter formato=new Formatter(fw);){    // Abrimos con recursos, se cierra automaticamente
  8.  
  9.             formato.format("Una línea de texto\n");
  10.             formato.format("Fecha actual:%tc\n",Calendar.getInstance());
  11.            
  12.            
  13.         } catch (IOException ex) {
  14.             System.err.printf("Erro en escritura:%s",ex.getMessage());
  15.         }
  • Líneas 2-5: Utilizando un objeto de la clase File comprobamos si el fichero existe para dar un valor a la variable booleana 'añadir'
  • Línea 6: Abrimos el flujo con la opción de añadir en función del valor de la variable booleano.
  • Línea 7: Creamos un objeto de la clase Formatter utilizando como destino el flujo FileWriter. Ahora cada vez que llamemos al método format escribirá la cadena en el fichero.
  • Líneas 9-10: Escribe en el fichero las cadenas con el formato indicado.


  • Si quisiéramos abrir el fichero para añadir datos, tendríamos que utilizar el construtor en el que se le pasa un booleano indicando si se añaden datos: try (java.io.FileWriter fw = new java.io.FileWriter("/tmp/salida.txt",true);


  • Podemos utilizar la clase Formatter junto a FileWriter para escribir en el fichero en cada llamada al método 'format' de la clase Formatter:
  1.         try (java.io.FileWriter fw = new java.io.FileWriter("/tmp/salida.txt");
  2.              Formatter formato = new Formatter(fw);  
  3.              ){    // Abrimos con recursos, se cierra automaticamente
  4.  
  5.             formato.format("%s%n","Una frase del fichero.");
  6.             formato.format(Locale.FRENCH, "%1$te %1$tB, %1$tY",
  7.                 Calendar.getInstance());          
  8.            
  9.         } catch (java.io.IOException ex) {
  10.             System.err.printf("Erro en escritura:%s",ex.getMessage());
  11.         }
  • Línea 2: Creammos un objeto de la clase Formatter utilizando el flujo FileWriter.
  • Líneas 4 y 5: Escribimos en el fichero.



BufferedWriter

  • Igual que el BufferedReader pero para la escritura.
  • Dispone entre otros de:
  • Un método newLine() para escribir un salto de línea.
  • Un método write(String cadena) para escribir una cadena.


  • Un ejemplo de uso:
  1.         try(FileWriter fw = new FileWriter("/tmp/salida.txt");
  2.             BufferedWriter bfw = new BufferedWriter(fw)){
  3.            
  4.             bfw.write("Esto es un texto");
  5.             bfw.newLine();
  6.             bfw.write("Esto es otro texto");
  7.             bfw.write(" que se escribirá en la misma línea");
  8.         } catch (IOException ex) {
  9.             Logger.getLogger(Escritura.class.getName()).log(Level.SEVERE, null, ex);
  10.         }


  • Otro ejemplo de uso con la clase Formatter:
  1.         String nuevaLinea = System.getProperty("line.separator");
  2.  
  3.         try(FileWriter fw = new FileWriter("/tmp/salida.txt");
  4.             BufferedWriter bfw = new BufferedWriter(fw);
  5.             Formatter formato = new Formatter(bfw);    
  6.             ){
  7.            
  8.             formato.format("Una línea de texto"+nuevaLinea);
  9.             formato.format("Fecha actual:%tc"+nuevaLinea,Calendar.getInstance());
  10.  
  11.         } catch (IOException ex) {
  12.             Logger.getLogger(Escritura.class.getName()).log(Level.SEVERE, null, ex);
  13.         }



PrintWriter

  • Permite enviar cadenas de caracteres con un formato a un flujo de salida (Writer, File, OutputStream o Cadena con nombre del fichero).


  • Si el fichero existe lo borra al abrirlo y si no existe lo crea.
Podríamos abrir un fichero y añadir información se utilizamos un objeto de la clase FileWriter (pasando true como segundo parámetro) y lo 'envolvemos' con un objeto de la clase PrintWriter de la forma:
  1. try (FileWriter fw = new FileWriter("/tmp/salida.txt",true);
  2.      PrintWriter pw = new PrintWriter(fw);) {
  3.  
  4. }


  • Sólo los constructores que tienen como parámetros un objeto de la clase File o una cadena de texto se permite especificar el juego de caracteres (charset)
Por lo tanto no vamos a poder utilizar esta clase para añadir datos a un fichero indicando el juego de caracteres a emplear.


  • Disponemos de un método format() para enviar los datos en base a alguna codificación regional (Locale).


  • Los métodos en esta clase no lanzan excepciones IOException en caso de error. El programador debe hacer uso de los métodos:
  • checkError(): Para comprobar si la llamada al método produjo un error.
  • clearError(): Una vez gestionado el error se debe 'limpiar' para poder chequear un posterior.


  • Si está habilitado el 'autoflush', cada vez que se invoque a los métodos 'println', 'printf' o 'format' se escribirá en el fichero. Sino será necesario llamar al método 'flush' o cerrar el flujo para que se envíen los datos al fichero.
  • Dispone del método print que se encuentra sobrecargado para poder enviar datos de múltiples tipos de datos diferentes (incluido objetos, haciendo uso del método toString()).
  • Dispone del método format() que permite indicar un formato a la salida de información como en la clase Formatter.


  • Veamos un ejemplo:
  1.         File fichero = new File("/tmp/salida.txt");
  2.         try(PrintWriter pw = new PrintWriter(fichero,"UTF-8")){
  3.            
  4.             pw.print("Una línea de texto");
  5.             pw.println();
  6.             pw.format(Locale.FRENCH, "%1$te %1$tB, %1$tY",
  7.                 Calendar.getInstance());      
  8.             pw.print(35);
  9.            
  10.         } catch (UnsupportedEncodingException ex) {
  11.             System.err.printf("Fichero no encontrado:%s",ex.getMessage());
  12.         } catch (FileNotFoundException ex) {
  13.             Logger.getLogger(Ficheros.class.getName()).log(Level.SEVERE, null, ex);
  14.         }


  • Veamos otro ejemplo en el que se ve como unas clases 'envuelven' a otras para aumentar las funcionalidades.
En este ejemplo vamos a hacer que un FileWriter sea 'envuelto' por un 'BufferedWriter' para hacer uso de un buffer y aumentar el rendimiento y a su vez, el BufferedWriter será 'envuelto' por un PrintWriter para poder enviar al flujo de salida diferentes tipos de datos, como flotantes, booleano o cualquiera de los que soporta la clase PrintWriter.
De esta forma nos evitamos tener que estar convirtiendo los datos a enviar al flujo.
Fijarse que el orden en el que se envuelven las clases es importante, ya que podríamos haber hecho que la clase BufferedWriter envolviera a la PrintWriter, pero de esa forma sólo, si quisiéramos utilizar el buffer sólo lo podríamos conseguir llamando a los métodos de BufferedWriter, no a los de PrintWriter.
  1.         try (FileWriter fw = new FileWriter("/tmp/salida.txt");
  2.              BufferedWriter bfw = new BufferedWriter(fw);
  3.              PrintWriter pw = new PrintWriter(bfw);
  4.              ) {
  5.            
  6.             pw.write("Ejemplo de cadena");
  7.             pw.print(10.2); // Ejjemplo de float utilizando PrintWriter
  8.             pw.print(true);  // Ejemplo de booleanno utilizando PrintWriter
  9.            
  10.         }   catch (IOException ex) {
  11.            System.err.printf("%nError:%s",ex.getMessage());
  12.         }



OutputStreamWriter

  • Al igual que se clase equivalente en lectura (InputStreamWriter) esta clase es un puente entre un flujo de bytes y un flujo de caracteres.
Va a convertir los bytes a caracteres.
  • Por lo tanto, en su constructor deberemos enviarle un flujo de bytes (que veremos en la siguiente sección).


  • Va a permitir escribir caracteres, array de caracteres y cadenas.
  • Vamos a poder abrir un fichero para añadir datos indicando el juego de caracteres a utilizar.
  1.             FileOutputStream fos = new FileOutputStream("fichero.txt",true);
  2.             OutputStreamWriter osw = new OutputStreamWriter(fos,"UTF-8");


  • Al igual que indicamos antes, vamos a poder 'envolver' los flujos para dar más funcionalidades:
  1.             FileOutputStream fos = new FileOutputStream("fichero.txt",true);
  2.             OutputStreamWriter osw = new OutputStreamWriter(fos,"UTF-8");
  3.             PrintWriter pw = new PrintWriter(osw);



CharArrayWriter

  • Igual que en el caso de la lectura, vamos a podes escribir caracteres o String a un array de caracteres.


  • Un ejemplo:
  1.         String nuevaLinea = System.getProperty("line.separator");
  2.        
  3.         try(CharArrayWriter caw = new CharArrayWriter()){
  4.             caw.write("Prueba de texto"+nuevaLinea);
  5.             caw.write("Otro texto");
  6.            
  7.             char[] datos = caw.toCharArray();
  8.            
  9.             String datosEscritos = new String(datos);
  10.             System.out.print(datosEscritos);
  11.         } catch (IOException ex) {
  12.             System.err.printf("Erro en escritura:%s",ex.getMessage());
  13.         }



PipedWriter

  • 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.



StringWriter

  • Permite añadir caracteres a un StringBuffer.
  • Permite escribir caracteres, cadenas, un array de caracteres y se puede indicar del array de caracteres o de la cadena a escribir que partes serán enviadas al buffer.
  • Un ejemplo:
  1.         try(StringWriter sw = new StringWriter()){
  2.            
  3.             sw.write("Prueba de caracteres");
  4.             sw.write("Nuevos caracteres pero solo añade Nuevos", 0, 6);
  5.  
  6.             StringBuffer sBuffer = sw.getBuffer();
  7.             System.out.println(sBuffer.toString());
  8.         } catch (IOException ex) {
  9.            System.err.printf("%nError:%s",ex.getMessage());
  10.         }




Ejercicios propuestos flujos de escritura de caracteres

  • Lee por teclado la siguiente información:
  • Nombre, edad y peso (puede tener decimales).
  • Se repetirá la lectura hasta que se teclee como nombre la letra E.
  • Por cada conjunto de datos (nombre, edad y peso) se escribirá una línea en el fichero 'salida'.txt' con formato UTF-8 (que debe ser especificado).
Cada dato estará separado por el carácter dos puntos.
El nombre ocupará 50 caracteres y estará alineado a la izquierda.
La edad ocupará 3 caracteres.
El peso debe mostrar 3 caracteres decimales.
  • En caso de que el fichero exista se añadirán nuevos datos.


  • Define un método de nombre descomponerCadena que reciba como parámetros el nombre y ruta de un fichero, una cadena y un booleano.
  • El objetivo del método será escribir cada carácter de la cadena en una línea del fichero, menos el salto de línea y los espacios.
  • El parámetro booleano indicará si queremos añadir contenido al fichero o borrar el contenido antes de añadir la información.




Leyendo y escribiendo caracteres

  • Normalmente vamos a utilizar los dos tipo de flujos (lectura / escritura).
  • Veamos unos ejemplos.


Ejercicio propuesto 1

  • Crea una clase de nombre LecturaEscritura.
  • Define un método de clase de nombre copiarSinVocales(String ficheroOrigen, String ficheroDestino) que copie el contenido del fichero origen en el fichero destino pero quitando todas las vocales.
El método debe devolver el número de vocales eliminadas.
Si el fichero origen no existe lanzará una excepción FileNotFoundException
Si el fichero destino no existe debe crearlo.



Solución Ejercicio 1

Clase LecturaEscritura

  1. import java.io.BufferedReader;
  2. import java.io.File;
  3. import java.io.FileNotFoundException;
  4. import java.io.FileReader;
  5. import java.io.FileWriter;
  6. import java.io.IOException;
  7. import java.io.PrintWriter;
  8. import java.util.Scanner;
  9.  
  10. public class LecturaEscritura {
  11.     private static final String VOCALES="AEIOUaeiou";
  12.    
  13.     public static long copiarSinVocales(String ficheroOrigen,String ficheroDestino) throws FileNotFoundException
  14.     {
  15.         int dato = 0;   // Caracter a leer del fichero
  16.         long numVocales = 0; // Número de vocales eliminadas
  17.  
  18.         File fOrigen = new File(ficheroOrigen);
  19.         if (!fOrigen.exists()) {
  20.             throw new FileNotFoundException("Fichero de origen no encontrado");
  21.         }
  22.        
  23.         try(FileReader fr = new FileReader(ficheroOrigen);
  24.             FileWriter fw = new FileWriter(ficheroDestino);    // Lo crea si no existe
  25.            ){
  26.             while((dato = fr.read())!=-1) {
  27.                 if (VOCALES.indexOf(dato)==-1){ // No es una vocal
  28.                     fw.write(dato);
  29.                 }
  30.                 else{
  31.                     numVocales++;
  32.                 }
  33.             }
  34.            
  35.         } catch (IOException ex) {
  36.             System.err.printf("%nError:%s",ex.getMessage());
  37.         }
  38.            
  39.         return numVocales;
  40.        
  41.     }
  42. }

Método main

  1.     public static void main(String[] arg){
  2.        
  3.         try {
  4.             long numVocales = LecturaEscritura.copiarSinVocales("/tmp/prueba.txt", "/tmp/pruebaSinVocales.txt");
  5.             System.out.printf("%nNúmero de vocales encontradas:%d",numVocales);
  6.                    
  7.         } catch (FileNotFoundException ex) {
  8.             System.out.println("Fichero no encontrado");
  9.         }
  10.        
  11.     }




Ejercicio propuesto 2

  • Crea en la misma clase que en el ejercicio anterior un método de clase de nombre procesarFichero(String ficheroOrigen, String ficheroDestino).
El fichero de entrada (ficheroOrigen) tendrá el formato: Coche:Potencia:Precio
El fichero de salida (ficheroDestino) tendrá de formato: Coche (50 caracteres de ancho, alineado a la izquierda)|Precio origen (3 decimales)|Precio rebajado (3 decimales).
El precio rebajado será el precio original menos un porcentaje guardado en forma de constante que nadie debe poder consultar y valor 10 (representa el 10%).
Si el fichero de destino existe se añadirán nuevos datos (esto no lo podemos hacer si no empleamos los flujos binarios de datos, como veremos más adelante. Tenéis como se haría en la línea 10 de la solución. Está comentada)
Si el fichero de origen no existe no se creará ningún fichero destino y se devolverá false.
El fichero de salida debe de tener como código de caracteres el charset "UTF-16".
En caso de que todo vaya bien el método devolverá true.





Posible Solución Ejercicio 2

Método procesarFichero

  1.     private static final float REBAJA = 10;
  2.  
  3.     public static boolean procesarFichero(String ficheroOrigen,String ficheroDestino){
  4.        
  5.         String lineaFichero = "";
  6.         File fOrigen = new File(ficheroOrigen);
  7.        
  8.         if (!fOrigen.exists()){
  9.             return false;
  10.         }
  11.        
  12.         try (PrintWriter pw = new PrintWriter(new File(ficheroDestino),"UTF-16"); // Veremos cuando demos los flujos binarios que podríamos poner algo así: PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream(ficheroDestino,true), "UTF-16"));
  13.              BufferedReader bfr = new BufferedReader(new FileReader(ficheroOrigen));
  14.             ) {
  15.             while((lineaFichero=bfr.readLine())!=null){ // Leemos el fichero hasta el final
  16.                 Scanner linea = new Scanner(lineaFichero);
  17.                 linea.useDelimiter(":");
  18.                 if (linea.hasNext()){
  19.                     String coche = linea.next();
  20.                     int potencia = linea.nextInt();
  21.                     float precio = linea.nextFloat();
  22.                    
  23.                     pw.format("%-50s|%.3f|%.3f%n",coche,precio,precio - (precio*REBAJA/100));
  24.                 }
  25.             }
  26.  
  27.            
  28.         }   catch (IOException ex) {
  29.             System.err.printf("%nError:%s",ex.getMessage());
  30.         }
  31.         return true;
  32.     }


Método main

  1. LecturaEscritura.procesarFichero("/tmp/origen.txt", "/tmp/destino.txt");



  • Una alternativa al método anterior podría ser esta (desde la línea 10), haciendo uso de la clase Formatter en vez de la clase PrintWriter:
  1.             try (BufferedWriter bfw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(ficheroDestino,false), "UTF-16"));
  2.              BufferedReader bfr = new BufferedReader(new FileReader(ficheroOrigen));
  3.             ) {
  4.             Formatter formato = new Formatter(bfw);
  5.             while((lineaFichero=bfr.readLine())!=null){ // Leemos el fichero hasta el final
  6.                 Scanner linea = new Scanner(lineaFichero);
  7.                 linea.useDelimiter(":");
  8.                 if (linea.hasNext()){
  9.                     String coche = linea.next();
  10.                     int potencia = linea.nextInt();
  11.                     float precio = linea.nextFloat();
  12.                     formato.format("%-50s|%.3f|%.3f%n",coche,precio,precio - (precio*10/100));
  13.                 }
  14.             }



Ejercicio propuesto 3

  • Crea un método de nombre conversionFicheros(String ficheroOrigen, String ficheroDestino).
El método debe crear un nuevo archivo 'ficheroDestino' con el mismo contenido de ficheroOrigen, pero convirtiendo el juego de caracteres del fichero origen a "UTF-16".
Deberás abrir el fichero origen con el juego de caracteres en el que está guardado (hazlo para los casos UTF-8 y UTF-16).
Puedes obtener la cadena que representa cada juego de caracteres haciendo uso de la clase StandardCharsets. Por ejemplo: String utf8 = StandardCharsets.UTF_8.name();



Solución ejercicio propuesto 3

  1.     public static void conversionFichero(String ficheroOrigen,String ficheroDestino) throws FileNotFoundException{
  2.         char[] datos = new char[1024];  // Vamos a leer de 1024 en 1024 bytes
  3.         int numCharLeidos = 0;
  4.        
  5.         File fichero = new File(ficheroOrigen);
  6.         if (!fichero.exists()){
  7.             throw new FileNotFoundException();
  8.         }
  9.         try ( FileInputStream fis = new FileInputStream(fichero);
  10.               InputStreamReader isr = new InputStreamReader(fis,StandardCharsets.UTF_8);
  11.               BufferedReader bfr = new BufferedReader(isr);   // Mejor con un buffer de por medio
  12.               FileOutputStream fos = new FileOutputStream(ficheroDestino);
  13.               OutputStreamWriter osr = new OutputStreamWriter(fos,StandardCharsets.UTF_16);
  14.                 BufferedWriter bfw = new BufferedWriter(osr);
  15.             ){    
  16.               while(((numCharLeidos = bfr.read(datos))) != -1){
  17.                   bfw.write(datos,0,numCharLeidos); // Fijarse que debemos de poner el número de char leidos por si la última lectura es menor al tamaño del array
  18.               }
  19.  
  20.         } catch (IOException ex) {
  21.             Logger.getLogger(FicheroSalida.class.getName()).log(Level.SEVERE, null, ex);
  22.         }
  23.        
  24.     }


Clases para lectura / escritura de bytes

  • Vamos a ver cuales son las clases que utiliza Java para la lectura y escritura de bytes.
  • Indicar que todas tienen como parte de su nombre XXXXStream para indicar que tenemos un flujo de bytes.
Dependiendo de si leemos o escribimos, tendremos InputXXX o OutputXXX.
Siempre que veamos una clase con parte de su nombre Stream sabremos que es una clase para lectura / escritura de bytes.


  • Para poder crear ficheros binarios o ver su contenido podemos hacer uso de cualquier editor binario de los que hay por la red.
Uno de ellos es: okteta.
Si estamos en una distribución basada en Ubuntu podemos ejecutar la siguiente orden desde un terminal: sudo apt-get install okteta


Clases para escritura de bytes

  • Van a permitir operaciones de escritura en forma de bytes.
  • En casi todas las clases de escritura de datos vamos a encontrar los siguientes métodos:
  • write(int byte): Escribe un solo byte.
  • write(byte[] b): Escribe de una sola vez los bytes indicados por el tamaño del array.
  • write(byte[] b,int offset, int length): Escribe de una sola vez el número de bytes indicado por length desde la posición offset del array.



FileOutputStream

  • Permite escribir bytes a un archivo, creándolo nuevo o añadiendo datos (constructor con segundo parámetro booleano).
  • El método write(int valor) permite enviar un entero, pero lo que se guardará será un cast a un byte del valor enviado.
  • Ejemplo de uso:
  1.         byte[] datos = {1,2,3,4,5,6};
  2.        
  3.         try(FileOutputStream fos = new FileOutputStream("/tmp/fichero.dat",true)){
  4.             fos.write(257);  // Escribirá el valor 01, ya que es un byte  (byte)257;
  5.             fos.write(datos,2,3);  // Guarda los valores 3,4 y 5
  6.            
  7.         } catch (FileNotFoundException ex) {
  8.             System.err.printf("%nError:%s",ex.getMessage());
  9.         } catch (IOException ex) {
  10.             System.err.printf("%nError:%s",ex.getMessage());
  11.         }
  • Resultado:
Prog ficher binario 3.jpg



DataOutputStream

  • Permite escribir en un fichero tipos de datos primitivos de Java.
  • El constructor de esta clase espera recibir un OutputStream.


  • Dispondremos de múltiples métodos writeXXXX() siendo XXXX un tipo primitivo de Java.


  • Ejemplo de uso:
  1.         try( FileOutputStream fos = new FileOutputStream("/tmp/datos.dat");
  2.             DataOutputStream dos = new DataOutputStream(fos);
  3.            )
  4.         {
  5.             dos.writeUTF("Angel");
  6.             dos.writeInt(30);
  7.             dos.writeUTF("Profesor");
  8.            
  9.         } catch (FileNotFoundException ex) {
  10.             System.err.printf("%nError:%s",ex.getMessage());
  11.         } catch (IOException ex) {
  12.             System.err.printf("%nError:%s",ex.getMessage());
  13.         }
  • Como vemos estamos escribiendo en un archivo binario, la cadena 'Angel', seguido de nueva línea,...
Veamos como se muestran estos datos en un editor hexadecimal:
Prog ficher binario 2.jpg


  • Analicemos el archivo:
  • Por cada carácter está utilizando dos bytes.
Además, al usar el método writeUTF() envía en primer lugar la longitud de la cadena guardada.
  • 00 05 => Número de bytes que ocupa la cadena.
  • 41 => A 6E => n 67 => g 65 => e 6C => l
  • 00 00 00 1E => 30 (los enteros ocupan 4 bytes)
  • 00 08 => Número de bytes que ocupa la cadena.
  • El resto es la cadena 'Profesor' (8 bytes)


  • Normalmente en un fichero binario de datos no vamos a tener 'saltos de línea'.
Lo que tendremos es una serie de registros con un tamaño determinado para cada campo en los que van a estar todos seguidos uno detrás de otro.
Sabiendo la longitud de cada campo, podremos leerlos.



BufferedOutputStream

  • Al igual que todas las clases BufferXXXX vistas, añade la funcionalidad de un buffer aumentando el rendimiento.
  • Disponemos del método flush() para forzar a vaciar el buffer y que se escriba a disco.


  • Ejemplo de uso:
  1.         byte[] datos = {1,2,3,4,5,6,7};
  2.        
  3.         try( FileOutputStream fis = new FileOutputStream("/tmp/datos.dat");
  4.              BufferedOutputStream bfos = new BufferedOutputStream(fis);
  5.             )
  6.         {
  7.             bfos.write(15); // 0xF en hexadecimal
  8.             bfos.write(datos,2,3);  // Posición 2 del array (desde el número 3, empieza en 0) y escribimos 3 bytes
  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.         }
Archivo creado:
Prog ficher binario 5.JPG



  • Aunque lo más lógico será utilizarlo así:
  1.         try( FileOutputStream fis = new FileOutputStream("/tmp/datos.dat");
  2.              BufferedOutputStream bfos = new BufferedOutputStream(fis);
  3.              DataOutputStream dis = new DataOutputStream(fis);
  4.             )
  5.         {
  6.             dis.writeUTF("Mandamos un texto"); // cada carácter pasará a un byte y los dos primeros indicarán cuantos bytes ocupan
  7.             dis.writeInt(3343);
  8.            
  9.         } catch (FileNotFoundException ex) {
  10.             System.err.printf("%nError:%s",ex.getMessage());
  11.         } catch (IOException ex) {
  12.             System.err.printf("%nError:%s",ex.getMessage());
  13.         }
Archivo creado:
Prog ficher binario 4.png



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 (en caso necesario).


  • 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.


  • En este enlace encontraréis una pequeña lista en la que se muestran las diferentes cabeceras de diferentes archivos binarios.


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 para indicar el nombre del fichero debemos de controlar la excepción de que el fichero no exista (FileNotFoundException).
  • Disponemos del método available() que nos informa del número de bytes estimado que quedan por leer. Por tanto, si abrimos el fichero y llamamos al método antes de leer ningún dato nos informará del tamaño del fichero.


  • Supongamos que tenemos un fichero binario /tmp/fichero.dat formado por los siguientes números:
Prog ficher binario 1.jpg
Queremos leerlo utilizando esta clase, dando como resultado la suma de todos ellos.
  1.         int dato = 0,suma = 0;
  2.        
  3.         try (FileInputStream fis = new FileInputStream("/tmp/fichero.dat")){
  4.             while((dato=fis.read())!=-1){
  5.                     suma += dato;
  6.             }
  7.             System.out.print(suma);
  8.         }catch (FileNotFoundException ex) {
  9.             System.err.printf("%nError:%s",ex.getMessage());
  10.         } catch (IOException ex) {
  11.             System.err.printf("%nError:%s",ex.getMessage());
  12.         }


  • Veamos ahora otro ejemplo utilizando un array:
  1.         int numBytesLeidos = 0, suma = 0;
  2.         byte[] buf = new byte[5];
  3.         try (FileInputStream fis = new FileInputStream("/tmp/fichero.dat")){
  4.             while((numBytesLeidos=fis.read(buf))!=-1){
  5.                 for(int cont=0;cont<numBytesLeidos;cont++){
  6.                    suma += buf[cont]& 0xFF;;
  7.                 }
  8.             }
  9.             System.out.print(suma);
  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.         }
  15.     }
  • Línea 6: Recordar que el tipo byte lleva signo y por lo tanto el número 255 representa el valor -1. Si queremos pasarlo a decimal, debemos de hacer una operación de 'and' con el valor 255.
En el ejemplo anterior no hizo falta hacerlo ya que guardamos la lectura en un entero y por tanto el valor guardo es el de 255.


  • Si leemos datos de texto con esta clase, debemos de tener en cuenta que leeremos de byte en byte.
Tendremos que ser nosotros los que convirtamos a cadena con un cast ((char)byteleido)) teniendo en cuenta los problemas que podemos tener dependiendo del charset del fichero.


  • Tener en cuenta que a pesar de ser un flujo de bytes, podríamos utilizar esta clase para que la clase InputStreamReader la 'envuelva' y podamos utilizarla como lectura de caracteres.
Esto ya lo comentamos al hablar de la clase InputStreamReader.
Por ejemplo:
  1. InputStreamReader isr = new InputStreamReader(new FileInputStream(nombreFichero), "UTF-8");



DataInputStream

  • Permite leer tipos primitivos de datos.
  • El constructor espera recibir un InputStream.
  • Disponemos de diferentes métodos readXXX() para leer tipos de datos de Java.
Nosotros leeremos cadenas que estén guardadas con el método writeUTF() de un DataOutputStream como veremos más adelante.
Cabe señalar que las cadenas guardadas con este formato tienen de formato: 4 bytes en el que se indica el número bytes que ocupa la cadena y después un byte para cada carácter.


  • Un ejemplo, en el que leeremos los dos primeros bytes y un entero (4 bytes) de un archivo bmp:
  1.         try(DataInputStream dis = new DataInputStream(new FileInputStream("/tmp/pinguino.bmp"))){
  2.            
  3.             byte signature1 = dis.readByte();
  4.             byte signature2 = dis.readByte();
  5.             int numero = dis.readInt();
  6.            
  7.             System.out.printf("Signatura:%x",signature1);
  8.             System.out.printf("Signatura:%x",signature2);
  9.         } catch (IOException ex) {
  10.             System.err.printf("%nError:%s",ex.getMessage());
  11.         }



BufferInputStream

  • Al igual que todas las clases BufferXXX implementa un buffer (un array internamente) para aumentar el rendimiento a la hora de realizar operaciones de lectura a disco.
Recordar que puede ser utilizada por otras clases para que 'envuelvan' el buffer y aumentar su rendimiento.


  • Dispone del método read para leer un byte o un array de bytes.
  • Dispone de los métodos:
  • mark() y reset(): para marcar y volver a una posición del fichero posteriormente.
  • skip(long num): para saltar un número de bytes indicado por su parámetro.
  • avaliable(): que devuelve el tamaño del fichero en bytes que queda por leer.


  • Veamos un ejemplo utilizando un buffer:
  1.         try( FileInputStream fis = new FileInputStream("/tmp/datos.dat");
  2.             BufferedInputStream bfis = new BufferedInputStream(fis);
  3.             DataInputStream dis = new DataInputStream(bfis)
  4.            )
  5.         {
  6.             String nombre = dis.readUTF();
  7.             int edad = dis.readInt();
  8.             String profesion = dis.readUTF();
  9.            
  10.             System.out.printf("%nNombre:%sEdad:%d%nProfesion:%s",nombre,edad,profesion);
  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.         }



Ejercicios propuestos para lectura / escritura de bytes

  • Busca por Internet que valores deben de tener las cabeceras de los archivos BMP y JPG para ser identificados.
Implementa un método de nombre comprobarTipoArchivo que devuelva un booleano y al que se le pasará como parámetro la ruta del fichero más el nombre y el tipo a comprobar.
El método deberá comprobar si el valor del campo de la cabecera que identifica al tipo de archivo coincide con el del archivo. Si es así devolverá true, en caso contrario devolverá false.
Deberás comprobar si el fichero existe y en caso de que no, el método lanzará una excepción FileNotFoundException.


  • Implementa un método de nombre leerFicheroTextoConStream que lea el contenido de un fichero de texto en formato UTF-16 utilizando la clase FileInputStream (necesitarás otra más) y muestre su contenido.


  • Escribe un método de nombre escrituraTextoBinario(String ficheroRuta) que lea por teclado cadenas de caracteres y las escríba en un archivo binario indicado en el parámetro. Cuando se introduza la palabra FIN se saldrá del método, informando del número de palabras escritas (valor de retorno).
Si el fichero existe se añadirán más datos.
Llama al método con la ruta: '/tmp/lecturas.dat', pero debes utilizar la función que te devuelve el separado de archivos utilizados por el S.O.


  • Escribe un método de nombre tratarTextoBinario(String ficheroRuta,Strinf ficheroRutaDestino) que lea el contenido del fichero binario indicado en el parámetro.
Si no existe lanzará una excepción que deberá ser controlada por el que hace uso del método.
Muestra el contenido de cada palabra.
Cuenta el número de letras, consonantes y vocales y escribe en un fichero binario indicado por el parámetro ficheroRutaDestino la siguiente información: palabra,nºletras, nºvocales y nºde consonantes. La palabra se guardará como string, pero los números se guardarán como enteros (no llevan coma separando cada campo, van seguidos).


  • Escribe un método de nombre leerArchivoBinario(String fichero) que lea, en caso de que exista, el contenido del archivo creado en el ejercio anterior.
Se debe mostrar una linea por cada palabra con el formato: palabra (50 caracteres alineado a la izquierda) | Nºletras (5 caracteres rellenados con ceros) | Nºconsonantes (5 caracteres rellenados con ceros) | Nºvocales (5 caracteres rellenados con ceros)



Posibles soluciones

  • Implementa un método de nombre leerFicheroTextoConStream que lea el contenido de un fichero de texto en formato UTF-16 utilizando la clase FileInputStream (necesitarás otra más) y muestre su contenido.


Opción 1:

En esta opción estamos haciendo uso de un InputStreamReader para indicar el juego de caracteres del fichero de entrada.
Fijarse que el array es de char, no de bytes, ya que esta clase transforma un flujo de bytes a un flujo de caracteres.
  1.     public static void leerFicheroTextoConStream(){
  2.        
  3.         char[] datos = new char[20];  // Leemos de 20 en 20 bytes
  4.         int bytesleidos = 0;
  5.         String contenido = "";
  6.        
  7.         try( FileInputStream fis = new FileInputStream("/tmp/salida_utf16.txt");
  8.              InputStreamReader isr = new InputStreamReader(fis,"UTF-16");
  9.             )
  10.         {
  11.            
  12.             while((bytesleidos=isr.read(datos)) != -1){
  13.                 contenido += new String(datos,0,bytesleidos);
  14.                
  15.             }
  16.  
  17.         } catch (FileNotFoundException ex) {
  18.             System.err.printf("%nError:%s",ex.getMessage());
  19.         } catch (IOException ex) {
  20.             System.err.printf("%nError:%s",ex.getMessage());
  21.         }
  22.         System.out.println(contenido);        
  23.     }
  • Línea 13: Fijarse que necesitamos indicar cuantos bytes hemos leído por la última iteración, ya que podríamos leer menos bytes que el tamaño del array.


Opción 2:

En este caso leemos en un array de bytes y después creamos una cadena indicando el juego de caracteres que debe emplearse para la conversión.
  1.     public static void leerFicheroTextoConStream(){
  2.        
  3.         byte[] datos;  // Leeremos todo el archivo de golpe
  4.         int bytesleidos = 0;
  5.         String contenido = "";
  6.        
  7.         try( FileInputStream fis = new FileInputStream("/tmp/salida_utf16.txt");
  8.             )
  9.         {
  10.             int tamañoArchivo = fis.available();
  11.             datos = new byte[tamañoArchivo];
  12.            
  13.             bytesleidos=fis.read(datos);
  14.             contenido += new String(datos,"UTF-16");
  15.  
  16.         } catch (FileNotFoundException ex) {
  17.             System.err.printf("%nError:%s",ex.getMessage());
  18.         } catch (IOException ex) {
  19.             System.err.printf("%nError:%s",ex.getMessage());
  20.         }
  21.         System.out.println(contenido);        
  22.     }
Fijarse que en este caso leemos de golpe el fichero.
Si quisiéramos podríamos hacerlo a partes, pero tendríamos que guardar cada parte en un array de bytes y una vez guardadas todas las partes, crear un String a partir de ese array con todo el contenido del fichero.
Por lo tanto, tendríamos que tener un array con el tamaño del fichero.
No podemos convertir a cadena los trozos individuales ya que podríamos coger la mitad del código de un carácter en una de las lecturas.



Acceso aleatorio a ficheros. Clase RandomAccessFile

  • Hasta ahora, el recorrido de los ficheros se realiza de forma secuencial. Esto quiere decir que para posicionarnos en una determinada posición del fichero debemos de leer previamente todas las posiciones anteriores.
  • Pero si tenemos un fichero en el que guardamos registros de datos (con un tamaño fijo determinado) puede ser útil que podemos posicionarnos en un registro determinado sin necesidad de pasar por los anteriores.
Además de esta ventaja, esta clase permite realizar operaciones de lectura/escritura sobre un archivo. No se necesitan dos clases como hasta ahora.


  • En el constructor enviaremos un objeto de la clase File o una cadena, que va a indicar el archivo a abrir, y un segundo parámetro (mode) que es una cadena que indica el modo de acceso al fichero:
  • "r": Lectura
  • "rw": Lectura / Escritura
  • Como podemos comprobar en el gráfico del principio, la clase RandomAccessFile incorpora las interfaces DataInput y DataOutput. Esto lleva consigo que esta clase va a disponer de los métodos writeXXX y readXXX de los tipos primitivos de Java, así como writeUTF y readUTF, pasando dichos datos a bytes.


  • Métodos más importantes:


  • Para añadir datos a un fichero debemos posicionarnos al final del mismo y llamar al método writeXXXX que queramos.
Si nos situamos en la posición de un registro existente, los datos serán reemplazados al escribir.


  • Ejemplo de uso:
Vamos a crear una clase con un método que escriba registros y otro método que permite leer un registro específico del fichero.
Los datos de cada registro hacen referencia a un micro y estarán formados por:
  • Un byte: que representa una velocidad en GHz.
  • Una cadena de tamaño 100 caracteres, que representa un modelo.
  • Un float que representa un precio.

Clase LecturaEscrituraBytes

  1. package ficheros.bytes;
  2.  
  3. import java.io.File;
  4. import java.io.FileNotFoundException;
  5. import java.io.IOException;
  6. import java.io.RandomAccessFile;
  7. import java.util.logging.Level;
  8. import java.util.logging.Logger;
  9.  
  10. public class LecturaEscrituraBytes {
  11.     private static final int TAM_CADENA = 20;
  12.     private static final int TAM_REGISTRO = (1+TAM_CADENA+4);// Cada registro ocupa 1 byte=velocidad; 20 bytes=modelo; 4 bytes=precio
  13.            
  14.    
  15.     public static void escribirRegistro(File archivo, byte velocidad, String modelo, float precio) throws FileNotFoundException{
  16.        
  17.         try (RandomAccessFile raf = new RandomAccessFile(archivo, "rw");) // Necesitamos escribir
  18.         {
  19.             raf.seek(raf.length());       // Nos posicionamos al final);
  20.            
  21.             raf.writeByte(velocidad);
  22.             raf.write(String.format("%20s",modelo).substring(0,20).getBytes());  
  23.             raf.writeFloat(precio);
  24.         } catch (IOException ex) {
  25.             Logger.getLogger(LecturaEscrituraBytes.class.getName()).log(Level.SEVERE, null, ex);
  26.         }
  27.        
  28.        
  29.     }
  30.     public static String leerRegistro(File archivo, int numRegistro){
  31.         int numRegistrosArchivo;
  32.         byte[] modeloArray = new byte[TAM_CADENA];
  33.         String cadenaRetorno = null;
  34.        
  35.         if (!archivo.exists() || numRegistro<1) return null;
  36.        
  37.        
  38.        
  39.         try (RandomAccessFile raf = new RandomAccessFile(archivo, "r");)
  40.         {                                                    // Necesitamos leer
  41.             numRegistrosArchivo = (int) (raf.length()/TAM_REGISTRO);
  42.             if (numRegistro > numRegistrosArchivo) return null;
  43.  
  44.             // Nos situamos
  45.             raf.seek((numRegistro-1)*TAM_REGISTRO);   // El registro 1 es el principio del archivo
  46.            
  47.             // Leemos el registro
  48.             byte velocidad = raf.readByte();
  49.             raf.read(modeloArray);
  50.             String modelo = new String(modeloArray);
  51.             float precio = raf.readFloat();
  52.  
  53.             cadenaRetorno = String.format("Velocidad:%d%nModelo:%s%nPrecio:%.2f",velocidad,modelo,precio);
  54.  
  55.         } catch (FileNotFoundException ex) {
  56.             Logger.getLogger(LecturaEscrituraBytes.class.getName()).log(Level.SEVERE, null, ex);
  57.         } catch (IOException ex) {
  58.             Logger.getLogger(LecturaEscrituraBytes.class.getName()).log(Level.SEVERE, null, ex);
  59.         }
  60.         finally {
  61.             return cadenaRetorno;
  62.         }
  63.     }
  64.    
  65. }


Clase PrincipalBytes

  1. /*
  2.  * To change this license header, choose License Headers in Project Properties.
  3.  * To change this template file, choose Tools | Templates
  4.  * and open the template in the editor.
  5.  */
  6. package ficheros.bytes;
  7.  
  8. import java.io.File;
  9. import java.io.FileNotFoundException;
  10. import java.util.logging.Level;
  11. import java.util.logging.Logger;
  12.  
  13. public class PrincipalBytes {
  14.    
  15.     public static void main(String[] arg){
  16.         File fichero = new File("/tmp/registros.dat");
  17.        
  18.         try {
  19.             LecturaEscrituraBytes.escribirRegistro(fichero, (byte)10, "AMD-2020", 100.2f);
  20.             LecturaEscrituraBytes.escribirRegistro(fichero, (byte)30, "INTEL-304", 200.243f);
  21.             LecturaEscrituraBytes.escribirRegistro(fichero, (byte)88, "AMD-3321-OK", 2432.22f);
  22.            
  23.             System.out.println(LecturaEscrituraBytes.leerRegistro(fichero, 1));
  24.             System.out.println(LecturaEscrituraBytes.leerRegistro(fichero, 3));
  25.             System.out.println(LecturaEscrituraBytes.leerRegistro(fichero, 2));
  26.            
  27.         } catch (FileNotFoundException ex) {
  28.             Logger.getLogger(PrincipalBytes.class.getName()).log(Level.SEVERE, null, ex);
  29.         }
  30.        
  31.     }
  32.    
  33. }


Prog ficher binario aleatorio 1.jpg




Escritura / Lectura de objetos: Serialización de objetos

  • En Java disponemos de una clases que permiten establecer un flujo de entrada (lectura) / salida (escritura) de objetos.
  • Cuando escribimos un objeto a disco lo que hace la clase ObjectOutputStream es convertir el contenido de cada uno de los campos a binario y lo guarda en disco.
  • Cuando leemos un objeto de disco, lo que hace la clase ObjectInputStream es leer un flujo de bytes que después, mediante un cast, guardará en cada uno de los campos del objeto.


  • La serialización es el proceso por el que un objeto se convierte en una secuencia de bytes, permitiendo guardar su contenido en un archivo.
Para que un objeto pueda ser serializado debe de implementar la interface java.io.Serializable. Esta interface no define ningún método, pero toda clase que la implemente, informará a la JVM que el objeto será serializado.


  • Todos los tipos primitivos de datos en Java son serializables al igual que los arrays.
  • Si una clase tiene como 'dato' algún objeto de otra clase, esa otra clase debe implementar la interface Serializable.
Podemos indicarle a Java que un atributo de una clase no sea serializado (y por lo tanto no lo guardará) con la palabra clave trascient de la forma: private transient String ejemplAtributo; (es un ejemplo).


  • Tips:
  • Estas clases también permiten escribir en un archivo binario los tipos de datos primitivos de Java.
  • Cuidado con el método readObject(). Este método no indica cuando se acaba el fichero, por lo que si estamos haciendo un bucle while leyendo objetos, la condición del while para salir podría ser llamando al método available() de un flujo de bytes asociado al fichero (leeríamos mientras el método devuelva un valor mayor que 0).
Otra forma sería capturar la excepción EOFException.
Otra forma sería saber cuantos objetos fueron escritos a disco y leer ese mismo número de objetos.
  • Cuando leemos un objeto, tenemos que hacer un cast a la clase a la que pertenece. Sin embargo, aplicando el polimorfismo, podemos hacer un cast a la clase 'común' pero aún así, el objeto tendrá toda la información de su clase original, de tal forma que si lo convertimos a su clase original (con otro cast) podremos acceder a todos sus métodos y propiedades.
  • Importante:La serialización no permite añadir objetos a un archivo. Es decir, si tengo un código en el que voy añadiendo en cada llamada un objeto, después no podré leerle utilizando el mismo ObjectInputStream. Tendré que hacer un new del ObjectInputStream por cada objeto leído. Esto es debido a que cada vez que se añade un objeto habiendo cerrado el archivo, se añade una cabecera, que tendría que ser leída por el ObjectInputStream y que sólo lo hace una vez.



Ejemplos de uso

  • Como comentamos, es una clase que va a permitir escribir tipos de datos primitivos, pero vamos a centrarnos en la funcionalidad de escribir objetos.


  • Vamos a partir de la siguiente clase:
  1. package ficheros.objetos;
  2.  
  3. import java.io.Serializable;
  4.  
  5. public class Persona implements Serializable{
  6.     private String nombre;
  7.     private String telefono;
  8.     private float sueldo;
  9.    
  10.     public Persona(String nombre,String telefono,float sueldo){
  11.         this.nombre = nombre;
  12.         this.telefono = telefono;
  13.         setSueldo(sueldo);
  14.     }
  15.  
  16.     private void setSueldo(float sueldo){
  17.         this.sueldo = sueldo < 0 ? 0 : sueldo;
  18.     }
  19.     public String getNombre() {
  20.         return nombre;
  21.     }
  22.  
  23.     public String getTelefono() {
  24.         return telefono;
  25.     }
  26.  
  27.     public float getSueldo() {
  28.         return sueldo;
  29.     }
  30.     @Override
  31.     public String toString(){
  32.         return String.format("Nombre:%s%nTelefono:%s%nSueldo:%.2f%n", nombre,telefono,sueldo);
  33.     }
  34. }
  • Línea 5: Fijarse que necesitamos implementar la interface.


Ejemplo 1: Escritura a disco

Clase OperacionesBasicas:

Método escribirPersonas: Escribe en el fichero indicado un array de objetos de la clase Persona.

  1. package ficheros.objetos;
  2.  
  3. import java.io.BufferedOutputStream;
  4. import java.io.FileNotFoundException;
  5. import java.io.FileOutputStream;
  6. import java.io.IOException;
  7. import java.io.ObjectOutputStream;
  8.  
  9. public class OperacionesBasicas {
  10.    
  11.    public static void escribirPersonas(String file, Persona pers[]){
  12.         try( FileOutputStream fos = new FileOutputStream(file,false);    // NO PODEMOS AÑADIR. SI LO HACEMOS SE AÑADE UNA CABECERA CADA VEZ
  13.                 BufferedOutputStream bos = new BufferedOutputStream(fos);
  14.                 ObjectOutputStream oos = new ObjectOutputStream(bos);  
  15.            ){
  16.             for(int cont=0;cont < pers.length;cont++){
  17.                 oos.writeObject(pers[cont]);
  18.             }
  19.            
  20.         } catch (FileNotFoundException ex) {
  21.             System.err.println("Error en el fichero:".concat(ex.getMessage()));
  22.         } catch (IOException ex) {
  23.             System.err.println("Error en la escritura:".concat(ex.getMessage()));
  24.         }
  25.        
  26.     }
  27.  
  28. }


Clase Principal:

  1. package ficheros.objetos;
  2.  
  3. import java.io.FileNotFoundException;
  4. import java.util.logging.Level;
  5. import java.util.logging.Logger;
  6.  
  7. public class PrincipalObjetos2 {
  8.    
  9.     public static void main(String arg[]){
  10.        
  11.         String fichero = "/tmp/personasprueba.dat";
  12.  
  13.         Persona[] personas = {new Persona("Pedro","989111111",1234.34f),new Persona("Juan","989222222",2222.34f)};
  14.         OperacionesBasicas.escribirPersonas(fichero, personas);
  15.     }
  16. }


Prog ficher binario ob 1.jpg


Ejemplo 2: Lectura desde disco

  • En este caso vamos a suponer que el número de objetos leídos nunca será superior a 100, que será el tamaño del array donde guardaremos los datos leídos desde disco

Clase OperacionesBasisca

Método leerPersonas(String file)

  1.     public static Persona[] leerPersonas(String file) throws FileNotFoundException{
  2.         File fichero = new File(file);
  3.         Persona[] personas = new Persona[100];
  4.  
  5.         if (!fichero.exists()){
  6.             throw new FileNotFoundException("Fichero no existe");
  7.         }
  8.         try( FileInputStream fis = new FileInputStream(fichero);
  9.              BufferedInputStream bufis = new BufferedInputStream(fis);
  10.             ObjectInputStream ois = new ObjectInputStream(bufis);
  11.            ){
  12.             int cont = 0;
  13.             while(bufis.available()>0) {
  14.                  personas[cont] = (Persona)ois.readObject();
  15.                  cont++;
  16.                  if (cont>personas.length) break;   // Número máximo de registros del array
  17.             }
  18.             return personas;
  19.            
  20.         } catch (IOException ex) {
  21.             System.err.println("Error en la lectura");
  22.             return null;
  23.         } catch (ClassNotFoundException ex) {
  24.             System.err.println("Clase no encontrada en la lectura");
  25.             return null;
  26.         }
  27.     }


Ejercicio propuesto: Lectura y escritura de varios objetos a disco

  • Vamos a crear una clase en la que va a estar definido un array con tres objetos de la clase Persona (clase definida anteriormente).
  • Dicha clase va a tener los siguientes métodos de clase:
  • Método crearPersonas(): creará el array de tres objetos personas con unos datos predefinidos.
  • Método borrarPersonas(): borra los datos del array.
  • Método cargarPersonas(String fichero): generará una excepción en caso de que el fichero no exista. Cargará los datos del fichero en el array y devolverá el array.
  • Método guardarPersonas(String fichero): guardará los datos del array en el fichero indicado. En el caso de que algo vaya mal devolverás false. En caso contrario devolverá true.



Solución ejercicio propuesto: Lectura y escritura de varios objetos a disco

Clase TratamientoPersonas:

  1. package ficheros.objetos;
  2.  
  3. import java.io.BufferedInputStream;
  4. import java.io.BufferedOutputStream;
  5.  
  6. import java.io.File;
  7. import java.io.FileInputStream;
  8. import java.io.FileNotFoundException;
  9. import java.io.FileOutputStream;
  10. import java.io.IOException;
  11. import java.io.ObjectInputStream;
  12. import java.io.ObjectOutputStream;
  13.  
  14.  
  15. public class TratamientoPersonas {
  16.    
  17.     private static Persona[] personas = new Persona[3];   // Vamos a crear 3 personas
  18.    
  19.     public static void crearPersonas(){
  20.         personas[0] = new Persona("Angel","9811111111",1000.11f);
  21.         personas[1] = new Persona("Juan","9812222222",1200.22f);
  22.         personas[2] = new Persona("Luis","9813333333",1300.33f);
  23.        
  24.     }
  25.    
  26.     public static void borrarPersonas(){
  27.         for(int cont=0;cont < personas.length; cont++){
  28.             personas[cont] = null;
  29.         }
  30.     }
  31.     public static boolean guardarPersonas(String file){
  32.         int cont=0; // Contador para saber cuantas personas llevamos guardadas en disco
  33.                
  34.        
  35.         try( FileOutputStream fos = new FileOutputStream(file,false);
  36.                 BufferedOutputStream bos = new BufferedOutputStream(fos);
  37.                 ObjectOutputStream oos = new ObjectOutputStream(bos);  
  38.            ){
  39.            
  40.             while(cont < personas.length){
  41.                 oos.writeObject(personas[cont]);
  42.                 cont++;
  43.             }
  44.            
  45.             return true;
  46.            
  47.         } catch (FileNotFoundException ex) {
  48.             System.err.println("Error en el fichero:".concat(ex.getMessage()));
  49.             return false;
  50.         } catch (IOException ex) {
  51.             System.err.println("Error en la escritura:".concat(ex.getMessage()));
  52.             return false;
  53.         }
  54.        
  55.     }
  56.    
  57.     // Vamos a utilizar el método avaliable para determinar cuando debemos de parar de leer.
  58.     // Queda como ejercicio hacerlo de las otras dos formas (mirar la wiki).
  59.     public static Persona[] cargarPersonas(String file) throws FileNotFoundException{
  60.         File fichero = new File(file);
  61.         Persona tempPersona;
  62.         if (!fichero.exists()){
  63.             throw new FileNotFoundException("Fichero no existe");
  64.         }
  65.         try( FileInputStream fis = new FileInputStream(fichero);
  66.              BufferedInputStream bufis = new BufferedInputStream(fis);
  67.             ObjectInputStream ois = new ObjectInputStream(bufis);
  68.            ){
  69.             borrarPersonas();
  70.             int cont = 0;
  71.             while(bufis.available()>0) {
  72.                  tempPersona = (Persona)ois.readObject();
  73.                  personas[cont] = tempPersona;
  74.                  cont++;
  75.                  if (cont>personas.length) break;   // Sabemos que tenemos que leer tres en este ejemplo pero de esta forma lo hacemos independiente
  76.             }
  77.             return personas;
  78.            
  79.         } catch (IOException ex) {
  80.             System.err.println("Error en la lectura");
  81.             return null;
  82.         } catch (ClassNotFoundException ex) {
  83.             System.err.println("Clase no encontrada en la lectura");
  84.             return null;
  85.         }
  86.     }
  87.    
  88. }


Clase Principal

  1. package ficheros.objetos;
  2.  
  3. import java.io.FileNotFoundException;
  4. import java.util.logging.Level;
  5. import java.util.logging.Logger;
  6.  
  7. public class Principal {
  8.    
  9.     public static void main(String arg[]){
  10.         TratamientoPersonas.crearPersonas();
  11.         if (!TratamientoPersonas.guardarPersonas("/tmp/personas.dat")){
  12.             System.out.println("Hubo un error en la escritura de datos...");
  13.             return;
  14.         }
  15.        
  16.         Persona[] personas;
  17.         try {
  18.             personas = TratamientoPersonas.cargarPersonas("/tmp/personas.dat");
  19.             for(int cont=0;cont<personas.length;cont++){
  20.                 System.out.println(personas[cont]);
  21.             }
  22.         } catch (FileNotFoundException ex) {
  23.             Logger.getLogger(PrincipalObjetos2.class.getName()).log(Level.SEVERE, null, ex);
  24.         }
  25.        
  26.     }
  27.    
  28. }



Ejercicio propuesto: Aplicando el polimorfismo

  • En este ejercicio se va a aplicar el polimorfimo en la lectura de objetos.
  • Enunciado:
Se quiere guardar información sobre animales para realizar un juego infantil. Los animales a mostrar en el juego son perros o gatos.
La información que se quiere guardar es la del nombre del animal, su edad.
En el caso de los perros, se quiere saber si tienen el rabo largo o no, y en el caso de los gatos si tienen bigotes largos o no.
También se querrá saber la forma de expresarse de cada uno. Los gatos harán "MIAU" y los perros "GUAU".
Vamos a necesitar un método (getTipo) que nos indique que clase de animal es (perro o gato) ya que habrá momentos en que trabajaremos con animales.
  • Una vez definidas las clases para guardar la información anterior, crea una clase de nombre OperacionesAnimales.
Dicha clase podrá guardar información sobre cinco perros y cinco gatos. Debes hacer uso de arrays estáticos de tamaño 5.
Dispondrá de un método addPerro(Perro perro) que permitirá añadir un perro (de los cinco posibles). Necesitarás un contador que te indique en qué posición del array debes de añadir el perro nuevo. Dicho método devolverá un boolean indicando si se añadió correctamente el perro. Si intentamos añadir un nuevo perro y el array está ocupado, devolverá false.
Lo mismo para el caso de los gatos (método addGato(Gato gato).


  • Dispondrá de un método guardarAnimalesADisco(String fichero) en el que se guardarán todos los objetos que se encuentren en los dos arrays (perros / gatos). Como no sabemos crear arrays dinámicos, escribiremos en el fichero, antes de los objetos, el número de objetos total que vamos a escribir.
  • Dispondrá de un método leerAnimalesDisco(String fichero) que leerá de disco los objetos guardados del paso anterior. Primero leeremos el número de animales guardados en disco para dar memoria al array que debe devolver el método. Después leeremos todos los animales que se irán guardando en el array que tenemos que devolver. Se actualizarán los datos de la clase (los arrays que guardan los gatos y los perros).
  • Crea una clase Principal con un método main que haga uso de la clase anterior.



Solución ejercicio propuesto: Aplicando el polimorfismo

Clase Animal

  1. package POO;
  2.  
  3. public abstract class Animal implements Serializable{
  4.     private String nombre;
  5.     private byte edad;
  6.  
  7.     public Animal(String nombre,byte edad){
  8.         this.nombre = nombre;
  9.         this.edad = edad;      
  10.     }
  11.     public String getNombre() {
  12.         return nombre;
  13.     }
  14.  
  15.     public void setNombre(String nombre) {
  16.         this.nombre = nombre;
  17.     }
  18.  
  19.     public byte getEdad() {
  20.         return edad;
  21.     }
  22.  
  23.     public void setEdad(byte edad) {
  24.         this.edad = edad;
  25.     }
  26.    
  27.     public abstract void expresar();
  28.     public abstract String getTipo();
  29. }
  • Fijarse que esta clase debe implementar también la interface Serializable.


Clase Gato

  1. package POO;
  2.  
  3. public class Gato extends Animal implements Serializable{
  4.  
  5.     private boolean bigotesLargos;
  6.  
  7.     public Gato(String nombre, byte edad, boolean bigotesLargos){
  8.         super(nombre,edad);
  9.         setBigoteLargo(bigotesLargos);
  10.     }
  11.    
  12.     public boolean isBigoteLargo() {
  13.         return bigotesLargos;
  14.     }
  15.  
  16.     public void setBigoteLargo(boolean raboLargo) {
  17.         this.bigotesLargos = raboLargo;
  18.     }
  19.  
  20.     @Override
  21.     public void expresar() {
  22.         System.out.println("MAULLA!!!");
  23.     }
  24.  
  25.     @Override
  26.     public String getTipo() {
  27.         return "GATO";
  28.     }
  29.    
  30.     @Override
  31.     public String toString(){
  32.         return String.format("%nTipo:%s > Nombre:%s.Edad:%d-Bigotes largos:%b", getTipo(),getNombre(),getEdad(),bigotesLargos);
  33.     }
  34.    
  35. }

Clase Perro

  1. package POO;
  2.  
  3. public class Perro extends Animal implements Serializable{
  4.  
  5.     private boolean raboLargo;
  6.  
  7.     public Perro(String nombre, byte edad, boolean raboLargo){
  8.         super(nombre,edad);
  9.         setRaboLargo(raboLargo);
  10.     }
  11.    
  12.     public boolean isRaboLargo() {
  13.         return raboLargo;
  14.     }
  15.  
  16.     public void setRaboLargo(boolean raboLargo) {
  17.         this.raboLargo = raboLargo;
  18.     }
  19.  
  20.     @Override
  21.     public void expresar() {
  22.         System.out.println("LADRA!!!");
  23.     }
  24.  
  25.     @Override
  26.     public String getTipo() {
  27.         return "PERRO";
  28.     }
  29.  
  30.     @Override
  31.     public String toString(){
  32.         return String.format("%nTipo:%s > Nombre:%s.Edad:%d-Rabo Largo:%b", getTipo(),getNombre(),getEdad(),raboLargo);
  33.     }
  34.    
  35.    
  36. }



Clase OperacionesAnimales

  1. package POO;
  2.  
  3. import java.io.FileInputStream;
  4. import java.io.FileNotFoundException;
  5. import java.io.FileOutputStream;
  6. import java.io.IOException;
  7. import java.io.ObjectInputStream;
  8. import java.io.ObjectOutputStream;
  9. import java.io.Serializable;
  10.  
  11.  
  12. public class OperacionesAnimales{
  13.    
  14.     private Perro[] perros = new Perro[5];  // Como no sabemos utilizar arrays dinámicos le damos un tamaño fijo.
  15.     private byte numPerros = 0;
  16.     private Gato[] gatos = new Gato[5];
  17.     private byte numGatos = 0;
  18.    
  19.     public Perro[] getPerros(){
  20.         return perros;
  21.     }
  22.     public Gato[] getGatos(){
  23.         return gatos;
  24.     }
  25.    
  26.     public boolean addPerro(Perro perro){
  27.         if (numPerros>perros.length){
  28.             return false;
  29.         }
  30.         perros[numPerros] = perro;
  31.         numPerros++;
  32.         return true;
  33.     }
  34.     public boolean addGato(Gato gato){
  35.         if (numGatos>gatos.length){
  36.             return false;
  37.         }
  38.         gatos[numGatos] = gato;
  39.         numGatos++;
  40.         return true;
  41.     }
  42.     public Animal[] leerAnimalesDisco(String fichero){
  43.         Animal[] animales;
  44.         int numAnimales = 0, contador = 0;
  45.         try ( FileInputStream fis = new FileInputStream(fichero);
  46.               ObjectInputStream oos = new ObjectInputStream(fis);
  47.         ){
  48.             numAnimales = oos.readInt();    // Cuando creamos el archivo escribimos el número de animales
  49.             animales = new Animal[numAnimales];
  50.            
  51.             numGatos = numPerros = 0;   // Inicializamos los arrays
  52.             for(int c = 0; c < gatos.length; c++){  
  53.                 gatos[c] = null;
  54.             }
  55.             for(int c = 0; c < perros.length; c++){  
  56.                 perros[c] = null;
  57.             }
  58.             while(fis.available() > 0){   // Otra forma de hacerlo: Podríamos usar el numAnimales como contador
  59.                                           // Cuidado con readObject, no indica cuando se acaba el fichero.
  60.                 animales[contador] = (Animal)oos.readObject();
  61.                 if (animales[contador].getTipo().equals("PERRO")){
  62.                     addPerro((Perro)animales[contador]);
  63.                     numPerros++;
  64.                 }
  65.                 else {
  66.                     addGato((Gato)animales[contador]);
  67.                     numGatos++;
  68.                 }
  69.  
  70.                 contador++;
  71.             }
  72.             return animales;
  73.         }   catch (FileNotFoundException ex) {
  74.                 System.err.printf("%nErro:%s",ex.getMessage());
  75.         } catch (IOException ex) {
  76.                 System.err.printf("%nErro:%s",ex.getMessage());
  77.         } catch (ClassNotFoundException ex) {
  78.             Logger.getLogger(LecturaEscrituraObjetos.class.getName()).log(Level.SEVERE, null, ex);
  79.         }
  80.         return null;
  81.     }
  82.        
  83.     public void guardarAnimalesADisco(String fichero){
  84.        
  85.            
  86.             try( FileOutputStream fos = new FileOutputStream(fichero);
  87.                  ObjectOutputStream oos = new ObjectOutputStream(fos);
  88.                ){
  89.                     oos.writeInt(numGatos+numPerros);  // Como aún no vimos arrays dinámicos tenemos que indicar cuantos animales se escriben a disco para despues en la lectura crear el array con el mismo número de animales.
  90.                     for(int c = 0;c < numGatos; c++){
  91.                             oos.writeObject(gatos[c]);
  92.                     }
  93.                     for(int c = 0;c < numPerros; c++){
  94.                             oos.writeObject(perros[c]);
  95.                     }
  96.                    
  97.             } catch (FileNotFoundException ex) {
  98.                 System.err.printf("%nErro:%s",ex.getMessage());
  99.             } catch (IOException ex) {
  100.                 System.err.printf("%nErro:%s",ex.getMessage());
  101.             }
  102.            
  103.     }
  104.        
  105. }



Clase Principal

  1. package POO;
  2.  
  3.  
  4. public class PrincipalObjetos {
  5.    
  6.     public static void main(String[] arg){
  7.        
  8.         OperacionesAnimales operAnimales = new OperacionesAnimales();
  9.         operAnimales.addGato(new Gato("Miau1",(byte)1,true));
  10.         operAnimales.addGato(new Gato("Miau2",(byte)2,false));
  11.        
  12.         operAnimales.addPerro(new Perro("Perro1",(byte)4,false));
  13.         operAnimales.addPerro(new Perro("Perro2",(byte)2,true));
  14.         operAnimales.addPerro(new Perro("Perro3",(byte)6,false));
  15.        
  16.         operAnimales.guardarAnimalesADisco("/tmp/datosAnimales.dat");
  17.        
  18.         Animal[] animales = operAnimales.leerAnimalesDisco("/tmp/datosAnimales.dat");
  19.        
  20.         if (animales == null){
  21.             System.out.println("No hay animales en disco");
  22.             return;
  23.         }
  24.        
  25.         for (int cont = 0; cont < animales.length; cont++){
  26.             if (animales[cont].getTipo().equals("PERRO")){
  27.                 Perro perro = (Perro)animales[cont];
  28.                 System.out.println(perro);
  29.             }
  30.             if (animales[cont].getTipo().equals("GATO")){
  31.                 Gato gato = (Gato)animales[cont];
  32.                 System.out.println(gato);
  33.             }
  34.         }
  35.        
  36.         // El método leerAnimalesDisco() devuelve un array con todos los animales pero recordar que el mismo método también actualiza el array de gatos y perros por lo que podríamos obtener los gatos de la forma: operAnimales.getGatos() y los perros de la forma: operAnimales.getPerros()
  37.     }
  38.    
  39. }



Entrada/Salida estándar

  • A nivel de sistema operativo (sobre todo en Linux/Unix) se identifica la entrada estándar (stdin) con el teclado y la salida estándar (stdout) como la pantalla. También se dispone de la salida de errores (stderr) que también va a la pantalla.
Podéis consultar un ejemplo de uso de las salidas estándares en Linux en el siguiente enlace.
  • Java tiene acceso a estos flujos estándar a través de la clase System.
Así:
  • Stdin: Es un objeto de la clase InputStream y es el flujo de entrada estándar (lectura por teclado). Dicho flujo se puede redirigir con el método System.setIn(InputStream). Podemos acceder a él de la forma: System.in.
  • Stdout: Es un objeto de la clase PrintStream. Se pueden utilizar los métodos print y println. Se puede redirigir llamando al método System.setOut(PrintStream). Podemos acceder a él de la forma: System.out.
  • Stderr: Es un objeto de la clase PrintStream. Se pueden utilizar los métodos print y println. Se puede redirigir llamando al método System.setErr(PrintStream). Podemos acceder a él de la forma: System.err.


  • Si cambiamos la entrada / salida / errores estándar y queremos volver a su 'valor original', tendremos que emplear la clase FileDescriptor para tener una referencia a los flujos originales de entrada, salida y errores de la forma: FileDescriptor.in, FileDescriptor.out y FileDescriptor.err.
Para redireccionar a la salida estándar por defecto, podríamos poner: System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out)));


  • En el comienzo de este curso vimos que para leer del teclado, hacíamos uso de la clase Scanner.
Esto era debido a que para entender la entrada estándar es necesario haber visto los flujos binarios de este punto.
Ahora podéis consultar el punto sobre la entrada de datos utilizando los flujos estándares.


  • 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"




Ejercicio propuesto

  • Crea una clase de nombre SalidaErroresFichero.
Crea un método de clase de nombre cambiarSalidaErrores con un parámetro en que se indica el nombre de un fichero.
Si el parámetro tiene el valor null, se asignará la salida de errores a la pantalla.
Si trae el nombre de un fichero, se asignará la salida de errores a dicho fichero.
Comprueba que al mandar algo al flujo de errores, lo escribe en el fichero.



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).