# data[].attributes.attachment_link.readable_text
Ejercicios de Entrada/Salida

4.1. Lectura y escritura básicas

En este primer ejercicio practicaremos la lectura y escritura básica con ficheros, utilizando las dos posibles alternativas: Streams y Readers/Writers.

Echa un vistazo a la clase sesion04. Ej1 que se proporciona en la plantilla de la sesión. Verás que hay un constructor vacío, y un campo llamado cabecera, que contiene una cadena de texto. También hay dos métodos vacíos, leeEscribeStream y leeEscribeWriter, y un método main que crea un objeto de tipo Ej1 y llama a estos dos métodos. Lo que vamos a hacer es rellenar esos dos métodos de la forma que se nos indica a continuación.

El primero de los métodos leeEscribeStream va a leer un fichero de entrada (el fichero entrada.dat que se os proporciona en la plantilla), y lo va a volcar a un fichero de salida (fichero salidaStream.dat), pero añadiéndole la cadena cabecera como encabezado del fichero. Para hacer todo eso empleará flujos de tipo stream ( InputStream para leer, OutputStream para escribir, o cualquier subclase derivada de éstas).

Primero obtendremos el flujo de entrada para leer del fichero. Utilizaremos un objeto de tipo FileInputStream, que es el stream preparado para leer de ficheros:

FileInputStream in = new FileInputStream("entrada.dat");

NOTA IMPORTANTE: vigilad dónde ponéis el fichero entrada.dat, porque puede que no lo encuentre. Una buena idea para que esta línea de código os funcione es ponerlo en la carpeta raíz del proyecto, y no dentro del paquete sesion08. De todas formas, podéis ponerlo en cualquier parte, siempre que después sepáis cómo encontrarlo desde Java.

Aquí tendremos que tener las mismas consideraciones que con el fichero de entrada, en cuanto a cómo localizarlo. En este caso no es tan importante, porque el fichero lo creará de todas formas, en un lugar u otro, pero debemos saber dónde lo va a crear. Con una línea como ésta, lo creará en la carpeta raíz del proyecto también.

# Esto es la cabecera del fichero que hay que introducir
Hola, este es el texto
del fichero de entrada
que debería copiarse en el fichero de salida

El segundo método, leeEscribeWriter, leerá el mismo fichero de entrada ( entrada.dat), y lo volcará a otro fichero de salida diferente ( salidaWriter.dat), empleando flujos de tipo Reader y Writer (como FileReader o FileWriter, o cualquier otro subtipo).

Igual que en el método anterior, primero obtendremos las variables para leer de la entrada y escribir en la salida. Para leer podríamos utilizar la clase FileReader, pero en su lugar vamos a utilizar la clase BufferedReader que nos va a permitir leer líneas enteras del fichero, en lugar de leer carácter a carácter. Para escribir, vamos a utilizar la clasePrintWriter, que también nos permitirá escribir líneas enteras en la salida.
BufferedReader br =  new BufferedReader(new FileReader("entrada.dat"));
PrintWriter pw = new PrintWriter(new FileWriter("salidaWriter.dat"));

Observad que para construir tanto el BufferedReader como el PritWriter nos valemos de un objeto FileReader o FileWriter, respectivamente. Lo que hacemos es simplemente crear un buffer de entrada ( BufferedReader) o de salida(PrintWriter) sobre el FileReader o el FileWriter para poder acumular cadenas de texto enteras antes de leerlas o escribirlas. Deberemos tener las mismas consideraciones que con el método anterior sobre dónde poner los ficheros para que el programa los encuentre.

El siguiente paso es leer el contenido de la entrada, e irlo volcando en la salida. Para leer datos de la entrada emplearemos el método readLine() deBufferedReader, que irá leyendo líneas enteras del fichero. Para escribir, utilizaremos el métodoprintln() de PrintWriter, que vuelca esas mismas líneas que leemos:
String linea = "";
while ((linea = br.readLine()) != null)
{
   pw.println(linea);
}

El uso de PrintWriter permite formatear la salida de la misma forma que si la estuviésemos sacando por pantalla, puesto que tiene los mismos métodos que el campo System.out (métodos println, print, etc).

Echa un vistazo a la documentación sobre el método readLinea. ¿Por qué se compara el dato que se lee con null?

Finalmente, lo que nos queda es cerrar tanto el flujo de entrada como el de salida:
br.close();
pw.close();

Compila el programa. Te dará errores porque se deben capturar las mismas excepciones que antes (FileNotFoundException e IOException). Captúralas y prueba el resultado.

NOTA: observa la API de la clase PrintWriter, y verás que tiene constructores que permiten crear este tipo de objetos a partir de Writers (como hemos hecho aquí) como a partir de OuputStreams (como habríamos hecho en el paso 2), con lo que podemos utilizar esta clase para dar formato a la salida de un fichero en cualquiera de los casos.

4.2. Trabajar con propiedades

En este segundo ejercicio practicaremos el uso de ficheros de propiedades, y el uso de la entrada y salida estándares. Echa un vistazo a la clase sesion04.Ej2 que se proporciona en la plantilla de la sesión. Sólo tiene un constructor vacío, y un método main que le llama. Vamos a completar el constructor de la forma que veremos a continuación.

Lo que vamos a hacer en el constructor es leer un fichero de propiedades (el fichero prop.txt que se proporciona en la plantilla), y luego pedirle al usuario que, por teclado, indique qué valores quiere que tengan las propiedades. Una vez establecidos los valores, volveremos a guardar el fichero de propiedades.

Lo primero que vamos a hacer es leer el fichero de propiedades. Para ello utilizaremos un objeto java.util.Properties, lo crearemos y llamaremos a su método load() para cargar las propiedades del fichero prop.txt:

