Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% encontró este documento útil (0 votos)
81 vistas65 páginas

Archivos

Descargar como pdf o txt
Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1/ 65

Unidad 6. Flujos y Archivos.

Todo programa procesa datos de entrada, y produce una salida. Por ello, resulta
fundamental el conocer las distintas formas de entrada/salida que se pueden dar
en un programa.

6.1 Definición:
Archivos: Desde el punto de vista informático, un archivo es una colección de
información que almacenamos en un soporte magnético para poderla
manipular en cualquier momento. Esta información se almacena como un
conjunto de registros, conteniendo todos ellos, generalmente, los mismos
campos. Cada campo almacena un dato de un tipo predefinido o de un tipo
definido por el usuario. El término "archivo" es una abstracción que
representa la información que se almacena físicamente en algún dispositivo,
como un disco duro, USB o cualquier otro dispositivo. Podemos pensar en un
archivo como en una colección de información relacionada lógicamente, como
el código fuente de un programa, o el conjunto de datos de los empleados de
una empresa.

Flujos: La entrada/salida en java se basa en el concepto de flujo, que es una


secuencia ordenada de datos que tienen una fuente (flujos de entrada) o un
destino (flujos de salida). Las clases de entrada/salida aíslan a los
programadores de los detalles específicos del sistema de funcionamiento de la
máquina, al tiempo que posibilitan el acceso a recursos del sistema por medio
de archivos (files). Java considera los archivos como flujos secuenciales de
bytes. Cada archivo termina con un marcador de fin de archivo o bien en un
número de byte específico registrado en una estructura de datos mantenida
por el sistema. Para poder leer o escribir en un archivo, es necesario "abrirlo"
primero. Cuando se abre un archivo, se crea un objeto y se asocia un flujo
(stream) a dicho objeto. Cuando comenzamos a ejecutar una aplicación de
Java, se crean automáticamente tres objetos de flujo: System.in, System.out
y System.err. Los flujos asociados a estos objetos proporcionan canales de

Pág. 1
comunicación entre un programa y un archivo o dispositivo en particular. Por
ejemplo, el objeto System.in (objeto de flujo de entrada estándar) permite a
un programa introducir bytes desde el teclado, el objeto System.out (objeto de
flujo de salida estándar) permite a un programa enviar datos a la pantalla, y el
objeto System.err (objeto de flujo de error estándar) permite a un programa
enviar mensajes de error a la pantalla.

6.2 Clasificación: Archivos de texto y binarios.


Si queremos procesar archivos en Java necesitamos importar el paquete java.io.
En la Figura 1 aparece representada parte de la jerarquía de clases relacionada
con la entrada/salida de datos. Este paquete incluye las definiciones de las clases
de flujos como FileInputStream (para el flujo de entrada de un archivo) y
FileOutputStream (para el flujo de salida a un archivo). Los archivos se abren
creando objetos de estas clases de flujo que se derivan de las clases InputStream
y OutputStream, respectivamente.

Figura 1. Parte de la jerarquía de clases del paquete java.io

Pág. 2
Las clases InputStream y OutputStream son clases abstractas que definen
métodos para realizar operaciones de entrada y salida.

Es importante destacar que los objetos InputStream poseen un método de


lectura, read(), que tiene el efecto lateral de eliminar el byte o bytes leídos, en vez
de copiarlos. Otro método interesante implementado por las subclases de
InputStream es el método close(), éste libera los recursos utilizados por el
sistema, y como consecuencia, hace que sean imposibles posteriores operaciones
de lectura. Siempre se debe cerrar un objeto InputStream, mediante el método
close(), cuando se haya dejado de utilizar.

Las sintaxis de dichos métodos son:

int read() throws IOException


void close() throws IOException

La clase OutputStream es similar a la clase InputStream. Una instancia de


OutputStream proporciona un lugar en donde nuestro programa puede escribir
uno o más bytes. También dispone del método close(), el cual, una vez ejecutado,
no permite realizar más operaciones sobre dicho flujo. Siempre se debe cerrar un
objeto OutputStream, mediante el método close(), cuando se haya dejado de
utilizar.

Entrada/Salida de texto.
Hasta este punto todos los programas que se han realizado obtienen sus datos
necesarios para su ejecución a partir de la entrada estándar y el resultado de
estos se visualizan en la salida estándar. Así mismo la aplicación almacena los
datos que manipula en su espacio de memoria definido, cuando se esta
ejecutando; esto es, que los datos introducidos a la memoria del computador se
perderán cuando la aplicación deje de funcionar.

Pág. 3
Para solucionar el problema que los datos persistan para posteriores ejecuciones
de una aplicación, es almacenar estos en archivos en el disco en vez de la
memoria del computador. Entonces, cada vez que se ejecute la aplicación que
trabaja con estos datos, podrá leer del archivo los que necesite y manipularlos.

Por ejemplo, si se desea almacenar en un archivo los datos relativos a una


agenda de datos personales, se podría diseñar cada registro con los campos
nombre, sexo, dirección, teléfono, etc. La Figura 2 muestra la estructura del
archivo:

Figura 2. Estructura de un registro en un archivo

nombre sexo dirección teléfono


campo registro
registro
11
archivo

Cada campo almacenara el dato correspondiente. El conjunto de campos


descritos forma lo que hemos denominado registro, y el conjunto de todos los
registros forman un archivo que se almacenara en el disco bajo un nombre.

Leer y escribir archivos


Los archivos nos van a permitir almacenar físicamente un conjunto de datos
relacionados lógicamente. En Java, los programadores deben estructurar los
archivos de forma que satisfagan las necesidades de las aplicaciones.

Flujos de bytes
Los datos pueden ser escritos o leídos de un archivo byte a byte utilizando flujos
de las clases FileInputStream y FileOutputStream.

Pág. 4
Clase FileOutputStream
Un flujo de la clase FileOutputStream permite escribir bytes en un archivo.
Además de los métodos que esta clase hereda de OutputStream, la clase
proporciona los constructores siguientes:

FileOutputStream(String nombre)
FileOutputStream(String nombre, boolean añadir)
FileOutputStream(File archivo)

El primer constructor abre un flujo de salida hacia el archivo especificado por


nombre, mientras que el segundo hace lo mismo, pero con la posibilidad de añadir
datos a un archivo existente (añadir = true); el tercero lo hace a partir de un objeto
File.

Clase FileInputStream
Un flujo de la clase FileInputStream permite leer bytes desde un archivo. Además
de los métodos que esta clase hereda de InputStream, la clase proporciona los
constructores siguientes:

FileInputStream (String nombre)


FileInputStream (File archivo)

El primer constructor abre un flujo de entrada desde el archivo especificado por


nombre, mientras que el segundo lo hace a partir de un objeto File.

Pág. 5
Ejemplos:
Programa que lee una línea de texto desde el teclado y la guarda en un archivo
denominado bytes.txt, utilizando para ello la clase FileOutputStream, de la cual se
crea un objeto que permita realizar dicha operación.

import java.io.FileOutputStream;
import java.io.IOException;

public class CEscribirBytes{


public static void main(String[] args){
// Declaración de variables
byte[] buffer = new byte[81]; // Declaración del array buffer de tipo byte
int nbytes;
// Declaración del objeto fos que se utiliza para la apertura y almacenamiento de la
// información al archivo
FileOutputStream fos = null;
try{
System.out.println("Escriba el texto que desea almacenar en el archivo:\n");
System.out.print("=> ");
// Lectura de la cadena de caracteres en el array buffer y contabilización de los mismos
nbytes = System.in.read(buffer);
// Apertura del archivo por medio dell objeto fos, si el archivo no existe se crea este, y, si
// ya existe este, es abierto para que los datos se escriban al final del archivo, porque la
// opción añadir está habilitada como true
fos = new FileOutputStream("C:\\Users\\JoséDaniel\\bytes.txt", true);
// Escritura de la cadena de caracteres al archivo desde la posición 0 del array hasta el
// número de bytes (nbytes) que se encuentren almacenados
fos.write(buffer,0,nbytes);
}
catch(IOException e){
System.out.print("Error: " + e.toString());
System.exit(1);
}
finally{
try{
// Cierre del archivo
if(fos != null)
fos.close();
}
catch(IOException e){
System.out.print("Error: " + e.toString());
System.exit(1);
}
}
System.exit(0);
}
}

Pág. 6
Nota: Si al ejecutar el programa se presenta el siguiente error, significa que no tienes permiso para
escribir en esa parte de tu disco duro, en mi caso yo utilice la ruta C:\\bytes.txt, para ejemplificar el
error al ejecutar el programa:

Escriba el texto que desea almacenar en el archivo:


=> Hola

Error: java.io.FileNotFoundException: C:\\bytes.txt, (Acceso denegado)Java Result: 1


BUILD SUCCESSFUL (total time: 9 seconds)

Para solucionar el problema, debes de localizar una ruta en la que se te permita almacenar un
archivo, por ejemplo, yo lo solucione cambiando la ruta a C:\\Users\\JoséDaniel\\Desktop\\bytes.txt,
y al ejecutar el programa no se debe presentar dicho error; la ejecución debe verse como aparece
en el siguiente mensaje:

Escriba el texto que desea almacenar en el archivo:

=> Hola
BUILD SUCCESSFUL (total time: 9 seconds)

Como te habrás dado cuenta, hasta este punto ya has capturado el primer programa que te
permite almacenar información en un archivo, si deseas comprobar que tu archivo existe, utiliza el
explorador de archivos de Windows para localizar el archivo bytes.txt, y trata de abrir este
haciendo doble clic en el para ver su contenido en el bloc de notas; como se muestra en la
Figura 3.

Figura 3. Visualización del contenido del archivo bytes.txt a través del bloc de notas

Pág. 7
El siguiente programa, realiza la lectura del texto almacenado en el archivo
bytes.txt creado por el programa anterior, almacenando este en un arreglo
denominada buffer.

import java.io.FileInputStream;
import java.io.IOException;

public class CLeerBytes{


public static void main(String[ ] args){
// Declaración del objeto fis que se utiliza para la apertura y lectura de la información
// contenida en el archivo al arreglo
FileInputStream fis = null;
// Declaración de variables
byte[ ] buffer = new byte[81];
int nCar;
try{
// Apertura del archivo para lectura
fis = new FileInputStream("C:\\Users\\JoséDaniel\\Desktop\\bytes.txt");
// Lectura de la cadena de caracteres al arreglo de byte
nCar = fis.read(buffer, 0, 81);
// Conversión del arreglo de bytes a cadena de caracteres
String str = new String(buffer, 0, nCar);
// Impresión de la información almacenada en el archivo
System.out.println(str);
}
catch(IOException e){
System.out.println("Error: " + e.toString());
}
finally{
try{
if(fis != null)
fis.close();
}
catch(IOException e){
System.out.println("Error: " + e.toString());
}
}
}
}

Al ejecutarse el programa debe de mostrar el siguiente contenido:

Hola
BUILD SUCCESSFUL (total time: 0 seconds)

Pág. 8
El siguiente programa permite estar realizando la captura de más de una cadena
de caracteres, escribiendo a estas en el archivo, como una sola cadena de
caracteres.

import java.io.FileOutputStream;
import java.io.IOException;
import javax.swing.JOptionPane;

