Java - FLUJOS DE ENTRADA Y SALIDA PDF
Java - FLUJOS DE ENTRADA Y SALIDA PDF
Java - FLUJOS DE ENTRADA Y SALIDA PDF
Los programas nos entregan informacin producto de las funciones que se le han definido,
esta debe provenir de alguna fuente de datos (discos, CD-RW, memoria) y seguramente
necesitaremos enviar esta informacin o datos hacia otro lugar para mostrarla o
almacenarla. Estos son los flujos (en ingls stream) de informacin.
"Un flujo es una ruta seguida por los datos de un programa. Un flujo de entrada enva datos
desde una fuente a un programa, y un flujo de salida enva datos desde un programa hacia
un destino".
En JAVA, un flujo es un objeto que sirve de intermediario entre el origen y el destino de los
datos. Esto tiene la ventaja que el programa leer y escribir en el flujo de informacin sin
importar el origen o el destino (la pantalla, un archivo, la memoria, Internet, etc.). Adems,
tampoco va a tener relevancia el tipo de dato que se encuentra en este objeto. Por otro lado,
esto significa un nuevo nivel de abstraccin pues al programa ya no le importa saber nada
acerca del dispositivo del cual vienen o al cual van los datos.
As, para leer informacin el programa tiene que abrir un flujo (objeto), de la misma manera
que tiene que hacerlo para escribirla o enviarla. Para ello JAVA contiene una serie de clases
que son parte del paquete java.io. Un programa que use flujos de entrada/salida (E/S)
deber importar el paquete: import java.io.*.
Existen dos tipos de flujos: los de Entrada, que sirven para leer datos, y los de Salida, que se
usan para guardar datos. En ambos casos, los flujos pueden ser flujos de bytes o flujos de
caracteres.
Los Flujos de Bytes, se utilizan para manejar bytes, enteros u otros tipos simples en el flujo,
con valores que van desde 0 a 255.
Los Flujos de Caracteres, manejan archivos de texto u otras fuentes de texto. Cualquier
clase de datos que comprenda texto debera utilizar este tipo de flujos.
1. Flujos de Entrada
La clase InputStream es la que se encarga de establecer el flujo de bytes de entrada de
informacin. Es una clase abstracta que es superclase de todas las subclases que
representan este flujo. En este sentido, el mtodo ms importante es read(), que son varios
mtodos sobrecargados que leen bytes ya sea individualmente o en conjunto.
Por otro lado, tenemos la superclase Reader, que al igual que la superclase InputStream lee
desde el origen, pero sus subclases leen caracteres, es decir, char en vez de byte.
2. Flujos de Salida
La clase OutputStream es la superclase de todas las clases que representan un flujo que se
encarga de escribir bytes un destino. Su mtodo ms importante es write()
Tambin, existen otra superclase que en vez de escribir o enviar bytes escribe caracteres:
Writer.
3. Flujo E/S Estndar
El paquete java.lang proporciona, por intermedio de la clase System tres flujos que son
abiertos una vez que el programa se carga en memoria para el uso de la salida estndar,
normalmente el monitor:
System.in, que es una subclase de la clase InputStream y que hace referencia a la entrada
estndar del sistema, normalmente el teclado. Se usa para leer datos introducidos por el
usuario.
System.out, que es subclase de PrintStream que a su vez es subclase de OutputStream y
que hace referencia a la salida estndar. Se utiliza para mostrar datos al usuario.
System.err, que es subclase de PrintStream que a su vez es subclase de OutputStream y
que hace referencia a la salida estndar. Se utiliza para mostrar mensajes de error al
usuario.
4. Flujo E/S desde y hacia Archivos
Una gran parte de la vida de una aplicacin en memoria, se dedicar a trabajar con archivos
o archivos, para intercambiar datos entre distintos dispositivos de almacenamiento, que se
utilizan refirindolos con una ruta de directorio y un nombre.
Las operaciones bsicas que se realizan con un archivo son: abrir el archivo, leer datos o
escribirlos y cerrar el archivo.
a. Flujos de Bytes.
Esta es la manera bsica de leer o escribir sobre un archivo. Para leer bytes se debe abrir
un flujo de entrada hacia el origen, y para escribir sobre l se abre un flujo de salida hacia el
destino.
Para crear el flujo de entrada del archivo se usa un objeto de la clase FileInputStream, a
cuyo constructor se le pasa como argumento una cadena que contiene la ruta del archivo y
su nombre, con su extensin, o slo el nombre del archivo si se encuentra en el directorio
actual de trabajo. Por ejemplo:
FileInputStream archivo = new FileInputStream("Ruta\Nombre.ext");
Una vez creado el flujo, se podrn leer bytes desde el flujo usando el mtodo read(), el cual
devuelve un valor entero que contiene la representacin del prximo byte en el flujo y
devuelve -1 cuando llega al final del flujo.
/* Lee los bytes desde un archivo */
import java.io.*;
public class EjemFile {
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
String archivo = "";
int enteroByte = 0;
System.out.print("Ingrese el nombre del archivo: ");
archivo = s.nextLine();
try{
FileInputStream f = new FileInputStream(archivo);
enteroByte = f.read();
while (enteroByte != -1){
System.out.print((char)enteroByte);
enteroByte = f.read();
}
f.close();
}
catch (IOException e){
System.out.println(e);
}
}
}
Este programa lee bytes desde un archivo existente. La ruta y/o nombre del archivo se
ingresa por teclado. Se debe considerar la forma como cada sistema operativo maneja el
acceso a sus directorios; as si se trabaja con sistemas UNIX o Linux se usa la barra
inclinada normal (/) para separar los directorios, en cambio, es sistemas DOS y Windows se
usa la barra invertida (\), pero como representa la secuencia de escape se debe usar as: '\\'.
En este programa, se crea el flujo, se lee el archivo byte a byte y se imprimen hasta el fin del
archivo con el que se est trabajando.
Sin embargo, en este caso, estamos leyendo el archivo byte a byte. Sera una mejor
solucin, traer a memoria una mayor cantidad de informacin para trabajar con ella en vez
de ir leyendo cada vez desde el archivo, mejorando as la velocidad de ejecucin de la
aplicacin. Esto se hace a travs de un buffer, "...un lugar donde se pueden guardar datos
antes de ser utilizados por un programa...".
En el caso del flujo de entrada, el buffer se llena con datos que no se han utilizado an. Y
cuando el programa los requiera los encontrar en el buffer antes de ir a buscarlo a la fuente
origen (archivo fsico). La clase con la que se logra esto es: BufferedInputStream, que crea
un flujo de entrada almacenado en un buffer reservado para un objeto InputStream que se
le pasa como argumento y que, en este caso particular, ser un objeto FileInputStream.
Para leer datos desde la entrada del buffer se usa el mtodo read(), sin argumentos, el cual
devuelve un entero de de valor 0 a 255 que representa el byte ledo. Si llega al final del flujo
y no quedan datos devuelve un -1. Para ejemplificar, tomaremos el mismo ejemplo anterior y
le agregaremos una lnea y modificaremos otra:
...
try{
FileInputStream f = new FileInputStream(archivo);
BufferedInputStream buffer = new BufferedInputStream(f);
enteroByte = buffer.read();
...
En el caso de los flujos de salida, este se crea con la clase FileOutputStream, a la cual se
le pasa como argumento la ruta y el nombre del archivo. Si el archivo ya existe, se borrar
su contenido. En caso de no existir se crear, pero si existe, y se quiere agregar su
contenido al final del archivo existe el constructor FileOutputStream (String rutaArchivo,
[true/false]), que permite esta operacin cuando se pasa true como el argumento booleano.
Es el mtodo write(Entero) el que permite escribir valores enteros y bytes en el flujo. En este
caso, es necesario cerrar expresamente el flujo hacia el archivo mediante el uso del mtodo
close().
Como ejemplo, el siguiente programa toma la entrada por teclado y la enva a un archivo de
texto (datos.txt). Si el archivo no existe, lo crear, y si ya existe lo sobreescribir. La entrada
es por un flujo estndar la cual termina al presionar la tecla Enter (10):
import java.io.*;
public class EjemFileOut {
public static void main(String[] args) {
int letra;
System.out.print("Ingrese un texto: ");
try{
FileOutputStream f = new FileOutputStream("datos.txt");
do{
letra = System.in.read();
f.write(letra);
}while(letra != 10);
f.close();
}
catch (IOException e){
System.out.println(e);
}
}
}
Para hacer lo anterior, pero sin perder el contenido original del archivo si este ya existe, si no
que agregar la final los bytes que se escriben en el flujo, se escribe el programa
exactamente igual, pero se utiliza el constructor que permite agregar los enteros a partir del
final del archivo, para lo cual se reemplaza la siguiente lnea:
FileOutputStream f = new FileOutputStream("datos.txt", true);
Tambin se puede establecer un buffer para los flujos de salida a travs de la clase
BufferOutputStream, donde se pasa como argumento el objeto del flujo de salida
establecido para el archivo a escribir. Se usa el mtodo write(entero), para escribir bytes en
el archivo, que representen valores enteros de 0 a 255. Los datos se escriben en el flujo y
slo se escriben en el archivo cuando el buffer se llena o cuando se llama expresamente al
mtodo flush().
import java.io.*;
public class EjemFileOut {
public static void main(String[] args) {
int letra;
System.out.print("Ingrese un texto: ");
try{
FileOutputStream f = new FileOutputStream("datos.txt");
BufferOutputStream buffer = new BufferOutputStream(f);
do{
letra = System.in.read();
buffer.write(letra);
}while(letra != 10);
Buffer.flush(); // Vacia el buffer al archivo
f.close();
}
catch (IOException e){
System.out.println(e);
}
}
}
b. Flujos de Caracteres
Estos flujos se usan para trabajar con cualquier texto que represente un conjunto de
caracteres UNICODE, que incluye los caracteres ASCII. Pueden ser archivos de texto,
HTML, archivos fuentes, etc.
El flujo de entrada para un archivo de texto, se establece a travs de la clase FileReader.
Esta clase es subclase de InputStreamReader, la cual lee un flujo de bytes y los convierte a
enteros "que representan" valores de los caracteres UNICODE. El mtodo a utilizar es
read(), el cual devuelve enteros, que es necesario convertir a char para visualizar los
caracteres y devuelve -1 cuando se dejan de leer caracteres. Por ejemplo :
import java.io.*;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
String archivo = "";
int enteroByte = 0;
System.out.print("Ingrese el nombre del archivo: ");
archivo = s.nextLine();
try{
FileReader f = new FileReader(archivo);
enteroByte = f.read();
while (enteroByte != -1){
System.out.print((char)enteroByte);
enteroByte = f.read();
}
f.close();
}
catch (IOException e){
System.out.println(e);
}
}
}
Al igual que en los casos anteriores, se puede usar un buffer para trabajar con los datos del
flujo de entrada en memoria y no directamente con el archivo. Para ello se utiliza la clase
BufferadReader, en donde se pasa como argumento un objeto Reader que sirve para
comunicarse con el archivo, en este caso, FileReader. Tambin tiene un mtodo read() otro
mtodo interesante es que se usa para leer una lnea entera del buffer: readLine(), que
devuelve un objeto String con la lnea y un valor null para la cadena cuando ya no quedan
ms lneas. Por ejemplo, el mismo programa anterior pero con modificaciones:
try{
FileReader f = new FileReader(archivo);
BufferedReader buffer = new BufferedReader(f);
String linea;
while (linea != null){
linea = buffer.readLine();
if (linea != null) System.out.print(linea + \n);
}
f.close();
El flujo se salida para escribir caracteres sobre archivos de texto, usa la clase FileWrite.
Con uno de sus constructores, FileWriter(String ruta_archivo), se reemplaza el contenido del
archivo original y lo reemplaza por el nuevo, si este exista; o en caso contrario; lo crea.
Ejemplo :
import java.io.*;
public class Main {
public static void main(String[] args) {
int letra;
System.out.print("Ingrese un texto: ");
try{
FileWriter f = new FileWriter("texto.txt");
do{
letra = System.in.read();
f.write(letra);
}while(letra != 10);
f.close();
}
catch (IOException e){
System.out.println(e);
}
}
}
Por otro lado, si no queremos perder el contenido de nuestro archivo tenemos el constructor
FileWriter(String ruta/archivo, boolean [true/false]), que escribe desde el final de un archivo
existente cuando se pasa como parmetro el valor boleano "true". Para esto se remplaza la
siguiente lnea:
FileWriter f = new FileWriter("texto.txt", true);
Finalmente, se puede utilizar un buffer para escribir nuestros caracteres en memoria y
despus mandarlos al archivo. Para establecer el buffer, se usa el constructor
BufferedWriter(Writer), donde "Writer" es el objeto que se crea para establecer el flujo.
Tambin tiene el mtodo write(), y el mtodo newLine(), que manda el o los caracteres de
"nueva lnea" segn la plataforma: en DOS retorno de carro ms salto de lnea , y en UNIX y
sistemas similares salto de lnea. Utilizar este mtodo es mejor pues el programa se hace
realmente multiplataforma. Se usa de la misma manera que lo hemos visto en los ejemplos
anteriores. Sin embargo, para poder utilizar el buffer se requiere tambin el mtodo flush(),
para vaciar el contenido del buffer en el archivo.
try{
FileWriter f = new FileWriter("texto.txt");
BufferedWriter buffer = new BufferedWriter(f);
do{
letra = System.in.read();
buffer.write(letra);
}while(letra != 10);
c. Flujos de atos
Los bytes y caracteres son datos tambin, pero por un archivo de datos entenderemos una
coleccin de informacin que se almacena en algn soporte magntico. Estos pueden ser
tipos primitivos u objetos. En el ltimo caso, se habla de un conjunto de registros, que
contienen los mismos campos, que pueden ser datos primitivos o un tipo definido por el
usuario, es decir otro objeto referenciado por el objeto que se almacena. En la POO, se
habla de objetos en vez de "registros" y de "atributos" en vez de campos.
Flujos de datos de ti!os !ri"itivos.
Muchas veces trabajar con bytes y caracteres puede llegar a ser muy restrictivo. JAVA
permite manejar flujos con los tipos primitivos, de modo que el manejo de ellos sea
transparente para la aplicacin.
Un flujo de entrada de datos de tipos primitivos, se hace con la clase DataInputStream, a la
cual se le pasa como argumento un objeto de flujo existente.
El flujo de salida, por su lado, es controlado por la clase DataOutputStream, que tambin
recibe como argumento un objeto de flujo, esta vez de salida.
Los mtodos de lectura y escritura para flujos de datos de tipos primitivos: boolean, byte,
short, int, long, float y double son:
Entrada Salida Significado
readBoolean() writeBoolean(boolean) Lee-escribe tipo boolean
readByte() writeByte(int) Lee tipo Byte - escribe tipo Byte un valor entero
writeBytes(String) Escribe una cadena como secuencia de bytes
readChar() writeChar(int) Lee tipo Char - Escribe como Char un valor entero
readChars(String) Lee una cadena como una secuencia de caracteres
readShort() writeShort(int) Lee tipo Short - Escribe como Short un valor entero
readInt() writeInt(Int) Lee - escribe tipo entero
readLong() writeLong(Long) Lee -escribe tipo long
readFloat() writeFloat(Float) Lee - escribe tipo float
readDouble() writeDouble(Double) Lee - escribe tipo double
readUTF(DataInput) writeUTF(String) Lee formato UTF-8 desde objeto DataInput - Escribe una
cadena en formato UTF-8
Cada mtodo devuelve un valor de retorno del mismo tipo sobre el que trabaja.
En el caso de readUTF() y writeUTF(), los datos obtenidos en el origen o escritos en el
destino, son convertidos a un formato portable entre distintos lenguajes y plataformas. En
este caso, se usa el estndar de caracteres UTF-8, que es un derivado de UNICODE.
Para trabajar con los flujos de datos primitivos, se debe crear un flujo asociado al origen o
destino de los datos, despus se asocia un objeto DataInputStream o DataOutputStream a
este flujo y se lee o escribe segn sea el caso. Por ejemplo estos 2 casos:
String cadena;
FileOutputStream f = new FileOutputStream("archivo.dat");
DataOutputStream d = new DataOutputStream(f);
d.writeUTF(cadena);
long dato;
FileInputStream f = new FileInputStream("enterosLong.dat");
DataInputStream d = new DataInputStream(f);
d.readLong(dato);
La extensin no es relevante, pero suele usarse ".dat", para indicar que se trata de un
archivo de datos binarios. A partir de ahora, utilizaremos la clase File, de cual hablaremos a
continuacin, con el objeto de identificar los archivos sobre los que se trabajaran. Primero
vamos a crear un archivo con datos de distinto tipo y despus lo leeremos.
Como ejemplo crearemos un programa, que ingrese registros en un archivo de datos y otro
que los lea y los muestre en pantalla.
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
FileOutputStream f = new FileOutputStream("datos.dat",true);
DataOutputStream d = new DataOutputStream(f);
String cadena = "Esto esta que arde";
int entero = 22;
d.writeUTF(cadena);
d.writeInt(entero);
f.close();
}
}
Este programa permite ingresar el archivo de datos en el que vamos a guardar la
informacin. Crea el objeto File que nos permitir determinar si el archivo de datos existe o
no. Si no existe se crea. Si existe vamos a escribir a partir del final del mismo. Se crea el
stream de salida, y un objeto DataOutputStream que nos va permitir escribir los datos en el
archivo.
Para leer los datos guardados tenemos el siguiente programa :
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
FileInputStream f = new FileInputStream("datos.dat");
DataInputStream d = new DataInputStream(f);
String cadena;
int entero;
try{
do{
cadena = d.readUTF();
entero = d.readInt();
System.out.println("Cadena: "+cadena+" entero:"+entero);
}while(true);
}
catch(EOFException e){
System.out.print("Fin del archivo");
}
f.close();
}
}
En la definicin del mtodo main, se usa la palabra reservada throws para sealar o indicar
que el mtodo en cuestin puede lanzar excepciones, en este caso, de la clase
IOException. Esto se hace para evitar tener que declarar, en cada caso que sta se
produzca, un try y un catch, que se produciran a nivel de la lectura de los datos contenidos
en el archivo. En estas situaciones, la excepcin no es atrapada en el mismo mtodo, no hay
un catch. Esto es simplemente un ejemplo para mostrar cmo evitar que se lancen
excepciones. Por otro lado, en los mtodos que eventualmente llamen a este tipo de
mtodos estn obligados a atrapar la excepcin.
Al igual que en el programa anterior, se declaran los objetos que hacen la lectura. Se crea
un objeto File con el objeto de verificar si el archivo existe y controlar la marca fin de archivo
(EOF). Si existe, se crean e inicializan los objetos del stream de entrada. Luego se leen los
datos del archivo y se ponen en pantalla. Finalmente, se cierra el flujo de datos.
Indudablemente, si queremos almacenar una coleccin de datos relacionados entre s, una
mejor y ms lgica solucin es el uso de objetos en vez del uso de bytes para ir guardando y
leyendo datos representados por tipos primitivos.
Flujo de datos con objetos
Esto se conoce como "Seriacin de Objetos". La idea es almacenar objetos con datos en un
archivo para hacerlos "persistentes", esto es que los datos no se pierdan cuando se deje de
usar al aplicacin en memoria principal. Ello porque la Memoria de Acceso Aleatorio (RAM) o
Principal, es voltil y los datos desaparecen una vez que ha terminado la aplicacin que los
controla. Se denomina "deseriacin" a la operacin de recuperar los datos desde el archivo
para usarlos en memoria.
Las clases que permiten estas operaciones son ObjectOutputStream, que permite escribir
en el flujo de salida, y ObjectInputStream, que permite reconstruir los objetos desde el flujo
de entrada. Ambas forman parte del paquete java.io. Actan sobre un flujo ya definido por
otro objeto. Por ejemplo :
//acta sobre el archivo
FileOutputStream f = new FileOutputStream("datos.dat");
//acta sobre los objetos
ObjectOutputStream obj = new ObjectOutputStream(f);
La seriacin es posible, slo cuando se implementa en la clase la interfaz Serializable. Que
tiene por objeto identificar los objetos serializables y no contiene ningn mtodo :
import java.io*;
public class MiClase implements Serializable {
Cuando un objeto tiene atributos que referencian a otros objetos, estos ltimos deben
escribirse tambin en el flujo. De esto se encarga el mtodo writeObject(), que recorre las
referencias recursivamente, escribiendo todos ellos. Por otro lado, el mtodo readObject(),
se encargar de recorrer recursivamente las referencias para recuperar los datos de los
objetos referenciados.
Como ejemplo, se crea una nueva clase denominada "Articulo", con sus respectivos
atributos y mtodos:
import java.io.Serializable;
public class Articulo implements Serializable {
private int cod;
private String nombre;
private int precio;
private boolean estado;
public Articulo(){}
public Articulo (int cod,String nombre,int precio,boolean estado){
this.cod = cod;
this.nombre = nombre;
this.precio = precio;
this.estado = estado;
}
public int getCod() { return cod; }
public boolean isEstado() { return estado; }
public String getNombre() { return nombre; }
public int getPrecio() { return precio; }
}
Se importa el paquete java.io para el uso de la interface "Serializable". Se definen dos
constructores: uno vaco por defecto y otro que recibe como argumentos los atributos de la
clase.
El siguiente programa, tiene por objetivo crear el archivo que va a seriar los objetos con
aquellos datos que ingresemos por teclado (objeto art). Si se abre el archivo de datos con un
editor de textos se ver que tiene un encabezado sealando, entre otras cosas, la clase de
objetos que almacena. Es por ello, que la seriacin se debe hacer en una sola sesin. Para
agregar objetos al archivo, o modificarlos o borrarlos, se usa la clase RandomAccessFile
del paquete java.io, que permite acceder a cualquier posicin (registro) dentro del archivo.
Luego en el mismo programa se realiza la lectura de los datos desde el archivo a travs del
objeto art2:
import java.io.*;
import java.util.Scanner;
public class Main {
public static void main(String[] args) throws IOException {
FileOutputStream f = new FileOutputStream("datos.dat");
ObjectOutputStream ob = new ObjectOutputStream(f);
Scanner ing = new Scanner(System.in);
/*
* Escritura del objeto hacia un archivo
*/
// Ingreso de datos
String cod, nom, pre, est;
System.out.print("Ingrese codigo: ");
cod = ing.nextLine();
System.out.print("Ingrese nombre: ");
nom = ing.nextLine();
System.out.print("Ingrese precio: ");
precio = ing.nextLine();
Articulo art = new Articulo(Integer.parseInt(cod),
nom,
Integer.parseInt(precio),
false);
ob.writeObject(art);
f.close();
/*
* Lectura del objeto desde un archivo
*/
Articulo art2 = new Articulo(); // Leer
FileInputStream f2 = new FileInputStream("datos.dat");
ObjectInputStream ob2 = new ObjectInputStream(f2);
try{
art2 = (Articulo)ob2.readObject();
}
catch(ClassNotFoundException e){
System.err.println("Clase no encontrada");
}
System.out.println("El codigo es: "+art2.getCod());
System.out.println("El nombre es: "+art2.getNombre());
System.out.println("El precio es: "+art2.getPrecio());
if(art2.isEstado())
System.out.println("Articulo Activo");
else
System.out.println("Articulo Desactivado");
f2.close();
}
}