Componentes clave
Clases Métodos
java.io.BufferedReader int read( ) void close( )
java.io.BufferedWriter void write(int valbytes) void close( )
Aunque FileReader y FileWriter proporcionan las capacidades para leer y escribir en un archivo de texto, emplearlos solos tal vez no siempre sea el método más efi ciente. La razón es que cada operación de lectura o escritura individual traduce (directa o indirectamente) en una operación en el archivo, y el acceso al archivo consume tiempo. A menudo, un mejor método es proporcionar un búfer para los datos. En este método, cada operación de entrada lee desde un bloque de datos y cada operación de salida escribe en un bloque de datos. Por tanto, el número de operaciones de archivo es reducido, lo que da como resultado el rendimiento mejorado.
Aunque es posible usar el búfer manualmente en las operaciones de E/S al leer y escribir matrices de caracteres (en lugar de caracteres individuales), este método no será óptimo en todos los casos. En cambio, para lograr los beneficios del rendimiento de usar el búfer para las operaciones de E/S, por lo general querrá envolver un flujo de caracteres dentro de una clase de
lector o escritor en búfer de Java. Al hacerlo así, en ocasiones aumentará de manera importante la velocidad de sus operaciones de E/S sin esfuerzo adicional de su parte.
Para crear un lector de flujo de entrada en búfer, use BufferedReader. Deriva de Reader e implementa las interfaces Closeable y Readable. [(Readable está empaquetado en java.lang y define un objeto que proporciona caracteres vía el método read( )].
Para crear un lector de flujo de salida en búfer, use BufferedWriter. Deriva de Writer e implementa las interfaces Closeable, Flushable y Appendable. [(Appendable está empaquetado en java.lang. Define un objeto al que pueden agregarse caracteres mediante el método append( )].
Paso a paso
Para usar el búfer con un fl ujo de archivo basado en caracteres, se requieren estos pasos:
1. Cree el flujo base. Para entrada, será una instancia de FileReader. En el caso de la salida, será una instancia de FileWriter.
2. Envuelva el flujo de archivo en el lector o escritor en búfer apropiado. Para el caso de la entrada, use BufferedReader. Para el caso de la salida, use BufferedWriter.
3. Realice todas las operaciones de E/S mediante el lector o escritor que usa el búfer. 4. Cierre el flujo en búfer. El cierre de éste causa automáticamente que se cierre el flujo de
archivo.
Análisis
Para crear un lector que usará el búfer, use BufferedReader. Defi ne dos constructores. El que usaremos es:
BufferedReader(Reader lect)
Esto crea un lector que usa el búfer para el fl ujo de entrada especifi cado por lect. Usa el tamaño de búfer predeterminado. En el caso de operaciones de entrada de archivo, pasará un FileReader a lect.
Para crear un escritor que usa el búfer para flujo de salida, use BufferedWriter. Define dos constructores. El que usaremos es:
BufferedWriter(OutputStream escr)
Esto crea un escritor que usa el búfer para el fl ujo de salida especifi cado por escr. Usa el tamaño de búfer predeterminado. Por tanto, para usar el búfer con las operaciones de archivo, pasará un FileWriter a escr.
Por ejemplo, suponiendo un archivo llamado Prueba.dat, en las siguientes líneas se muestra cómo crear un BufferedReader y un BufferedWriter vinculado con ese archivo.
BufferedReader bw = new BufferedReader (new FileReader("Prueba.dat")); BufferedWriter br = new BufferedWriter (new FileWriter("Prueba.dat"));
Para leer de un BufferedReader, puede usar cualquiera de los métodos de read( ), que se heredan de Reader. Aquí se muestra el que usaremos:
Devuelve el siguiente carácter en el fl ujo (en los 16 bits de orden inferior de un entero) o –1 si se llega al fi nal del fl ujo. Si ocurre un error durante la lectura, se lanza una IOException.
Para escribir en un flujo en búfer, puede usar cualquier versión del método write( ), que se hereda de Writer. Aquí se muestra el que usaremos:
void write(int car) throws IOException
Este método escribe el carácter especifi cado por car en el archivo. Aunque valbyte se declare como entero, sólo se escriben en el fl ujo los 16 bits de orden inferior. Si ocurre un error durante la escritura, se lanza una IOException. Otras versiones de write( ) pueden dar salida a una matriz de caracteres.
Cuando haya terminado con el lector o escritor que usa el búfer, debe cerrarlo al llamar a close( ). Aquí se muestra:
void close( ) throws IOException.
Al cerrar un BufferedReader o un BufferedWriter también se causa que el flujo original se cierre automáticamente.
Ejemplo
El siguiente ejemplo usa un BufferedReader y un BufferedWriter para copiar un archivo. En el proceso, invierte las mayúsculas y minúsculas de las letras. En otras palabras, las minúsculas se vuelven mayúsculas, y viceversa. Todos los demás caracteres quedan sin cambio.
// Usa un BufferedReader y un BufferedWriter para
// copiar un archivo de texto, invirtiendo las mayúsculas // y minúsculas en el proceso.
//
// Para usar este programa, especifique el nombre de los // archivos de origen y destino. Por ejemplo, para // copiar un archivo llamado prueba.txt en uno llamado // prueba.inv, use la siguiente línea de comandos: //
// java CopiarInvertir test.txt test.inv //
import java.io.*; class CopiarInvertir {
public static void main(String args[ ]) {
BufferedReader br; BufferedWriter bw;
// Primero se asegura de que se han especificado ambos archivos. if(args.length != 2) {
System.out.println("Uso: CopiarInvertir De A"); return;
// Abre un FileReader envuelto en un BufferedReader. try {
br = new BufferedReader(new FileReader(args[0])); } catch(FileNotFoundException exc) {
System.out.println("No se ha encontrado el archivo de entrada"); return;
}
// Abre un FileWriter envuelto en un BufferedWriter. try {
bw = new BufferedWriter(new FileWriter(args[1])); } catch(IOException exc) {
System.out.println("Error al abrir el archivo de salida"); // Cierra el lector de entrada abierto.
try {
br.close( );
} catch(IOException exc2) {
System.out.println("Error al cerrar el archivo de entrada"); }
return; }
// Copia el archivo, invirtiendo las mayúsculas y minúsculas // en el proceso. Debido a que se usan flujos en búfer, las // operaciones de lectura y escritura usan el búfer,
// automáticamente, lo que da un mejor rendimiento. try { int i; char ch; do { i = br.read( ); if(i != –1) { if(Character.isLowerCase((char) i)) bw.write(Character.toUpperCase((char) i)); else if(Character.isUpperCase((char) i)) bw.write(Character.toLowerCase((char) i)); else bw.write((char) i); } } while(i != –1); } catch(IOException exc) { System.out.println("Error de archivo"); } try { br.close( ); } catch(IOException exc) {
System.out.println("Error al cerrar el archivo de entrada"); }
try {
bw.close( );
} catch(IOException exc) {
System.out.println("Error al cerrar el archivo de salida"); }
} }
Opciones
Es fácil ver de primera mano la mejora en el rendimiento que se obtiene al envolver un fl ujo de archivo en un fl ujo que usa el búfer, al hacer el siguiente experimento. En primer lugar, ejecute el programa de ejemplo, como se muestra, en un archivo de texto muy largo. Ahora observe cuánto tarda en ejecutarse. Luego, modifi que el programa para que use FileReader y FileWriter directamente. En otras palabras, no envuelva ninguno en una clase que use el búfer. Luego, vuelva a ejecutar el programa con el mismo archivo de texto largo. Si el archivo que está empleando es lo sufi cientemente largo, observará con facilidad que la versión que no usa el búfer ocupa más tiempo.
Como regla general, el búfer predeterminado proporcionado por BufferedReader y BufferedWriter es suficiente, pero es posible especificar un tamaño de búfer elegido por usted. Sin embargo, esto es más aplicable a situaciones en que un archivo está organizado en bloques de caracteres, y en que cada bloque tiene un tamaño fijo. Francamente, cuando se trabaja con archivos de texto, esta situación no es común. Por tanto, el tamaño de búfer predeterminado suele ser la elección apropiada. Para especificar el tamaño del búfer para un BufferedReader, use este constructor:
BufferedReader(Reader lect, int longit)
Esto crea un lector que usa el búfer con base en lect, que tiene una longitud de búfer longit, que debe ser mayor que cero. Para especifi car el tamaño del búfer para un BufferWriter, use este constructor:
BufferedWriter(OutputStream esch, int longit)
Esto crea un escritor que usa el búfer en Esch que tiene una longitud de búfer de longit, que debe ser mayor que cero.
BufferedReader sobreescribe los métodos mark( ) y reset( ) especificados por Reader. Esto es importante porque las implementaciones predeterminadas proporcionadas por Reader (heredadas por FileReader) simplemente lanzan una IOException. Al sobreescribir estos métodos, BufferedReader le permite moverse dentro de un búfer. Esta capacidad le resultará útil en ciertas situaciones.
BufferedReader proporciona un método que le resultará especialmente útil en algunos casos: readLine( ). Este método lee una línea de texto completo. Aquí se muestra:
String readLine( ) throws IOException
Devuelve una cadena que contiene los caracteres leídos. Devuelve null si se hace un intento de leer al final del flujo. La cadena devuelta por readLine( ) no termina con los caracteres de final de línea, como un retorno de carro o un alimentador de línea. Sin embargo, la operación de lectura sí consume esos caracteres.
En la solución anterior se ha mostrado cómo leer y escribir archivos en forma lineal, un byte o carácter tras otro. Sin embargo, Java también le permite acceder al contenido de un archivo en orden aleatorio o directo. Para ello, utilizará, RandomAccessFile, que encapsula un archivo de acceso aleatorio. RandomAccessFile soporta colocación de solicitudes, lo que signifi ca que puede leer o escribir en cualquier lugar dentro del archivo.
RandomAccessFile está orientada a byte, pero no deriva de InputStream u OutputStream. En cambio, implementa las interfaces DataInput y DataUutput, que definen los métodos básicos de E/S, como readInt( ) y writeDouble( ), que leen y escriben tipos primitivos de Java. También proporciona varios métodos read( ) y write( ) basados en bytes. Además se implementa la interfaz Closeable.
Paso a paso
Para leer y escribir bytes en un orden no secuencial, se requieren los siguientes pasos: 1. Abra un archivo de acceso aleatorio al crear una instancia de RandomAccessFile.
2. Use el método seek( ) para colocar el apuntador al archivo en el lugar en que quiera leer o escribir.
3. Use los métodos de RandomAccessFile para leer o escribir datos. 4. Cierre el archivo.
Análisis
RandomAccessFile proporciona dos constructores. El que usaremos aquí se muestra a continuación:
RandomAccessFile(String nombreArchivo, String acceso) throws FileNotFoundException El nombre del archivo se pasa en nombreArchivo y acceso determina el tipo de acceso a archivo que se permite. Si acceso es "r", puede leerse el archivo, pero no escribirse en él. Si es "rw", el archivo se abre en el modo de lectura y escritura. Otros valores de acceso válido se describen bajo Opciones. Se lanza una FileNotFoundException si el archivo no puede abrirse para acceso "r" o si el archivo no puede abrirse o crearse para acceso "rw".