Logo Studenta

Capítulo 17-Deitel 17-0719-0764

¡Este material tiene más páginas!

Vista previa del material en texto

Archivos, fl ujos y serialización 
de objetos 17
Sólo puedo suponer que un 
documento “No archivar” 
se archiva en un archivo 
“No archivar”.
—Senador Frank Church
Audiencia del subcomité de inteligencia 
del Senado, 1975
La conciencia … no aparece 
a sí misma cortada en pequeños 
pedazos. … Un “río” o un “flujo” 
son las metáforas por las cuales se 
describe con más naturalidad.
—William James
O b j e t i v o s
En este capítulo aprenderá a:
■ Crear, leer, escribir y actualizar 
archivos.
■ Obtener información acerca 
de los archivos y directorios.
■ Comprender la jerarquía 
de clases de flujos de 
entrada/salida en Java.
■ Conocer las diferencias entre 
los archivos de texto y los 
archivos binarios.
■ Utilizar las clases Scanner 
y Formatter para procesar 
archivos de texto.
■ Utilizar las clases 
FileInputStream 
y FileOutputStream 
para leer de, y escribir en, 
archivos.
■ Utilizar las clases 
ObjectInputStream 
y ObjectOutputStream para 
leer objetos de, y escribir 
objetos en, archivos.
■ Utilizar un cuadro de diálogo 
JFileChooser.
720 Capítulo 17 Archivos, fl ujos y serialización de objetos
 17.1 Introducción 
 17.2 Archivos y fl ujos 
 17.3 La clase File 
 17.4 Archivos de texto de acceso secuencial 
17.4.1 Creación de un archivo de texto de acceso 
secuencial
17.4.2 Cómo leer datos de un archivo de texto de 
acceso secuencial
17.4.3 Caso de estudio: un programa de solicitud 
de crédito
17.4.4 Actualización de archivos de acceso secuencial
 17.5 Serialización de objetos
17.5.1 Creación de un archivo de acceso secuencial 
mediante el uso de la serialización de objetos
17.5.2 Lectura y deserialización de datos de un archivo 
de acceso secuencial
 17.6 Clases adicionales de java.io 
17.6.1 Interfaces y clases para entrada y salida basada 
en bytes
17.6.2 Interfaces y clases para entrada y salida basada 
en caracteres
 17.7 Abrir archivos con JFileChooser 
 17.8 Conclusión
