Componentes clave
Clases Métodos
java.io.BufferedInputStream int read( ) void close( ) java.io.BufferedOutputStream void int read( )
void close( )
En las dos soluciones anteriores se mostró el procedimiento general para leer y escribir bytes en un archivo empleando FileInputStream y FileOutputStream. Aunque no hay nada erróneo en esto, ninguna de esas clases proporciona uso automático del búfer. Esto signifi ca que cada operación de lectura o escritura interactúa, al fi nal de cuentas, con varios controladores de E/S en el nivel del sistema (lo que proporciona acceso al archivo físico). Éste no suele ser un método efi ciente. En muchos casos, una mejor manera consiste en usar el búfer para el fl ujo de datos. En el caso de la salida, los datos se almacenan en un búfer hasta que éste se llena. Luego todo el búfer se escribe en el archivo en una sola operación. En el caso de entrada, se lee un búfer completo del archivo y luego cada operación de entrada obtiene sus datos del búfer. Cuando el búfer se agota, se obtiene el siguiente búfer del archivo. Este mecanismo causa que el número de operaciones individuales de archivo se reduzca en gran medida.
Aunque es posible usar manualmente el búfer para las operaciones de E/S al leer y escribir matrices de bytes (en lugar de bytes individuales), ésta no es una solución conveniente ni apropiada en muchos casos. En cambio, para lograr los beneficios de rendimiento de usar el búfer para las operaciones de E/S de archivos, por lo general querrá envolver un flujo de archivo dentro de una de las clases de flujo de búfer de Java. Al hacerlo así se mejorará de manera importante la velocidad de sus operaciones de E/S sin esfuerzo adicional de su parte.
Para crear un flujo de entrada en búfer, use BufferedInputStream. Deriva de InputStream y de FilterInputStream. Implementa la interfaz Closeable.
Para crear un flujo de salida en búfer, use BufferedOutputStream. Deriva de OutputStream y de FilterOutputStream. Implementa las interfaces Closeable y Flushable.
Paso a paso
Para usar el búfer con un fl ujo de archivo, se requieren estos pasos:
1. Cree el flujo de archivo. Para entrada, sería una instancia de FileInputStream. En el caso de la salida, sería una instancia de FileOutputStream.
2. Envuelva el flujo de archivo en el flujo de búfer apropiado. Para el caso de la entrada, envuelva el flujo de archivo en un BufferedInputStream. Para el caso de la salida, en un BufferedOutputStream.
3. Realice todas las operaciones de E/S mediante el flujo en búfer.
4. Cierre el flujo que usará el búfer. El cierre de éste causa automáticamente que se cierre el flujo de archivo.
Análisis
Para crear un fl ujo de entrada que se almacenará en búfer, use BufferedInputStream. Defi ne dos constructores. El que usaremos es:
BufferedInputStream(InputStream flujo)
Esto crea un fl ujo en entrada en búfer que usa el búfer para el fl ujo de entrada especifi cado por fl ujo. Usa el tamaño de búfer predeterminado.
Para crear un flujo de salida que se almacenará en búfer, use BufferedOutputStream. Define dos constructores. El que usaremos es:
BufferedOutputStream(OutputStream flujo)
Esto crea un fl ujo en salida en búfer que usa el búfer para el fl ujo de salida especifi cado por fl ujo. Usa el tamaño de búfer predeterminado.
Para leer de un flujo en búfer, puede usar read( ). Para escribir en un flujo en búfer, puede usar write( ). (Consulte Lea bytes de un archivo y Escriba bytes en un archivo, para conocer más detalles).
Cuando haya terminado con el flujo en búfer, debe cerrarlo al llamar a close( ). Aquí se muestra: void close( ) throws IOException.
Al cerrar un fl ujo en búfer también se causa que el fl ujo original se cierre automáticamente. Si ocurre un error, se lanzará una IOException.
Ejemplo
En el siguiente ejemplo se muestran BufferedInputStream y BufferedOutputStream en acción. Se crea un programa que copia un archivo. Debido a que se usan fl ujos de bytes, puede copiarse cualquier tipo de archivo, incluidos los que contienen texto, datos o programas.
// Usa flujos en buffer para copiar un archivo. //
// Para usar este programa, especifique el nombre // de los archivos de origen y de destino. Por
// ejemplo, para copiar un archivo llamado ejemplo.dat // en uno llamado ejemplo.bak, use la siguiente
// línea de comandos: //
// java CopiarArchivoBufer ejemplo.dat ejemplo.bak //
import java.io.*;
class CopiarArchivoBufer {
public static void main(String args[ ]) {
BufferedInputStream fin; BufferedOutputStream fout;
// Primero se asegura de que se han especificado ambos archivos. if(args.length != 2) {
System.out.println("Uso: CopiarArchivoBufer De A"); return;
}
// Abre un archivo de entrada que está envuelto en un BufferedInputStream. try {
fin = new BufferedInputStream(new FileInputStream(args[0])); } catch(FileNotFoundException exc) {
System.out.println("Archivo de entrada no encontrado"); return;
}
// Abre un archivo de salida que está envuelto en un BufferedOutputStream. try {
fout = new BufferedOutputStream(new FileOutputStream(args[1])); } catch(FileNotFoundException exc) {
System.out.println("Error al abrir el archivo de salida"); // Cierra el archivo de entrada abierto.
try {
fin.close( );
} catch(IOException exc2) {
System.out.println("Error al cerrar el archivo de entrada"); }
return; }
// Copia el archivo.
// Debido al uso de flujos en búfer, las operaciones // de lectura y escritura se incluyen en búfer // automáticamente, lo que da un mejor rendimiento. try {
do { i = fin.read( ); if(i != –1) fout.write(i); } while(i != –1); } catch(IOException exc) { System.out.println("Error de archivo"); } try { fin.close( ); } catch(IOException exc) {
System.out.println("Error al cerrar el archivo de entrada"); }
try {
fout.close( );
} catch(IOException exc) {
System.out.println("Error al cerrar el archivo de salida"); }
} }
Opciones
La mejora en el rendimiento que se genera con el uso de fl ujos en búfer puede ser muy importante. En realidad, no tiene que depender de cronometrajes en nanosegundos. Suele ser muy evidente con la simple observación. Esto es cierto en el programa de copia de archivos que acabamos de mostrar. Para ver la diferencia de lo que hace el uso del búfer, use primero el programa de ejemplo como se muestra para copiar un archivo muy grande y observe cuánto tarda en ejecutarse. Luego, modifi que el programa para que use FileInputStream y FileOutputStream directamente. En otras palabras, no envuelva el archivo dentro de un fl ujo de entrada en búfer. Luego, vuelva a ejecutar el programa, copiando el mismo archivo largo. Si el archivo que está copiando es lo sufi cientemente grande, notará con facilidad que la versión que no usa el búfer toma más tiempo.
Aunque el tamaño de los búferes que proporcionan automáticamente BufferedInputStream y BufferedOutputStream suelen ser suficientes, es posible especificar su tamaño. Podría hacer esto si sus datos están organizados en bloques de tamaño fijo y estará operando de bloque en bloque. Con el fin de especificar el tamaño del búfer para entrada en búfer, use el siguiente constructor.
BufferedInputStream(InputStream flujo int longit)
Esto crea un fl ujo de entrada en búfer que usa el búfer para el fl ujo especifi cado en fl ujo. La longitud del búfer se pasa vía longit, que debe ser mayor que cero. Para especifi car el tamaño del búfer para una salida en búfer, use el siguiente constructor:
BufferedOutputStream(OutputStream flujo int longit)
Esto crea un fl ujo de salida en búfer que usa el búfer para el fl ujo especifi cado en fl ujo. La longitud del búfer se pasa vía longit, que debe ser mayor que cero.
BufferedInputStream sobreescribe los métodos mark( ) y reset( ) especificados por
InputStream. Esto es importante porque en éste último, mark( ) no hace nada y reset( ) lanza una IOException. Al sobreescribir estos métodos, BufferedInputStream le permite moverse dentro de un búfer. En ciertas situaciones, esta capacidad resulta útil.
Aunque los fl ujos de bytes son técnicamente sufi cientes para manejar todas las tareas de entrada de archivos (porque todos los archivos pueden tratarse como fl ujos de bytes), Java ofrece un mejor método cuando se opera con datos de carácter: los fl ujos de carácter. Los fl ujos basados en carácter operan directamente en objetos de tipo char (en lugar de bytes). Por tanto, cuando se trabaja con archivos que contienen texto, los fl ujos basados en carácter suelen ser la mejor opción.
Para leer caracteres de un archivo, puede usar FileReader, que se deriva de InputStreamReader y Reader. (InputStreamReader proporciona el mecanismo que traduce bytes en caracteres).
FileReader implementa las interfaces Closeable y Readable. [(Readable está empaquetada en java. lang y define un objeto que proporciona caracteres mediante el método read( )]. Cuando se lee un archivo vía FileReader, la traducción de bytes en caracteres se maneja automáticamente.
Paso a paso
Para leer un archivo empleando FileReader se requieren tres pasos principales: 1. Abra el archivo empleando FileReader.
2. Lea del archivo usando el método read( ). 3. Cierre el archivo al llamar a close( ).
Análisis
Para abrir un archivo, simplemente cree un objeto de FileReader, que defi ne tres constructores. El que usaremos es:
FileReader(String nombreArchivo) throws FileNotFoundException
Aquí, nombreArchivo es el nombre de un archivo. Lanza una FileNotFoundException, si el archivo no existe.
Para leer un carácter de un archivo, puede usar esta versión de read( ) (heredada de Reader): int read( ) throws IOException
Cada vez que se le llama, lee un solo carácter de un archivo y devuelve el carácter como un valor entero. read( ) devuelve –1 cuando se encuentra el fi nal del archivo. Lanzará una IOException si ocurre un error de E/S. Otras versiones de read( ) pueden dar entrada a varios caracteres al mismo tiempo y ponerlos en una matriz.
Cuando haya terminado con el archivo, debe cerrarlo al llamar a close( ). Aquí se muestra: void close( ) throws IOException
Si ocurre un error cuando trata de cerrar el archivo, se lanza una IOException.