Sesión 9

En esta sesión practicaremos conceptos algo más avanzados de Entrada/Salida, como la lectura de tokens desde ficheros, y la lectura de objetos complejos.

1. En este primer ejercicio practicaremos la lectura de tokens de un fichero, y su almacenamiento para realizar alguna operación.

  1. Antes de comenzar, lee el apartado 2.3.4 (Lectura de tokens) del tema 2 de teoría.
  2. Echa un vistazo a la clase Ej6.java que se proporciona en la plantilla de la sesión. Verás que hay un constructor vacío, y un método main que le llama. Rellenaremos el constructor como se indica en los siguientes pasos.
  3. Lo que vamos a hacer es que el constructor acceda a un fichero (fichero matriz.txt) que tiene una matriz m x n. Dicho fichero tiene la siguiente estructura:
    ; Comentario de cabecera
    m n
    A11 A12 A13...
    A21 A22 A23...
    ...
    

    donde m son las filas, n las columnas, y después aparece la matriz puesta por filas, con un espacio en blanco entre cada elemento.

    El ejercicio leerá la matriz (utilizando un StreamTokenizer sobre el fichero), construirá una matriz (array) con los datos leídos, después elevará al cuadrado cada componente, y volcará el resultado en un fichero de salida.

    1. Primero obtendremos el flujo de entrada para leer del fichero, y el StreamTokenizer:
      public Ej6()
      {
      	StreamTokenizer st = 
      	   new StreamTokenizer(new FileReader("matriz.txt"));
      }
    2. Después establecemos qué caracteres van a identificar las líneas de comentarios. En este caso, los comentarios se identifican por punto y coma: 
      public Ej6()
      {
      	StreamTokenizer st = 
      	   new StreamTokenizer(new FileReader("matriz.txt"));
      
      	st.commentChar(';');
      }
    3. Después del comentario irán el número de filas y de columnas. Utilizamos el método nextToken del tokenizer para leerlos, y luego accedemos al campo nval para obtener qué valor numérico se ha leído en cada caso:
      public Ej6()
      {
      	StreamTokenizer st = 
      	   new StreamTokenizer(new FileReader("matriz.txt"));
      
      	st.commentChar(';');
      
      	int filas, columnas;
      	
      	st.nextToken();
      	filas = (int)(st.nval);				// Filas
      
      	st.nextToken();
      	columnas = (int)(st.nval);			// Columnas
      }

      NOTA: asumimos que el fichero va a tener un formato correcto, y no tenemos que controlar que haya elementos no deseados por enmedio.

      ¿Qué se habría leído en primer lugar si no hubiésemos identificado la primera línea como comentario? ¿Dónde podríamos haber consultado ese valor leído?

    4. Lo siguiente es ir leyendo los elementos de la matriz. Construimos un array de enteros de filas x columnas, y luego lo vamos rellenando con los valores que nos dé el StreamTokenizer:
      public Ej6()
      {
      	...
      	int[][] matriz = new int[filas][columnas];
      	int t;
      			
      	for (int i = 0; i < filas; i++)
      		for (int j = 0; j < columnas; j++)
      		{
      			t = st.nextToken();
      			if (t != StreamTokenizer.TT_EOF)
      			{
      				matriz[i][j] = (int)(st.nval);
      			}
      		}				
      }
    5. Por último, calculamos el cuadrado de cada elemento de la matriz (utilizamos el método pow de la clase Math), y guardamos la matriz resultado en otro fichero de salida (matrizSal.txt), con el mismo formato que el de entrada:
      public Ej6()
      {
      	...
      			
      	for (int i = 0; i < filas; i++)
      		for (int j = 0; j < columnas; j++)
      		{
      			matriz[i][j] = (int)(Math.pow(matriz[i][j], 2));
      		}				
      
      	// Volcamos la salida a fichero
      			
      	PrintWriter pw = new PrintWriter (new FileWriter("matrizSal.txt"));
      	pw.println ("; Matriz resultado");
      	pw.println ("" + filas + " " + columnas);
      	for (int i = 0; i < filas; i++)
      	{
      		for (int j = 0; j < columnas; j++)
      		{
      			pw.print("" + matriz[i][j] + " ");
      		}				
      		pw.println();
      	}
      	pw.close();
      }
    6. Compila y ejecuta el programa (captura las excepciones adecuadas para que te compile bien). Comprueba que el fichero de salida genera el resultado adecuado: 
      ; Matriz resultado
      3 3
      1 4 9 
      16 25 36 
      49 64 81 

      Prueba también a pasarle este mismo fichero como entrada al programa, y que genere otro fichero de salida diferente.

  4. (OPTATIVO) Supongamos que tenemos un fichero de entrada cuya estructura debe ser una alternancia de palabras y números, es decir, debe haber una palabra seguida siempre de un número:

    hola 1 pepe 2 otra 53 adios 877
    Construye varios ficheros ejemplo de entrada, e implementa la clase LeeFicheroAlternado, que lea estos ficheros mediante un StreamTokenizer. La clase deberá lanzar una excepción del tipo adecuado, indicando error de sintaxis, si el fichero no tiene la estructura adecuada. En caso de ser correcto, mostrará su contenido en pantalla.

2. En este segundo ejercicio practicaremos cómo utilizar los ficheros para almacenar y leer objetos complejos. Hasta ahora sólo hemos trabajado con enteros o cadenas, y para leerlos basta con leer un stream de bytes, o utilizar un tokenizer y procesar el fichero de la forma que nos convenga.