Resumen | Ejercicios de autoevaluación | Respuestas a los ejercicios de autoevaluación | Ejercicios | Marcar la diferencia 
17.1 Introducción1
El almacenamiento de datos en variables y arreglos es temporal; los datos se pierden cuando una variable 
local queda fuera de alcance, o cuando el programa termina. Las computadoras utilizan archivos para la 
retención a largo plazo de datos, incluso hasta después de que terminan los programas que crean esos 
datos. Usted utiliza archivos a diario, para tareas como escribir un documento o crear una hoja de cálcu-
lo. Las computadoras almacenan archivos en dispositivos de almacenamiento secundario como discos 
duros, discos ópticos y cintas magnéticas. Nos referimos a los datos que se mantienen en archivos como 
datos persistentes, ya que existen más allá de la duración de la ejecución del programa. En este capítulo 
explicaremos cómo los programas en Java crean, actualizan y procesan archivos.
Empezaremos con un análisis sobre la arquitectura de Java para manejar archivos mediante programa-
ción. Luego explicaremos que los datos pueden almacenarse en archivos de texto y archivos binarios, y 
cubriremos las diferencias entre ellos. Demostraremos cómo obtener información sobre archivos y direc-
torios mediante el uso de la clase File, y después dedicaremos varias secciones a los distintos mecanismos 
para escribir datos en, y leer datos de, archivos. Le mostraremos cómo crear y manipular archivos de texto 
de acceso secuencial. Al trabajar con archivos de texto, el lector puede empezar a manipular archivos con 
rapidez y facilidad. Sin embargo, como veremos más adelante, es difícil leer datos de los archivos de texto 
y devolverlos al formato de los objetos. Por fortuna, muchos lenguajes orientados a objetos (incluyen-
do Java) ofrecen distintas formas de escribir objetos en (y leer objetos de) archivos (lo que se conoce como 
serialización y deserialización de objetos). Para demostrar esto, recreamos algunos de los programas de 
acceso secuencial que utilizaban archivos de texto, esta vez almacenando objetos en archivos binarios.
17.2 Archivos y flujos
Java considera a cada archivo como un flujo de bytes secuencial (figura 17.1). Cada sistema operativo 
proporciona un mecanismo para determinar el fin de un archivo, como el marcador de fin de archivo 
o la cuenta de bytes totales en el archivo que se registra en una estructura de datos administrativa, 
mantenida por el sistema. Un programa de Java que procesa un flujo de bytes simplemente recibe una 
indicación del sistema operativo cuando el programa llega al fin del flujo; el programa no necesita sa-
ber cómo representa la plataforma subyacente a los archivos o flujos. En algunos casos, la indicación de 
1 Las técnicas que se muestran en este capítulo se basan en Java SE 6. Java SE 7 introduce nuevas API en el sistema de ar-
chivos para interactuar con los archivos y directorios. En el sitio Web complementario del libro (al que puede acceder en 
www.pearsonhighered.com/deitel) publicamos una versión de este capítulo, en el que implementamos el uso de estas 
API de Java SE 7.
17.2 Archivos y fl ujos 721
fin de archivo ocurre como una excepción. En otros casos, la indicación es un valor de retorno de un 
método invocado en un objeto procesador de flujos.
Fig. 17.1 � La manera en que Java ve a un archivo de n bytes.
0 1 2 3 4 5 6 7 8 9 ...
...
n-1
marcador de fin de archivo
Flujos basados en bytes y basados en caracteres
Los flujos de archivos se pueden utilizar para la entrada y salida de datos, ya sea como bytes o carac-
teres. Los flujos basados en bytes reciben y envían datos en su formato binario. Los flujos basados 
en caracteres reciben y envían datos como una secuencia de caracteres. Por ejemplo, si se almacenara 
el valor 5 usando un flujo basado en bytes, sería en el formato binario del valor numérico 5, o 101. 
Si se almacenara el valor 5 usando un flujo basado en caracteres, sería en el formato binario del ca-
rácter 5, o 00000000 00110101 (ésta es la representación binaria para el valor numérico 53, el cual 
indica el carácter 5 en el conjunto de caracteres Unicode®). La diferencia entre las dos formas es 
que el valor numérico se puede utilizar como un entero en los cálculos, mientras que el carácter 5 
es simplemente un carácter que puede utilizarse en una cadena de texto, como en “Sarah Miller 
tiene 15 años de edad”. Los archivos que se crean usando flujos basados en bytes se conocen como 
archivos binarios, mientras que los archivos que se crean usando flujos basados en caracteres se 
conocen como archivos de texto. Los archivos de texto se pueden leer con editores de texto, mien-
tras que los archivos binarios se leen mediante programas que comprenden el contenido específico 
del archivo y su orden.
Flujos estándar de entrada salida y error estándar
Un programa de Java abre un archivo creando un objeto y asociándole un flujo de bytes o de carac-
teres. El constructor del objeto interactúa con el sistema operativo para abrir el archivo. Java tam-
bién puede asociar flujos con distintos dispositivos. De hecho, Java crea tres objetos flujo que se 
asocian con dispositivos cuando un programa de Java empieza a ejecutarse: System.in, System.out 
y System.err. Por lo general, System.in (el objeto flujo de entrada estándar) permite a un pro-
grama recibir bytes desde el teclado; el objeto System.out (el objeto flujo estándar de salida) gene-
ralmente permite a un programa mostrar datos en la pantalla; y el objeto System.err (el objeto 
flujo estándar de error) normalmente permite a un programa mostrar en la pantalla mensajes de 
error basados en caracteres. Cada uno de estos flujos puede redirigirse. Para System.in, esta capaci-
dad permite al programa leer bytes desde un origen distinto. Para System.out y System.err, esta 
capacidad permite que la salida se envíe a una ubicación distinta, como un archivo en disco. La clase 
System proporciona los métodos setIn, setOut y setErr para redirigir los flujos estándar de entrada, 
salida y error, respectivamente.
El paquete java.ioLos programas de Java realizan el procesamiento de archivos utilizando clases del paquete java.io. 
Este paquete incluye definiciones para las clases de flujo como FileInputStream (para la entrada 
basada en bytes desde un archivo), FileOutputStream (para la salida basada en bytes hacia un ar-
chivo), FileReader (para la entrada basada en caracteres desde un archivo) y FileWriter (para la 
salida basada en caracteres hacia un archivo), que heredan de las clases InputStream, OutputStream, 
Reader y Writer, respectivamente. Por lo tanto, los métodos de estas clases de flujos pueden aplicarse 
a los flujos de archivos también. 
722 Capítulo 17 Archivos, fl ujos y serialización de objetos
Java contiene clases que permiten al programador realizar operaciones de entrada y salida con ob-
jetos o variables de tipos de datos primitivos. Los datos se siguen almacenando como bytes o caracteres 
tras bambalinas, lo cual permite al programador leer o escribir datos en forma de valores int, String 
u otros tipos de datos, sin tener que preocuparse por los detalles acerca de convertir dichos valores 
al formato de bytes. Para realizar dichas operaciones de entrada y salida, pueden usarse objetos de las 
clases ObjectInputStream y ObjectOutputStream junto con las clases de flujos de archivos basadas 
en bytes FileInputStream y FileOutputStream (en breve hablaremos con más detalle sobre estas clases). 
Es posible consultar la jerarquía completa de tipos del paquete java.io en la documentación en línea, 
en la página: 
download.oracle.com/javase/6/docs/api/java/io/package-tree.html
Como puede ver en la jerarquía, Java ofrece muchas clases para realizar operaciones de entrada/salida. 
En este capítulo usaremos varias de estas clases para implementar programas de procesamiento de ar-
chivos, que crean y manipulan archivos de acceso secuencial. En el capítulo 27 utilizaremos las clases 
de flujos en forma extensa, para implementar aplicaciones de red. 
Además de las clases de java.io, las operaciones de entrada y salida basadas en caracteres se pue-
den llevar a cabo con las clases Scanner y Formatter. La clase Scanner se utiliza con mucha frecuen-
cia para recibir datos del teclado; también puede leer datos desde un archivo. La clase Formatter 
permite mostrar datos con formato a cualquier flujo basado en texto, en forma similar al método 
System.out.printf. En el apéndice G, se presentan los detalles acerca de la salida con formato me-
diante printf. Todas estas características se pueden utilizar también para dar formato a los archivos 
de texto.
17.3 La clase File 
En esta sección presentamos la clase File, que es especialmente útil para recuperar información acerca 
de un archivo o directorio de un disco. Los objetos de la clase File no abren archivos ni proporcionan 
herramientas para procesarlos. No obstante, los objetos File se utilizan frecuentemente con objetos de 
otras clases de java.io para especificar los archivos o directorios que van a manipularse. 
Creación de objetos File
La clase File proporciona cuatro constructores. El constructor con un argumento String especifica 
el nombre de un archivo o directorio que se asociará con el objeto File. El nombre puede conte-
ner información sobre la ruta, así como el nombre de un archivo o directorio. La ruta de un archivo 
o directorio especifica su ubicación en el disco. La ruta incluye algunos o todos los directorios que 
conducen a ese archivo o directorio. Una ruta absoluta contiene todos los directorios, empezando con 
el directorio raíz, que conducen a un archivo o directorio específico. Cada archivo o directorio en 
un disco duro específico tiene el mismo directorio raíz en su ruta. Por lo general, una ruta relativa 
empieza desde el directorio en el que la aplicación empezó a ejecutarse, y es por lo tanto una ruta 
“relativa” al directorio actual. El constructor con dos argumentos String especifica una ruta abso-
luta o relativa como el primer argumento, y el archivo o directorio a asociar con el objeto File como 
el segundo argumento. El constructor con argumentos File y String usa un objeto File existente, 
que especifica el directorio padre del archivo o directorio especificado por el argumento String. 
El cuarto constructor usa un objeto URI para localizar el archivo. Un Identificador uniforme de 
recursos (URI) es una forma más general de un Localizador uniforme de recursos (URL), el cual 
se utiliza para localizar sitios Web. Por ejemplo, http://www.deitel.com/ es el URL para el sitio Web 
de Deitel & Associates. Los URI para localizar archivos varían entre los distintos sistemas operativos. 
En plataformas Windows, el URI:
file://C:/datos.txt
17.3 La clase File 723
identifica al archivo datos.txt, almacenado en el directorio raíz de la unidad C:. En plataformas 
UNIX/Linux, el URI
file:/home/estudiante/datos.txt
identifica el archivo datos.txt almacenado en el directorio home del usuario estudiante.
La figura 17.2 muestra una lista de los métodos más comunes de File. En download.oracle.com/
javase/6/docs/api/java/io/File.html encontrará la lista completa.
Fig. 17.2 � Métodos de File.
Método Descripción
boolean canRead() Devuelve true si la aplicación actual puede leer un archivo; false en caso contrario.
boolean canWrite() Devuelve true si la aplicación actual puede escribir en un archivo; false en caso 
contrario.
boolean exists() Devuelve true si el archivo o directorio representado por el objeto File existe; false 
en caso contrario.
boolean isFile() Devuelve true si el nombre especifi cado como argumento para el constructor de 
File es un archivo; false en caso contrario.
boolean isDirectory() Devuelve true si el nombre especifi cado como argumento para el constructor de 
File es un directorio; false en caso contrario.
boolean isAbsolute() Devuelve true si los argumentos especifi cados para el constructor de File indican 
una ruta absoluta a un archivo o directorio; false en caso contrario.
String getAbsolutePath() Devuelve un objeto String con la ruta absoluta del archivo o directorio.
String getName() Devuelve un objeto String con el nombre del archivo o directorio.
String getPath() Devuelve un objeto String con la ruta del archivo o directorio.
String getParent() Devuelve un objeto String con el directorio padre del archivo o directorio (es decir, 
el directorio en el que puede encontrarse ese archivo o directorio).
long length() Devuelve la longitud del archivo, en bytes. Si el objeto File representa a un 
directorio, se devuelve un valor no especifi cado. 
long lastModified() Devuelve una representación dependiente de la plataforma de la hora en la que se 
hizo la última modifi cación en el archivo o directorio. El valor devuelto es útil sólo 
para compararlo con otros valores devueltos por este método.
String[] list() Devuelve un arreglo de objetos String, los cuales representan el contenido de un 
directorio. Devuelve null si el objeto File no representa a un directorio.
Demostración de la clase File
La figura 17.3 pide al usuario que introduzca el nombre de un archivo o directorio, y después usa 
la clase File para imprimir información en pantalla acerca del nombre de archivo o directorio intro-
ducido.
El programa empieza pidiendo al usuario un archivo o directorio (línea 12). En la línea 13 se 
introduce el nombre del archivo o directorio y se pasa al método analizarRuta (líneas 17 a 50). 
El método crea un nuevo objeto File (línea 20) y asigna su referencia a nombre. En la línea 22 se 
724 Capítulo 17 Archivos, fl ujos y serialización de objetos
Fig. 17.3 � Uso de la clase File para obtener información sobre archivos y directorios (parte 1 de 2).
 1 // Fig. 17.3: DemostracionFile.java
 2 // Clase File utilizada para obtener información sobre archivos y directorios.
 3 import java.io.File;
 4 import java.util.Scanner;
 5
 6 public class DemostracionFile
 7 {
 8 public static void main( String[] args )
 9 {
10 Scanner entrada = new Scanner( System.in );
11
12 System.out.print(“Escriba aqui el nombre del archivo o directorio: ” );
13 analizarRuta( entrada.nextLine() );
14 } // fin de main
15
16 // muestra información acerca del archivo especificado por el usuario
17 public static void analizarRuta( String ruta )
18 {
19 // crea un objeto File con base en la entrada del usuario
20 File nombre = new File( ruta );
21
22 if ( nombre.exists() ) // si existe el nombre, muestra información sobre él
23 {
24 // muestra información del archivo (o directorio)
25 System.out.printf(
26 “%s%s\n%s\n%s\n%s\n%s%s\n%s%s\n%s%s\n%s%s\n%s%s”,
27 nombre.getName(), “ existe”,
28 ( nombre.isFile() ? “es un archivo” : “no es un archivo” ),
29 ( nombre.isDirectory() ? “es un directorio” : 
30 “no es un directorio” ),
31 ( nombre.isAbsolute() ? “es ruta absoluta” : 
32 “no es ruta absoluta” ), “Ultima modificacion: ”,
33 nombre.lastModified(), “Tamanio: ”, nombre.length(), 
34 “Ruta: ”, nombre.getPath(), “Ruta absoluta: ”,
35 nombre.getAbsolutePath(), “Padre: ”, nombre.getParent() );
36
37 if ( nombre.isDirectory() ) // muestra el listado del directorio
38 {
39 String[] directorio = nombre.list();
40 System.out.println( “\n\nContenido del directorio:\n” );
41
42 for ( String nombreDirectorio : directorio )
43 System.out.printf( “%s\n”, nombreDirectorio );
44 } // fin de if
45 } // fin de if exterior
46 else // no es archivo o directorio, muestra mensaje de error
47 {
48 System.out.printf( “%s %s”, ruta, “no existe.” );
49 } // fin de else 
50 } // fin del método analizarRuta
51 } // fin de la clase DemostracionFile
17.3 La clase File 725
invoca el método exists de File para determinar si el nombre introducido por el usuario existe (ya 
sea como archivo o directorio) en el disco. Si el nombre no existe, el control procede a las líneas 46 
a 49 y muestra un mensaje en la pantalla, que contiene el nombre que escribió el usuario, seguido de 
“no existe”. En caso contrario, se ejecuta el cuerpo de la instrucción if (líneas 22 a 45). El programa 
imprime el nombre del archivo o directorio (línea 27), seguido de los resultados de probar el objeto 
File con isFile (línea 28), isDirectory (línea 29) e isAbsolute (línea 31). A continuación, el pro-
grama muestra los valores devueltos por lastModified (línea 33), length (línea 33), getPath (línea 
34), getAbsolutePath (línea 35) y getParent (línea 35). Si el objeto File representa un directorio 
(línea 37), el programa obtiene una lista del contenido del directorio como un arreglo de objetos 
String, usando el método list de File (línea 39), y muestra la lista en la pantalla.
El primer resultado de este programa demuestra un objeto File asociado con el directorio jfc 
del JDK. El segundo resultado demuestra un objeto File asociado con el archivo README.txt del 
Fig. 17.3 � Uso de la clase File para obtener información sobre archivos y directorios (parte 2 de 2).
Escriba aqui el nombre del archivo o directorio: E:\Archivos de programa\Java\
jdk1.6.0_11\demo\jfc
jfc existe
no es un archivo
es un directorio
es ruta absoluta
Ultima modificacion: 1228404395024
Tamanio: 4096
Ruta: E:\Archivos de programa\Java\jdk1.6.0_11\demo\jfc
Ruta absoluta: E:\Archivos de programa\Java\jdk1.6.0_11\demo\jfc
Padre: E:\Archivos de programa\Java\jdk1.6.0_11\demo
Contenido del directorio:
CodePointIM
FileChooserDemo
Font2DTest
Java2D
Laffy
Metalworks
Notepad
SampleTree
Stylepad
SwingApplet
SwingSet2
SwingSet3
Escriba aqui el nombre del archivo o directorio: E:\Archivos de programa\Java\
jdk1.6.0_11\demo\jfc\Java2D\README.txt
readme.txt existe
es un archivo
no es un directorio
es ruta absoluta
Ultima modificacion: 1228404384270
Tamanio: 7518
Ruta: E:\Archivos de programa\Java\jdk1.6.0_11\demo\jfc\Java2D\README.txt
Ruta absoluta: E:\Archivos de programa\Java\jdk1.6.0_11\demo\jfc\Java2D\README.txt
Padre: E:\Archivos de programa\Java\jdk1.6.0_11\demo\jfc\Java2D
726 Capítulo 17 Archivos, fl ujos y serialización de objetos
ejemplo de Java 2D que viene con el JDK. En ambos casos, especificamos una ruta absoluta en nues-
tra computadora personal. 
Un carácter separador se utiliza para separar directorios y archivos en la ruta. En un equipo 
Windows, el carácter separador es la barra diagonal inversa (\). En un sistema UNIX, el carácter separa-
dor es la barra diagonal (/). Java procesa ambos caracteres en forma idéntica en el nombre de una ruta. 
Por ejemplo, si deseamos utilizar la ruta
c:\Archivos de programa\Java\jdk1.6.0_11\demo/jfc
que emplea uno de cada uno de los caracteres separadores antes mencionados, Java de todas formas pro-
cesa la ruta en forma apropiada. Al construir objetos String que representen la información de una ruta, 
use File.separator para obtener el carácter separador apropiado del equipo local, en vez de utilizar / o 
\ de manera explícita. Esta constante devuelve un objeto String que consiste de un carácter: el separador 
apropiado para el sistema.
Error común de programación 17.1
Usar \ como separador de directorios en vez de \\ en una literal de cadena es un error 
lógico. Una sola \ indica que la \ y el siguiente carácter representan una secuencia de 
escape. Para insertar una \ en una literal de cadena, debe usar \\.
17.4 Archivos de texto de acceso secuencial
A continuación crearemos y manipularemos archivos de acceso secuencial, en donde se guardan los re-
gistros en orden, en base al campo clave de registro. Empezaremos con los archivos de texto, que permi-
ten al lector crear y editar rápidamente archivos que puedan ser leídos por los humanos. Hablaremos 
sobre crear, escribir datos en, leer datos de y actualizar los archivos de texto de acceso secuencial. También 
incluiremos un programa de consulta de crédito para obtener datos específicos de un archivo.
17.4.1 Creación de un archivo de texto de acceso secuencial
Java no impone una estructura en un archivo; las nociones tales como los registros no existen como parte del 
lenguaje Java. Por lo tanto, el programador debe estructurar los archivos de manera que cumplan con 
los requerimientos de sus aplicaciones. En el siguiente ejemplo veremos cómo imponer una estructura 
de registros con claves en un archivo.
El programa de las figuras 17.4, 17.5 y 17.8 crea un archivo simple de acceso secuencial, que podría 
utilizarse en un sistema de cuentas por cobrar para ayudar a administrar el dinero que deben a una com-
pañía los clientes a crédito. Por cada cliente, el programa obtiene un número de cuenta, el nombre del 
cliente y su saldo (es decir, el monto que el cliente aún debe a la compañía por los bienes y servicios reci-
bidos). Los datos obtenidos para cada cliente constituyen un “registro” para ese cliente. El número de 
cuenta se utiliza como la clave de registro en esta aplicación; el archivo se creará y mantendrá en orden 
basado en el número de cuenta. El programa supone que el usuario introduce los registros en orden de 
número de cuenta. En un sistema completo de cuentas por cobrar (basado en archivos de acceso secuen-
cial), se proporcionaría una herramienta para ordenar datos, de manera que el usuario pudiera introducir 
los registros en cualquier orden. Después, los registros se ordenarían y se escribirían en el archivo.
La clase RegistroCuenta
La clase RegistroCuenta (figura 17.4) encapsula la información de registro del cliente utilizada por 
los ejemplos en este capítulo. La clase RegistroCuenta se declara en el paquete com.deitel.cap17 
(línea 3), de forma que se pueda importar en varios ejemplos de este capítulo para reutilizarla (en la 
sección 8.14 encontrará información sobre cómo compilar y usar sus propios paquetes). La clase 
RegistroCuenta contiene las variables de instancia private llamadas cuenta, primerNombre, 
apellidoPaterno y saldo (líneas 7 a 10),además de los métodos establecer y obtener para acceder a 
17.4 Archivos de texto de acceso secuencial 727
estos campos. Aunque los métodos establecer no validan los datos en este ejemplo, deberían hacerlo en 
un sistema de “nivel industrial”.
Fig. 17.4 � La clase RegistroCuenta mantiene la información para una cuenta (parte 1 de 2).
 1 // Fig. 17.4: RegistroCuenta.java
 2 // La clase RegistroCuenta mantiene la información de una cuenta.
 3 package com.deitel.cap17; // se empaqueta para reutilizarla
 4
 5 public class RegistroCuenta
 6 {
 7 private int cuenta;
 8 private String primerNombre;
 9 private String apellidoPaterno;
10 private double saldo;
11
12 // el constructor sin argumentos llama a otro constructor con valores predeterminados
13 public RegistroCuenta() 
14 {
15 this( 0, “”, “”, 0.0 ); // llama al constructor con cuatro argumentos
16 } // fin del constructor de RegistroCuenta sin argumentos
17
18 // inicializa un registro
19 public RegistroCuenta( int cta, String nombre, String apellido, double sal )
20 {
21 establecerCuenta( cta );
22 establecerPrimerNombre( nombre );
23 establecerApellidoPaterno( apellido );
24 establecerSaldo( sal );
25 } // fin del constructor de RegistroCuenta con cuatro argumentos
26
27 // establece el número de cuenta 
28 public void establecerCuenta( int cta )
29 {
30 cuenta = cta;
31 } // fin del método establecerCuenta
32
33 // obtiene el número de cuenta 
34 public int obtenerCuenta() 
35 { 
36 return cuenta; 
37 } // fin del método obtenerCuenta
38
39 // establece el primer nombre 
40 public void establecerPrimerNombre( String nombre )
41 {
42 primerNombre = nombre;
43 } // fin del método establecerPrimerNombre
44
45 // obtiene el primer nombre 
46 public String obtenerPrimerNombre() 
47 { 
48 return primerNombre; 
49 } // fin del método obtenerPrimerNombre
728 Capítulo 17 Archivos, fl ujos y serialización de objetos
Para compilar la clase RegistroCuenta, abra una ventana de símbolo del sistema, cambie al direc-
torio fig17_05 de este capítulo (que contiene el archivo RegistroCuenta.java) y escriba lo siguiente:
javac -d .. RegistroCuenta.java
Esto coloca a RegistroCuenta.class en la estructura de directorios de su paquete, y coloca el paquete en 
la carpeta cap17 que contiene todos los ejemplos para este capítulo. Cuando compile la clase RegistroCuen-
ta (o cualquier otra clase que se reutilice en este capítulo), debe colocarla en un directorio común. Cuando 
compile o ejecute clases que utilicen a RegistroCuenta (por ejemplo, CrearArchivoTexto en la figura 
17.5), debe especificar el argumento de línea de comandos –classpath para javac y java, como en
javac -classpath .;c:\ejemplos\cap17 CrearArchivoTexto.java
java -classpath .;c:\ejemplos\cap17 CrearArchivoTexto
El directorio actual (que se especifica con .) se incluye en la ruta de clases para asegurar que el compila-
dor pueda localizar otras clases en el mismo directorio que el de la clase que se está compilando. El se-
parador de ruta que se utiliza en los comandos anteriores debe ser el apropiado para su plataforma: un 
punto y coma (;) en Windows y dos puntos (:) en UNIX/Linux/Mac OS X. Los anteriores comandos 
asumen que el paquete que contiene a RegistroCuenta se encuentra en el directorio C:\ejemplos\cap17 
en un equipo Windows.
La clase CrearArchivoTexto
Ahora examinemos la clase CrearArchivoTexto (figura 17.5). La línea 14 declara la variable Formatter 
llamada salida. Como vimos en la sección 17.2, un objeto Formatter muestra en pantalla objetos 
Fig. 17.4 � La clase RegistroCuenta mantiene la información para una cuenta (parte 2 de 2).
50
51 // establece el apellido paterno
52 public void establecerApellidoPaterno( String apellido )
53 {
54 apellidoPaterno = apellido;
55 } // fin del método establecerApellidoPaterno
56
57 // obtiene el apellido paterno
58 public String obtenerApellidoPaterno()
59 {
60 return apellidoPaterno;
61 } // fin del método obtenerApellidoPaterno
62
63 // establece el saldo
64 public void establecerSaldo( double sal )
65 {
66 saldo = sal;
67 } // fin del método establecerSaldo
68
69 // obtiene el saldo
70 public double obtenerSaldo()
71 {
72 return saldo;
73 } // fin del método obtenerSaldo
74 } // fin de la clase RegistroCuenta
17.4 Archivos de texto de acceso secuencial 729
String con formato, usando las mismas herramientas de formato que el método System.out.printf. 
Un objeto Formatter puede enviar datos a varias ubicaciones, como la pantalla o a un archivo, como lo 
hacemos aquí. El objeto Formatter se instancia en la línea 21, en el método abrirArchivo (líneas 17 a 
34). El constructor que se utiliza en la línea 21 recibe un argumento: un objeto String que contiene el 
nombre del archivo, incluyendo su ruta. Si no se especifica una ruta, como se da aquí el caso, la JVM 
asume que los archivos están en el directorio desde el cual se ejecutó el programa. Para los archivos 
de texto, utilizamos la extensión .txt. Si el archivo no existe, se creará. Si se abre un archivo existente, 
su contenido se trunca; todos los datos en el archivo se descartan. En este punto, el archivo se abre para 
escritura y el objeto Formatter resultante se puede utilizar para escribir datos en el archivo. 
Fig. 17.5 � Escribir datos en un archivo de texto secuencial mediante la clase Formatter (parte 1 de 3).
 1 // Fig. 17.5: CrearArchivoTexto.java
 2 // Escribir datos en un archivo de texto secuencial mediante la clase Formatter.
 3 import java.io.FileNotFoundException;
 4 import java.lang.SecurityException;
 5 import java.util.Formatter;
 6 import java.util.FormatterClosedException;
 7 import java.util.NoSuchElementException;
 8 import java.util.Scanner;
 9