Properties p = new Properties();
p.load(new FileInputStream("prop.txt"));

Observa que para cargar las propiedades, al método load le debemos pasar un InputStream desde el que leerlas. En este caso le pasamos un FileInputStream con el fichero prop.txt.

Ahora ya tenemos en el objeto p todas las propiedades del fichero. Vamos a irlas recorriendo una a una, e indicando al usuario que teclee su valor. Para recorrer las propiedades obtendremos un Enumeration con sus nombres, y luego lo iremos recorriendo, y sacándolo por pantalla:

Enumeration en = p.propertyNames();
while (en.hasMoreElements())
{
   String prop = (String)(en.nextElement());
   System.out.println("Introduzca valor para propiedad " + prop);
}

Observa el orden en que van mostrándose las propiedades. ¿Es el mismo que el que hay en el fichero? ¿A qué crees que puede deberse? (AYUDA: cuando nosotros enumeramos una serie de características, no tenemos que seguir un orden necesariamente. Del mismo modo, cuando introducimos valores en una tabla hash, el orden en que se guardan no es el mismo que el orden en que los introducimos).

Lo que hacemos con este bucle es sólo recorrer los nombres de las propiedades y sacarlos por pantalla. Nos falta hacer que el usuario teclee los valores correspondientes. Para ello utilizaremos un objeto de tipo BufferedReader, que en este caso leerá líneas de texto que el usuario entre desde teclado:

...
Enumeration en = p.propertyNames();
BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); ...

observad que construimos el BufferedReader para leer de un InputStream (no de un Reader). Esto lo podemos hacer si nos ayudamos de la "clase puente" InputStreamReader, que transforma un tipo de lector en otro.

Lo que nos queda por hacer es pedirle al usuario que, para cada nombre de propiedad, introduzca su valor, y luego asignarlo a la propiedad correspondiente:

...
while (en.hasMoreElements())
{
   String prop = (String)(en.nextElement());
   System.out.println("Introduzca valor para propiedad " + prop);
 String valor = in.readLine(); p.setProperty(prop, valor); }

Finalmente, cerramos el buffer de entrada, y guardamos las propiedades en el fichero.

in.close();
p.store(new FileOutputStream("prop.txt"), "Cabecera del fichero");

Compilad y ejecutad el programa. Para que os compile deberéis capturar las excepciones que se os indique en los errores de compilación.

Añadid el código necesario al ejercicio para que, además de poder modificar los valores de las propiedades, podamos añadir por teclado nuevas propiedades al fichero, y guardarlas con las existentes.

4.3. Leyendo matrices

Vamos a practicar la lectura de tokens de un fichero, y su almacenamiento para realizar alguna operación. Echa un vistazo a la clase sesion04.Ej3 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.

Lo que vamos a hacer es que el constructor acceda a un fichero (fichero matriz.txt de la plantilla) 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.

Primero obtendremos el flujo de entrada para leer del fichero, y el StreamTokenizer:

StreamTokenizer st =  new StreamTokenizer(new FileReader("matriz.txt"));

Después establecemos qué caracteres van a identificar las líneas de comentarios. En este caso, los comentarios se identifican por punto y coma:

st.commentChar(';');

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:

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?

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:

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

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. Utiliza un objeto PrintWriter para facilitar el volcado de la matriz al fichero.

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.4. Guardar datos personales

En este 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.

En la plantilla tenemos la clase datos.Persona que permite almacenar datos personales para una serie de personas. Por otro lado, tenemos en la plantilla la clase io.LeeGuardaPersona. Tiene dos métodos leePersonas y guardaPersonas que deberemos implementar:

El método leePersonas deberá crear un ObjectInputStream y leer de un fichero que se le pase como parámetro una serie de objetos de tipodatos.Persona, y guardarlos en un ArrayList, que devolverá cuando termine de leer

ArrayList al = new ArrayList();
try
{
   ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fichero));
   while (true)
   {
      Persona p = (Persona)(ois.readObject());
      al.add(p);
   }
} catch (Exception e) {
}
return al;

Te parecerá raro utilizar un bucle infinito para leer el fichero. El motivo es sencillo. Cuando trabajamos con ObjectInputStreams no hay forma de saber cuándo acaba el fichero, porque leemos objetos complejos enteros, hasta que ya no hay más. En ese momento, se provoca una excepción, y el bucle terminará cuando dicha excepción salte, lo que indicará el fin de fichero. Después de eso, devolvemos la lista con los objetos que haya guardados y listo.

El método guardaPersonas recibirá como parámetro un ArrayList con objetos de tipodatos.Persona, y un nombre de fichero, y deberá guardar los objetosPersona del ArrayList en el fichero que se le dice mediante unObjectOutputStream.

Para que todo esto te funcione bien, la clase datos.Persona debe ser serializable, es decir, debe implementar la interfazjava.io.Serializable. Haz que la implemente.

Una vez lo tenga todo listo, utiliza el método main que viene con la clase para probar su funcionamiento. Dicho método crea unArrayList, lo llena con algunasPersonas, y luego llama a guardaPersonas. Aquí vuestro método deberá guardar los datos adecuadamente en fichero. Después, elmain llama al método leePersonas para leer las personas del fichero que se indique, y mostrar sus datos por pantalla. Ejecuta la clase cuando la tengas terminada, para ver si tus métodos funcionan como deben.

Opcionalmente, haz que el método main de LeeGuardaPersona muestre ordenadamente las personas.

¿Qué pasaría si Persona no implementase la interfaz Serializable? ¿Qué excepción saltaría al ejecutar?

# data[].attributes.attachment_link.title
PROGRAMACIÓN para niños 👦 Conceptos básicos 💻 Parte 1

# data[].attributes.body
Examinaremos los flujos de texto en cuanto a entradas se refiere