[icono]Manual de Java Volver a índice

Capítulo 10. Entrada/Salida.


La mayoría de los programas no pueden alcanzar sus metas sin acceder a datos externos. Estos datos se recuperan a partir de un origen de entrada. Los resultados de un programa se envían a un destino de salida. La noción genérica de una fuente de entrada puede representar muchos tipos de entrada distintos: desde un archivo de disco, un teclado o un conector (socket) de red. Estas abstracciones son una manera limpia de tratar la E/S sin necesitar que todo el código comprenda la diferencia entre un teclado y una red.

Java llama flujo a esta abstracción y la implementa con varias clases del paquete java.io. El flujo de E/S representa todos los orígenes y destinos de los datos detrás de una interfaz uniforme. La entrada está encapsulada en la clase InputStream y la salida en la clase OutputStream. Estas dos clases abstractas son las que todos los objetos deberían referenciar cuando tratan la E/S en general.

File

Un File es el único objeto del paquete de E/S que referencia a un archivo de disco real. La clase File no especifica cómo se recupera o almacena la información en los archivos; sólo describe las propiedades de un objeto archivo. Los archivos son un origen y un destino primario de los datos dentro de la mayoría de los programas. A pesar de que se les imponente restricciones severas cuando se utilizan dentro de applets, los archivos siguen siendo un recurso básico para almacenar información persistente y compartida.

Los objetos archivo se pueden crear utilizando uno de los tres constructores disponibles. El ejemplo siguiente crea tres archivo: f1, f2 y f3. El primer objeto File se construye utilizando un trayecto de directorio como único argumento. El segundo se crea utilizando dos argumentos, el trayecto y el nombre de archivo. El tercero se crea utilizando el trayecto de archivo asignado a f1 y un nombre de archivo; f3 refiere al mismo archivo que f2.

File f1 = new File("/");
File f2 = new File("/","autoexec.bat");
File f3 = new File(f1, "autoexec.bat");

Directorios

Un directorio es un File que contiene una lista de otros archivos y directorios. Cuando se crea un Objeto File y es un directorio, el método isDirectory devolverá true. En este caso, se puede llamar al método list sobre ese objeto para extraer la lista de los otros archivos y directorios que contiene. Si se llama al método list sobre un objeto File que no sea un directorio se provoca una NullPointerException en tiempo de ejecución.

import java.io.File;
class Dirlist  {
    	public static void main(String args[])  {
		String dirname = "/java";
		File f1 = new File(dirname);
		if (f1.isDirectory())  {
	    		System.out.println("Directorio de " + dirname);
			String s[] = f1.list();
	    		for (int y = 0; i < s.length; y++)  {
				System.out.println(s[i] + " es un directorio");
	    		}  else  {
				System.out.println(s[i] + " es un archivo");
	    		}
		}  else  {
	    		System.out.println(dirname + " no es un directorio");
		}
    	}
}

La ejecución de este programa lista el contenido del directorio /java de nuestro PC.

FilenameFilter

A menudo se desea limitar el número de archivos devueltos por el método list para que se incluyan únicamente aquellos archivos que cumplan un cierto patrón de nombre de archivo. Con este fin, el paquete java.io incluye una interfaz llamada FilenameFilter. Un objeto que implemente FilenameFilter tiene un único método, accept, al que se llama una vez por cada archivo de una lista. El método accept devuelve el valor true si se debiera incluir el archivo en la lista. El ejemplo siguiente amplía el programa anterior restringiendo la visibilidad de los nombres de archivo devueltos por el método list. La restricción se aplica a archivos con nombres que terminan con la extensión de archivo que se pasa como parámetro cuando se construye le objeto:

import java.io.*;
public class OnlyExt implements FilenameFilter  {
    	String ext;
    	public OnlyExt(String ext)  {
		this.ext = "." + ext;
    	}
    	public boolean accept (File dir, String name)  {
    		return name.endsWith(ext);
    	}
}

InputStream

InputStream es una clase abstracta que define el modelo de Java para el flujo de entrada. Todos los métodos de esta clase lanzarán una IOException si se producen condiciones de error. Este es un breve resumen de los métodos de InputStream:

OutputStream

Igual que InputStream, OutputStream es una clase abstracta que define el flujo de salida. Todos los métodos de esta clase devuelven un valor void y lanzan una IOException en caso de error. Esta es una lista de los métodos de OutputStream:

Flujo de archivo

FileInputStream

La clase FileInputStream utiliza archivos de datos reales como base del flujo de entrada. El ejemplo siguiente crea dos FileInputStreams que están utilizando el mismo archivo de disco real. Cualquiera de los dos constructores disponibles en esta clase pueden lanzar una FileNotFoundException.

InputStream f0 = new FileInputStream("/autoexec.bat");
File f = new file("/autoexec.bat");
InputStream f1 = new FileInputStream(f);

Aunque probablemente el primer constructor es el que más se utiliza habitualmente, el segundo permite examinar el archivo más de cerca utilizando sus métodos antes de asignarlo a un flujo de entrada. Cuando se crea un FileInputStream, también se abre para lectura. FileInputStream sobrescribe seis de los métodos de la clase abstracta InputStream. Si se intentan utilizar los métodos mark o reset en un FileInputStream se generará una IOException.

FileOutputStream