10 import com.deitel.cap17.RegistroCuenta;
11
12 public class CrearArchivoTexto
13 {
14 private Formatter salida; // objeto usado para enviar texto al archivo
15
16 // permite al usuario abrir el archivo
17 public void abrirArchivo()
18 {
19 try
20 {
21 salida = new Formatter( “clientes.txt” ); // abre el archivo
22 } // fin de try
23 catch ( SecurityException securityException )
24 {
25 System.err.println(
26 “No tiene acceso de escritura a este archivo.” );
27 System.exit( 1 ); // termina el programa
28 } // fin de catch
29 catch ( FileNotFoundException fileNotFoundException )
30 {
31 System.err.println( “Error al abrir o crear el archivo.” );
32 System.exit( 1 ); // termina el programa
33 } // fin de catch
34 } // fin del método abrirArchivo
35
36 // agrega registros al archivo
37 public void agregarRegistros()
38 {
39 // objeto que se va a escribir en el archivo
40 RegistroCuenta registro = new RegistroCuenta();
730 Capítulo 17 Archivos, fl ujos y serialización de objetos
41
42 Scanner entrada = new Scanner( System.in );
43
44 System.out.printf( “%s\n%s\n%s\n%s\n\n”,
45 “Para terminar la entrada, escriba el indicador de fin de archivo ”,
46 “cuando se le pida que escriba los datos de entrada.”,
47 “En UNIX/Linux/Mac OS X escriba <ctrl> d y oprima Intro”,
48 “En Windows escriba <ctrl> z y oprima Intro” );
49
50 System.out.printf( “%s\n%s”, 
51 “Escriba el numero de cuenta (> 0), primer nombre, apellido paterno y saldo.”,
52 “? ” );
53
54 while ( entrada.hasNext() ) // itera hasta encontrar el indicador de fin de 
archivo
55 {
56 try // envía valores al archivo
57 {
58 // obtiene los datos que se van a enviar
59 registro.establecerCuenta( entrada.nextInt() ); // lee el número de cuenta
60 registro.establecerPrimerNombre( entrada.next() ); // lee el primer nombre
61 registro.establecerApellidoPaterno(entrada.next() ); // lee el apellido 
paterno
62 registro.establecerSaldo( entrada.nextDouble() ); // lee el saldo
63
64 if ( registro.obtenerCuenta() > 0 )
65 {
66 // escribe el nuevo registro
67 salida.format( “%d %s %s %.2f\n”, registro.obtenerCuenta(), 
68 registro.obtenerPrimerNombre(), registro.obtenerApellidoPaterno(),
69 registro.obtenerSaldo() );
70 } // fin de if
71 else
72 {
73 System.out.println(
74 “El numero de cuenta debe ser mayor que 0.” );
75 } // fin de else
76 } // fin de try
77 catch ( FormatterClosedException formatterClosedException )
78 {
79 System.err.println( “Error al escribir en el archivo.” );
80 return;
81 } // fin de catch
82 catch ( NoSuchElementException elementException )
83 {
84 System.err.println( “Entrada invalida. Intente de nuevo.” );
85 entrada.nextLine(); // descarta la entrada para que el usuario intente 
de nuevo
86 } // fin de catch
87
88 System.out.printf( “%s %s\n%s”, “Escriba el numero de cuenta (> 0),”,
89 “primer nombre, apellido paterno y saldo.”, “? ” );
90 } // fin de while
91 } // fin del método agregarRegistros
92
Fig. 17.5 � Escribir datos en un archivo de texto secuencial mediante la clase Formatter (parte 2 de 3).
17.4 Archivos de texto de acceso secuencial 731
En las líneas 23 a 28 se maneja la excepción tipo SecurityException, que ocurre si el usuario 
no tiene permiso para escribir datos en el archivo. En las líneas 29 a 33 se maneja la excepción tipo 
FileNotFoundException, que ocurre si el archivo no existe y no se puede crear uno nuevo. Esta excep-
ción también puede ocurrir si hay un error al abrir el archivo. En ambos manejadores de excepciones 
podemos llamar al método static System.exit, y pasarle el valor 1. Este método termina la aplicación. 
Un argumento de 0 para el método exit indica la terminación exitosa del programa. Un valor distinto 
de cero, como el 1 en este ejemplo, por lo general indica que ocurrió un error. Este valor se pasa a la ven-
tana de comandos en la que se ejecutó el programa. El argumento es útil si el programa se ejecuta desde 
un archivo de procesamiento por lotes en los sistemas Windows, o una secuencia de comandos 
de shell en sistemas UNIX/Linux/Mac OS X. Los archivos de procesamiento por lotes y las secuen-
cias de comandos de shell ofrecen una manera conveniente de ejecutar varios programas en secuencia. 
Cuando termina el primer programa, el siguiente programa empieza su ejecución. Es posible utilizar 
el argumento para el método exit en un archivo de procesamiento por lotes o secuencia de comandos 
de shell, para determinar si deben ejecutarse otros programas. Para obtener más información acerca de 
los archivos de procesamiento por lotes o las secuencias de comandos de shell, consulte la documenta-
ción de su sistema operativo.
El método agregarRegistros (líneas 37 a 91) pide al usuario que introduzca los diversos cam-
pos para cada registro, o la secuencia de teclas de fin de archivo cuando termine de introducir los 
datos. La figura 17.6 enlista las combinaciones de teclas para introducir el fin de archivo en varios sis-
temas computacionales. 
93 // cierra el file
94 public void cerrarArchivo()
95 {
96 if ( salida != null )
97 salida.close();
98 } // fin del método cerrarArchivo
99 } // fin de la clase CrearArchivoTexto
Fig. 17.5 � Escribir datos en un archivo de texto secuencial mediante la clase Formatter (parte 3 de 3).
Fig. 17.6 � Combinaciones de teclas de fin de archivo.
Sistema operativo Combinación de teclas
UNIX/Linux/Mac OS X <Enter> <Ctrl> d
Windows <Ctrl> z
En la línea 40 se crea un objeto RegistroCuenta, el cual se utilizará para almacenar los valores del 
registro actual introducido por el usuario. En la línea 42 se crea un objeto Scanner para leer la entra-
da del usuario mediante el teclado. En las líneas 44 a 48 y 50 a 52 se pide al usuario que introduzca 
los datos.
En la línea 54 se utiliza el método hasNext de Scanner para determinar si se ha introducido la 
combinación de teclas de fin de archivo. El ciclo se ejecuta hasta que hasNext encuentra los indica-
dores de fin de archivo.
En las líneas 59 a 62 se leen datos del usuario y se almacena la información del registro en el ob-
jeto RegistroCuenta. Cada instrucción lanza una excepción tipo NoSuchElementException (que se 
maneja en las líneas 82 a 86) si los datos se encuentran en el formato incorrecto (por ejemplo, un ob-
jeto String cuando se espera un valor int), o si no hay más datos que introducir. Si el número de cuenta 
es mayor que 0 (línea 64), la información del registro se escribe en clientes.txt (líneas 67 a 69) me-
diante el método format, que puede efectuar un formato idéntico al del método System.out.printf, 
que se utilizó en muchos de los ejemplos de capítulos anteriores. El método format envía un objeto 
732 Capítulo 17 Archivos, fl ujos y serialización de objetos
String con formato al destino de salida del objeto Formatter, en este caso el archivo clientes.txt. La 
cadena de formato “%d &s &s &.2f\n” indica que el registro actual se almacenará como un entero (el 
número de cuenta) seguido de un objeto String (el primer nombre), otra String (el apellido paterno) y 
un valor de punto flotante (el saldo). Cada pieza de información se separa de la siguiente mediante un 
espacio, y el valor tipo double (el saldo) se imprime en pantalla con dos dígitos a la derecha del punto 
decimal (como lo indica el .2 en %.2f). Los datos en el archivo de texto se pueden ver con un editor, o 
posteriormente mediante un programa diseñado para leer el archivo (sección 17.4.2). 
Cuando se ejecutan las líneas 67 a 69, si se cierra el objeto Formatter se lanza una excepción tipo 
FormatterClosedException. Esta excepción se maneja en las líneas 77 a 81. [Nota: también puede enviar 
datos a un archivo de texto mediante la clase java.io.PrintWriter, la cual también cuenta con los mé-
todos format y printf para imprimir datos con formato].
En las líneas 94 a 98 se declara el método cerrarArchivo, el cual cierra el objeto Formatter y el 
archivo de salida subyacente. En la línea 97 se cierra el objeto, mediante una llamada simple al método 
close. Si el método close no se llama en forma explícita, el sistema operativo comúnmente cierra el 
archivo cuando el programa termina de ejecutarse; éste es un ejemplo de las “tareas de mantenimiento” 
del sistema operativo. Sin embargo, siempre debemos cerrar un archivo en forma explícita cuando ya 
no lo necesitemos.
Caracteres separadores de línea específicos de la plataforma
En las líneas 67 a 69 se imprime una línea de texto seguida de una nueva línea (\n). Si usa un editor 
de texto para abrir el archivo clientes.txt que se produce, tal vez no todos los registros se muestre 
en una línea separada. Por ejemplo, en el Bloc de notas (Microsoft Windows) los usuarios verán una 
línea continua de texto. Esto ocurre debido a que las distintas plataformas usan distintos caracteres 
separadores de líneas. En UNIX/Linux/Mac OS X, el separador de líneas es una nueva línea (\n). 
En Windows, es una combinación de retorno de carro y avance de línea, lo cual se representa como \r\n. 
Puede usar el especificador de formato %n en una cadena de control de formato para imprimir un se-
parador de línea específico de la plataforma, con lo cual asegura que sea posible abrir y ver el archivo de 
texto correctamente en un editor de texto para la plataforma en la que se creó el archivo. El método 
System.out.println imprime un separador de líneas específico de la plataforma después de su argu-
mento. Además, sin importar el separador de línea que se utilice en un archivo de texto, un programa 
de Java puede aún reconocer las líneas de textoy leerlas.
La clase PruebaCrearArchivoTexto
La figura 17.7 ejecuta el programa. En la línea 8 se crea un objeto CrearArchivoTexto, el cual se 
utiliza posteriormente para abrir, agregar registros y cerrar el archivo (líneas 10 a 12). Los datos de 
ejemplo para esta aplicación se muestran en la figura 17.8. En la ejecución de ejemplo para este pro-
grama, el usuario introduce información para cinco cuentas, y después introduce el fin de archivo 
para indicar que ha terminado de introducir datos. La ejecución de ejemplo no muestra cómo apare-
cen realmente los registros de datos en el archivo. En la siguiente sección, para verificar que el archivo 
se haya creado sin problemas, presentamos un programa que lee el archivo e imprime su contenido. 
Como es un archivo de texto, también puede verificar la información con sólo abrir el archivo en un 
editor de texto.
Fig. 17.7 � Prueba de la clase CrearArchivoTexto (parte 1 de 2).
 1 // Fig. 17.7: PruebaCrearArchivoTexto.java
 2 // Prueba de la clase CrearArchivoTexto.
 3
 4 public class PruebaCrearArchivoTexto
 5 {
17.4 Archivos de texto de acceso secuencial 733
17.4.2 Cómo leer datos de un archivo de texto de acceso secuencial
Los datos se almacenan en archivos, para poder procesarlos según sea necesario. En la sección 17.4.1 
demostramos cómo crear un archivo para acceso secuencial. Esta sección muestra cómo leer los datos 
en forma secuencial desde un archivo de texto. Demostraremos cómo puede utilizarse la clase Scanner 
para recibir datos de un archivo, en vez del teclado.
La aplicación de las figuras 17.9 y 17.10 lee registros del archivo “clientes.txt” creado por la 
aplicación de la sección 17.4.1 y muestra el contenido de los registros. En la línea 13 de la figura 17.9 
se declara un objeto Scanner, que se utilizará para obtener los datos de entrada del archivo.
Fig. 17.7 � Prueba de la clase CrearArchivoTexto (parte 2 de 2).
 6 public static void main( String[] args )
 7 {
 8 CrearArchivoTexto aplicacion = new CrearArchivoTexto();
 9
10 aplicacion.abrirArchivo();
11 aplicacion.agregarRegistros();
12 aplicacion.cerrarArchivo();
13 } // fin de main
14 } // fin de la clase PruebaCrearArchivoTexto
Para terminar la entrada, escriba el indicador de fin de archivo
cuando se le pida que escriba los datos de entrada.
En UNIX/Linux/Mac OS X escriba <ctrl> d y oprima Intro
En Windows escriba <ctrl> z y oprima Intro
Escriba el numero de cuenta (> 0), primer nombre, apellido paterno y saldo.
? 100 Bob Jones 24.98
Escriba el numero de cuenta (>0), primer nombre, apellido paterno y saldo.
? 200 Steve Doe -345.67
Escriba el numero de cuenta (>0), primer nombre, apellido paterno y saldo.
? 300 Pam White 0.00
Escriba el numero de cuenta (>0), primer nombre, apellido paterno y saldo.
? 400 Sam Stone -42.16
Escriba el numero de cuenta (>0), primer nombre, apellido paterno y saldo.
? 500 Sue Rich 224.62
Escriba el numero de cuenta (>0), primer nombre, apellido paterno y saldo.
? ^Z
Fig. 17.8 � Datos de ejemplo para el programa de las figuras 17.5 a 17.7.
Datos de ejemplo
100 Bob Jones 24.98
200 Steve Doe -345.67
300 Pam White 0.00
400 Sam Stone -42.16
500 Sue Rich 224.62
734 Capítulo 17 Archivos, fl ujos y serialización de objetos
Fig. 17.9 � Lectura de un archivo secuencial mediante un objeto Scanner (parte 1 de 2).
 1 // Fig. 17.9: LeerArchivoTexto.java
 2 // Este programa lee un archivo de texto y muestra cada registro.
 3 import java.io.File;
 4 import java.io.FileNotFoundException;
 5 import java.lang.IllegalStateException;
 6 import java.util.NoSuchElementException;
 7 import java.util.Scanner;
 8
 9 import com.deitel.cap17.RegistroCuenta;