Imaginemos que trabajamos con un objeto complejo que encapsula diferentes tipos de datos (enteros, cadenas, vectores, etc). A la hora de guardar este elemento en fichero, se nos plantea el problema de cómo representar su información para volcarla. De la misma forma, a la hora de leerlo, también debemos saber cómo extraer y recomponer la información del objeto. Veremos que hay clases Java que hacen todo este trabajo mucho más sencillo.

  1. Antes de comenzar, lee los apartados 2.3.5 (Acceso a ficheros o recursos dentro de un JAR), 2.3.6 (Codificación de datos) y 2.3.7 (Serialización de objetos) del tema 2 de teoría.
  2. Echa un vistazo al fichero Ej7.java que se proporciona en la plantilla de la sesión. Tiene una clase principal (Ej7), con un constructor vacío y un método main que le llama. 

    También tiene una clase interna llamada ObjetoFichero. Observa que dicha clase interna tiene diferentes tipos de campos: una cadena, un entero, un double y un Vector.  Tiene un constructor que asigna valores a los campos, y luego dos métodos: uno addCadena que añade cadenas al Vector, y otro imprimeObj que devuelve una cadena que representa los valores de los campos del objeto. Es importante también resaltar que esta clase es Serializable, es decir, implementa la interfaz Serializable, lo que permitirá que se pueda guardar y leer de flujos o ficheros como un objeto complejo en bloque.
  3. Lo que vamos a hacer en el constructor de Ej7 es crear varios objetos de tipo ObjetoFichero, y luego guardarlos en un fichero de salida (ficheroObj.dat). Finalmente, leeremos los objetos de ese fichero de salida y mostraremos por pantalla los valores de sus campos, para comprobar que se han guardado y leído de forma correcta.
    1. Lo primero es crear varios objetos (por ejemplo, dos) de tipo ObjetoFichero, cada uno con valores diferentes:
      public Ej7()
      {
      	ObjetoFichero of = new ObjetoFichero ("cad1", 1, 1.5);
      	of.addCadena("cad2");
      	of.addCadena("cad3");
      
      	ObjetoFichero of2 = new ObjetoFichero ("cad1b", 2, 2.5);
      	of2.addCadena("cad2b");
      	of2.addCadena("cad3b");
      
      }
      El vector de elementos de cada objeto tiene 3 cadenas de texto, más aparte los valores que le hemos dado a los otros campos de la clase.
    2. Ahora guardaremos en el fichero de salida (ficheroObj.dat) estos dos objetos creados. Vamos a utilizar la clase ObjectOutputStream que permite abrir un flujo de salida y meter en él objetos complejos, siempre que sean Serializables, como el nuestro. Simplemente basta con utilizar su método writeObject, y pasarle el objeto que queremos guardar:
      public Ej7()
      {
      	...
      
      	ObjectOutputStream oos = 
      	   new ObjectOutputStream(new FileOutputStream("ficheroObj.dat"));
      	oos.writeObject(of);
      	oos.writeObject(of2);
      	oos.close();
      }

      Observa lo sencillo que resulta guardar objetos complejos de esta forma. La única condición que deben cumplir es que deben ser Serializables.

    3. Finalmente, leeremos los objetos guardados del fichero que hemos generado, y sacaremos por pantalla los valores de sus campos. Para leer los ficheros utilizaremos el análogo a la clase anterior, es decir, un objeto de tipo ObjectInputStream, que permite leer objetos complejos enteros (siempre que sean serializables), desde flujos de entrada. Sólo hay que utilizar su método readObject, que extraerá cada objeto de ese tipo que tengamos en el flujo.

      public Ej7()
      {
      	...
      
      	ObjectInputStream ois = 
      	   new ObjectInputStream(new FileInputStream("ficheroObj.dat"));
      	ObjetoFichero ofLeido1 = (ObjetoFichero)(ois.readObject());
      	ObjetoFichero ofLeido2 = (ObjetoFichero)(ois.readObject());
      
      }

      Es importante hacer notar que el método readObject devuelve un objeto de tipo Object, que luego nosotros debemos convertir (con un cast) al tipo de datos que necesitemos.

      Para sacar el valor de los campos por pantalla, recordemos que cada objeto de tipo ObjetoFichero tiene un método llamado imprimeObj que devuelve una cadena que representa su contenido. Así que basta con imprimir esa cadena:

      public Ej7()
      {
      	...
      	System.out.println(ofLeido1.imprimeObj());
      	System.out.println(ofLeido2.imprimeObj());
      }
    4. Compilad y ejecutad el programa (capturad las excepciones necesarias para que compile), y observad si se muestran por pantalla los valores correctos. Eso será prueba de que los objetos se han guardado y leído bien.

      ¿Qué pasaría si ObjetoFichero no implementase la interfaz Serializable? ¿Qué excepción saltaría al ejecutar?
  4. (OPTATIVO) Crea una nueva clase VectorObjetoFichero que internamente sea una lista de elementos de tipo ObjetoFichero. Puedes utilizar cualquier tipo de colección de Java (Vector, List, ArrayList, etc). Después, haz que la clase sea Serializable, y prueba a guardar y leer objetos de la misma en diferentes ficheros.

PARA ENTREGAR