Archivos
Archivos
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.
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.
Pág. 2
Las clases InputStream y OutputStream son clases abstractas que definen
métodos para realizar operaciones de entrada y salida.
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.
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)
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:
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;
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:
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:
=> 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;
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;
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.
Pág. 10
import java.io.FileInputStream;
import java.io.IOException;
import javax.swing.JOptionPane;
Pág. 11
cadena += "\n";
byte arrayByte[] = cadena.getBytes();
// Escritura de la cadena de caracteres al archivo en secuencias de bytes
fos.write(arrayByte);
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
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)
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);
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;
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;
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;
Pág. 18
}
}
}
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.
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.
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.
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:
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.
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.
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.
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.
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:
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.swing.JOptionPane;
import java.io.File;
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;
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;
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;
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.
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.
"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:
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.
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import javax.swing.JOptionPane;
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.
Pág. 37
Lectura en archivos directos
Algunos métodos de lectura son:
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import javax.swing.JOptionPane;
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();
}
}
}
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.
Pág. 39
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import javax.swing.JOptionPane;
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;
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.
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import javax.swing.JOptionPane;
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.
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.
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);
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:
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.
Programa
seriar deseriar
flujo flujo
Objetos
ObjectOutputStream ObjectInputStream
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.*;
Como la interfaz Serializable está vacía no hay que escribir ningún método extra
en la clase.
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:
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;
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;
}
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;
// 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;
}
Pág. 56
public void setEstatura(double estatura) {
this.estatura = 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.
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:
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);
}
}
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.
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.
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
Pág. 63
super(out);
}
// Redefinición del método que escribe la cabecera para que no haga nada.
@Override
protected void writeStreamHeader() throws IOException {
}
}
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:
Pág. 65