10
11 public class LeerArchivoTexto
12 {
13 private Scanner entrada;
14
15 // permite al usuario abrir el archivo
16 public void abrirArchivo()
17 {
18 try
19 {
20 entrada = new Scanner( new File( “clientes.txt” ) );
21 } // fin de try
22 catch ( FileNotFoundException fileNotFoundException )
23 {
24 System.err.println( “Error al abrir el archivo.” );
25 System.exit( 1 );
26 } // fin de catch
27 } // fin del método abrirArchivo
28
29 // lee registro del archivo
30 public void leerRegistros()
31 {
32 // objeto que se va a escribir en la pantalla
33 RegistroCuenta registro = new RegistroCuenta();
34
35 System.out.printf( “%-9s%-15s%-18s%10s\n”, “Cuenta”,
36 “Primer nombre”, “Apellido paterno”, “Saldo” );
37
38 try // lee registros del archivo, usando el objeto Scanner
39 {
40 while ( entrada.hasNext() )
41 {
42 registro.establecerCuenta( entrada.nextInt() ); // lee el número de cuenta
43 registro.establecerPrimerNombre( entrada.next() ); // lee el primer nombre
44 registro.establecerApellidoPaterno( entrada.next() ); // lee el apellido 
paterno
45 registro.establecerSaldo( entrada.nextDouble() ); // lee el saldo
46
47 // muestra el contenido del registro
48 System.out.printf( “<%-9d%-15s%-18s%10.2f\n”,
49 registro.obtenerCuenta(), registro.obtenerPrimerNombre(),
50 registro.obtenerApellidoPaterno(), registro.obtenerSaldo() );
51 } // fin de while
52 } // fin de try
17.4 Archivos de texto de acceso secuencial 735
El método abrirArchivo (líneas 16 a 27) abre el archivo en modo de lectura, creando una instan-
cia de un objeto Scanner en la línea 20. Pasamos un objeto File al constructor, el cual especifica que 
el objeto Scanner leerá datos del archivo “clientes.txt” ubicado en el directorio desde el que se ejecu-
ta la aplicación. Si no puede encontrarse el archivo, ocurre una excepción tipo FileNotFoundException. 
La excepción se maneja en las líneas 22 a 26. 
Fig. 17.9 � Lectura de un archivo secuencial mediante un objeto Scanner (parte 2 de 2).
53 catch ( NoSuchElementException elementException )
54 {
55 System.err.println( “El archivo no esta bien formado.” );
56 entrada.close();
57 System.exit( 1 );
58 } // fin de catch
59 catch ( IllegalStateException stateException )
60 {
61 System.err.println( “Error al leer del archivo.” );
62 System.exit( 1 );
63 } // fin de catch
64 } // fin del método leerRegistros
65
66 // cierra el archivo y termina la aplicación
67 public void cerrarArchivo()
68 {
69 if ( entrada != null )
70 entrada.close(); // cierra el archivo
71 } // fin del método cerrarArchivo
72 } // fin de la clase LeerArchivoTexto
Fig. 17.10 � Prueba de la clase LeerArchivoTexto.
 1 // Fig. 17.10: PruebaLeerArchivoTexto.java
 2 // Este programa prueba la clase LeerArchivoTexto.
 3
 4 public class PruebaLeerArchivoTexto
 5 {
 6 public static void main( String[] args )
 7 {
 8 LeerArchivoTexto aplicacion = new LeerArchivoTexto();
 9
10 aplicacion.abrirArchivo();
11 aplicacion.leerRegistros();
12 aplicacion.cerrarArchivo();
13 } // fin de main
14 } // fin de la clase PruebaLeerArchivoTexto
Cuenta Primer nombre Apellido paterno Saldo
100 Bob Jones 24.98
200 Steve Doe -345.67
300 Pam White 0.00
400 Sam Stone -42.16
500 Sue Rich 224.62
736 Capítulo 17 Archivos, fl ujos y serialización de objetos
El método leerRegistros (líneas 30 a 64) lee y muestra registros del archivo. En la línea 33 se crea 
el objeto RegistroCuenta llamado registro, para almacenar la información del registro actual. En las 
líneas 35 y 36 se muestran encabezados para las columnas, en los resultados de la aplicación. En las líneas 
40 a 51 se leen datos del archivo hasta llegar al marcador de fin de archivo (en cuyo caso, el método has-
Next devolverá false en la línea 40). En las líneas 42 a 45 se utilizan los métodos nextInt, next y next-
Double de Scanner para recibir un int (el número de cuenta), dos objetos String (el primer nombre y el 
apellido paterno)y un valor double (el saldo). Cada registro es una línea de datos en el archivo. Los valo-
res se almacenan en el objeto registro. Si la información en el archivo no está bien formada (por ejemplo, 
que haya un apellido paterno en donde debe haber un saldo), se produce una excepción tipo NoSuchEle-
mentException al momento de introducir el registro. Esta excepción se maneja en las líneas 53 a 58. Si el 
objeto Scanner se cerró antes de introducir los datos, se produce una excepción tipo IllegalStateEx-
ception (que se maneja en las líneas 59 a 63). Si no ocurren excepciones, la información del registro se 
muestra en pantalla (líneas 48 a 50). Observe en la cadena de formato de la línea 48 que el número de 
cuenta, primer nombre y apellido paterno están justificados a la izquierda, mientras que el saldo está 
justificado a la derecha y se imprime con dos dígitos de precisión. Cada iteración del ciclo introduce una 
línea de texto del archivo de texto, la cual representa un registro.
En las líneas 67 a 71 se define el método cerrarArchivo, el cual cierra el objeto Scanner. El método 
main se define en la figura 17.10, en las líneas 6 a 13. En la línea 8 se crea un objeto LeerArchivoTexto, 
el cual se utiliza entonces para abrir, agregar registros y cerrar el archivo (líneas 10 a 12).
17.4.3 Caso de estudio: un programa de solicitud de crédito
Para obtener datos secuencialmente de un archivo, por lo general los programas empiezan desde el 
principio del archivo y leen todos los datos en forma consecutiva, hasta encontrar la información desea-
da. Podría ser necesario procesar el archivo secuencialmente varias veces (desde el principio del archivo) 
durante la ejecución de un programa. La clase Scanner no proporciona la habilidad de reposicionarse 
hasta el principio del archivo. Si es necesario leer el archivo de nuevo, el programa debe cerrar el archivo 
y volver a abrirlo.
El programa de las figuras 17.11 a 17.13 permite a un gerente de créditos obtener listas de clien-
tes con saldos de cero (es decir, los clientes que no deben dinero), saldos con crédito (es decir, los clientes 
a quienes la compañía les debe dinero) y saldos con débito (es decir, los clientes que deben dinero a la 
compañía por los bienes y servicios recibidos en el pasado). Un saldo con crédito es un monto negativo, 
y un saldo con débito es un monto positivo.
La enumeración OpcionMenu
Empezamos por crear un tipo enum (figura 17.11) para definir las distintas opciones del menú que 
tendrá el usuario. Las opciones y sus valores se enlistan en las líneas 7 a 10. El método obtenerValor 
(líneas 19 a 22) obtiene el valor de una constante enum específica.
Fig. 17.11 � Enumeración para las opciones del menú del programa de consulta de crédito (parte 1 de 2).
 1 // Fig. 17.11: OpcionMenu.java
 2 // Enumeración para las opciones del programa de consulta de crédito.
 3
 4 public enum OpcionMenu
 5 {
 6 // declara el contenido del tipo enum
 7 SALDO_CERO( 1 ),
 8 SALDO_CREDITO( 2 ),
 9 SALDO_DEBITO( 3 ),
10 FIN( 4 );
17.4 Archivos de texto de acceso secuencial 737
La clase ConsultaCredito
La figura 17.12 contiene la funcionalidad para el programa de consulta de crédito, y la figura 17.13 
contiene el método main que ejecuta el programa. Este programa muestra un menú de texto y permite 
al gerente de créditos introducir una de tres opciones para obtener información sobre un crédito. 
La opción 1 (SALDO_CERO) muestra las cuentas con saldos de cero. La opción 2 (SALDO_CREDITO) muestra 
las cuentas con saldos con crédito. La opción 3 (SALDO_DEBITO) muestra las cuentas con saldos con 
débito. La opción 4 (FIN) termina la ejecución del programa.
Fig. 17.11 � Enumeración para las opciones del menú del programa de consulta de crédito (parte 2 de 2).
Fig. 17.12 � Programa de consulta de crédito (parte 1 de 4).
 1 // Fig. 17.12: ConsultaCredito.java
 2 // Este programa lee un archivo secuencialmente y muestra su
 3 // contenido con base en el tipo de cuenta que solicita el usuario
 4 // (saldo con crédito, saldo con débito o saldo de cero).
 5 import java.io.File;
 6 import java.io.FileNotFoundException;
 7 import java.lang.IllegalStateException;
 8 import java.util.NoSuchElementException;
 9 import java.util.Scanner;
10
11 import com.deitel.cap17.RegistroCuenta;
12
13 public class ConsultaCredito
14 {
15 private OpcionMenu tipoCuenta;
16 private Scanner entrada;
17 private final static OpcionMenu[] opciones = { OpcionMenu.SALDO_CERO,
18 OpcionMenu.SALDO_CREDITO, OpcionMenu.SALDO_DEBITO,
19 OpcionMenu.FIN };
20
21 // lee los registros del archivo y muestra sólo los registros del tipo apropiado
22 private void leerRegistros()
23 {
24 // objeto que se va a escribir en el archivo
25 RegistroCuenta registro = new RegistroCuenta(); 
11
12 private final int valor; // opción actual del menú
13
14 // constructor
15 OpcionMenu( int valorOpcion )
16 {
17 valor = valorOpcion;
18 } // fin del constructor del tipo enum OpcionMenu
19
20 // devuelve el valor de una constante
21 public int obtenerValor()
22 {
23 return valor;
24 } // fin del método obtenerValor
25 } // fin del tipo enum OpcionMenu
738 Capítulo 17 Archivos, fl ujos y serialización de objetos
Fig. 17.12 � Programa de consulta de crédito (parte 2 de 4).
26
27 try // lee registros
28 { 
29 // abre el archivo para leer desde el principio
30 entrada = new Scanner( new File( “clientes.txt” ) );
31
32 while ( entrada.hasNext() ) // recibe los valores del archivo
33 {
34 registro.establecerCuenta( entrada.nextInt() ); // lee número de cuenta
35 registro.establecerPrimerNombre( entrada.next() ); // lee primer nombre
36 registro.establecerApellidoPaterno( entrada.next() ); // lee apellido 
paterno
37 registro.establecerSaldo( entrada.nextDouble() ); // lee saldo
38
39 // si el tipo de cuenta es apropiado, muestra el registro
40 if ( debeMostrar( registro.obtenerSaldo() ) )
41 System.out.printf( “%-10d%-12s%-12s%10.2f\n”,
42 registro.obtenerCuenta(), registro.obtenerPrimerNombre(),
43 registro.obtenerApellidoPaterno(), registro.obtenerSaldo() );
44 } // fin de while
45 } // fin de try
46 catch ( NoSuchElementException elementException )
47 {
48 System.err.println( “El archivo no esta bien formado.” );
49 entrada.close();
50 System.exit( 1 );
51 } // fin de catch
52 catch ( IllegalStateException stateException )
53 {
54 System.err.println( “Error al leer del archivo.” );
55 System.exit( 1 );
56 } // fin de catch
57 catch ( FileNotFoundException fileNotFoundException )
58 {
59 System.err.println( “No se puede encontrar el archivo.” );
60 System.exit( 1 );
61 } // fin de catch
62 finally
63 {
64 if ( entrada != null )
65 entrada.close(); // cierra el objeto Scanner y el archivo
66 } // fin de finally
67 } // fin del método leerRegistros
68
69 // usa el tipo de registro para determinar si el registro debe mostrarse
70 private boolean debeMostrar( double saldo )
71 {
72 if ( ( tipoCuenta == OpcionMenu.SALDO_CREDITO )
73 && ( saldo < 0 ) )
74 return true;
75
76 else if ( ( tipoCuenta == OpcionMenu.SALDO_DEBITO )
77 && ( saldo > 0 ) )
78 return true;
17.4 Archivos de texto de acceso secuencial 739
Fig. 17.12 � Programa de consulta de crédito (parte 3 de 4).
79
80 else if ( ( tipoCuenta == OpcionMenu.SALDO_CERO )
81 && ( saldo == 0 ) )
82 return true;
83
84 return false;
85 } // fin del método debeMostrar
86
87 // obtiene solicitud del usuario
88 private OpcionMenu obtenerSolicitud()
89 {
90 Scanner textoEnt = new Scanner( System.in);
91 int solicitud = 1;
92
93 // muestra opciones de solicitud
94 System.out.printf( “\n%s\n%s\n%s\n%s\n%s\n”,
95 “Escriba solicitud”, “ 1 - Lista de cuentas con saldos de cero”,
96 “ 2 - Lista de cuentas con saldos con credito”,
97 “ 3 - Lista de cuentas con saldos con debito”, “ 4 - Finalizar ejecucion” );
98
99 try // trata de recibir la opción del menú 
100 {
101 do // recibe solicitud del usuario
102 {
103 System.out.print( “\n? ” );
104 solicitud = textoEnt.nextInt();
105 } while ( ( solicitud < 1 ) || ( solicitud > 4 ) );
106 } // fin de try
107 catch ( NoSuchElementException elementException )
108 {
109 System.err.println( “Entrada invalida.” );
110 System.exit( 1 );
111 } // fin de catch
112
113 return opciones[ solicitud - 1 ]; // devuelve valor de enum para la opción
114 } // fin del método obtenerSolicitud
115
116 public void procesarSolicitudes()
117 {
118 // obtiene la solicitud del usuario (saldo de cero, con crédito o con débito)
119 tipoCuenta = obtenerSolicitud();
120
121 while ( tipoCuenta != OpcionMenu.FIN )
122 {
123 switch ( tipoCuenta )
124 {
125 case SALDO_CERO:
126 System.out.println( “\nCuentas con saldos de cero:\n” );
127 break;
128 case SALDO_CREDITO:
129 System.out.println( “\nCuentas con saldos con credito:\n” );
130 break;
740 Capítulo 17 Archivos, fl ujos y serialización de objetos
Fig. 17.12 � Programa de consulta de crédito (parte 4 de 4).
131 case SALDO_DEBITO:
132 System.out.println( “\nCuentas con saldos con debito:\n” );
133 break;
134 } // fin de switch
135
136 leerRegistros();
137 tipoCuenta = obtenerSolicitud();
138 } // fin de while
139 } // fin del método procesarSolicitudes
140 } // fin de la clase ConsultaCredito
Fig. 17.13 � Prueba de la clase ConsultaCredito.
Fig. 17.14 � Salida de ejemplo del programa de consulta de crédito de la figura 17.13 (parte 1 de 2).
 1 // Fig. 17.13: PruebaConsultaCredito.java
 2 // Este programa prueba la clase ConsultaCredito.
 3
 4 public class PruebaConsultaCredito
 5 { 
 6 public static void main( String[] args )
 7 {
 8 ConsultaCredito aplicacion = new ConsultaCredito();
 9 aplicacion.procesarSolicitudes();
10 } // fin de main
11 } // fin de la clase PruebaConsultaCredito
Escriba solicitud
 1 - Lista de cuentas con saldos de cero
 2 - Lista de cuentas con saldos con credito
 3 - Lista de cuentas con saldos con debito
 4 - Finalizar ejecucion