public class CEscribirBytes1 {

public static void main(String[] args) throws IOException {


// Declaración de variables
char resp = 'S';
String cadena = "";
Object opcion[] = {"Si", "No"};
// Declaración del objeto fos que se utiliza para la apertura y almacenamiento de la
// información al archivo
FileOutputStream fos = null;
try {
// Apertura del archivo por medio del objeto fos
fos = new FileOutputStream("C:\\Users\\JoséDaniel\\Desktop\\bytes1.txt", true);
while (resp == 'S') {
// Lectura de la cadena de caracteres
cadena = JOptionPane.showInputDialog(null, "Introduce la cadena de caracteres:",
"Entrada de datos", JOptionPane.INFORMATION_MESSAGE);
// Copiamos el texto leído en la variable cadena a un array de tipo byte
byte arrayByte[] = cadena.getBytes();
// Escritura de la cadena de caracteres al archivo en secuencias de bytes
fos.write(arrayByte);
int seleccion = JOptionPane.showOptionDialog(null,
"Deseas ingresar otro cadena?", "Mensaje...",
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE, null, opcion, "Si");
if (seleccion == 0) {
resp = 'S';
}
if (seleccion == 1) {
resp = 'N';
}
if (seleccion == -1) {
JOptionPane.showMessageDialog(null,
"Error usted no eligió Si o No.", "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
}
} catch (IOException e) {
JOptionPane.showMessageDialog(null, "Error: " + e.toString(), "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
System.exit(1);
} finally {
try {
// Cierre del archivo
if (fos != null) {

Pág. 9
fos.close();
}
} catch (IOException e) {
JOptionPane.showMessageDialog(null, "Error: " + e.toString(), "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
}
System.exit(0);
}
}

Una vez que ejecutaste el programa, localiza tu archivo donde almacenaste las
cadenas de caracteres en secuencias de byte y ábrelo con el bloc de notas; y
checa como fueron almacenadas las cadenas de caracteres.

Ahora, se construirá el proceso de lectura de las cadenas de caracteres


almacenadas, para ello captura el siguiente programa y ejecútalo.

Pág. 10
import java.io.FileInputStream;
import java.io.IOException;
import javax.swing.JOptionPane;

public class CLeerBytes1 {


public static void main(String[] args) {
// Declaración del objeto fis que se utiliza para la apertura y lectura de la información
// contenida en el archivo
FileInputStream fis = null;
String cadena = "";
try {
// Apertura del archivo para lectura
fis = new FileInputStream("C:\\Users\\JoséDaniel\\Desktop\\bytes1.txt");
// El método read() de FileReader, no tiene parámetros; pero devuelve un valor numérico
// que si le hacemos un casting a (char) este será legible por nosotros.
int dato = fis.read();
// Una vez obtenido el valor numérico a la variable dato, se inicia un ciclo while que
// permite estar comparando a la variable dato con el valor que devuelve el método
// read(), el ciclo se termina cuando se alcanza el final del archivo, o sea que cuando
// el método read() ha devuelto -1.
while(dato != -1){
// Se lee byte a byte la información contenida en el arreglo, convirtiendo esta
// a caracter y almacenandolo en la variable cadena
cadena += (char)dato;
dato = fis.read();
}
// Visualización del contenido de la variable cadena
JOptionPane.showMessageDialog(null, cadena, "Salida de datos",
JOptionPane.INFORMATION_MESSAGE);
} catch (IOException e) {
JOptionPane.showMessageDialog(null, "Error: " + e.toString(), "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
JOptionPane.showMessageDialog(null, "Error: " + e.toString(), "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
}
}
}
}

Como habrás notado, las cadenas de caracteres son visualizadas éstas de


acuerdo a como fueron almacenadas en el archivo, unidas todas. Si se desea que
cada cadena sea almacenada en forma individual en el archivo y no como una
gran cadena de caracteres, modifique el programa CEscribirBytes1; colocando la
siguiente línea de código, antes de copiar el texto leído en la variable cadena a un
array de tipo byte, como se muestra en el código escrito en azul:

Pág. 11
cadena += "\n";
byte arrayByte[] = cadena.getBytes();
// Escritura de la cadena de caracteres al archivo en secuencias de bytes
fos.write(arrayByte);

Vuelve a ejecutar tu programa, modificando el nombre del archivo de salida para


que veas la diferencia.

Pág. 12
Actividad 1: Modifique los programas de escritura y lectura de datos, para que
estos permitan escribir y leer los siguientes datos a, un, y desde un archivo:

Nombre
Sexo
Edad

Recuerde que se están utilizando las clases FileInputStream y


FileOutputStream, los valores se están almacenando de tipo byte, por lo cual se
deben de convertir y almacenar como una secuencia de bytes; como se muestra
en los siguientes datos almacenados:

Jose Daniel,Masculino,58
Oscar Daniel,Masculino,26

Pág. 13
Flujos de caracteres
Una vez que se ha trabajado con flujos de bytes, hacerlo con flujos de caracteres
es prácticamente lo mismo. Esto nos será útil cuando se necesite trabajar con
texto representado por un conjunto de caracteres ASCII. Las clases que definen
estos flujos son subclase de Reader, como FileWriter y FileReader.

FileWriter
Un flujo de la clase FileWriter permite escribir caracteres (char) en un archivo.
Además de los métodos que esta clase hereda de Writer, la clase proporciona los
constructores siguientes:

FileWriter(String nombre)
FileWriter(String nombre, boolean añadir)
FileWriter(File achivo)

El primer constructor abre un flujo de salida hacia el archivo especificado por


nombre, mientras que el segundo hace lo mismo, pero con la posibilidad de añadir
datos a un archivo existente (añadir = true); el tercero lo hace a partir de un objeto
File.

FileReader
Un flujo de la clase FileReader permite leer caracteres desde un archivo. Además
de los métodos que esta clase hereda de Reader, la clase proporciona los
constructores siguientes:

FileReader(String nombre);
FileReader(File archivo);

El primer constructor abre un flujo de entrada desde el archivo especificado por


nombre, mientras que el segundo lo hace a partir de un objeto File.

Pág. 14
Ejemplos:
El siguiente programa es una versión similar al del programa CEscribirBytes,
adaptado para escribir caracteres en lugar de bytes.

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Scanner;
import javax.swing.JOptionPane;

public class CEscribirCars {


public static void main(String[] args) throws IOException {
char caracter;
char resp = 'S';
String cadena;
Object opcion[] = {"Si", "No"};
FileWriter fw = null;
try {
fw = new FileWriter("C:\\Users\\JoséDaniel\\Desktop\\texto.txt", true);
while (resp == 'S') {
// Lectura del caracter a traves de una cadena de caracteres
cadena = JOptionPane.showInputDialog(null, "Caracter:","Entrada de datos",
JOptionPane.INFORMATION_MESSAGE);
// Se obtiene el primer caracter de la cadena
caracter = cadena.charAt(0);
// Se escribe el caracter obtenido de la cadena al archivo
fw.write(caracter);
int seleccion = JOptionPane.showOptionDialog(null,
"Deseas ingresar otro caracter?", "Mensaje...",
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE, null, opcion, "Si");
if (seleccion == 0) {
resp = 'S';
}
if (seleccion == 1) {
resp = 'N';
}
if (seleccion == -1) {
JOptionPane.showMessageDialog(null,
"Error usted no eligió Si o No.", "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
}
} catch (IOException e) {
JOptionPane.showMessageDialog(null,"Error: " + e.toString(), "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
} finally {
try {
if (fw != null) {
fw.close(); // Cierre del archivo
}
} catch (IOException e) {

Pág. 15
JOptionPane.showMessageDialog(null,"Error: " + e.toString(), "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
}
}
}
}

Pág. 16
Programa que permite leer los caracteres almacenados en el archivo de texto,
mostrando estos como una cadena de caracteres.

import java.awt.HeadlessException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import javax.swing.JOptionPane;

public class CLeerCars {


public static void main(String[] args) {
String datosChar = "";
FileReader fr = null;
File archivo;
archivo = new File("C:\\Users\\JoséDaniel\\Desktop\\texto.txt");
try {
//Abrir el archivo indicado en la variable archivo
fr = new FileReader(archivo);
// Leer el primer carácter del archivo y se debe almacenar en una variable de tipo int
int caracter = fr.read();
// Se recorre el archivo hasta encontrar el carácter -1 que indica el final del archivo
while (caracter != -1) {
// Se almacena cada caracter leido del archivo convertido a char en una cadena de
// caracteres
datosChar += (char) caracter;
// Se lee el siguiente carácter del archivo para ir recorriendo este
caracter = fr.read();
}
// Se visualiza el contenido almacenado en la cadena de caracteres
JOptionPane.showMessageDialog(null, datosChar, "Salida de datos",
JOptionPane.INFORMATION_MESSAGE);
} catch (FileNotFoundException e) {
//Operaciones en caso de no encontrar el archivo y mostrar el error producido por la
// excepción
JOptionPane.showMessageDialog(null, "Error: archivo no encontrado " + e.getMessage(),
"Mensaje de error", JOptionPane.INFORMATION_MESSAGE);
} catch (IOException | HeadlessException e) {
//Operaciones en caso de error general
JOptionPane.showMessageDialog(null, "Error de lectura del archivo" + e.getMessage(),
"Mensaje de error", JOptionPane.INFORMATION_MESSAGE);
} finally {
//Operaciones que se ejecutaran en cualquier caso. Si hay un error o no.
try {
//Cerrar el archivo si se ha abierto
if (fr != null) {
fr.close();
}
} catch (Exception e) {
JOptionPane.showMessageDialog(null, "Error al cerrar el archivo" + e.getMessage(),
"Mensaje de error", JOptionPane.INFORMATION_MESSAGE);
}
}
}
}

Pág. 17
De igual forma, si se tiene la necesidad de almacenar cadenas de caracteres de
manera independiente dentro del archivo, solo basta modificar en la línea de
lectura de datos por parte del usuario y en la línea de escritura hacia el archivo.

import java.io.FileWriter;
import java.io.IOException;
import javax.swing.JOptionPane;

public class CEscribirCars1 {

public static void main(String[] args) {


char resp = 'S';
String cadena;
Object opcion[] = {"Si", "No"};
FileWriter fw = null;
try {
fw = new FileWriter("C:\\Users\\JoséDaniel\\Desktop\\cadenas.txt", true);
while (resp == 'S') {
// Lectura del caracter a traves de una cadena de caracteres
cadena = JOptionPane.showInputDialog(null, "Ingresa una cadena de caracteres:",
"Entrada de datos...", JOptionPane.INFORMATION_MESSAGE);
// Se le concatena el salto de linea para escribir en el archivo las cadenas
// de manera independiente dentro de este
cadena += "\n";
fw.write(cadena);
int seleccion = JOptionPane.showOptionDialog(null,
"Deseas ingresar otra cadena de caracteres?", "Mensaje...",
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE, null, opcion, "Si");
if (seleccion == 0) {
resp = 'S';
}
if (seleccion == 1) {
resp = 'N';
}
if (seleccion == -1) {
JOptionPane.showMessageDialog(null,
"Error usted no eligió Si o No.", "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
System.exit(1);
}
}
} catch (IOException e) {
JOptionPane.showMessageDialog(null, "Error: " + e.toString(), "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
} finally {
try {
if (fw != null) {
fw.close(); // Cierre del archivo
}
} catch (IOException e) {
JOptionPane.showMessageDialog(null, "Error: " + e.toString(), "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
}

Pág. 18
}
}
}

Ahora para leer las cadenas de caracteres almacenadas en el archivo, utilice el


mismo programa que se utilizo para la lectura de caracteres (CLeerCars),
modificando solo la línea de código, en que se indica el nombre del archivo donde
fueron almacenadas las cadenas de caracteres, como se muestra a continuación :

archivo = new File("C:\\Users\\JoséDaniel\\Desktop\\cadenas.txt");

Actividad 2: Haciendo uso de la Actividad 1, modifique los programas de escritura


y lectura de datos, para que estos permitan escribir y leer los siguientes datos a,
un, y desde un archivo:

Nombre
Sexo
Edad

Recuerde que se están utilizando las clases FileReader y FileWriter, los valores
se están almacenando de tipo caracter, por lo cual se deben de convertir y
almacenar como una secuencia de caracteres; como se muestra en los siguientes
datos almacenados:

Jose Daniel,Masculino,58
Oscar Daniel,Masculino,26

Pág. 19
6.3 Operaciones básicas y tipos de acceso.
En ocasiones se desea almacenar en un archivo datos de tipos primitivos
(boolean, byte, doublé, float, long, int y short) para posteriormente recuperarlos
como tal. Para estos casos, el paquete java.io proporciona las clases
DataInputStream y DataOuputSteam, las cuales permiten leer y escribir,
respectivamente datos de cualquier tipo primitivo.

Un flujo DataInputStream sólo puede leer datos almacenados en un archivo a


través de un flujo DataOutputStream.

DataOutputStream
Un flujo de la clase DataOutputStream, deriva indirectamente de OutputStream,
permite a una aplicación escribir en un flujo de salida subordinado, datos de
cualquier tipo primitivo.

Todos los métodos proporcionados por esta clase están definidos en la interfaz
DataOutput implementada por la misma.

Los métodos más utilizados de esta clase se resumen en la Tabla 1.

Tabla 1. Métodos para la escritura de tipos primitivos utilizados por la clase DataOutputStream

Método Descripción
writeBoolean Escribe un valor de tipo boolean.
writeByte Escribe un valor de tipo byte.
writeBytes Escribe un String cómo una secuencia de bytes.
writeChar Escribe un valor de tipo char.
writeChars Escribe un String como una secuencia de caracteres.
writeShort Escribe un valor de tipo short.
writeInt Escribe un valor de tipo int.
writeLong Escribe un valor de tipo long.
writeFloat Escribe un valor de tipo float.
writeDouble Escribe un valor de tipo double.
Escribe una cadena de caracteres en formato UTF-8; los dos
writeUFT primeros bytes especifican el número de bytes de datos escritos a
continuación.

Pág. 20
DataInputStream
Un flujo de la clase DataInputStream, deriva indirectamente de InputStream,
permite a una aplicación leer de un flujo de entrada subordinado, datos de
cualquier tipo primitivo escritos por un flujo de la clase DataOutputStream.

Todos los métodos proporcionados por esta clase están definidos en la interfaz
DataInput implementada por la misma.

Los métodos más utilizados de esta clase se resumen en la Tabla 2.

Tabla 2. Métodos para la lectura de tipos primitivos utilizados por la clase DataInputStream

Método Descripción
readBoolean Devuelve un valor de tipo boolean.
readByte Devuelve un valor de tipo byte.
readShort Devuelve un valor de tipo short.
writeChar Devuelve un valor de tipo char.
readInt Devuelve un valor de tipo int.
readLong Devuelve un valor de tipo long.
readFloat Devuelve un valor de tipo float.
readDouble Devuelve un valor de tipo double.
Devuelve una cadena de caracteres en formato UTF- 8; los dos
readUFT primeros bytes especifican el número de bytes de datos que
serán leídos a continuación..

Después de lo expuesto hasta ahora, acerca del trabajo con archivos; habrá
observado que la metodología de trabajo se repite. Es decir, para escribir datos en
un archivo:

 Definimos un flujo hacia el archivo en el que deseamos escribir datos.


 Leemos los datos del dispositivo de entrada o de otro archivo y los
escribimos en nuestro archivo. Este proceso se hace normalmente registro
a registro. Para ello, utilizaremos los métodos proporcionados por la interfaz
del flujo.
 Cerramos el flujo.

Pág. 21
Para leer datos de un archivo existente:
 Abrimos un flujo desde el archivo del cual queremos leer los datos.
 Leemos los datos del archivo y los almacenamos en variables de nuestro
programa con el fin de trabajar con ellos. Este proceso se hace
normalmente registro a registro. Para ello, utilizaremos los métodos
proporcionados por la interfaz del flujo.
 Cerramos el flujo.

Esto pone de manifiesto que un archivo no es más que un medio permanente de


almacenamiento de datos, dejando esos datos disponibles para cualquier
programa que necesite manipularlos. Lógicamente, los datos serán recuperados
del archivo con el mismo formato con el que fueron escritos, de lo contrario los
resultados serán inesperados. Es decir, si los datos son guardados en el orden:
una cadena, otra cadena y un long, tendrán que ser recuperados en ese orden y
con este mismo formato. Sería un error recuperar primero un long, después una
cadena y finalmente otra cadena, o recuperar primero una cadena, después un
float y finalmente la otra cadena; etc. No lo permitiría el programa.

Archivos secuenciales
Cuando hablamos de archivos, habitualmente se utilizan cuatro términos: campo,
registro, archivo y base de datos. Se puede decir que estos términos forman una
estructura de datos agrupados y relacionados de alguna manera en particular
donde uno contiene al otro y que nos facilitan la manera de almacenarlos y
recuperarlos.

a) Un campo es el elemento de datos básico, como ejemplo: un campo


individual contiene un valor único, como el apellido de un empleado, una
fecha o un valor leído por un sensor. Lo más importante de un campo es su
longitud (que puede ser fija o variable) y el tipo de datos que contenga (ya
sea una cadena ASCII o un dígito decimal).

Pág. 22
b) Un registro (que se implementa como class en Java) es un conjunto de
campos relacionados que pueden tratarse como una unidad por algunos
programas de aplicación. Por ejemplo: un registro de nombre “empleado”
contendría campos tales como nombre, RFC, fecha de contratación, etc. Un
registro puede ser de longitud variable en el caso de que el número de
campos pueda variar, esto dependerá de su diseño.
c) Un Archivo es un conjunto de registros similares. Los usuarios y las
aplicaciones se refieren a él por un nombre que es único y puede crearse y
borrarse. Las restricciones al control de acceso suelen aplicarse a los
archivos. Es decir en un sistema compartido, el acceso de los usuarios y los
programas se garantiza o deniega a archivos completos, en otros casos se
aplica a los registros e incluso a los campos.
d) Una Base de datos es un conjunto de datos relacionados. El aspecto
fundamental es que está diseñada para ser usada por varias aplicaciones
diferentes. Puede contener toda la información relativa a una organización o
proyecto.

La forma más sencilla de almacenar un conjunto de registros en un archivo es


mediante la organización secuencial. En este tipo de archivos, los registros son
escritos consecutivamente cuando el archivo es creado, por lo tanto, deben ser
accedidos (leídos) de ese modo cuando se consultan.

La característica más importante de esta técnica de organización de archivos es


que solo permite el acceso secuencial, es decir, para acceder al registro k, se
deben recorrer los k - 1 registros anteriores (como si fuese un arreglo).

La ventaja más importante es la capacidad de acceder al “siguiente” registro


rápidamente, además de que son muy sencillos de usar y de aplicar. Si la
secuencia de acceso a registros en un archivo secuencial es conforme al
ordenamiento físico de los mismos, entonces los tiempos de acceso serán muy
buenos, sin embargo, si el acceso no está basado en el orden físico de los

Pág. 23
registros, entonces la eficiencia del programa puede ser terrible dando lugar a
tiempos de acceso muy altos, provocando una desventaja.

La clase DataOutputStream es útil para escribir datos del tipo primitivo de Java
en forma portable. Esta clase tiene un sólo constructor que toma un objeto de la
clase OutputStream o sus derivadas como parámetro. El papel del objeto
DataOutputStream es proporcionar acceso de alto nivel a un archivo convirtiendo
los valores primitivos de Java en una secuencia de bytes, que se escriben al
archivo utilizando un objeto de tipo FileOutputStream. A continuación en el
siguiente código se crea un flujo denominado flujoSalidaDatos.

File salida = new File("datos.dat");


FileOutputStream flujoSalida = new FileOutputStream(salida);
DataOutputStream flujoSalidaDatos = DataOutputStream(flujoSalida);

El código mostrado se puede simplificar en una sola línea:

DataOutputStream flujoSalidaDatos = new DataOutputStream(new


FileOutputStream("datos.dat"));

Como se puede observar, para crear el objeto, debemos pasar cómo parámetro un
FileInputStream para un DataInputStream y un FileOutputStream para un
DataOutputStream.

Pág. 24
Ejemplos:

Elabore un programa que almacene en un archivo de acceso secuencial el


inventario de una ferretería, para que el dueño de ésta esté informado de las
distintas herramientas que tiene, cuántas tiene de cada una y su costo. La
información a almacenar se muestra en la Tabla 3.

Tabla 3. Datos de una ferretería que se escribirán en un archivo de acceso secuencial.


Código Herramienta Cantidad Costo
1 Lijadora eléctrica 18 400.00
2 Martillo 128 90.45
3 Sierra caladora 16 155.20
4 Podadora de césped 10 928.70
5 Sierra eléctrica 8 1234.25
6 Destornillador 236 53.40
7 Mazo 32 234.25
8 Llave española 65 70.15

import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.swing.JOptionPane;
import java.io.File;

public class CArchSecEsc01 {


public static void main(String[] args) {
// Declaración del objeto
DataOutputStream dos = null;
// Declaración de variables para la lectura de los datos
String articulo;
int cantidad;
float costo;
char resp = 'S';
Object opcion[] = {"Si", "No"};
try {
dos = new DataOutputStream(new FileOutputStream(archivo, true));
while (resp == 'S') {
articulo = JOptionPane.showInputDialog(null, "Articulo", "Entrada de datos",
JOptionPane.INFORMATION_MESSAGE);
if (articulo.length() != 0) {
cantidad = Integer.parseInt(JOptionPane.showInputDialog(null, "Cantidad",
"Entrada de datos...", JOptionPane.INFORMATION_MESSAGE));
costo = Float.parseFloat(JOptionPane.showInputDialog(null, "Costo",
"Entrada de datos...", JOptionPane.INFORMATION_MESSAGE));
// Se escriben los datos al flujo de salida
dos.writeUTF(articulo);
dos.writeInt(cantidad);
dos.writeFloat(costo);

Pág. 25
// Los datos son pasados del buffer al medio de almacenamiento (archivo)
dos.flush();
int seleccion = JOptionPane.showOptionDialog(null,
"Deseas agregar un artículo más?", "Entrada de datos",
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE, null, opcion, "Si");
if (seleccion == 0) {
resp = 'S';
} else {
if (seleccion == 1) {
resp = 'N';
} else {
JOptionPane.showMessageDialog(null,
"Error usted no eligió Si o No.",
"Mensaje de error...", JOptionPane.ERROR_MESSAGE);
}
}
} else {
JOptionPane.showMessageDialog(null,
"Error el nombre del artículo no puede estar vacío",
"Mensaje de error", JOptionPane.ERROR_MESSAGE);
}
}
} catch (IOException e) {
JOptionPane.showMessageDialog(null, "Error: " + e.toString(), "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
} finally {
try {
if (dos != null) {
dos.close(); // Cierre del archivo
}
} catch (IOException e) {
JOptionPane.showMessageDialog(null, "Error: " + e.toString(), "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
}
}
}
}

Pág. 26
Una vez que se ha comprobado que el programa almacena los datos en un archivo,
procedemos a elaborar el programa que lea los datos del inventario de una ferretería del
archivo de acceso secuencial, para conocer las distintas herramientas que se tienen,
cuántas se tiene de cada una y su costo.

import java.io.DataInputStream;
import java.io.EOFException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import javax.swing.JOptionPane;
import java.io.File;

public class CArchSecLec01 {

public static void main(String[] args) {


File archivo = new File("C:\\Users\\JoséDaniel\\Desktop\\ferreteria.dat");
DataInputStream dis;
dis = null;
String salida = "Ferretería el tornillo suelto\n\n";
try {
dis = new DataInputStream(new FileInputStream(archivo));
while (true) {
// Se leen los datos del archivo
salida = salida + "Artículo: " + dis.readUTF()+ "\n" + "Cantidad: "
+ dis.readInt() + "\n" + "Precio: $ " + dis.readFloat() + "\n";
}
// Al terminar de leer el archivo, se genera una excepción al encontrar el final de éste y se
// visualiza el contenido del mismo en la excepción que se genero (EOFException e)
} catch (EOFException e) {
JOptionPane.showMessageDialog(null, salida, "Inventario",
JOptionPane.INFORMATION_MESSAGE);
//Operaciones en caso de no encontrar el archivo y mostrar el error producido por la excepción
}catch (FileNotFoundException e) {
JOptionPane.showMessageDialog(null, "Error: archivo no encontrado " + e.getMessage(),
"Mensaje de error", JOptionPane.INFORMATION_MESSAGE);
//Operaciones en caso de error general
}catch (IOException e) {
JOptionPane.showMessageDialog(null, "Error de lectura del archivo" + e.getMessage(),
"Mensaje de error", JOptionPane.INFORMATION_MESSAGE);
} finally {
// Operaciones que se ejecutarn en cualquier caso. Si existe un error o no.
try {
//Cerrar el archivo si se ha abierto
if (dis != null) {
dis.close();
}
} catch (Exception e) {
JOptionPane.showMessageDialog(null, "Error al cerrar el archivo" + e.getMessage(),
"Mensaje de error", JOptionPane.INFORMATION_MESSAGE);
}
}
}
}

Pág. 27
Continuando con los ejemplos, elabore el programa que almacene los datos de una
persona tales como: nombre, sexo, edad, peso, estatura y si dispone de auto.

import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.swing.JOptionPane;
import java.io.File;

public class CArchSecEsc02 {


public static void main(String[] args) {
// Declaración de los campos a escribir en el archivo
String nombre, sexo = "";
int edad;
float peso, estatura;
boolean disponeAuto = false;
// Declaración de variables y objetos
int seleccion;
char resp = 'S';
Object opcion[] = {"Si", "No"};
Object[] genero = {"Masculino", "Femenino"};
// Se crea el objeto dos para el flujo de salida
DataOutputStream dos = null;
File archivo = new File("C:\\Users\\JoséDaniel\\Desktop\\agenda.dat");
try {
dos = new DataOutputStream(new FileOutputStream(archivo, true));
while (resp == 'S') {
nombre = JOptionPane.showInputDialog(null, "Nombre",
"Entrada de datos...", JOptionPane.INFORMATION_MESSAGE);
if (nombre.length() != 0) {
Object eleccion = JOptionPane.showInputDialog(null, "Sexo",
"Entrada de datos…", JOptionPane.QUESTION_MESSAGE, null,
genero, genero[0]);
if (eleccion == null) {
JOptionPane.showMessageDialog(null,
"Error usted eligió Cancelar o Cerrar...", "Mensaje de error...",
JOptionPane.ERROR_MESSAGE);
} else {
if (eleccion == "Masculino") {
sexo = "Masculino";
} else {
sexo = "Femenino";
}
}
edad = Integer.parseInt(JOptionPane.showInputDialog(null, "Edad",
"Entrada de datos...", JOptionPane.INFORMATION_MESSAGE));
peso = Float.parseFloat(JOptionPane.showInputDialog(null, "Peso",
"Entrada de datos...", JOptionPane.INFORMATION_MESSAGE));
estatura = Float.parseFloat(JOptionPane.showInputDialog(null,
"Estatura", "Entrada de datos...",
JOptionPane.INFORMATION_MESSAGE));
seleccion = JOptionPane.showOptionDialog(null,
"Dispones de auto propio?", "Entrada de datos...",
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE, null, opcion, "Si");

Pág. 28
if (seleccion == 0) {
disponeAuto = true;
} else {
if (seleccion == 1) {
disponeAuto = false;
} else {
JOptionPane.showMessageDialog(null,
"Error usted no eligió Si o No.",
"Mensaje de error...", JOptionPane.ERROR_MESSAGE);
}
}
// Se escriben los datos al flujo de salida
dos.writeUTF(nombre);
dos.writeUTF(sexo);
dos.writeInt(edad);
dos.writeFloat(peso);
dos.writeFloat(estatura);
dos.writeBoolean(disponeAuto);
// Los datos son pasados del buffer al archivo
dos.flush();
seleccion = JOptionPane.showOptionDialog(null,
"Deseas agregar más datos?", "Entrada de datos...",
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE, null, opcion, "Si");
if (seleccion == 0) {
resp = 'S';
} else {
if (seleccion == 1) {
resp = 'N';
} else {
JOptionPane.showMessageDialog(null,
"Error usted no eligió Si o No.",
"Mensaje de error...", JOptionPane.ERROR_MESSAGE);
}
}
} else {
JOptionPane.showMessageDialog(null,
"Error el nombre de la persona no puede estar vacío....",
"Mensaje de error...", JOptionPane.ERROR_MESSAGE);
}
}
} catch (IOException e) {
JOptionPane.showMessageDialog(null, "Error: " + e.toString(), "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
} finally {
try {
if (dos != null) {
dos.close(); // Cierre del archivo
}
} catch (IOException e) {
JOptionPane.showMessageDialog(null, "Error: " + e.toString(), "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
}
}
}
}

Pág. 29
Para mostrar el contenido del archivo creado por la aplicación anterior, vamos a
escribir otro programa con la clase CArchSecLec02.

import java.io.DataInputStream;
import java.io.EOFException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import javax.swing.JOptionPane;
import java.io.File;

public class CArchSecLec02 {


public static void main(String[] args) {
File archivo = new File("C:\\Users\\JoséDaniel\\Desktop\\agenda.dat");
DataInputStream dis;
dis = null;
String salida = "Datos almacenados en la agenda\n\n";
try {
dis = new DataInputStream(new FileInputStream(archivo);
while (true) { // Se leen los datos del archivo
salida = salida + "Nombre: " + dis.readUTF() + "\n" + "Sexo: " + dis.readUTF()
+ "\n" + "Edad: " + dis.readInt() + "\n" + "Peso: " + dis.readFloat()
+ "\n" + "Estatura: " + dis.readFloat() + "\n" + "Dispone de auto: "
+ dis.readBoolean() + "\n\n";
}
// Cuando se llega al final del archivo, se lanza una excepción llamada EOFException y se
// visualiza el contenido de éste
}catch (EOFException e) {
JOptionPane.showMessageDialog(null, salida, "Salida de datos",
JOptionPane.INFORMATION_MESSAGE);
//Operaciones en caso de no encontrar el archivo y mostrar el error producido por la excepción
}catch (FileNotFoundException e) {
JOptionPane.showMessageDialog(null, "Error: archivo no encontrado " + e.getMessage(),
"Mensaje de error", JOptionPane.INFORMATION_MESSAGE);
//Operaciones en caso de error general
}catch (IOException e) {
JOptionPane.showMessageDialog(null, "Error de lectura del archivo" + e.getMessage(),
"Mensaje de error", JOptionPane.INFORMATION_MESSAGE);
}finally {
// Operaciones que se ejecutarn en cualquier caso. Si existe un error o no.
try {
//Cerrar el archivo si se ha abierto
if (dis != null) {
dis.close();
}
} catch (Exception e) {
JOptionPane.showMessageDialog(null, "Error al cerrar el archivo" + e.getMessage(),
"Mensaje de error", JOptionPane.INFORMATION_MESSAGE);
}
}
}
}

Pág. 30
Hasta este punto hemos visto como agregar y visualizar registros de un archivo de acceso
secuencial, el proceso de eliminación o dar de baja un registro en un archivo secuencial,
implica tener que estar procesando dos archivos a la vez, el segundo de ellos es un
archivo temporal, un algoritmo de eliminación física quedaría como sigue:

1. Abrir el archivo original en modo lectura (el que contiene los datos).
2. Abrir un archivo llamado temporal en modo escritura (donde se escriben los datos).
3. Indicar el dato del campo, por el cual se busca el registro (debe ser un dato único).
4. Iniciar un ciclo de lectura del archivo original.
a) Dentro del ciclo leer un registro del archivo original.
b) Comparar el dato del registro leído, con el dato del campo proporcionado, si los
datos no son exactamente iguales, se escribe el registro leído al archivo
temporal. Y si los datos son iguales no se hace nada, continuando en ambos
casos con la lectura de los registros siguientes hasta encontrar el final del
archivo.
c) Fin de ciclo (cerrar el ciclo).
4. Cerrar ambos archivos (método close()).
5. Eliminar el archivo original (archivoOriginal.delete()).
6. Renombrar el archivo temporal con el nombre de archivo original, como se muestra
en la instrucción: archivoTemporal.renameTo(archivoOriginal).

El punto 5 y 6 debe realizarse desde el programa en ejecución en Java, para ello ten
presente que las rutas del archivo original y archivo temporal sean válidos y que tengas
permisos de lectura y escritura.

Este mismo algoritmo, se utiliza para la modificación de un registro en particular, solo que
en el inciso b, la condición debe cambiar, a que el dato del campo debe ser exactamente
igual al dato almacenado en el registro, para proceder a la modificación del dato de los
campos y escribir los registros en el archivo temporal.

Pág. 31
Actividad 3:
Una agencia inmobiliaria dispone de una serie de casas en oferta. Estas casas
pueden ser viviendas en zonas residenciales, o zonas populares. En cualquier
caso, se guarda la dirección de la casa, el número de metros cuadrados, una
descripción sobre su contenido y estado de la casa, precio. Los clientes que
solicitan casas a la inmobiliaria dejan sus datos como: RFC, nombre, teléfono, y
por cada visita que soliciten, se almacena la fecha y la impresión del cliente sobre
la casa. Elabore un programa que presente un menú principal, para que la
inmobiliaria realice las siguientes operaciones:

1. Agregar: debe agregar los datos al archivo, para ello es necesario que a tu
registro le adiciones un campo que tenga un dato único (número de visita),
ya que el dato del RFC no podría ser único, debido a que un cliente puede
tener más de una visita para la adquisición de una vivienda. En este caso
tienes que implementar un método que reciba como parámetro el número
de visita, y recorrer cada uno de los registros en el archivo; para verificar
que no exista y poder escribir los datos en el archivo, en caso contrario no
permitir la escritura.
2. Consultar: debe realizar la visualización de los datos de un registro en
particular, para ello se tendría que solicitar el número de visita, si este es
localizado dentro del archivo se debe mostrar la información, en otro caso
emitir un error.
3. Eliminar: para eliminar un registro del archivo, se debe de solicitar el
número de visita, si este es localizado dentro del archivo se procede a la
eliminación del registro, en caso de no encontrar el registro que se está
localizando, y se ha alcanzado el final del archivo, se debe emitir un error.
4. Modificar: para modificar un registro del archivo, se debe de solicitar el
número de visita, si este es localizado dentro del archivo se procede a la
modificación de los datos del registro, almacenando el nuevo registro en el
archivo temporal, en caso de no encontrar el registro que se está
localizando, y se ha alcanzado el final del archivo, se debe emitir un error.

Pág. 32
5. Visualizar: debe visualizar el contenido del archivo.
6. Salir: Finaliza el programa.

Cada una de las opciones que se han de realizar debes de implementarlas en


métodos o archicos por separado. Además, toma en cuenta el algoritmo señalado
anteriormente para el proceso de eliminación o modificación, ya que no existe
ninguna instrucción en el lenguaje Java que permita realizar las operaciones antes
citadas.

Pág. 33
Archivo de acceso directo.
Se dice que un archivo es de acceso u organización directa cuando para acceder
a un registro n cualquiera no se tiene que pasar por los n -1 registros anteriores.
Por esta característica son de acceso mucho más rápidos que los archivos
secuenciales.

Aunque lo anterior no quiere decir que son mejores que los secuenciales, es decir
es el propio problema planteado quien exigirá una solución u otra.

Una característica de los archivos de acceso directo es que sus registros sean de
un tamaño fijo o predeterminado.

Los archivos de este tipo pertenecen a la clase RandomAccessFile. La cual tiene


dos constructores:

RandomAccessFile(File file, String modo)


RandomAccessFile(String name, String modo)

Donde el modo puede ser:

 "r" (Lectura)
 "w" (Escritura)
 "rw" (Lectura y escritura y si ya existe, sobrescribe)

Esta clase contiene muchos métodos, algunos de las cuales son los siguientes:

 void close() cierra el flujo de datos y libera los recursos de sistema


asociados
 long getFilePointer() retorna la posición actual del puntero interno del
archivo
 long length() retorna la longitud en bytes del archivo

Pág. 34
 void seek(long pos) coloca el puntero interno en la posición pos, medida
desde el principio del archivo, para la próxima lectura o escritura.
 int skipBytes(int n) salta n bytes.

Escritura en archivos directos


Algunos métodos de grabación son:

 void writeChar(int v) graba un dato de tipo char (2 bytes).


 void writeChars(String s) graba un string como una secuencia de
caracteres (2 bytes para cada carácter)
 void writeInt(int v) graba un dato de tipo entero (4 bytes).
 void writeDouble(double v) graba un dato de tipo doble (8 bytes).

Ejemplo: Programa que realiza la escritura en un archivo de acceso directo.

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import javax.swing.JOptionPane;

public class EscribeArchivoDirecto {


public static void main(String[] args) throws IOException {
// Declaración de variables
int seleccion;
char resp = 'S';
Object opcion[] = {"Si", "No"};
long tamReg = 58; // tamaño de registro
// Declaración de los campos a escribir en el archivo
int clave, edad;
String nombre = "";
RandomAccessFile raf;
raf = null;
// Creando el objeto file con la ruta del archivo
File file = new File("C:\\Users\\JoséDaniel\\Desktop\\datos.dat");
try {
raf = new RandomAccessFile(file, "rw");
while (resp == 'S') {
clave = Integer.parseInt(JOptionPane.showInputDialog(null,
"Clave del registro", "Entrada de datos...",
JOptionPane.INFORMATION_MESSAGE));
nombre = JOptionPane.showInputDialog(null, "Nombre",
"Entrada de datos...", JOptionPane.INFORMATION_MESSAGE);
if (nombre.length() < 25) { // Dejando String en 25 caracteres
for (int i = nombre.length(); i < 25; i++) {

Pág. 35
nombre = nombre + " ";
}
} else {
nombre = nombre.substring(0, 25);
}
edad = Integer.parseInt(JOptionPane.showInputDialog(null, "Edad",
"Entrada de datos...", JOptionPane.INFORMATION_MESSAGE));
if (raf.length() != 0) {
// Se escribe en el archivo en forma secuencial, siendo este de acceso directo
// Se posiciona al final del último registro
raf.seek(raf.length());
}
//archi.seek(tamReg * (clave - 1));
raf.writeInt(clave);
raf.writeChars(nombre);
raf.writeInt(edad);
seleccion = JOptionPane.showOptionDialog(null,
"Deseas agregar más datos?", "Entrada de datos...",
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE, null, opcion, "Si");
if (seleccion == 0) {
resp = 'S';
} else {
if (seleccion == 1) {
resp = 'N';
} else {
JOptionPane.showMessageDialog(null,
"Error usted no eligió Si o No.",
"Mensaje de error...", JOptionPane.ERROR_MESSAGE);
}
}
}
} catch (NumberFormatException e) {
JOptionPane.showMessageDialog(null,
"Error dato no válido, este debe ser numérico...",
"Mensaje de error...", JOptionPane.ERROR_MESSAGE);
} catch (FileNotFoundException e) {
JOptionPane.showMessageDialog(null,
"Error la ruta o el nombre del archivo no fue localizado...",
"Mensaje de error...", JOptionPane.ERROR_MESSAGE);
} finally {
raf.close();
}
}
}

En este ejemplo, como usted podrá notar cuando se visualice el contenido del
archivo, los registros están guardados en forma secuencial, debido a que en el
código existe la condición que el puntero de registros sea colocado siempre al final
del archivo, por lo tanto la escritura del registro se hace al final del mismo; dicho
código que realiza tal acción es:

Pág. 36
if (raf.length() != 0) {
raf.seek(raf.length()); // Se posiciona el puntero de registros al final
// del archivo
}

En estos caso se recomienda que las claves que identifican al registro como único
sigan la secuencia 1, 2, 3, 4, 5,…, N, lo cual es ilógico obligar a un usuario
capturar dicha secuencia. Por lo tanto se tendría que buscar otra solución.

Ten presente que un archivo de acceso directo tiene un tamaño de registro fijo y
es importante que dicho tamaño se respete (por ese motivo se ajusta la variable
nombre), para el caso de las variables Strings dentro del código se están
ajustando a 25 caracteres, si el String es más corto que dicho tamaño se tendrá
que ampliar con caracteres en blanco ” ”, si el tamaño es más grande el String se
tendrá que recortar con el método substring(), como se muestra en el programa
de ejemplo.

También es importante recordar que Java grabará cada carácter de la cadena


String usando dos (2) bytes, es decir que el registro ejemplo quedará grabado en
disco en 58 bytes: 50 para la cadena String y 4 bytes por cada entero. Es
importante conocer el tamaño de registros grabados en disco porque esta
información se usará en algunas funciones.

String nombre (25 * 2 = 50 bytes)


int clave (4 bytes)
int edad (4 bytes)

Por lo tanto, el tamaño del registro para este ejemplo es de 58 bytes, y es


importante que tu recuerdes cuantos bytes necesita cada tipo de dato primitivo
(materia, fundamentos de programación).

Pág. 37
Lectura en archivos directos
Algunos métodos de lectura son:

 double readDouble() lee un dato de tipo double.


 float readFloat() lee un dato de tipo float.
 int readInt() lee un dato de tipo entero.
 char readChar() lee un dato tipo char (de a un caracter).

Ejemplo: Programa que realiza la lectura de datos en un archivo de acceso


directo.

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import javax.swing.JOptionPane;

public class LeerArchivoDirecto {


public static void main(String[] args) throws IOException {
int clave = 0;
String nombre = "";
int edad = 0;
long tamReg = 58; // tamaño de registro
long cantReg = 0; // cantidad de registros
String salida = "Datos almacenados en el archivo datos.dat\n\n";
File file;
file = new File("C:\\Users\\JoséDaniel\\Desktop\\datos1.dat");
RandomAccessFile raf;
raf = null;
try {
raf = new RandomAccessFile(file, "r");
//Calculando cantidad de registros
cantReg = raf.length() / tamReg;
for (int r = 0; r < cantReg; r++) {
clave = raf.readInt();
// Leyendo String
for (int i = 0; i < 25; ++i) {
nombre += raf.readChar();
}
edad = raf.readInt();
salida = salida + "Clave: " + clave + "\n"
+ "Nombre: " + nombre + "\n" + "Edad: " + edad + "\n\n";
// Inicializa la variable nombre o java encadena con la siguiente
nombre = "";
}
JOptionPane.showMessageDialog(null, salida, "Salida de datos",
JOptionPane.INFORMATION_MESSAGE);

Pág. 38
raf.close(); // Cierre del archivo
} catch (FileNotFoundException e) {
JOptionPane.showMessageDialog(null,
"Error la ruta o el nombre del archivo no fue localizado...", "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
} catch (IOException ioe) {
JOptionPane.showMessageDialog(null,
"Error al tratar de leer del archivo...",
"Mensaje de error", JOptionPane.ERROR_MESSAGE);
} finally {
raf.close();
}
}
}

Primero, lo que se realiza en el programa es conocer la cantidad de registros que


se encuentran almacenados en disco, para esto primero se ocupan dos variables
de tipo long: cantReg y tamReg, la cantidad de registros se obtiene con la
operación y método de la clase RamdomAccessFile siguiente:

cantReg = raf.length() / tamReg;

Es así como se obtiene la cantidad de registros almacenados, y ya con esta


información se construye un ciclo for para empezar a recorrer registro a registro en
el archivo.

Toma en cuenta que para leer el String se está usando un ciclo de lectura de 25
caracteres y recuerda que al final se debe poner el String en nulo = ”” porque sino
en la siguiente lectura se tendrá un String concatenado al siguiente String.

Una vez explicado el funcionamiento de un archivo de acceso directo, las clases


que intervienen para escribir y leer en el, asi como los métodos que se utilizan
para el desplazamiento del puntero dentro del archivo; continuemos haciendo la
modificación necesaria al programa EscribeArchivoDirecto, para que este funcione
de acuerdo a lo que es un archivo de acceso directo; para ello crea un nuevo
proyecto llamado EscribeArchivoDirecto1, cambia el nombre del archivo de salida
de datos a (datos1.dat).

Pág. 39
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import javax.swing.JOptionPane;

public class EscribeArchivoDirecto1 {


public static void main(String[] args) throws IOException {
// Declaración de variables
int seleccion;
char resp = 'S';
Object opcion[] = {"Si", "No"};
long tamReg = 58, tamArchivo = 0, tamBytes = 0; // tamaño de registro y archivo
// Declaración de los campos a escribir en el archivo
int clave, edad, claveAlm;
String nombre = "";
// Creando el objeto file con la ruta del archivo
File file = new File("C:\\Users\\JoséDaniel\\Desktop\\datos1.dat");
RandomAccessFile raf;
raf = null;
try {
raf = new RandomAccessFile(file, "rw");
while (resp == 'S') {
clave = Integer.parseInt(JOptionPane.showInputDialog(null,
"Clave del registro", "Entrada de datos...",
JOptionPane.INFORMATION_MESSAGE));
nombre = JOptionPane.showInputDialog(null, "Nombre",
"Entrada de datos...", JOptionPane.INFORMATION_MESSAGE);
if (nombre.length() < 25) { // Dejando String en 25 caracteres
for (int i = nombre.length(); i < 25; i++) {
nombre = nombre + " ";
}
} else {
nombre = nombre.substring(0, 25);
}
edad = Integer.parseInt(JOptionPane.showInputDialog(null, "Edad",
"Entrada de datos...", JOptionPane.INFORMATION_MESSAGE));
if (raf.length() > 0) {
tamArchivo = raf.length(); // Tamaño del archivo
tamBytes = tamReg * (clave - 1); // numero de bytes a desplazarse
if (tamBytes < tamArchivo) {
// Posicionamos el puntero de registros al incio del archivo
raf.seek(0);
// Se posiciona el puntero de registros en base al numero de bytes calculados
raf.seek(tamReg * (clave - 1));
// Se recupera el valor de la clave del registro almacenado.
claveAlm = raf.readInt();
if (clave != claveAlm) {
// Posicionamos el puntero de registros al incio del archivo
raf.seek(0);
// Se posiciona el puntero de registros en el lugar donde se almaceranan
// los datos
raf.seek(tamReg * (clave - 1));
// Se escriben los datos al archivo
raf.writeInt(clave);
raf.writeChars(nombre);

Pág. 40
raf.writeInt(edad);
} else {
JOptionPane.showMessageDialog(null,
"Error la clave ya existe en el archivo",
"Mensaje de error", JOptionPane.ERROR_MESSAGE);
}
} else {
// Posicionamos el puntero de registros al incio del archivo
raf.seek(0);
// Se posiciona el puntero de registros en el lugar donde se almaceranan los datos
raf.seek(tamReg * (clave - 1));
// Se escriben los datos al archivo
raf.writeInt(clave);
raf.writeChars(nombre);
raf.writeInt(edad);
}
} else {
// Se posiciona el puntero de registros en el lugar donde se almaceranan los datos
raf.seek(tamReg * (clave - 1));
// Se escriben los datos al archivo
raf.writeInt(clave);
raf.writeChars(nombre);
raf.writeInt(edad);
}
seleccion = JOptionPane.showOptionDialog(null,
"Deseas agregar más datos?", "Entrada de datos...",
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE, null, opcion, "Si");
if (seleccion == 0) {
resp = 'S';
} else {
if (seleccion == 1) {
resp = 'N';
} else {
JOptionPane.showMessageDialog(null,
"Error usted no eligió Si o No.",
"Mensaje de error...", JOptionPane.ERROR_MESSAGE);
}
}
}
raf.close(); // Cierre del archivo
} catch (NumberFormatException e) {
JOptionPane.showMessageDialog(null,
"Error dato no válido, este debe ser numérico...",
"Mensaje de error...", JOptionPane.ERROR_MESSAGE);
} catch (FileNotFoundException e) {
JOptionPane.showMessageDialog(null,
"Error la ruta o el nombre del archivo no fue localizado...",
"Mensaje de error...", JOptionPane.ERROR_MESSAGE);
} finally {
raf.close();
}
}
}

Pág. 41
Una vez modificado y ejecutado el programa, podras darte cuenta que los
registros en el archivo ocupan su posición en base a la clave proporcionada que
permite el desplazamiento de bytes dentro del archivo. Por ejemplo, suponga los
siguientes datos:

Clave: 5
Nombre: Juan
Edad: 23

Clave: 3
Nombre: Adriana
Edad: 21

Clave: 6
Nombre: Itzel
Edad: 22

Una vez que se ha terminado de introducir los datos antes propuestos, ejecute
nuevamente el programa LeerArchivoDirecto, el cual permite realizar la lectura de
un archivo de acceso directo, para lo cual no se te olvide modificar el nombre del
archivo a leer (datos1.dat) mostrando en pantalla lo siguiente:

Pág. 42
Como te habras dado cuenta, los registros que haz introducido ocupan su posición dentro
del archivo, no importando el orden en que se hayan metido estos, y posiblemente
preguntándote el porque existen otros registros en blanco; recuerda que en un archivo de
acceso directo, se tiene que desplazar el puntero de registro a la posición donde se desea
escribir la información. En este caso cuando se introdujo el registro de clave 5, también se
metieron los cuatro registros anteriores al de la clave 5, sin información.

Ahora bien, cuando se desea mostrar los registros almacenados en un archivo de acceso
directo, como el que se esta utilizando, pero no mostrando los registros en blanco,
modifiquemos el programa LeerArchivoDirecto para que solo muestre aquellos que
contienen información.

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import javax.swing.JOptionPane;

public class LeerArchivoDirecto1 {


public static void main(String[] args) throws IOException {
int clave = 0;
String nombre = "";
int edad = 0;
long tamReg = 58; // tamaño de registro
long cantReg = 0; // cantidad de registros
String salida = "Datos almacenados en el archivo datos1.dat\n\n";
File file;
file = new File("C:\\Users\\JoséDaniel\\Desktop\\datos1.dat");
RandomAccessFile raf;
raf = null;
try {
raf = new RandomAccessFile(file, "r");
//Calculando cantidad de registros
cantReg = raf.length() / tamReg;
for (int r = 0; r < cantReg; r++) {
clave = raf.readInt();
// Leyendo String
for (int i = 0; i < 25; ++i) {
nombre += raf.readChar();
}
edad = raf.readInt();
if (clave != 0) {
salida = salida + "Clave: " + clave + "\n"
+ "Nombre: " + nombre + "\n" + "Edad: " + edad + "\n\n";
}
// Inicializa la variable nombre a null
nombre = "";
}
JOptionPane.showMessageDialog(null, salida, "Salida de datos",

Pág. 43
JOptionPane.INFORMATION_MESSAGE);
raf.close(); // Cierre del archivo
} catch (FileNotFoundException e) {
JOptionPane.showMessageDialog(null,
"Error la ruta o el nombre del archivo no fue localizado...", "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
} catch (IOException ioe) {
JOptionPane.showMessageDialog(null,
"Error al tratar de leer del archivo...",
"Mensaje de error", JOptionPane.ERROR_MESSAGE);
} finally {
raf.close();
}
}
}

Pág. 44
Búsqueda en archivos directos
Cuando se desea desplegar un y solo un registro de información, proporcionando
un dato de búsqueda (generalmente la clave del registro), se observa la gran
diferencia con los archivos secuenciales. No es necesario leer todo el archivo.
Dado que al tener una clave numérica el registro, éste es localizado directamente
en el archivo, aquí se ve la importancia de grabar una clave numérica.

Ejemplo: Programa que realiza la búsqueda de un registro en un archivo de


acceso directo, a partir de la clave proporcionada.

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import javax.swing.JOptionPane;

public class BuscaArchivoDirecto {


public static void main(String[] args) throws IOException {
// Declaración de variables
int seleccion;
char resp = 'S';
Object opcion[] = {"Si", "No"};
int clave = 0, edad = 0;
String nombre = "";
long tamReg = 58; // Tamaño de registro
long cantReg = 0; // cantidad de registros
File file = new File("C:\\Users\\JoséDaniel\\Desktop\\datos1.dat");
RandomAccessFile raf;
raf = null;
try {
raf = new RandomAccessFile(file, "r");
while (resp == 'S') {
clave = Integer.parseInt(JOptionPane.showInputDialog(null,
"Clave del registro", "Entrada de datos...",
JOptionPane.INFORMATION_MESSAGE));
// Calculamos la cantidad de registro
cantReg = raf.length() / tamReg;
if (clave <= cantReg) {
// Posicionamos el puntero de registros al incio del archivo
raf.seek(0);
// Se posiciona el puntero de registros en base al numero de bytes calculados
raf.seek(tamReg * (clave - 1));
clave = raf.readInt();
if (clave != 0) {
// Leyendo String
for (int i = 0; i < 25; ++i) {
nombre += raf.readChar();
}
edad = raf.readInt();

Pág. 45
JOptionPane.showMessageDialog(null, "Clave: " + clave + "\n"
+ "Nombre: " + nombre + "\n" + "Edad: " + edad, "Salida de datos",
JOptionPane.INFORMATION_MESSAGE);
} else {
JOptionPane.showMessageDialog(null, "El registro se encuentra vacío",
"Salida de datos", JOptionPane.INFORMATION_MESSAGE);
}
// Inicializa la variable nombre o java encadena con la siguiente
nombre = "";
} else {
JOptionPane.showMessageDialog(null, "Error la clave no existe",
"Mensaje de error", JOptionPane.ERROR_MESSAGE);
}
seleccion = JOptionPane.showOptionDialog(null,
"Deseas realiza otra búsqueda?", "Entrada de datos...",
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE, null, opcion, "Si");
if (seleccion == 0) {
resp = 'S';
} else {
if (seleccion == 1) {
resp = 'N';
} else {
JOptionPane.showMessageDialog(null,
"Error usted no eligió Si o No.",
"Mensaje de error...", JOptionPane.ERROR_MESSAGE);
}
}
}
raf.close(); // Cierre del archivo
} catch (FileNotFoundException e) {
JOptionPane.showMessageDialog(null,
"Error la ruta o el nombre del archivo no fue localizado...", "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
} catch (IOException ioe) {
JOptionPane.showMessageDialog(null,
"Error al tratar de leer del archivo...",
"Mensaje de error", JOptionPane.ERROR_MESSAGE);
} finally {
raf.close();
}
}
}

Pág. 46
Baja o eliminación de registros en archivos de acceso directo.
En archivos de acceso directo no se debe eliminar físicamente el o los registros de
los archivos, dado que la clave del registro esta enlazada directamente a la
posición que dicho registro tiene en disco.

Por otra parte, si se elimina físicamente un registro, no hay manera de recuperar


esa información posteriormente.

Es por ello que una de las tecnicas comunes es la eliminación o la baja lógica.
Esta consiste en incluir un campo de estado (status, bandera o semáforo) en el
registro, y antes de mandarlo a disco se le agrega a dicho campo el carácter 'A'
(alta), y cuando se quiere dar de baja, solo se pondría dicho campo en 'B', o bien
utilizar un campo de tipo booleano, true para dar de alta y false para dar de baja; y
todos los programas de lectura, búsqueda y filtros deberán revisar este campo
antes de hacer algo con el registro.

Otra técnica para la eliminación de un registro en forma definitiva es colocar toda


la información a nula, esto quiere decir que si son campos numéricos el valor que
se le asigna es 0, si es un caracter a un valor ( '' ) que indica el vacío o si son
cadenas de caracteres a un valor ( "" ). Por ejemplo: clave = 0, nombre = "", edad
= 0, y suponiendo que estuviese presente el campo sexo, y su dato solo fuese un
carácter (M/F), este seria de la siguiente forma sexo = '', o si fuese un campo
booleano a false.

Pág. 47
Actividad 4.
1. Modifique el archivo EscribeArchivoDirecto1, agregando a este el campo
status, para que cuando se guarde un registro al archivo se coloque el
carácter 'A', que significa que esta dado de alta; recuerda que el tamaño del
registro ya no va ser el mismo, debido a que se esta colocando un nuevo
campo; por lo tanto este cambiara. Aprovechando la modificación, valide que
el campo clave no debe aceptar valores negativos o iguales a cero, el campo
nombre no puede ir vacio, el campo edad debe estar en un rango de (1 –
120) y el campo status no se debe capturar, este se debe escribir al archivo
cuando se vacian los demás datos, con el valor de 'A', que significa que se
esta dando de alta el registro.
2. Elabore el programa EliminaRegistroDirecto, que permita realizar la
eliminación o la baja lógica de un registro del archivo, para ello debe solicitar
la clave del registro que se desea eliminar, (apoyate en el programa
BuscaArchivoDirecto) una vez que localizas el registro, se debe de leer los
datos de este y presentar al usuario, preguntando si se desea eliminar, si la
respuesta es afirmativa se vuelve a escribir el registro, pero con el status =
'B', que significa que el registro esta dado de baja. Para esta operación vas a
utilizar el método: getFilePointer(), una vez que haz modificado el status y
antes de almacenar nuevamente el registro, vas a realizar lo siguiente:

raf.seek(raf.getFilePointer() - tamReg);

Esta instrucción, lo que permite es regresar el apuntador de registros a la


posición de donde se leyó el registro, y a continuación se realiza la escritura
de cada uno de los campos al archivo.

3. Modifique el programa LeerArchivoDirecto1, que permite visualizar los


registros que tengan información, pero que además no estén dados de baja.
4. Modificar el programa BuscaArchivoDirecto, para que realice la búsqueda de
un registro en un archivo de acceso directo, a partir de la clave
proporcionada, y si este es encontrado, se debe de mostrar siempre y
Pág. 48
cuando el status sea 'A', es decir que está dado de alta, o, emitir un mensaje
indicando que esta dado de baja.

Nota: puedes realizar los programas en forma independiente o implementar


módulos y un programa con un menú que interactue con estos. Además debes
tomar en cuenta cuando tu archivo en donde vas almacenar la información, es de
tipo o modo: "r" (lectura), "w" (escritura) o "rw" (lectura y escritura).

Actividad 5.
Una vez concluida la Actividad 4, utilice los archivos o métodos realizados de
acuerdo a como usted haya trabajado, para implementar lo siguiente:

a) Elabore un programa o módulo que interactue con un archivo de acceso


directo, llamado producto.dat, que contenga registros de productos con los
campos: código, nombre, stock, precio, categoría y estado. El campo
código es la clave única del registro, el cual no puede duplicarse, la
categoría puede ser: 'M' = Medicinal, 'P' = Perfumería, 'A' = Abarrotes, 'E' =
Electronica y 'O' = otros; el campo estado se refiere a que va a estar dado
de alta cuando se ingresa este con el valor de 'A'.
b) Elabore un programa o módulo que permita leer y desplegar el archivo de
productos.
c) Elabore un programa o módulo que realice el filtrado del archivo de
productos por categoría. El usuario debe de elegir la categoria, escribiendo
sólo los registros de dicha categoría hacia otro archivo (no escribir los que
estén dados de baja).
d) Elabore un programa o módulo que aumente un 5% los precios de los
productos de una categoría, el usuario debe elegir esta (no modificar los
que estén dados de baja).
e) Elabore un programa o módulo que permita modificar los datos de un
producto, ingresando para ello, el código correspondiente, no permita

Pág. 49
modificar el código del producto. Si el registro está dado de baja, sólo
mostrar un mensaje indicando el estado de este.

Pág. 50
6.4 Manejo de objetos persistentes.
Hasta esta parte hemos aprendido cómo escribir y leer grupos de datos a y desde
un archivo. Pero en un desarrollo orientado a objetos debemos pensar en objetos;
por lo tanto, ese grupo de datos al que nos hemos referido no lo trataremos
aisladamente; más bien se corresponderá con los atributos de un objeto, por
ejemplo de la clase Persona, lo que nos conducirá a escribir y leer objetos a y
desde un archivo.

Normalmente la operación de enviar una serie de objetos a un archivo en disco


para hacerlos persistentes recibe el nombre de seriación, y la operación de leer o
recuperar su estado del archivo para reconstruirlos en memoria recibe el nombre
de deseriación. Para realizar estas operaciones de una forma automática, el
paquete java.io proporciona las clases ObjectOutputStream y
ObjectInputStream. Ambas clases dan lugar a flujos que procesan sus datos; en
este caso, se trata de convertir el estado de un objeto (los atributos excepto las
variables estáticas), incluyendo la clase del objeto y el prototipo de la misma, en
una secuencia de bytes y viceversa. Por esta razón los flujos
ObjectOutputStream y ObjectInputStream deben ser construidos sobre otros
flujos que canalicen esos bytes a y desde el archivo. La Figura 6 responde a este
proceso:

Figura 6. los flujos ObjectOutputStream y ObjectInputStream

Programa
seriar deseriar
flujo flujo
Objetos
ObjectOutputStream ObjectInputStream

flujo Archivo en flujo


FileOutputStream el disco FileInputStream

Pág. 51
Para poder seriar los objetos de una clase, ésta debe de implementar la interfaz
Serializable. Se trata de una interfaz vacía; esto es, sin ningún método; su
propósito es simplemente identificar clases cuyos objetos se pueden seriar.

Las siguientes líneas de código, definen la clase Persona como una clase cuyos
objetos se pueden seriar:

import java.io.*;

public class Persona implements Serializable{


// Cuerpo de la clase
}

Como la interfaz Serializable está vacía no hay que escribir ningún método extra
en la clase.

Escribir objetos en un archivo


Un flujo de la clase ObjectOutputStream permite enviar datos de tipos primitivos
y objetos hacia un flujo OutputStream o derivado; concretamente, cuando se trate
de almacenarlos en un archivo, utilizaremos un flujo FileOutputStream.
Posteriormente, esos objetos podrán ser reconstruidos a través de un flujo
ObjectInputStream.

Para escribir un objeto en un flujo ObjectOutputStream utilizaremos el método


writeObject. Los objetos pueden incluir Strings y matrices, y el almacenamiento
de los mismos puede combinarse con datos de tipos primitivos, ya que esta clase
implementa la interfaz DataOutput. Este método lanzará una excepción
NotSerializableException si se intenta escribir un objeto de una clase que no
implementa la interfaz Serializable.

Pág. 52
Por ejemplo, el siguiente fragmento de código construye un ObjectOutputStream
sobre un FileOutputStream, y lo utiliza para almacenar un String y un objeto
Persona en un archivo denominado datos:

FileOutputStream fos = new FileOutputStream(“datos”);


ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeUTF(“archivo datos”);
oos.writeObject(new Persona(nombre, dirección, teléfono));
oos.close();

Ejemplo: crear el programa con la clase AddObjetos, para que permita almacenar
objetos del tipo Persona en un archivo.

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import javax.swing.JOptionPane;

public class AddObjetos {


// Metodos
static boolean escribirRegistro(File archivo, Persona objPersona) {
boolean resultado = false;
// Declaración del objeto que realiza la salida de datos hacia el archivo
ObjectOutputStream oos = null;
try {
// Se crea un flujo hacia el archivo que permita escribir el objeto
oos = new ObjectOutputStream(new FileOutputStream(archivo, true));
// Se almacena el objeto de tipo Persona en el archivo
oos.writeObject(objPersona);
resultado = true;
} catch (IOException ioe) {
JOptionPane.showMessageDialog(null, ioe, "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
} finally {
if (oos != null) {
try {
oos.close(); // Cerrar el flujo
} catch (IOException ex) {
JOptionPane.showMessageDialog(null, ex, "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
}
}
}
return resultado;
}

Pág. 53
static Persona leerDatos() {
// Declaración de los campos de la clase persona a leer
String nombre, sexo = "";
int edad;
double peso, estatura;
// Declaración de variables y objetos
Object genero[] = {"Masculino", "Femenino"};
Persona objPers = null;
try {
// Lectura de datos
nombre = JOptionPane.showInputDialog(null, "Nombre", "Entrada de datos...",
JOptionPane.INFORMATION_MESSAGE);
if (nombre.length() != 0) {
Object eleccion = JOptionPane.showInputDialog(null, "Sexo", "Entrada de datos",
JOptionPane.QUESTION_MESSAGE, null, genero, genero[0]);
if (eleccion == null) {
JOptionPane.showMessageDialog(null, "Error usted eligió Cancelar o Cerrar",
"Mensaje de error...", JOptionPane.ERROR_MESSAGE);
} else {
if (eleccion == "Masculino") {
sexo = "Masculino";
} else {
sexo = "Femenino";
}
}
edad = Integer.parseInt(JOptionPane.showInputDialog(null, "Edad",
"Entrada de datos...", JOptionPane.INFORMATION_MESSAGE));
peso = Double.parseDouble(JOptionPane.showInputDialog(null, "Peso",
"Entrada de datos...", JOptionPane.INFORMATION_MESSAGE));
estatura = Double.parseDouble(JOptionPane.showInputDialog(null, "Estatura",
"Entrada de datos...", JOptionPane.INFORMATION_MESSAGE));
// Se crea un objeto de tipo Persona
objPers = new Persona(nombre, sexo, edad, peso, estatura);
} else {
JOptionPane.showMessageDialog(null,
"Error el nombre de la persona no puede estar vacío...",
"Mensaje de error...", JOptionPane.ERROR_MESSAGE);
}
}catch (NumberFormatException nfe) {
JOptionPane.showMessageDialog(null, "Error: " + nfe.getMessage(),
"Mensaje de error...", JOptionPane.ERROR_MESSAGE);
} catch (NullPointerException npe) {
JOptionPane.showMessageDialog(null, "Error: usted eligio Cancelar o Cerrar...",
"Mensaje de error...", JOptionPane.ERROR_MESSAGE);
}
return objPers;
}

public static void main(String[] args) {


// Declaración de variables y objetos
char resp = ' ';
int seleccion;
Object opcion[] = {"Si", "No"};
// Objeto que identifica al archivo
File archivo = null;
// Objeto de tipo persona

Pág. 54
Persona objetoPers;
archivo = new File("C:\\Users\\JoséDaniel\\Desktop\\datosper.dat");
resp = 'S';
while (resp == 'S') {
// Lectura de los datos para crear el objeto de tipo Persona, por medio del
// método leerDatos() que retorna un objeto de tipo Persona a objetoPers
objetoPers = leerDatos();
if(objetoPers != null){
// Si el objetoPers es diferente de null, se llama al método escribirRegistro(),
// pasandole a este la dirección del archivo y el objeto construido
if(escribirRegistro(archivo, objetoPers)){
JOptionPane.showMessageDialog(null, "Registro almacenado en el archivo",
"Mensaje", JOptionPane.INFORMATION_MESSAGE);
}else{
JOptionPane.showMessageDialog(null, "Registro no almacenado en el archivo",
"Mensaje", JOptionPane.INFORMATION_MESSAGE);
}
seleccion = JOptionPane.showOptionDialog(null, "Deseas agregar más datos?",
"Entrada de datos", JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE, null, opcion, "Si");
if (seleccion == 0) {
resp = 'S';
} else {
if (seleccion == 1) {
resp = 'N';
} else {
JOptionPane.showMessageDialog(null, "Error usted no eligió Si o No.",
"Mensaje de error...", JOptionPane.ERROR_MESSAGE);
}
}
}else{
JOptionPane.showMessageDialog(null, "Error al construir el objeto",
"Mensaje de error...", JOptionPane.ERROR_MESSAGE);
}
}
System.exit(0);
}
}

Pág. 55
// Implementación de la clase Persona

import java.io.Serializable;

class Persona implements Serializable {


// Atributos
String nombre;
String sexo;
int edad;
double peso, estatura;

// Métodos constructores
Persona() { }

Persona(String nombre, String sexo, int edad, double peso, double estatura) {
this.nombre = nombre;
this.sexo = sexo;
this.edad = edad;
this.peso = peso;
this.estatura = estatura;
}

// Métodos set´s y get´s


public String getNombre() {
return this.nombre;
}

public void setNombre(String nombre) {


this.nombre = nombre;
}

public void setSexo(String sexo) {


this.sexo = sexo;
}

public String getSexo() {


return this.sexo;
}

public void setEdad(int edad) {


this.edad = edad;
}

public int getEdad() {


return this.edad;
}

public void setPeso(double peso) {


this.peso = peso;
}

public double getPeso() {


return this.peso;
}

Pág. 56
public void setEstatura(double estatura) {
this.estatura = estatura;
}

public double getEstatura() {


return this.estatura;
}
}

Hasta éste punto debe de haber funcionado nuestro módulo principal (main) y los
módulos leerDatos() y escribirRegistro(), ahora falta el módulo que permita leer del
archivo los objetos guardados (registros); para lo cual haremos uso de la clase
que realiza dicha acción.

Pág. 57
Leer objetos desde un archivo.
Un flujo de la clase ObjectInputStream permite recuperar datos de tipos
primitivos y objetos desde un flujo InputStream o derivado; concretamente,
cuando se trate de datos de tipos primitivos y objetos almacenados en un archivo,
utilizaremos un flujo FileInputStream. La clase ObjectInputStream implementa la
interfaz DataInput para permitir leer también datos de tipos primitivos.

Para leer un objeto desde un flujo ObjectInputStream utilizaremos el método


readObject. Si se almacenaron objetos y datos de tipos primitivos, deben ser
recuperados en el mismo orden.

Por ejemplo, el siguiente código construye un ObjectInputStream sobre un


FileInputStream, y lo utiliza para recuperar un String y un objeto Persona de un
archivo denominado datos.

FileInputStream fos = new FileInputStream(“datos”);


ObjectInputStream ois = new ObjectInputStream(fos);
String str = (String) ois.readUTF();
Persona persona = (Persona) ois.readObject();
ois.close();

Ejemplo: Crear el método mostrarRegistros para que permita recuperar objetos


del tipo Persona desde un archivo.

static void mostrarRegistros(File archivo){


// Declaración de las variables a utilizar para leer los datos desde el archivo
// y almacenar estos en un String
String nombre, sexo;
int edad;
double peso, estatura;
String datosSal = "";
// Declaración del objeto persona
Persona persona;
// Declaración del objeto para la entrada de datos desde el archivo
ObjectInputStream ois = null;
// Establecer el formato de número de punto flotante dos decimales
DecimalFormat dosDigitos = new DecimalFormat("0.00");

Pág. 58
try{
// Verifica si el archivo existe
if(archivo.exists()){
if(archivo.length() > 0){
// Si el archivo existe y éste contiene información, se abre el archivo
ois = new ObjectInputStream(new FileInputStream(archivo));
// Lectura de los datos desde el archivo. Cuando se alcance el final de éste,
// Java lanzará una excepción del tipo EOFException y en ese momento se
// imprime la información de los objetos almacenados
while(true){
// Lectura del objeto desde el archivo hacia el objeto persona
persona = (Persona)ois.readObject();
// Extracción de los datos del objeto persona hacia las variables
nombre = persona.getNombre();
sexo = persona.getSexo();
edad = persona.getEdad();
peso = persona.getPeso();
estatura = persona.getEstatura();
// Almacenando los datos en la variable datosSal
datosSal = datosSal + "Nombre: " + nombre + "\n" + "Sexo: " + sexo
+ "\n" + "Edad: " + edad + "\n" + "Peso: " + dosDigitos.format(peso)
+ "\n" + "Estatura: " + dosDigitos.format(estatura) + "\n\n";
}
}else{
JOptionPane.showMessageDialog(null, "Error el archivo esta vacio",
"Mensaje", JOptionPane.ERROR_MESSAGE);
}
}else{
JOptionPane.showMessageDialog(null, "Error el archivo no existe", "Mensaje",
JOptionPane.ERROR_MESSAGE);
}
}catch(EOFException e){
// Se muestra el contenido de los registros almacenados en datos Sal
JOptionPane.showMessageDialog(null, datosSal, "Salida de datos",
JOptionPane.INFORMATION_MESSAGE);
}catch(ClassNotFoundException e){
JOptionPane.showMessageDialog(null, "Error: " + e.getMessage(), "Mensaje",
JOptionPane.ERROR_MESSAGE);
} catch (FileNotFoundException ex) {
JOptionPane.showMessageDialog(null, ex, "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
} catch (IOException ex) {
JOptionPane.showMessageDialog(null, ex, "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
}finally{
if(ois != null){
try {
ois.close(); // Cerrar el flujo
} catch (IOException ex) {
JOptionPane.showMessageDialog(null, ex, "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
}
}
}
}

Pág. 59
Una vez construido el método mostrarRegistros() es necesario probar el
funcionamiento de este, para lo cual, vamos a modificar el método principal de la
clase:

public static void main(String[] args) {


// Declaración de variables y objetos
char resp = ' ';
int seleccion;
int opcion1 = 0;
String opciones = "Operaciones básicas\n\n" + "[1] Agregar\n" + "[2] Visualizar\n"
+ "[3] Salir\n" + "\nSeleccione la opción deseada";
Object opcion[] = {"Si", "No"};
// Objeto que identifica al archivo
File archivo = null;
// Objeto de tipo persona
Persona objetoPers;
archivo = new File("C:\\Users\\JoséDaniel\\Desktop\\datosper.dat");
try {
do {
do {
opcion1 = Integer.parseInt(JOptionPane.showInputDialog(null,
opciones, "Menú principal",
JOptionPane.INFORMATION_MESSAGE));
} while (opcion1 < 1 || opcion1 > 3);
switch (opcion1) {
case 1:
resp = 'S';
while (resp == 'S') {
objetoPers = leerDatos();
if (objetoPers != null) {
if (escribirRegistro(archivo, objetoPers)) {
JOptionPane.showMessageDialog(null,
"Registro almacenado en el archivo", "Mensaje",
JOptionPane.INFORMATION_MESSAGE);
} else {
JOptionPane.showMessageDialog(null,
"Registro no almacenado en el archivo", "Mensaje",
JOptionPane.INFORMATION_MESSAGE);
}
seleccion = JOptionPane.showOptionDialog(null,
"Deseas agregar más datos?", "Entrada de datos",
JOptionPane.YES_NO_CANCEL_OPTION,
JOptionPane.QUESTION_MESSAGE, null, opcion, "Si");
if (seleccion == 0) {
resp = 'S';
} else {
if (seleccion == 1) {
resp = 'N';
} else {
JOptionPane.showMessageDialog(null,
"Error usted no eligió Si o No.", "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
}

Pág. 60
}
} else {
JOptionPane.showMessageDialog(null,
"Error al construir el objeto", "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
}
}
break;
case 2:
mostrarRegistros(archivo);
break;
case 3:
System.exit(0);
break;
}
} while (opcion1 != 3);
} catch (HeadlessException | NumberFormatException e) {
JOptionPane.showMessageDialog(null,
"Error dato no válido, este debe ser numérico", "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
}
}

Una vez que se ha modificado el método principal, se prueba el funcionamiento de


este con los demás métodos; para ello, te recomiendo que elimines el archivo
donde se están almacenando los registros (datosper.dat), y sigue los siguientes
pasos para hacer una prueba a la aplicación:

1.Introduce los datos de un registro.


2.Visualiza el contenido del archivo
3.Vuelve a introducir los datos de otro registro.
4.Seleciona nuevamente visualizar el contenido del archivo.

Como habras notado, ocurre un error y no te visualiza el contenido del archivo,


continuemos con la lectura del documento para conocer acerca de dicho error.

Un problema que existe con la clase ObjectOutputStream es que al instanciarla,


escribe bytes con información en la cabecera del archivo, antes incluso de que
escribamos datos en él. Como la clase ObjectInputStream lee correctamente estos
bytes de cabecera, aparentemente no pasa nada y ni siquiera nos enteramos que
existen.

Pág. 61
Pero, el problema se presenta si escribimos datos en el archivo y lo cerramos.
Luego volvemos a abrirlo para añadir nuevos datos, creando un nuevo
ObjectOutputStream.

Esto hace que se escriba una nueva cabecera justo al final del archivo. Luego se
irán añadiendo los objetos que vayamos escribiendo. El archivo contendrá la
información como se muestra en la Figura 7, con dos cabeceras.

Figura 7. Contenido de un archivo con dos cabeceras

Como notaras en la Figura 7, existe en la primera sesión una cabecera y dos


objetos de tipo Persona; posteriormente cuando se ingresa para agregar nuevos
objetos de tipo Persona, se vuelve a escribir la cabecera después del ultimo objeto
de tipo Persona escrito en la primera sesión, y luego se escriben los objetos.

Posiblemente te preguntes, como se soluciona el error, bueno, uno de los


problemas que se tiene cuando se lee el archivo para recuperar la información, y
que cuando se crea el objeto ObjectInputStream, lo que realiza este primeramente
es leer la cabecera del principio para obtener información del tipo de objeto
almacenado, y posteriormente leer los objetos. Pero cuando esa lectura se
encuentra con una segunda cabecera que se añadió al abrir el archivo por
segunda vez para añadirle registros a este, se obtiene un error del tipo
StreamCorruptedException y por lo tanto no puede leer más objetos.

Una solución es, usar un solo objeto ObjectOuptutStream para escribir todo el
contenido del archivo. Sin embargo, esto no es siempre posible. Por ejemplo, si
nuestro programa permite almacenar información en una agenda, un día

Pág. 62
escribimos información de dos contactos, cerramos la agenda y apagamos el
computador.

Al día siguiente, queremos añadir a otros tres contactos más, encendemos el


computador, abrimos la agenda y por más que buscamos a nuestros antiguos
contactos no queda ni rastro de ellos. No se puede pretender que en una sola vez
se capturen a todos nuestros contactos, y que no volvamos a meter a nadie más.

La solución que se ha encontrado (que seguramente no es la única) es hacer


herencia (derivación) de la clase ObjectOutputStream y redefinir el método
writeStreamHeader() como se muestra en el código siguiente, vacío, para que no
haga nada.

protected void writeStreamHeader() throws IOException { }

Una vez realizado lo anterior, se utiliza el método writeUnshared() para poder


agregar un objeto al archivo.

Continuando con el ejemplo, se tiene un programa con lo comentado hasta aquí.


Para lo cual se utilizaran las clases: Persona, se construirá la clase RedefineOOS
junto con el método writeStreamHeader() redefinido para que no haga nada, y la
clase AddObjetos, que permita escribir objetos de tipo Persona en un archivo.

Implementa la clase RedefineOOS dentro del mismo proyecto, que herede de la


clase ObjectOutputStream para que no escriba una cabecera al principio del
archivo.

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;

public class RedefineOOS extends ObjectOutputStream {


// Constructor que recibe un OutputStream
public RedefineOOS(OutputStream out) throws IOException {

Pág. 63
super(out);
}

// Constructor sin parámetros


protected RedefineOOS() throws IOException,
SecurityException {
super();
}

// Redefinición del método que escribe la cabecera para que no haga nada.
@Override
protected void writeStreamHeader() throws IOException {
}
}

Concluida la implementación de la clase RedefineOOS dentro del proyecto, se


procede a modificar el método escribirRegistro(), quedando este de la siguiente
manera (instrucciones en azul):

static boolean escribirRegistro(File archivo, Persona objPersona) {


boolean resultado = false;
// Declaración del objeto que realiza la salida de datos hacia el archivo
ObjectOutputStream oos = null;
try {
// Si el archivo no existe en la dirección indicada
if (!archivo.exists()) {
// Se crea un flujo hacia el archivo que permita escribir el objeto
oos = new ObjectOutputStream(new FileOutputStream(archivo));
// Se almacena el objeto de tipo Persona en el archivo
oos.writeObject(objPersona);
resultado = true;
}else {
// Si el archivo existe se abre este y se usa un ObjectOutputStream
// que no escriba una cabecera en el archivo
oos = new RedefineOOS(new FileOutputStream(archivo, true));
// Se escribe el objeto en el archivo utilzando el método writeUnshared()
oos.writeUnshared(objPersona);
resultado = true;
}
} catch (IOException ioe) {
JOptionPane.showMessageDialog(null, ioe, "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
} finally {
if (oos != null) {
try {
oos.close(); // Cerrar el flujo
} catch (IOException ex) {
JOptionPane.showMessageDialog(null, ex, "Mensaje de error",
JOptionPane.ERROR_MESSAGE);
}
}
}
return resultado;
}

Pág. 64
Una vez que se ha modificado el método escribirRegistro() y agregado la clase
RedefineOOS al proyecto, se procede nuevamente a probar la aplicación,
verificando que ya no existe el error que se presentaba cuando se volvía a
introducir registros al archivo. Antes de ejecutar la aplicación, vuelve a eliminar el
archivo donde se almacenan los datos, y comprueba que tu clase AddObjetos,
tenga todos los import siguientes:

import java.awt.HeadlessException;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.text.DecimalFormat;
import javax.swing.JOptionPane;

Actividad 6.
Una vez que ha comprobado cómo funciona la escritura y lectura de objetos a y
desde un archivo, apóyese de dicha aplicación para implementar lo siguiente:

a) Elabore una clase que interactúe con módulos y un archivo de acceso


secuencial llamado producto.dat, que contenga registros de productos con
los campos: código, nombre, stock, precio, categoría y estado. El campo
código es la clave única del registro, el cual no puede duplicarse, la
categoría puede ser: 'M' = Medicinal, 'P' = Perfumería, 'A' = Abarrotes, 'E' =
Electronica y 'O' = otros; el campo estado se refiere a que va a estar dado
de alta cuando se ingresa este con el valor de 'A'. Para ello tendrá que
hacer uso de las clases: ObjectOutputStream, ObjectInputStream,
FileOutputStream y FileInputStream.
b) Elabore el módulo que permita leer y desplegar el archivo de productos.
c) Elabore el módulo que realice el filtrado del archivo de productos por
categoría. El usuario debe de elegir la categoría, escribiendo sólo los
registros de dicha categoría hacia otro archivo (no escribir los que estén
dados de baja).

Pág. 65

También podría gustarte