FileOutputStream comparte el mismo estilo de constructores que FileInputStream. Sin embargo, la creación de FileOutputStream no depende de que el archivo ya exista. FiliOutputStream creará el archivo antes de abrirlo como salida cuando se crea el objeto. Si se intenta abrir un archivo de sólo lectura como punto final de un FiliOutputStream, se lanzará una IOException.

ByteArrayInputStream

ByteArrayInputStream (flujo de entrada de matriz de bytes) es una implementación de un flujo de entrada que utiliza una matriz de bytes como origen. Esta clase tiene dos constructores, y ambos necesita una matriz de bytes que proporcione el origen de los datos. Un ByteArrayInputStream implementa un método adicional además de los admitidos por FileInputStream, reset() reinicializa el puntero del flujo situándolo al comienzo del mimo, que en este caso es el comienzo de la matriz de bytes que hemos utilizado en el constructor.

ByteArrayOutputStream

ByteArrayOutputStream tiene dos constructores. En la primera forma, se crea un buffer de 32 bytes. En la segunda, se crea un buffer con un tamaņo igual al argumento en bytes, que en este ejemplo es 1024 bytes:

OutputStream out0 = new ByteArrayOutputStream();
OutputStream out1 = new ByteArrayOutputStream(1024);

El tener una matriz de bytes como destino de la salida proporciona algunas oportunidades nuevas para un OutputStream, y la clase ByteArrayOutputStream se aprovecha de ellas. Igual que la mayoría de los otros métodos de escritura, devuelven un void y lanzan una IOException cuando hay una condición de error.

StringBufferInputStream

Un StringBufferInputStream es idéntico a ByteArrayInputStream a excepción de que el buffer interno es una cadena en lugar de una matriz de bytes y que no existe la clase StringBufferOutputStream correspondiente. Tiene un constructor:

StringBufferInputStream(String s);

Flujos filtrados

Los flujos filtrados amplían los flujos básicos, proporcionando una sincronización. En un sistema de E/S multihilo, se pueden producir resultados inesperados cuando se permite que múltiples hilos operen sobre el mismo flujo. Aunque es posible tener hilos múltiples en Java leyendo o escribiendo en el mismo flujo, hay una buena razón para permitir que sólo un hilo tenga acceso directo a un flujo de E/S único. Todos los constructores y métodos proporcionados en esta clase son idénticos a los mencionados anteriormente para InputStream y OutputStream, a excepción de que están sincronizados.

BufferedOutputStream

La salida a BufferedOutputStream es idéntica a cualquier OutputStream con la excepción de que se aņade un método de entrada con buffer, la salida con buffer no proporciona ninguna funcionalidad adicional. En Java, los buffers para la salida están ahí para incrementar el rendimiento. La forma del primer constructor, BufferedOutputStream(OutputStream sal), crea un buffer de 32 bytes, mientras que la del segundo, BufferedOutputSteram(OutputStream sal, int tamaņo), permite especificar el tamaņo de buffer a utilizar.

PushbackInputStream

Una de las utilidades originales de los buffers es la implementación de la de la devolución. La devolución se utiliza en un InputStream para permitir que se lea un carácter y después se devuelva al flujo de entrada. Proporciona un mecanismo para "mirar" lo que viene por un InputStream sin perderlo. Aunque la clase PushbackInputStream (flujo de entrada con devolución) está bastante limitada en cuanto a ámbito, ya que un intento de devolver más de un solo carácter provoca que se lance una IOException. Esta clase tiene un único constructor, PushbackInputStream (InputStream ent).

SequenceInputStream

La clase SequenceInputStream (flujo de entrada secuencial) admite la utilidad original de concatenar múltiples InputStreams en uno sólo. La construcción de un Sequence es diferente de cualquier otro InputStream. Un constructor SequenceInputStream utiliza como argumento un par de InputStreams o una Enumeration de InputStreams:

SequenceInputStream(Enumeration e);
SequenceInputStream(InputStream s0, InputStream s1);

Al funcionar, la clase satisface las peticiones de lectura del primer InputStream hasta que se termina y después conmuta al segundo. En el caso de una Enumeration, continúa a lo largo de todos los InputStreams hasta llegar al último.

PrintStream

La clase PrintStream proporciona todas las utilidades de formato que hemos estado utilizando desde el principio del libro de los descriptores de archivo de System. Pensabas que se introducía "System.out.println" sin pensar demasiado en las clases que proporcionaban el formato de la salida que se presentaba. PrintStream tiene dos constructores, PrintStream(OutputStream sal) y PrintStream(OutputStream sal, boolean auto), donde auto controla si java vacía el flujo de salida cuando vaya a la salida un carácter de línea nueva "\n". En el primer constructor no se vacía.

Los objetos PrintStream de Java admiten los métodos print y println para todos los tipos, incluyendo Object. Si un argumento no se un tipo simple, los métodos PrintStream llamarán al método toString del objeto y a continuación imprimirán el resultado.

Beneficios de los flujos

La interfaz de flujos de datos de E/S en Java proporciona una abstracción limpia para una tarea compleja y a menudo incómoda. La composición de las clases de flujo filtradas permite que se construya dinámicamente una interfaz de flujo personalizada que se adapte a los requisitos de trasferencia de datos. Los programas de Java que utilicen las clases abstractas y de alto nivel InputStream y OutputStream funcionarán adecuadamente en el futuro incluso cuando se invierten clases de flujo concretas nuevas y mejoradas. Como veréis en el capítulo siguiente, esté modelo funciona muy bien cuando pasamos de un conjunto de flujos basados en un sistema de archivos a los flujos de red y de conector.