? 1
Cuentas con saldos de cero:
300 Pam White 0.00
Escriba solicitud
 1 - Lista de cuentas con saldos de cero
 2 - Lista de cuentas con saldos con credito
 3 - Lista de cuentas con saldos con debito
 4 - Finalizar ejecucion
? 2
Cuentas con saldos con credito:
200 Steve Doe -345.67
400 Sam Stone -42.16
17.4 Archivos de texto de acceso secuencial 741
Para recolectar la información de los registros, se lee todo el archivo completo y se determina si 
cada uno de los registro cumple o no con los criterios para el tipo de cuenta seleccionado . El método 
procesarSolicitudes (líneas 116 a 139 de la figura 17.12) llama al método obtenerSolicitud para 
mostrar las opciones del menú (línea 119), traduce el número introducido por el usuario en un objeto 
OpcionMenu y almacena el resultado en la variable OpcionMenu llamada tipoCuenta. En las líneas 
121 a 138 se itera hasta que el usuario especifique que el programa debe terminar. En las líneas 123 a 
134 se muestra un encabezado para imprimir el conjunto actual de registros en la pantalla. En la lí-
nea 136 se hace una llamada al método leerRegistros (líneas 22 a 67), el cual itera a través del ar-
chivo y lee todos los registros.
La línea 30 del método leerRegistros abre el archivo en modo de lectura con un objeto Scanner. 
El archivo se abrirá en modo de lectura con un nuevo objeto Scanner cada vez que se haga una llamada 
a este método, para que podamos leer de nuevo desde el principio del archivo. En las líneas 34 a 37 se 
lee un registro. En la línea 40 se hace una llamada al método debeMostrar (líneas 70 a 85), para determi-
nar si el registro actual cumple con el tipo de cuenta solicitado. Si debeMostrar devuelve true, el pro-
grama muestra la información de la cuenta. Al llegar al marcador de fin de archivo, el ciclo termina y en 
la línea 65 se hace una llamada al método close de Scanner para cerrar el objeto Scanner y el archivo. 
Observe que esto ocurre en un bloque finally, el cual se ejecutará sin importar que se haya leído o 
no el archivo con éxito. Una vez que se hayan leído todos los registros, el control regresa al método 
procesarSolicitudes y se hace una llamada otra vez al método obtenerSolicitud (línea 137) para 
obtener la siguiente opción de menú del usuario. La figura 17.13 contiene el método main, y llama 
al método procesarSolicitudes en la línea 9.
17.4.4 Actualización de archivos de acceso secuencial
En muchos archivos secuenciales, los datos no se pueden modificar sin el riesgo de destruir otros datos en 
el archivo. Por ejemplo, si el nombre “White” tuviera que cambiarse a “Worthington”, el nombre anterior 
no podría simplemente sobrescribirse, debido a que el nuevo nombre requiere más espacio. El registro 
para White se escribió en el archivo como
300 Pam White 0.00
Si el registro se sobrescribe empezando en la misma ubicación en el archivo que utiliza el nuevo nombre, 
el registro será
300 Pam Worthington 0.00
El nuevo registro es más extenso (tiene más caracteres) que el registro original. Los caracteres más 
allá de la segunda “o” en “Worthington” sobrescribirán el principio del siguiente registro secuencial 
Fig. 17.14 � Salida de ejemplo del programa de consulta de crédito de la figura 17.13 (parte 2 de 2).
Escriba solicitud
 1 - Lista de cuentas con saldos de cero
 2 - Lista de cuentas con saldos con credito
 3 - Lista de cuentas con saldos con debito
 4 - Finalizar ejecucion
? 3
Cuentas con saldos con debito:
100 Bob Jones 24.98
500 Sue Rich 224.62
? 4
742 Capítulo 17 Archivos, fl ujos y serialización de objetos
en el archivo. El problema aquí es que los campos en un archivo de texto (y por ende, los registros) 
pueden variar en tamaño. Por ejemplo, 7, 14, –117, 2074 y 27383 son todos valores int almacenados 
en el mismo número de bytes (4) internamente, pero son campos con distintos tamaños cuando se 
muestran en la pantalla, o se escriben en un archivo como texto. Por lo tanto, los registros en un archi-
vo de acceso secuencial comúnmente no se actualizan por partes. En vez de ello, por lo general se 
sobrescribe todo el archivo. Para realizar el cambio anterior, los registros antes de 300 Pam White 0.00 
se copian a un nuevo archivo, se escribe el nuevo registro (que puede tener un tamaño distinto al que 
está sustituyendo) y se copian los registros después de 300 Pam White 0.00 al nuevo archivo. Es incon-
veniente actualizar sólo un registro, pero razonable si una porción substancial de los registros nece-
sitan actualización.
17.5 Serialización de objetos
En la sección 17.4 demostramos cómo escribir los campos individuales de un objeto RegistroCuen-
ta en un archivo como texto, y cómo leer esos campos de un archivo y colocar sus valores en un ob-
jeto RegistroCuenta en la memoria. En los ejemplos, se usó RegistroCuenta para agregar la infor-
mación de un registro. Cuando las variables de instancia de un objeto RegistroCuenta se enviaban a 
un archivo en disco, se perdía cierta información, como el tipo de cada valor. Por ejemplo, si se lee el 
valor “3” de un archivo, no hay forma de saber si el valor proviene de un int, un String o un double. 
Enun disco sólo tenemos los datos, no la información sobre los tipos. Si el programa que va a leer 
estos datos “sabe” a qué tipo de objeto corresponden, entonces simplemente se leen y se colocan en 
objetos de ese tipo. Por ejemplo, en la sección 17.4.2 sabemos que introduciremos un int (el número 
de cuenta), seguido de dos objetos String (el primer nombre y el apellido paterno) y un double (el 
saldo). También sabemos que estos valores se separan mediante espacios, y sólo se coloca un registro 
en cada línea. Algunas veces no sabremos con exactitud cómo se almacenan los datos en un archivo. 
En tales casos, sería conveniente poder escribir o leer un objeto completo de un archivo. Java cuenta 
con dicho mecanismo, el cual se conoce como serialización de objetos. Un objeto serializado es un 
objeto que se representa como una secuencia de bytes, la cual incluye los datos del objeto, así como 
información acerca del tipo del objeto y los tipos de los datos almacenados en el mismo. Una vez que 
se escribe un objeto serializado en un archivo, se puede leer de ese archivo y deserializarse; es decir, 
la información del tipo y los bytes que representan al objeto y sus datos se puede utilizar para recrear 
el objeto en memoria. 
Observación de ingeniería de software 17.1
El mecanismo de serialización realiza copias exactas de los objetos. Ésta es una forma sim-
ple de clonar objetos sin tener que sobrescribir el método clone de Object.
Las clases ObjectInputStream y ObjectOutputStream
Las clases ObjectInputStream y ObjectOutputStream, que implementan en forma respectiva a las 
interfaces ObjectInput y ObjectOutput, permiten leer/escribir objetos completos de/en un flujo 
(posiblemente un archivo). Para utilizar la serialización con los archivos, inicializamos los objetos 
ObjectInputStream y ObjectOutputStream con objetos flujo que pueden leer y escribir informa-
ción desde/hacia los archivos; objetos de las clases FileInputStream y FileOutputStream, en for-
ma respectiva. La acción de inicializar objetos flujo con otros objetos flujo de esta forma se conoce 
algunas veces como envoltura: el nuevo objeto flujo que se va a crear envuelve al objeto flujo 
especificado como un argumento del constructor. Por ejemplo, para envolver un objeto File-
InputStream en un objeto ObjectInputStream, pasamos el objeto FileInputStream al constructor 
de ObjectInputStream.
17.5 Serialización de objetos 743
Las interfaces ObjectOutput y ObjectInput
La interfaz ObjectOutput contiene el método writeObject, el cual toma un objeto Object como un 
argumento y escribe su información a un objeto OutputStream. Una clase que implementa a la interfaz 
ObjectOutput (como ObjectOutputStream) declara este método y se asegura de que el objeto que se va 
a producir implemente la interfaz Serializable (que veremos en breve). De manera correspondiente, la 
interfaz ObjectInput contiene el método readObject, el cual lee y devuelve una referencia a un objeto 
Object de un objeto InputStream. Una vez que se lee un objeto, su referencia puede convertirse en el tipo 
actual del objeto. Como veremos en el capítulo 27, las aplicaciones que se comunican a través de una red 
(como Internet) también pueden transmitir objetos completos a través de la red.
17.5.1 Creación de un archivo de acceso secuencial mediante 
el uso de la serialización de objetos
En esta sección y en la sección 17.5.2 vamos a crear y manipular archivos de acceso secuencial, usando la 
serialización de objetos. La serialización de objetos que mostraremos aquí se realiza mediante flujos basa-
dos en bytes, de manera que los archivos secuenciales que se creen y manipulen serán archivos binarios. 
Recuerde que, por lo general, los archivos binarios no se pueden ver en los editores de texto estándar. Por 
esta razón, escribimos una aplicación separada que sabe cómo leer y mostrar objetos serializados. Empe-
zaremos por crear y escribir objetos serializados a un archivo de acceso secuencial. El ejemplo es similar al 
de la sección 17.4, por lo que sólo nos enfocaremos en las nuevas características.
Definición de la clase RegistroCuentaSerializable
Para empezar, modificaremos nuestra clase RegistroCuenta de manera que los objetos de esta clase 
puedan serializarse. La clase RegistroCuentaSerializable (figura 17.15) implementa a la interfaz 
Serializable (línea 7), la cual permite serializar y deserializar los objetos de la clase Registro-
CuentaSerializable con objetos ObjectOutputStream y ObjectInputStream, respectivamente. 
La interfaz Serializable es una interfaz de marcado. Dicha interfaz no contiene métodos. Una clase 
que implementa a Serializable se marca como objeto Serializable. Esto es importante, ya que 
un objeto ObjectOutputStream no enviará un objeto como salida a menos que sea un objeto Seria-
lizable, lo cual es el caso para cualquier objeto de una clase que implemente a Serializable.
Fig. 17.15 � La clase RegistroCuentaSerializable para los objetos serializables (parte 1 de 3).
 1 // Fig. 17.15: RegistroCuentaSerializable.java
 2 // La clase RegistroCuentaSerializable para objetos serializables.
 3 package com.deitel.cap17; // empaquetada para reutilizarla
 4
 5 import java.io.Serializable;
 6
 7 public class RegistroCuentaSerializable implements Serializable
 8 {
 9 private int cuenta;
10 private String primerNombre;
11 private String apellidoPaterno;
12 private double saldo;
13
14 // el constructor sin argumentos llama al otro constructor con valores 
predeterminados
15 public RegistroCuentaSerializable() 
16 {
17 this( 0, “”, “”, 0.0 );
18 } // fin del constructor de RegistroCuentaSerializable sin argumentos
744 Capítulo 17 Archivos, fl ujos y serialización de objetos
Fig. 17.15 � La clase RegistroCuentaSerializable para los objetos serializables (parte 2 de 3).
19
20 // el constructor con cuatro argumentos inicializa un registro
21 public RegistroCuentaSerializable(
22 int cta, String nombre, String apellido, double sal )
23 {
24 establecerCuenta( cta );
25 establecerPrimerNombre( nombre );
26 establecerApellidoPaterno( apellido );
27 establecerSaldo( sal );
28 } // fin del constructor de RegistroCuentaSerializable con cuatro argumentos
29
30 // establece el número de cuenta
31 public void establecerCuenta( int cta )
32 {
33 cuenta = cta;
34 } // fin del método establecerCuenta
35
36 // obtiene el número de cuenta
37 public int obtenerCuenta() 
38 { 
39 return cuenta; 
40 } // fin del método obtenerCuenta
41
42 // establece el primer nombre 
43 public void establecerPrimerNombre( String nombre )
44 {
45 primerNombre = nombre;
46 } // fin del método establecerPrimerNombre
47
48 // obtiene el primer nombre 
49 public String obtenerPrimerNombre() 
50 { 
51 return primerNombre; 
52 } // fin del método obtenerPrimerNombre
53
54 // establece el apellido paterno 
55 public void establecerApellidoPaterno( String apellido )
56 {
57 apellidoPaterno = apellido;
58 } // fin del método establecerApellidoPaterno
59
60 // obtiene el apellido paterno
61 public String obtenerApellidoPaterno() 
62 {
63 return apellidoPaterno; 
64 } // fin del método obtenerApellidoPaterno
65
66 // establece el saldo 
67 public void establecerSaldo( double sal )
68 {
69 saldo = sal;
70 } // fin del método establecerSaldo
71
17.5 Serialización de objetos 745
En una clase Serializable, cada variable de instancia debe ser Serializable. Cualquier variable 
de instancia que no sea serializable debe declararse como transient, para indicar que debe ignorarse 
durante el proceso de serialización. De manera predeterminada, todas las variables de tipos primitivos 
son serializables. Para las variables de tipos de referencias, hay que verificar la documentación de la 
clase (y posiblemente de sus superclases) para asegurar que el tipo sea Serializable. Por ejemplo, 
los objetos Stringson Serializable. De manera predeterminada, los arreglos son serializables; 
no obstante, en un arreglo de tipo por referencia, los objetos referenciados tal vez no lo sean. La clase 
RegistroCuentaSerializable contiene los miembros de datos private llamados cuenta, primer-
Nombre, apellidoPaterno y saldo; todos ellos son Serializable. Esta clase también proporciona 
métodos public establecer y obtener para acceder a los campos private.
Escritura de objetos serializados en un archivo de acceso secuencial
Ahora hablaremos sobre el código que crea el archivo de acceso secuencial (figuras 17.16 y 17.17). Aquí 
nos concentraremos sólo en los nuevos conceptos. Como dijimos en la sección 17.2, un programa 
puede abrir un archivo creando un objeto de las clases de flujo FileInputStream o FileOuptutStream. 
En este ejemplo, el archivo se abrirá en modo de salida, por lo que el programa crea un objeto Fi-
leOutputStream (línea 21 de la figura 17.16). El argumento String que se pasa al constructor de 
FileOutputStream representa el nombre y la ruta del archivo que se va a abrir. Los archivos existentes 
que se abren en modo de salida de esta forma se truncan. Elegimos la extensión de archivo .ser para 
los archivos binarios que contienen objetos serializados, pero esto no es obligatorio.
Error común de programación 17.2
Es un error lógico abrir un archivo existente en modo de salida cuando, de hecho, el usua-
rio desea preservar ese archivo. La clase FileOutputStream cuenta con un constructor 
sobrecargado que nos permite abrir un archivo y adjuntar datos al final del mismo. 
Esto preserva el contenido del archivo.
Fig. 17.15 � La clase RegistroCuentaSerializable para los objetos serializables (parte 3 de 3).
Fig. 17.16 � Archivo secuencial creado mediante ObjectOutputStream (parte 1 de 3).
72 // obtiene el saldo
73 public double obtenerSaldo() 
74 { 
75 return saldo; 
76 } // fin del método obtenerSaldo
77 } // fin de la clase RegistroCuentaSerializable
 1 // Fig. 17.16: CrearArchivoSecuencial.java
 2 // Escritura de objetos en forma secuencial a un archivo, con la clase ObjectOutputStream.
 3 import java.io.FileOutputStream;
 4 import java.io.IOException;
 5 import java.io.ObjectOutputStream;
 6 import java.util.NoSuchElementException;
 7 import java.util.Scanner;
 8
 9 import com.deitel.cap17.RegistroCuentaSerializable;
10
11 public class CrearArchivoSecuencial
12 {
13 private ObjectOutputStream salida; // envía los datos a un archivo
746 Capítulo 17 Archivos, fl ujos y serialización de objetos
14
15 // permite al usuario especificar el nombre del archivo
16 public void abrirArchivo()
17 {
18 try // abre el archivo
19 {
20 salida = new ObjectOutputStream(
21 new FileOutputStream( “clientes.ser” ) );
22 } // fin de try
23 catch ( IOException ioException )
24 {
25 System.err.println( “Error al abrir el archivo.” );
26 } // fin de catch
27 } // fin del método abrirArchivo
28
29 // agrega registros al archivo
30 public void agregarRegistros()
31 {
32 RegistroCuentaSerializable registro; // objeto que se va a escribir al archivo
33 int numeroCuenta = 0; // número de cuenta para el objeto registro
34 String primerNombre; // primer nombre para el objeto registro
35 String apellidoPaterno; // apellido paterno para el objeto registro
36 double saldo; // saldo para el objeto registro
37
38 Scanner entrada = new Scanner( System.in );
39
40 System.out.printf( “%s\n%s\n%s\n%s\n\n”,
41 “Para terminar de introducir datos, escriba el indicador de fin de archivo”, 
42 “Cuando se le pida que introduzca los datos.”,
43 “En UNIX/Linux/Mac OS X escriba <ctrl> d y oprima Intro”,
44 “En Windows escriba <ctrl> z y oprima Intro” );
45
46 System.out.printf( "%s\n%s", 
47 “Escriba el numero de cuenta (> 0), primer nombre, apellido y saldo.”,
48 “? ” );
49
50 while ( entrada.hasNext() ) // itera hasta el indicador de fin de archivo
51 {
52 try // envía los valores al archivo
53 {
54 numeroCuenta = entrada.nextInt(); // lee el número de cuenta
55 primerNombre = entrada.next(); // lee el primer nombre
56 apellidoPaterno = entrada.next(); // lee el apellido paterno
57 saldo = entrada.nextDouble(); // lee el saldo
58
59 if ( numeroCuenta > 0 )
60 {
61 // crea un registro nuevo
62 registro = new RegistroCuentaSerializable( numeroCuenta,
63 primerNombre, apellidoPaterno, saldo );
64 salida.writeObject( registro ); // envía el registro como salida
65 } // fin de if
Fig. 17.16 � Archivo secuencial creado mediante ObjectOutputStream (parte 2 de 3).
17.5 Serialización de objetos 747
Fig. 17.16 � Archivo secuencial creado mediante ObjectOutputStream (parte 3 de 3).
66 else
67 {
68 System.out.println(
69 “El numero de cuenta debe ser mayor de 0.” );
70 } // fin de else
71 } // fin de try
72 catch ( IOException ioException )
73 {
74 System.err.println( “Error al escribir en el archivo.” );
75 return;
76 } // fin de catch
77 catch ( NoSuchElementException elementException )
78 {
79 System.err.println( “Entrada invalida. Intente de nuevo.” );
80 entrada.nextLine(); // descarta la entrada para que el usuario intente 
de nuevo
81 } // fin de catch
82
83 System.out.printf( “%s %s\n%s”, “Escriba el numero de cuenta (>0),”,
84 “primer nombre, apellido y saldo.”, “? ” );
85 } // fin de while
86 } // fin del método agregarRegistros
87
88 // cierra el archivo y termina la aplicación
89 public void cerrarArchivo() 
90 {
91 try // cierra el archivo
92 {
93 if ( salida != null )
94 salida.close();
95 } // fin de try
96 catch ( IOException ioException )
97 {
98 System.err.println( “Error al cerrar el archivo.” );
99 System.exit( 1 );
100 } // fin de catch
101 } // fin del método cerrarArchivo
102 } // fin de la clase CrearArchivoSecuencial
Fig. 17.17 � Prueba de la clase CrearArchivoSecuencial (parte 1 de 2).
 1 // Fig. 17.17: PruebaCrearArchivoSecuencial.java
 2 // Prueba de la clase CrearArchivoSecuencial.
 3
 4 public class PruebaCrearArchivoSecuencial
 5 {
 6 public static void main( String[] args )
 7 {
 8 CrearArchivoSecuencial aplicacion = new CrearArchivoSecuencial();
 9
10 aplicacion.abrirArchivo();
11 aplicacion.agregarRegistros();
748 Capítulo 17 Archivos, fl ujos y serialización de objetos
La clase FileOutputStream cuenta con métodos para escribir arreglos tipo byte y objetos byte in-
dividuales en un archivo, pero nosotros queremos escribir objetos en un archivo. Por esta razón, envolve-
mos un objeto FileOutputStream en un objeto ObjectOutputStream, pasando el nuevo objeto Fi-
leOutputStream al constructor de ObjectOutputStream (líneas 20 y 21). El objeto ObjectOutputStream 
utiliza al objeto FileOutputStream para escribir objetos en el archivo. En las líneas 20 y 21 se podría 
lanzar una excepción tipo IOException si ocurre un problema al abrir el archivo (por ejemplo, cuando se 
abre un archivo para escribir en una unidad de disco con espacio insuficiente, o cuando se abre un archi-
vo de sólo lectura para escribir datos). Si es así, el programa muestra un mensaje de error (líneas 23 a 26). 
Si no ocurre una excepción, el archivo se abre y se puede utilizar la variable salida para escribir objetos en 
el archivo.
Este programa asume que los datos se introducen de manera correcta y en el orden de número de 
registro apropiado. El método agregarRegistros (líneas 30 a 86) realiza la operación de escritura. En las 
líneas

Otros materiales

Materiales relacionados