Entrada y salida de archivos H. Tejeda Abril 2016
´Indice 1. Introducci´ on
1
2. Clases Path y Files
3
3. Organizaci´ on de archivos, flujos, y b´ ufers de datos
10
4. Clases entrada/salida
12
5. Archivos secuenciales de datos
17
6. Archivos de acceso aleatorio
21
7. Escritura de registros en archivos de acceso aleatorio
23
8. Lectura de registros en archivos de acceso aleatorio
27
Se da una introducci´ on de como se guarda la informaci´on en un sistema de c´omputo, y los medios que este tiene para llevarlo a cabo. Despu´es se comentan algunas clases que pueden ser usadas para realizar algunas tareas con archivos.
1.
Introducci´ on
Los elementos de datos pueden ser guardados en dos tipos de dispositivos de almacenamiento en un sistema de c´ omputo: Almacenamiento vol´ atil es temporal; valores que son volatiles, como los que son guardados en variables, se pierden cuando la computadora deja de ser energizada. RAM (random
1
access memory) es el almacenamiento temporal dentro de una computadora. Tambi´en se le conoce como memoria de almacenamiento vol´atil. Almacenamiento no vol´ atil es almacenamiento permanente; no se pierde al desenergizar la computadora. Cuando se escribe un programa Java y se guarda en el disco, se est´a usando almacenamiento permanente. Un archivo de computadora es una colecci´on de datos guardados en un dispositivo no vol´ atil. El archivo existe en dispositivos de almacenamiento permanente, tales como discos duros, discos ZIP, memorias USB, carretes de cinta magn´etica, discos compactos, etc. Se pueden clasificar los archivos por la forma como estos guardan los datos: Archivos de texto contienen datos que pueden ser le´ıdos por un editor de texto porque los datos han sido codificados usando un esquema como ASCII o Unicode. Algunos archivos de texto son archivos de datos como uno que tenga una n´omina con n´ umero de empleado, nombre y salario; otros archivos son archivos de programa o archivos de aplicaci´ on que guardan instrucciones de software. Archivos binarios contienen datos que no han sido codificados como texto. Ejemplos de estos archivos son im´ agenes, m´ usica, archivos de programa compilados. Los archivos tienen muchas caracter´ısticas com´ unes—cada archivo tiene un tama˜ no que indica el espacio que ocupa en una secci´ on del disco u otro dispositivo de almacenamiento, y cada archivo tiene un nombre y un tiempo espec´ıfico de creaci´on. Cuando se guarda un archivo permanente, este se puede poner en el directorio principal o ra´ ız del dispositivo de almacenamiento. Para una mejor organizaci´on los sistemas de c´omputo organizan sus archivos en carpetas o directorios. Se pueden crear directorios dentro de otros para formar una jerarqu´ıa. La jerarqu´ıa de directorios en el cual reside un archivo es su camino. Por ejemplo, el siguiente es el camino completo para un archivo de Linux llamado nano, el cual est´a en el directorio bin, y este a su vez dentro de usr: /usr/bin/nano En el sistema operativo de las ventanas, la diagonal invertida (\) es el delimitador de camino. En los sistemas operativos UNIX o variantes se usa una diagonal (/) como delimitador. Cuando se trabaja con archivos en una aplicaci´on, se realizan con estos las siguientes tareas: Determinar si un camino o archivo existe. Abrir un archivo. Escribir a un archivo. Leer desde un archivo.
2
Cerrar un archivo. Borrar un archivo. Java da clases incorporadas que contienen m´etodos para realizar estas tareas.
2.
Clases Path y Files
Se usa la clase Path para crear objetos que contengan informaci´on acerca de archivos y directorios, tal como su lugar, tama˜ no, fecha de creaci´on, y si existen. Se usa la clase Files para realizar operaciones en archivos y directorios, como borrar, conocer sus atributos, creaci´on de flujos de entrada y salida. Para usar las clases Path y Files se puede usar la siguiente sentencia al inicio del programa: import java.nio.file.*; El paquete nio (new input/output) agrupa las clases desarrolladas bastante despu´es de las primeras versiones de Java. Las clases Path y Files son nuevas en Java 7; reemplazan la funcionalidad de la clase File usada en versiones m´ as viejas de Java.
Crear un camino Para crear un camino, primero se debe determinar el sistema de archivos por defecto en la computadora anfitri´ on usando una sentencia como la siguiente: FileSystem fs = FileSystems.getDefault(); La sentencia crea un objeto FileSystem usando el m´etodo getDefault() de la clase FileSystems, se est´an usando dos clases diferentes. La clase FileSystem se usa para declarar el objeto y FileSystems es una clase que contiene m´ etodos de f´ abrica que ayudan en la creaci´on del objeto. Despu´es de crear un objeto FileSystem, se puede definir una Path (ruta) usando el m´etodo getPath(): Path ruta = fs.getPath("/usr/bin/nano"); Si se tiene que usar la diagonal invertida (backslash) para construir la ruta esta deber´a escaparse en la sentencia donde se use. Otra forma de hacerlo ser´ıa concatenar cadenas con los nombres de directorios y archivos e intercalar llamadas al m´etodo de instancia getSeparator() con un objeto FileSystem: Path rutaArchivo = fs.getPath(fs.getSeparator()+"usr"+fs.getSeparator()+ "bin"+fs.getSeparator()+nano); 3
Otra forma para crear un Path es usar la clase Paths. La clase Paths es una clase de ayuda que evita crea un objeto FileSystem. El m´etodo get() de Paths llama al m´etodo getPath() del sistema de archivos. Se puede crear un objeto Path usando la siguiente sentencia: Path rutaArchivo = Paths.get("/usr/bin/nano"); Cuando una aplicaci´ on declara un camino y se quiere usar la aplicaci´on con un archivo diferente, se podr´ıa cambiar solo el String pasado al m´etodo de instanciaci´on. Cada Path es absoluto o relativo. Un camino absoluto es un camino completo; no necesita otra informaci´on para localizar un archivo. Un camino relativo depende del directorio de trabajo para la localizaci´ on. Una ruta simple como ArchivoEjemplo.txt es relativo. Cuando se trabaja con un camino que contiene s´ olo una archivo, se asume que el archivo est´a en el mismo directorio. De igual forma cuando se refiere a un camino relativo como datos/ArchivoEjemplo.txt, se asume que el directorio datos es un subdirectorio del directorio actual, y ArchivoEjemplo.txt se asume que est´a dentro de ese folder.
Recuperaci´ on de informaci´ on acerca de un camino En el cuadro 1 se resumen varios m´etodos Path u ´tiles. El m´etodo toString() de la clase Object es anulado; haciendo que devuelva un String de la representaci´on de Path. El m´etodo getFileName() devuelve la u ´ltima sentencia en una lista de caminos; la mayor´ıa de las veces es un archivo, pero podr´ıa ser el nommbre de una carpeta. M´ etodo String toString() Path getFileName() int getNameCount() Path getName(int)
Descripci´ on Devuelve la representaci´on String del Path. Regresa el archivo o directorio dado por este Path; este es el u ´ltimo elemento en la secuencia de elementos nombre. Devuelve la cantidad de elementos nombre en el Path. Regresa el nombre en la posici´on del Path indicado por el par´ametro entero. Cuadro 1: Algunos m´etodos de la clase Path
Un elemento de Path es accedido por un ´ındice. El elemento de la cima en la estructura directorio est´a localizado en el ´ındice cero; el elemento m´as bajo en la estructura es accedido por el m´etodo getName() y tiene un ´ındice que es uno menos que la cantidad de elementos en la lista. Para saber la cantidad de elementos en la lista usar el m´etodo getNameCount() y para recuperar el nombre en la posici´on indicada por el argumento usar getNameCount. En el c´odigo 1 se tiene un programa que crea un Path y usa algunos de los m´etodos del cuadro 1.
4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
import j a v a . n i o . f i l e . ∗ ; public c l a s s DemoPath { public s t a t i c void main ( S t r i n g [ ] a r g s ) { Path caminoArch = Paths . g e t ( ” / e t c / passwd ” ) ; int c o n t a d o r = caminoArch . getNameCount ( ) ; System . out . p r i n t l n ( ” El camino e s ” + caminoArch . t o S t r i n g ( ) ) ; System . out . p r i n t l n ( ” El nombre d e l a r c h i v o e s ” + caminoArch . getName ( contador − 1 ) ) ; System . out . p r i n t l n ( ”Hay ” + c o n t a d o r + ” e l e m e n t o s en e l camino d e l a r c h i v o ” ) ; f o r ( int x = 0 ; x < c o n t a d o r ; ++x ) System . out . p r i n t l n ( ”\ tElemento ” + x + ” e s ” + caminoArch . getName ( x ) ) ; } }
C´ odigo 1: La clase DemoPath. Al ejecutar el programa DemoPath usando el archivo /etc/passwd se obtiene la siguiente salida: $ java DemoPath El camino es /etc/passwd El nombre del archivo es passwd Hay 2 elementos en el camino del archivo Elemento 0 es etc Elemento 1 es passwd
Conversi´ on de un camino relativo a absoluto El m´etodo toAbsolutePath() convierte un camino relativo a uno absoluto. El c´odigo 2 muestra una aplicaci´ on que pide al usuario un nombre de archivo y, si se puede, lo convierte a un camino absoluto. 1 2 3 4 5 6 7 8 9 10 11 12 13 14
import j a v a . u t i l . Scanner ; import j a v a . n i o . f i l e . ∗ ; public c l a s s DemoPath2 { public s t a t i c void main ( S t r i n g [ ] a r g s ) { S t r i n g nombre ; Scanner t e c l a d o = new Scanner ( System . i n ) ; System . out . p r i n t ( ” I n g r e s a r e l nombre de un a r c h i v o >> ” ) ; nombre = t e c l a d o . n e x t L i n e ( ) ; Path caminoDado = Paths . g e t ( nombre ) ; Path caminoCompleto = caminoDado . t o A b s o l u t e P a t h ( ) ; System . out . p r i n t l n ( ” El camino completo e s ” + caminoCompleto . t o S t r i n g ( ) ) ; } }
C´ odigo 2: La clase DemoPath2. Cuando se ejecuta la aplicaci´ on DemoPath2, el nombre ingresado podr´ıa representar un camino 5
relativo. Si la entrada representa un camino absoluto, no se modifica la entrada, caso contrario el programa crea un camino absoluto asignando al archivo el directorio actual. Enseguida se muestra una ejecuci´on del programa. $ java DemoPath2 Ingresar el nombre de un archivo >> DemoPath.java El camino completo es /home/fismat/DemoPath.java
Revisar accesibilidad de un archivo Para saber si un archivo existe y que el programa pueda accederlo como se requiera, se puede usar el m´etodo checkAccess(). La siguiente sentencia import permite el acceso a constantes que pueden ser usadas como argumentos al m´etodo: import static java.nio.file.AccessMode.*; Mediante un objeto Path, referido por archivo, la sintaxis para usar el m´etodo checkAccess() es la siguiente: archivo.getFileSystem().provider().checkAccess(); Cualquiera de los siguientes argumentos puede ser usado con el m´etodo checkAccess(): Sin argumentos—Revisa que el archivo exista; como una alternativa, se puede sustituir el m´etodo Files.exists() y pasarle un argumento Path. READ—Revisa que el archivo exista y que tenga permiso de lectura. WRITE—Revisa que el archivo exista y que tenga permiso de escritura. EXECUTE—Revisa que el archivo exista y que tenga permiso de ejecuci´on. Nota. La caracter´ıstica static import de Java se logra cuando se pone la palabra reservada static entre import y el nombre del paquete siendo importado. Esta caracter´ıstica permite usar constantes static sin el nombre nombre de su clase. Por ejemplo, si se quita static de la sentencia import para java.nio.file.AccessMode, se deber´a referir a la constante READ por su nombre completo como AccessMode.READ; cuando se usa static, se puede referir a esta s´olo como READ.
Se pueden usar argumentos m´ ultiples con el m´etodo checkAccess(), separados por comas. Si el archivo no puede ser accedido como se indica en la llamada del m´etodo, una IOException es lanzada. El c´ odigo 3 muestra una aplicaci´on que declara un Path y revisa si el archivo puede ser le´ıdo y ejecutado. En la l´ınea 3 del c´ odigo 3, se debe importar el paquete java.io.IOException porque una excepci´ on podr´ıa ser instanciada y lanzada.
6
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
import j a v a . n i o . f i l e . ∗ ; import s t a t i c j a v a . n i o . f i l e . AccessMode . ∗ ; import j a v a . i o . IOException ; public c l a s s DemoPath3 { public s t a t i c void main ( S t r i n g [ ] a r g s ) { Path a r c h i v o = Paths . g e t ( ” / e t c / passwd ” ) ; System . out . p r i n t l n ( ” Path e s ” + a r c h i v o . t o S t r i n g ( ) ) ; try { a r c h i v o . g e t F i l e S y s t e m ( ) . p r o v i d e r ( ) . c h e c k A c c e s s ( a r c h i v o ,READ,EXECUTE) ; System . out . p r i n t l n ( ” El a r c h i v o puede s e r l e´ıdo y e j e c u t a d o ” ) ; } catch ( IOException e ) { System . out . p r i n t l n ( ” El a r c h i v o no puede s e r usado por l a a p l i c a c i ´ on” ) ; } } }
C´ odigo 3: La clase DemoPath3. A continuaci´ on se muestra la salida del c´odigo 3 al ser ejecutado. $ java DemoPath3 Path es /etc/passwd El archivo no puede ser usado por la aplicaci´ on Nota. Un programa podr´ıa encontrar a un archivo utilizable, pero luego el archivo podr´ıa hacerse no utilizable antes de ser usado en una sentencia siguiente. Este tipo de error es llamado error TOCTTOU (Time Of Check To Time Of Use).
Borrar un camino El m´etodo delete() de la clase Files acepta un par´ametro Path y borra el u ´ltimo elemento (archivo o directorio) en un camino o lanza una excepci´on si falla el borrado. Por ejemplo: Si se intenta borrar un archivo que no existe, una NoSuchFileException es lanzada. Un directorio se puede borrar cuando est´a vac´ıo. Si se intenta borrar un directorio que contiene archivos, una DirectoryNotEmptyException es lanzada. Si se intenta borrar un archivo pero no se tiene permiso, una SecurityException es lanzada. Otros errores de entrada/salida provocan una IOException. El c´odigo 4 muestra un programa que muestra un mensaje apropiado en cada uno de los escenarios precedentes despu´es de intentar borrar un archivo.
7
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
import j a v a . n i o . f i l e . ∗ ; import j a v a . i o . IOException ; public c l a s s DemoPath4 { public s t a t i c void main ( S t r i n g [ ] a r g s ) { Path caminoArchivo = Paths . g e t ( ” / e t c / passwd ” ) ; try { F i l e s . d e l e t e ( caminoArchivo ) ; System . out . p r i n t l n ( ” El a r c h i v o o d i r e c t o r i o e s t ´a b o r r a d o ” ) ; } catch ( N o S u c h F i l e E x c e p t i o n e ) { System . out . p r i n t l n ( ”No e x i s t e e l a r c h i v o o d i r e c t o r i o ” ) ; } catch ( DirectoryNotEmptyException e ) { System . out . p r i n t l n ( ” El d i r e c t o r i o no e s t ´a vac´ıo ” ) ; } catch ( S e c u r i t y E x c e p t i o n e ) { System . out . p r i n t l n ( ”No t i e n e p e r m i s o para b o r r a r ” ) ; } catch ( IOException e ) { System . out . p r i n t l n ( ” E x c e p t i o n IO” ) ; } } }
C´ odigo 4: La clase DemoPath4. El m´etodo deleteIfExists() de la clase Files tambi´en puede ser usado para borrar un archivo, pero si el archivo no existe, no se lanza excepci´on.
Determinar atributos de archivo El m´etodo readAttributes() de la clase Files se puede usar para recuperar informaci´on u ´til de un archivo. Se deben dar dos argumentos al m´etodo, un objeto Path y BasicFileAttributes.class, y devuelve una instancia de la clase BasicFileAttributes. Se podr´ıa crear una instancia con una sentencia como la siguiente: BasicFileAttributes atrib = Files.readAttributes(caminoArchivo, BasicFileAttributes.class); Despu´es de crear un objeto BasicFileAttributes, se dispone de un conjunto de m´etodos para recuperar informaci´ on del archivo. Como, el m´etodo size() que da el tama˜ no del archivo en bytes, los m´etodos creationTime() y lastModifiedTime() que dan fechas del archivo. El c´ odigo 5 contiene un programa que usa estos m´etodos.
8
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
import j a v a . n i o . f i l e . ∗ ; import j a v a . n i o . f i l e . a t t r i b u t e . ∗ ; import j a v a . i o . IOException ; public c l a s s DemoPath5 { public s t a t i c void main ( S t r i n g [ ] a r g s ) { Path caminoArchivo = Paths . g e t ( ” / e t c / passwd ” ) ; try { BasicFileAttributes atrib = F i l e s . r e a d A t t r i b u t e s ( caminoArchivo , B a s i c F i l e A t t r i b u t e s . c l a s s ) ; System . out . p r i n t l n ( ” Fecha de c r e a c i ´on : ” + a t r i b . c r e a t i o n T i m e ( ) ) ; ´ l t i m a m o d i f i c a c i ´on : ” + System . out . p r i n t l n ( ” U a t r i b . lastModifiedTime () ) ; System . out . p r i n t l n ( ” Tama˜ n o : ” +a t r i b . s i z e ()+ ” b y t e s ” ) ; } catch ( IOException e ) { System . out . p r i n t l n ( ” E x c e p c i ´on E/S” ) ; } } }
C´ odigo 5: La clase DemoPath5. Los m´etodos de fecha en el programa DemoPath5 devuelven un objeto FileTime que es convertido a una String en la llamada al m´etodo println(). Los objetos FileTime son representados en el siguiente formato: aaaa-mm-ddThh:mm:ss El formato descrito se puede ver en la siguiente salida: $ java DemoPath5 Fecha de creaci´ on: 2013-10-23T15:16:20Z ´ Ultima modificaci´ on: 2013-10-23T15:16:20Z Tama~ no: 5253 bytes Para determinar la relaci´ on de fechas entre dos archivos, se puede usar el m´etodo compareTo(). En el c´odigo 6 se muestra como se podr´ıan comparar las fechas de creaci´on de dos archivos. El m´etodo compareTo() regresa un valor menor que cero si el primer FileTime est´a antes que el argumento de FileTime. Regresar´ a el m´etodo un valor mayor que cero si el primer FileTime es posterior al del argumento y cero si los valores de FileTime son los mismos.
9
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
import j a v a . n i o . f i l e . ∗ ; import j a v a . n i o . f i l e . a t t r i b u t e . ∗ ; import j a v a . i o . IOException ; public c l a s s DemoPath6 { public s t a t i c void ma i n ( S t r i n g [ ] a r g s ) { Path a r c h 1 = Paths . g e t ( ” / e t c / passwd ” ) ; Path a r c h 2 = Paths . g e t ( ” / e t c / h o s t s ” ) ; try { BasicFileAttributes attr1 = F i l e s . r e a d A t t r i b u t e s ( arch1 , B a s i c F i l e A t t r i b u t e s . c l a s s ) ; BasicFileAttributes attr2 = F i l e s . r e a d A t t r i b u t e s ( arch2 , B a s i c F i l e A t t r i b u t e s . c l a s s ) ; FileTime time1 = a t t r 1 . c r e a t i o n T i m e ( ) ; FileTime time2 = a t t r 2 . c r e a t i o n T i m e ( ) ; System . out . p r i n t l n ( ”La f e c h a de c r e a c i ´on de a r c h 1 e s : ” + time1 ) ; System . out . p r i n t l n ( ”La f e c h a de c r e a c i ´on de a r c h 2 e s : ” + time2 ) ; i f ( time1 . compareTo ( time2 ) < 0 ) System . out . p r i n t l n ( ” a r c h 1 f u e c r e a d o a n t e s que a r c h 2 ” ) ; e l s e i f ( time1 . compareTo ( time2 ) > 0 ) System . out . p r i n t l n ( ” a r c h 1 f u e c r e a d o despu´e s que a r c h 2 ” ) ; else System . out . p r i n t l n ( ” a r c h 1 y a r c h 2 f u e r o n c r e a d o s a l mismo tiempo ” ) ; } catch ( IOException e ) { System . out . p r i n t l n ( ” E x c e p c i ´on E/S” ) ; } } }
C´ odigo 6: La clase DemoPath6.
Nota. Java soporta clases especializadas para atributos de archivos DOS y de Windows, como oculto, o s´ olo lectura y atributos de archivos POSIX usados en sistemas como UNIX, donde los archivos tiene due˜ no de grupo.
3.
Organizaci´ on de archivos, flujos, y b´ ufers de datos
Se pueden guardar datos en variables dentro de un programa, pero es temporal el almacenamiento. Por otra parte, cuando la aplicaci´ on termina, las variables dejan de existir y los valores de datos se pierden. Las variables son guardadas en la memoria principal o primaria (RAM) de la computadora. Para retener datos por alguna cantidad significativa de tiempo, se deben guardar los datos en un dispositivo de almacenamiento permanente, secundario, como un disco. La pieza u ´til m´ as peque˜ na de datos para la mayor´ıa de los usuarios es el car´acter. Un car´ acter puede ser cualquier letra, n´ umero, o s´ımbolos especiales (como signos de puntuaci´on) que forman los datos. Los caracteres est´ an formados con bits (los ceros y los unos que representan los valores l´ogicos usados por el sistema de c´ omputo), pero los usuarios no les importa si la representaci´ on de alg´ un car´acter es hecha con alguna combinaci´on de ceros y unos, o con alguna otra. Los usuarios est´an interesados con el significado que pueda tener alg´ un car´acter, trat´andose de alguna letra
10
esta podr´ıa ser una inicial, alg´ un c´ odigo, etc. Un car´acter podr´ıa ser cualquier grupo de bits y no necesariamente representar una letra o un n´ umero; por ejemplo, algunos “caracteres” producen un sonido o controlan el monitor. Tambi´en, los caracteres no son necesariamente creados con una sola tecla; por ejemplo, las secuencias escapadas son usadas para crea el car´acter ’\n’, el cual inicia una nueva l´ınea. En ocasiones, se puede pensar en un car´acter como una unidad de informaci´ on en vez de un dato con una apariencia particular. Por ejemplo, el car´acter matem´atico π y la letra griega pi parecen iguales, pero tienen dos valores Unicode diferentes. En las organizaciones que usan datos, estas agrupan los caracteres en campos. Un campo es un grupo de caracteres que tienen alg´ un significado. Los caracteres J, u, a y n podr´ıan representar el nombre. Otros campos de datos podr´ıan representar elementos como los apellidos, n´ umero de seguridad social, c´ odigo postal, o salario. Los campos est´ an agrupados para formar registros. Un registro es una colecci´on de campos que contienen datos acerca de una entidad. Por ejemplo, el nombre, los apellidos, el n´ umero de seguridad social, el c´odigo postal y el salario representan un registro de una persona. En las clases, vistas previamente, como Empleado o Estudiante, se pueden pensar que los datos guardados en cada una de estas son un registro. Estas clases contienen variables individuales que representan los campos de datos. Los registros son agrupados para crear archivos. Archivos de datos estructurados consisten de registros relacionados entre ellos, tal como un archivo del personal de una empresa que contiene un registro por cada empleado. La cantidad de registros de un archivo puede ser de unos cuantos registros hasta de millones. Un archivo de datos puede ser usado como un archivo de acceso secuencial cuando cada registro es accedido uno despu´es de otro en el orden en el cual fueron guardados. La mayor´ıa de las veces, cada registro es guardado en orden usando el valor de alg´ un campo, por ejemplo por el n´ umero de seguridad social. Cuando los registros no son usados en secuencia, el archivo es usado como archivo de acceso aleatorio. Cuando los registros est´ an guardados en archivo de datos, sus campos pueden estar organizados en una l´ınea, o un car´ acter puede ser usado para separarlos. Un archivo de valores separados por coma o CSV es uno en el cual cada valor en un registro est´a separado del siguiente por una coma. Este formato es empleado en bases de datos y hojas de c´alculo. Antes de que una aplicaci´ on pueda usar un archivo de datos, esta deber´a abrir el archivo. Una aplicaci´on Java abre un archivo creando un objeto y asoci´andole un flujo de bytes. De igual manera, cuando se termina de usar un archivo, el programa deber´a cerrar el archivo para que ya no siga disponible para la aplicaci´on. Si se falla al cerrar un archivo de entrada, uno en el cual se est´an leyendo datos, usualmente no hay consecuencias; los datos todav´ıa existir´an en el archivo. Pero si se falla al cerrar un archivo de salida, uno en el cual se est´an escribiendo datos, los datos podr´ıan hacerse inaccesibles. Siempre se deber´a cerrar cada archivo que se abra, y se deber´a cerrar tan pronto como ya no se ocupe. Cuando se deja un archivo abierto sin raz´on, este consume recursos del sistema, y el rendimiento del sistema decrece. Podr´ıa ser tambi´en que otro programa este esperando para usar el archivo, como en una red de computadoras. Java ve un archivo de registros como una serie de bytes. Cuando se realiza una operaci´ on de entrada en una aplicaci´ on, esto se puede ver como bytes yendo en el programa desde un dispositivo de entrada a trav´es de un flujo o stream, el cual funciona como una tuber´ıa o canal. Cuando se realiza salida, algunos bytes salen fuera de la aplicaci´on a trav´es de otro flujo a un dispositivo de salida. Un flujo es un objeto, y al igual que otros objetos, los flujos tienen datos y m´etodos. Los 11
m´etodos permiten realizar acciones como abrir, cerrar, leer, y escribir. La mayor´ıa de los flujos van en una sola direcci´on, cada flujo es de entrada o salida. Se podr´ıan abrir varios flujos simult´ aneamente en una aplicaci´on. Las operaciones de entrada y salida son las m´as lentas en cualquier sistema de c´omputo por las limitaciones impuestas por el hardware. Por esa raz´on, los programadores profesionales frecuentemente usan b´ ufers. Un b´ ufer es una localidad de memoria donde los bytes est´an guardados despu´es de ser l´ogicamente escritos pero antes de ser enviados al dispositivo. Usar un b´ ufer para acumular entrada o salida antes de realizar el comando de E/S mejora el rendimiento del programa. Cuando se usa un b´ ufer de salida, en ocasiones se vac´ıa antes de cerrarlo. El vaciar un b´ ufer de memoria hace que se limpie cualquier byte que haya sido enviado a un b´ ufer para salida pero que todav´ıa no ha sido sacado a un dispositivo de hardware.
4.
Clases entrada/salida
Enseguida se muestra una relaci´ on jer´arquica parcial de algunas de las clases Java usadas para operaciones de entrada y salida (E/S); este muestra que InputStream, OutputStream, y Reader son subclases de la clase Object. Estas tres subclases son abstractas teniendo m´etodos que deben ser anuladas en sus clases hijas. Las capacidades de estas clases est´an resumidas en cuadro 2. Object | +--InputStream | | | +--FileInputStream | | | +--FilterInputStream | | | +--BufferedInputStream | +--OutputStream | | | +--FileOutputStream | | | +--FilterOutputStream | | | +--BufferedOutputStream | | | +--PrintStream | +--Reader | +--BufferedReder | +--BufferedWriter
12
Clase InputStream FileInputStream
Descripci´ on Clase abstracta que contiene m´etodos para realizar entrada. Hija de InputStream que tiene capacidad para leer de los archivos de disco. FilterInputStream Contiene algunos otros flujos de entrada, los cuales son usados como la fuente base de datos, posiblemente transformando los datos, o agregando funcionalidad adicional. BufferedInputStream Hija de FilterInputStream, que a su vez es hija de InputStream; BufferedInputStream maneja la entrada del dispositivo de entrada est´andar (o defecto) de un sistema, generalmente el teclado. OutputSream Clase abstracta que contiene m´etodos para realizar la salida. FileOutputStream Hija de OutputStream que permite escribir a archivos de disco. FilterOutputStream Es la superclase de todas las clases que filtran los flujos de salida. Estos flujos est´an en la cima de alg´ un flujo de salida ya existente (el flujo de salida subyacente) el cual lo usa como su drenaje b´ asico de datos, pero posiblemente transformando los datos o proporcionando funcionalidad adicional. BufferedOutputStream Hija de FilterOutputStream, la cual a su vez es hija de OutputStream; BufferedOutputStream maneja la salida del dispositivo de salida est´andar (o defecto) de un sistema, usualmente el monitor. PrintStream Hija de FilterOutputStream, la cual es a su vez hija de OutputStream; System.out es un objeto PrintStream. Reader Clase abstracta para leer flujos de caracteres; los u ´nicos m´etodos que una subclase debe implementar son read(char[], int, int) y close(). BufferedReader Lee texto de un flujo de caracteres de entrada, almacenando en el b´ ufer caracteres para contar con lectura eficiente de caracteres, arreglos, y l´ıneas. BufferedWriter Escribe texto a un flujo de caracteres de entrada, almacenando en el b´ ufer caracteres para contar con escritura eficiente de caracteres, arreglos, y l´ıneas. Cuadro 2: Descripci´ on de las clases seleccionadas usadas para entrada y salida
13
La clase OutputStream puede ser usada para producir salida. El cuadro 3 muestra algunos de los m´etodos comunes de la clase. Se puede usar OutputStream para escribir todos los bytes de un arreglo o una parte. Cuando se termina de usar un OutputStream, se debe vaciar y cerrar. M´ etodo OutputStream void close() void flush() void write(btye[] b) void write(btye[] b, int desp, int tam)
Descripci´ on Cierra el flujo de salida y libera cualquier recurso del sistema asociado con el flujo. Vac´ıa el flujo de salida; si algunos bytes est´an en el buffer, estos son escritos. Escribe todos los bytes al flujo de salida del arreglo de byte especificado. Escribe bytes al flujo de salida del arreglo especificado de byte iniciando en la posici´on desp para una cantidad tam de caracteres.
Cuadro 3: Descripci´ on de las clases seleccionadas usadas para entrada y salida La clase System de Java contiene un objeto PrintStream llamado System.out, el cual ha sido usado con los m´etodos print() y println(). La clase System tambi´en define otro objeto PrintStream llamado System.err. La salida de ambos objetos PrintStream pueden ir al mismo dispositivo, como sucede habitualmente. System.err es reservado para mensajes de error y System.out es para salida v´alida. Estos se puede redirigir a otra localidad, como un archivo de disco o una impresora. Se puede crear un objeto OutputStream propio y asignarlo a System.out, pero no se necesita hacer. En el c´odigo 7 se muestra como se hace. Para esto la aplicaci´on SalidaAPantalla declara una String de calificaciones de letra permitidas en un curso. Luego, el m´etodo getBytes() convierte la String a un arreglo byte. Un objeto OutputStream es declarado, y System.out le es asignado en un bloque try. El m´etodo write() acepta el arreglo byte y lo manda al dispositivo de salida, y luego el flujo de salida es vaciado y cerrado. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
import j a v a . i o . ∗ ; public c l a s s S a l i d a A P a n t a l l a { public s t a t i c void main ( S t r i n g [ ] a r g s ) { S t r i n g s = ”ABCDEF” ; byte [ ] d a t o s = s . g e t B y t e s ( ) ; OutputStream s a l i d a = null ; try { s a l i d a = System . out ; s a l i d a . write ( datos ) ; salida . flush (); salida . close (); } catch ( E x c e p t i o n e ) { System . out . p r i n t l n ( ” Mensaje : ” + e ) ; } } }
C´ odigo 7: La clase SalidaAPantalla.
14
Escribir a un archivo En vez de asignar el dispositivo de salida est´andar al OutputStream, como se hace en la aplicaci´ on SalidaAPantalla, c´ odigo 7, se puede hacer la asignaci´on a un archivo. Para realizarlo se construye un objeto BufferedOutputStream y se asigna a la referencia OutputStream. Si se quiere cambiar un dispositivo de salida de una aplicaci´on solo se requiere modificar este para asignar un nuevo objeto al OutputStream. Java permite asignar un archivo a un objeto Stream as´ı la pantalla de salida y el archivo de salida trabajan en la misma forma. Se puede crear un archivo escribible usando el m´etodo newOutputStream() de la clase Files y pas´andole un argumento Path y una StandardOpenOption. El m´etodo crea un archivo si este todav´ıa no exist´ıa, abre el archivo para escritura, y regresa un OutputStream que puede ser usado para escribir bytes al archivo. El cuadro 4 muestra los argumentos StandardOpenOption que pueden usarse como el segundo argumento del m´etodo newOutputStream. Si no se indica alguna opci´ on y el archivo no existe, se crea un archivo nuevo, y si el archivo existe, este se trunca, lo anterior es equivalente a usar las opciones CREATE y TRUNCATE EXISTING. Si se quiere agregar a un archivo existente, o crear el archivo si este no existe inicilmente usar las opciones CREATE y APPEND. Si se usa solo APPEND y el archivo no existe, se genera una excepci´on. StandardOpenOption WRITE APPEND TRUNCATE EXISTING CREATE NEW CREATE DELETE ON CLOSE
Descripci´ on Abre el archivo para escritura. Agrega nuevos datos al final del archivo; usar esta opci´on con WRITE o CREATE. Trunca el archivo existente a cero bytes as´ı el contenido del archivo es reemplazado; usar esta opci´on con WRITE. Crea un nuevo archivo s´olo si este no existe; lanzando una excepci´ on si el archivo ya existe. Abre un archivo si existe o crea uno nuevo si no existe. Borra el archivo cuando el flujo es cerrado; empleada con archivos temporales los existen mientras se ejecuta el programa.
Cuadro 4: Constantes StandardOpenOption seleccionadas. La aplicaci´on SalidaAArchivo, c´ odigo 8, escribe una String de bytes a un archivo. Las diferencias respecto a la aplicaci´ on SalidaAPantalla, c´odigo 7, se resumen enseguida: Se usan sentencias adicionales import, l´ıneas 2 y 3. El nombre de la clase est´ a cambiado, l´ınea 4. Un Path es declarado para el archivo grados.txt, l´ınea 6. En vez de asignar System.out a la referencia OutputStream, un objeto BufferedOutputStream es asignado, l´ınea 11.
15
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
import j a v a . i o . ∗ ; import j a v a . n i o . f i l e . ∗ ; import s t a t i c j a v a . n i o . f i l e . StandardOpenOption . ∗ ; public c l a s s S a l i d a A A r c h i v o { public s t a t i c void main ( S t r i n g [ ] a r g s ) { Path a r c h i v o = Paths . g e t ( ” g r a d o s . t x t ” ) ; S t r i n g s = ”ABCDEF” ; byte [ ] d a t o s = s . g e t B y t e s ( ) ; OutputStream s a l i d a = null ; try { s a l i d a = new BufferedOutputStream ( F i l e s . newOutputStream ( a r c h i v o , CREATE) ) ; s a l i d a . write ( datos ) ; salida . flush (); salida . close (); } catch ( E x c e p t i o n e ) { System . out . p r i n t l n ( ” Mensaje : ” + e ) ; } } }
C´ odigo 8: Aplicaci´ on SalidaAArchivo.
Leer desde un archivo Se usa un InputStream de forma similar a un OutputStream. Si se quiere, se puede crear un InputStream, asign´ andole System.in, y usando el m´etodo read() con el objeto creado para recuperar la entrada del teclado. Sin embargo, es m´as eficiente usar la clase Scanner para la entrada del teclado y mejor usar la clase InputStream para entrada de datos que han sido guardados en un archivo. Se puede usar el m´etodo newInputStream() de la clase Files para abrir un archivo para lectura. El m´etodo acepta un par´ ametro Path y devuelve un flujo que puede leer bytes de un archivo. La aplicaci´on LeerArchivo, c´ odigo 9, lee el archivo grados.txt que fue creado previamente. El Path es declarado, un InputStream es declarado usando el Path, y en la l´ınea 8 un flujo es asignado a la referencia InputStream. Nota. Si se necesitan leer m´ ultiples l´ıneas del archivo se podr´ıa usar un ciclo tal como el mostrado enseguida. El ciclo contin´ uamente lee y muestra l´ıneas del archivo hasta que el m´etodo readLine() regresa null para indicar que no hay m´as datos disponibles.
while (s = lector.readLine() != null) System.out.println(s); En la l´ınea 9 de la clase LeerArchivo, un BufferedReader es declarado. Un BufferedReader lee una l´ınea de texto de un flujo entrada-car´acter, almacenar caracteres en el b´ ufer hace que la lectura sea m´as eficiente. 16
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
import j a v a . i o . ∗ ; import j a v a . n i o . f i l e . ∗ ; public c l a s s L e e r A r c h i v o { public s t a t i c void main ( S t r i n g [ ] a r g s ) { Path a r c h i v o = Paths . g e t ( ” g r a d o s . t x t ” ) ; InputStream e n t r a d a = null ; try { e n t r a d a = F i l e s . newInputStream ( a r c h i v o ) ; B u f f e r e d R e a d e r l e c t o r = new B u f f e r e d R e a d e r ( new InputStreamReader ( e n t r a d a ) ) ; S t r i n g s = null ; s = l e c t o r . readLine ( ) ; System . out . p r i n t l n ( s ) ; lector . close (); } catch ( E x c e p t i o n e ) { System . out . p r i n t l n ( ” Mensaje : ” + e ) ; } } }
C´ odigo 9: Clase LeerArchivo. Cuando se usa la clase BufferedReader, se debe importar del paquete java.io en el programa. La siguiente tabla muestra algunos m´etodos u ´tiles de BufferedReader. M´etodo BufferedReader close() read() read(char[] buffer, int de, int can) readLine() skip(long n)
Descripci´ on Cierra el flujo y cualquier recurso asociado con este. Lee un s´olo car´acter. Lee caracteres en una porci´on de un arreglo desde la posici´on de para can caracteres. Lee una l´ınea de texto. Salta el n´ umero especificado de caracteres.
Cuadro 5: M´etodos BufferedReader.
5.
Archivos secuenciales de datos
En ocasiones se quiere guardar algo m´ as que un String en un archivo. Se podr´ıa tener un archivo de datos de registros personales que incluyan un n´ umero de identificaci´on, un nombre, y un salario para cada empleado de una organizaci´ on. La aplicaci´on EscribirArchivoEmpleado, c´odigo 10, lee n´ umeros de identificaci´ on, nombres y salarios desde el teclado y los manda, separados con comas, a un archivo. 1 2 3 4 5 6
import j a v a . n i o . f i l e . ∗ ; import j a v a . i o . ∗ ; import s t a t i c j a v a . n i o . f i l e . StandardOpenOption . ∗ ; import j a v a . u t i l . Scanner ; public c l a s s E s c r i b i r A r c h i v o E m p l e a d o { public s t a t i c void main ( S t r i n g [ ] a r g s ) {
17
Scanner e n t r a d a = new Scanner ( System . i n ) ; Path a r c h i v o = Paths . g e t ( ” empleados . t x t ” ) ; S t r i n g s = ”” ; String delimitador = ” ,” ; int i d ; S t r i n g nombre ; double s a l a r i o ; f i n a l int SALIR = 9 9 9 ; try { OutputStream s a l i d a = new BufferedOutputStream ( F i l e s . newOutputStream ( a r c h i v o , CREATE) ) ; B u f f e r e d W r i t e r e s c r i t o r = new B u f f e r e d W r i t e r (new OutputStreamWriter ( s a l i d a ) ) ; System . out . p r i n t ( ” I n g r e s a r n´ u mero i d e n t i f i c a d o r >> ” ) ; id = entrada . nextInt ( ) ; while ( i d != SALIR ) { System . out . p r i n t ( ” I n g r e s a r nombre d e l empleado #” + i d + ” >> ” ) ; entrada . nextLine ( ) ; nombre = e n t r a d a . n e x t L i n e ( ) ; System . out . p r i n t ( ” I n g r e s a r s a l a r i o >> ” ) ; s a l a r i o = e n t r a d a . nextDouble ( ) ; s = i d + d e l i m i t a d o r + nombre + d e l i m i t a d o r + s a l a r i o ; e s c r i t o r . write ( s , 0 , s . length ( ) ) ; e s c r i t o r . newLine ( ) ; System . out . p r i n t ( ” I n g r e s a r s i g u i e n t e n´ u mero i d e n t i f i c a d o r o ” + SALIR + ” para t e r m i n a r >> ” ) ; id = entrada . nextInt ( ) ; } escritor . close (); } catch ( E x c e p t i o n e ) { System . out . p r i n t l n ( ” Mensaje : ” + e ) ; }
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
} }
C´ odigo 10: La clase EscribirArchivoEmpleado. En el c´odigo 10 del programa EscribirArchivoEmpleado en las l´ıneas 18—19 se crea un BufferedWriter llamado escritor. La clase BufferedWriter es la contraparte de BufferedReader. Esta escribe texto a un flujo de salida, almancenando en el b´ ufer los caracteres. La clase tiene tres m´etodos write() sobrecargados que proporcionan escritura eficiente de caracteres de un String, de arreglos char, y de un car´ acter. El cuadro 6 contiene los m´etodos definidos en la clase BufferedWriter. En la aplicaci´ on EscribirArchivoEmpleado, los String de los datos de empleado son constru´ıdos dentro de un ciclo que se ejecuta mientras el usuario no ingrese el valor SALIR. Cuando el String est´a completo, con n´ umero de identificaci´on, nombre y salario separados con comas, el String es enviado al escritor con lo indicado en la l´ınea 30. El m´etodo write() acepta el String desde la posici´on cero con su tama˜ no completo. Despu´es de que el String es escrito, se escribe enseguida el car´acter de nueva l´ınea del sistema. Un archivo de datos podr´ıa no requerir un caracter de nueva l´ınea despu´es de cada registro, cada 18
M´etodo BufferedWriter close() flush() newline() write(String s, int de, int can) write(char[] a, int de, int can) write(int c)
Descripci´ on Cierra el flujo, vaci´andolo primero. Vac´ıa el flujo. Escribe un separador de l´ınea. Escribe un String desde la posici´on de para una longitud can. Escribe un arreglo char desde la posici´on de para una longitud can. Escribe un s´olo car´acter.
Cuadro 6: M´etodos BufferedWriter. nuevo registro podr´ıa estar separado con una coma o cualquier otro car´acter u ´nico que no fue usado como parte de los datos, el usar nueva l´ınea facilita la lectura del archivo. Como no todas las plataformas usan ’\n’ para separar las l´ıneas, la clase BufferedWriter contiene el m´etodo newLine() que usa el separador de l´ınea actual de la plataforma. Tambi´en se podr´ıa escribir el valor de System.getProperty("line.separator"), que es como lo hace el m´etodo newLine(). Cualquiera de los m´etodos de entrada o salida en el programa EscribirArchivoEmpleado podr´ıa lanzar una excepci´ on, as´ı que este c´ odigo es puesto en bloque try. La aplicaci´on LeerArchivoEmpleado, c´odigo 11, lee el archivo empleado.txt creado por la aplicaci´ on EscribirArchivoEmpleado. El programa declara un InputStream para el archivo, luego crea un BufferedReader usando el InputStream. La primera l´ınea es le´ıda en un String; mientras el m´etodo readLine() no devuelva null, el String es mostrado y una nueva l´ınea es le´ıda. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
import j a v a . i o . ∗ ; import j a v a . n i o . f i l e . ∗ ; public c l a s s LeerArchivoEmpleado { public s t a t i c void main ( S t r i n g [ ] a r g s ) { Path a r c h i v o = Paths . g e t ( ” empleados . t x t ” ) ; S t r i n g s = ”” ; try { InputStream e n t r a d a = new B u f f e r e d I n p u t S t r e a m ( F i l e s . newInputStream ( a r c h i v o ) ) ; B u f f e r e d R e a d e r l e c t o r = new B u f f e r e d R e a d e r ( new InputStreamReader ( e n t r a d a ) ) ; s = l e c t o r . readLine ( ) ; while ( s != null ) { System . out . p r i n t l n ( s ) ; s = l e c t o r . readLine ( ) ; } lector . close (); } catch ( E x c e p t i o n e ) { System . out . p r i n t l n ( ” Mensaje : ” + e ) ; } } }
C´ odigo 11: La clase LeerArchivoEmpleado.
19
Otras aplicaciones podr´ıan no querer usar el archivo de datos s´olo como String como lo hace la aplicaci´on LeerArchivoEmpleado. La aplicaci´on LeerArchivoEmpleado2, c´odigo 12, tambi´en lee del archivo String pero las divide en campos usables. El m´etodo split() de la clase String acepta un argumento que identifica el delimitador de campos, para los ejemplos dados, una coma, y regresa un arreglo de String. Cada elemento del arreglo tiene un campo. Luego los m´etodos como parseInt() o parseDouble() pueden ser usados para obtener del String dividido sus respectivos tipos de datos. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
import j a v a . i o . ∗ ; import j a v a . n i o . f i l e . ∗ ; public c l a s s LeerArchivoEmpleado2 { public s t a t i c void main ( S t r i n g [ ] a r g s ) { Path a r c h i v o = Paths . g e t ( ” empleados . t x t ” ) ; S t r i n g [ ] a r r e g l o = new S t r i n g [ 3 ] ; S t r i n g s = ”” ; String delimitador = ” ,” ; int i d ; S t r i n g nombre ; double s a l a r i o ; double n e t o ; f i n a l double HORAS SEMANA = 4 0 ; double t o t a l = 0 . 0 ; try { InputStream e n t r a d a = new B u f f e r e d I n p u t S t r e a m ( F i l e s . newInputStream ( a r c h i v o ) ) ; B u f f e r e d R e a d e r l e c t o r = new B u f f e r e d R e a d e r (new InputStreamReader ( e n t r a d a ) ) ; s = l e c t o r . readLine ( ) ; while ( s != null ) { arreglo = s . s p l i t ( delimitador ) ; id = Integer . parseInt ( arreglo [ 0 ] ) ; nombre = a r r e g l o [ 1 ] ; s a l a r i o = Double . p a r s e D o u b l e ( a r r e g l o [ 2 ] ) ; n e t o = s a l a r i o ∗ HORAS SEMANA; System . out . p r i n t l n ( ” i d #” + i d + ” ” + nombre + ” $” + s a l a r i o + ” $” + neto ) ; t o t a l += n e t o ; s = l e c t o r . readLine ( ) ; } lector . close (); } catch ( E x c e p t i o n e ) { System . out . p r i n t l n ( ” Mensaje : ” + e ) ; } System . out . p r i n t l n ( ” El pago t o t a l de l a n´o mina e s $ ” + t o t a l ) ; } }
C´ odigo 12: La clase LeerArchivoEmpleado2. Conforme cada registro es le´ıdo y dividido en la clase LeerArchivoEmpleado2, su campo salario es usado para calcular el pago bruto del empleado usando una jornada de 40 horas a la semana. Luego es pago bruto es acumulado para producir el pago total de la n´omina despu´es de que todos 20
los datos han sido procesados.
6.
Archivos de acceso aleatorio
Los negocios guardan datos en orden secuencial cuando usan los registros para procesamiento por lotes, lo cual involucra realizar las mismas tareas con muchos registros, uno despu´es de otro. Por ejemplo, cuando una empresa genera recibos de pagos, los registros para el per´ıodo de pago son reunidos en un lote y los recibos son calculados e impresos en secuencia. No importa cual recibo sea generado primero porque los recibos son enviados por correo. Un acceso secuencial es ineficiente para muchas aplicaciones. Estas aplicaciones, como aplicaciones en tiempo real, requieren que un registro sea accedido inmediatamente mientras un cliente espera. Un programa en el cual el usuario hace peticiones directas es un programa interactivo. Un representante del servicio al cliente requiere archivos de acceso aleatorio, tambi´en llamados de acceso directo o de acceso instant´ aneo, en vez de archivos de acceso secuencial. Ya que tomar´ıa mucho tiempo acceder los registros de los clientes si estos deben ser le´ıdos secuencialmente. Se puede usar la clase FileChannel para crear archivos de acceso aleatorio. Un objeto canal de archivo es un medio para leer y escribir a un archivo. Un canal de archivo es buscable, es decir, se puede buscar por una localidad espec´ıfica del archivo y las operaciones pueden iniciar en cualquier posici´on especificada. El cuadro 7 muestra algunos m´etodos FileChannel. M´etodo FileChannel FileChannel open(Path arch, OpenOption... opciones) long position() FileChannel position(long nuevaPosicion) int read(ByteBuffer b´ ufer) long size() int write(ByteBuffer b´ ufer)
Descripci´ on Abre o crea un archivo, devolviendo un canal de archivo. Devuelve la posici´on del canal de archivo. Pone la nueva posici´on del canal de archivo. Lee una secuencia de bytes del canal en el b´ ufer. Devuelve el tama˜ no actual del archivo de canal. Escribe una secuencia de bytes al canal desde el b´ ufer.
Cuadro 7: M´etodos FileChannel. Varios m´etodos del cuadro 7 usan un objeto ByteBuffer. Un ByteBuffer es un lugar para bytes que est´an esperando ser le´ıdos o escritos. Un arreglo de bytes puede ser envuelto, o empacado, en un ByteBuffer usando el m´etodo wrap() de ByteBuffer. Envolviendo un arreglo de bytes en un b´ ufer hace que los cambios hechos al b´ ufer tambi´en sean hechos al arreglo, y viceversa. Crear un FileChannel para escritura de datos aleatoria requiere crear un ByteBuffer y otros pasos: Se puede usar el m´etodo newByteChannel() de la clase Files para obtener un ByteChannel para un Path. El m´etodo newByteChannel acepta argumentos Path y StandardOpenOption que indicar como el archivo ser´ a abierto. El ByteChannel regresado por el m´etodo newByteChannel() puede entonces ser convertido a un FileChannel usando una sentencia parecida a la siguiente: 21
FileChannel fc = (FileChannel)Files.newByteChannel(archivo, READ, WRITE); Se puede crear un arreglo byte. El arreglo byte se puede construir desde un String usando el m´etodo getBytes() como sigue: String s = "ABC"; byte[] datos = s.getBytes(); El arreglo byte puede ser envuelto en un ByteBuffer de esta forma: ByteBuffer salida = ByteBuffer.wrap(datos); Luego el ByteBuffer lleno puede ser escrito al FileChannel declarado como en la siguiente instrucci´ on: fc.write(salida); Se puede revisar si el contenido de un ByteBuffer ha sido usado mediante el m´etodo hasRemaining(). Despu´es de escribir el contenido de un ByteBuffer, se puede escribir el mismo contenido del ByteBuffer nuevamente usando el m´etodo rewind() para reposicionar el ByteBuffer al inicio del b´ ufer. La aplicaci´on PruebaAccesoAleatorio, c´odigo 13 usa los pasos previos para declarar un archivo y escribir algunos bytes aleatoriamente en las posiciones 0, 22, y 12. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
import j a v a . i o . ∗ ; import j a v a . n i o . f i l e . ∗ ; import j a v a . n i o . c h a n n e l s . F i l e C h a n n e l ; import j a v a . n i o . B y t e B u f f e r ; import s t a t i c j a v a . n i o . f i l e . StandardOpenOption . ∗ ; public c l a s s P r u e b a A c c e s o A l e a t o r i o { public s t a t i c void main ( S t r i n g [ ] a r g s ) { Path a r c h i v o = Paths . g e t ( ” numeros . t x t ” ) ; crearArchivo ( archivo ) ; S t r i n g s = ”ABC” ; byte [ ] d a t o s = s . g e t B y t e s ( ) ; B y t e B u f f e r s a l i d a = B y t e B u f f e r . wrap ( d a t o s ) ; F i l e C h a n n e l f c = null ; try { f c = ( F i l e C h a n n e l ) F i l e s . newByteChannel ( a r c h i v o , READ, WRITE) ; fc . position (0); while ( s a l i d a . hasRemaining ( ) ) fc . write ( salida ) ; s a l i d a . rewind ( ) ; fc . position (22); while ( s a l i d a . hasRemaining ( ) ) fc . write ( salida ) ; s a l i d a . rewind ( ) ;
22
fc . position (16); while ( s a l i d a . hasRemaining ( ) ) fc . write ( salida ) ; fc . close ();
24 25 26 27
} catch ( E x c e p t i o n e ) { System . out . p r i n t l n ( ” Mensaje : ” + e ) ; }
28 29 30 31 32 33
} public s t a t i c void c r e a r A r c h i v o ( Path a r c h i v o ) { S t r i n g s = ” 0123456789012345678901234567890123456789 ” ; try { OutputStream s a l i d a = new BufferedOutputStream ( F i l e s . newOutputStream ( a r c h i v o , CREATE) ) ; s a l i d a . write ( s . getBytes ( ) ) ; salida . flush (); salida . close (); } catch ( E x c e p t i o n e ) { System . out . p r i n t l n ( ” Mensaje : ” + e ) ; } }
34 35 36 37 38 39 40 41 42 43 44 45 46
}
C´ odigo 13: La clase PruebaAccesoAleatorio.
7.
Escritura de registros en archivos de acceso aleatorio
La escritura de caracteres en localidades aleatorias de archivo de texto, como en la apliaci´ on PruebaAccesoAleatorio, es de valor limitado. Cuando se guardan registros en un archivo, el acceso es m´as u ´til por registro, como acceder el quinto registro, en vez de recuperar el quinto byte. Para lograr lo anterior, se debe multiplicar la posici´on del registro que se quiere acceder por el tama˜ no del registro. Por ejemplo, si se guardan registros que son de tama˜ no 50 bytes, el primer registro est´a en la posici´ on cero, el segundo registro en la posici´on 50, el tercero en la posici´on 100, y as´ı sucesivamente. Es decir, se puede acceder el n-´esimo registro en un FileChannel llamado fc con la siguiente sentencia: fc.position((n - 1) * 50); Una aproximaci´ on para escribir un archivo de acceso aleatorio es colocar los registros en el archivo usando un campo llave. Un campo llave es el campo en un registro que hace al registro u ´nico del resto. Por ejemplo, suponer que se quiere guardar n´ umeros identificador de empleado, apellidos, y salarios en un archivo de acceso aleatorio. En un archivo de empleados, varios registros podr´ıan tener el mismo apellido o salario, pero cada registro tiene un n´ umero identificador de empleado, as´ı que este campo puede ser el campo llave. El primer paso en la creaci´ on del archivo empleado de acceso aleatorio es la creaci´on de un archivo que guarde los registros por defecto—por ejemplo, usando ceros para los n´ umeros identificadores 23
y los salarios y blancos para los apellidos. Para este caso, suponer que cada n´ umero identificador de empleado es de tres d´ıgitos. La aplicaci´on CrearArchivoVacioEmpleados, c´odigo 14, crea 1,000 registros del tipo descrito. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
import j a v a . i o . ∗ ; import j a v a . n i o . B y t e B u f f e r ; import j a v a . n i o . f i l e . ∗ ; import s t a t i c j a v a . n i o . f i l e . StandardOpenOption . ∗ ; public c l a s s CrearArchivoVacioEmpleados { public s t a t i c void main ( S t r i n g [ ] a r g s ) { Path a r c h i v o = Paths . g e t ( ” e m p l e a d o s a l e a t o r i o . t x t ” ) ; String s = ” 000 , ,000.00 ” + System . g e t P r o p e r t y ( ” l i n e . s e p a r a t o r ” ) ; f i n a l int NUM REGS = 1 0 0 0 ; try { OutputStream s a l i d a = new BufferedOutputStream ( F i l e s . newOutputStream ( a r c h i v o , CREATE) ) ; B u f f e r e d W r i t e r e s c r i t o r = new B u f f e r e d W r i t e r (new OutputStreamWriter ( s a l i d a ) ) ; f o r ( int c o n t = 0 ; c o n t < NUM REGS; ++c o n t ) e s c r i t o r . write ( s , 0 , s . length () ) ; escritor . close (); } catch ( E x c e p t i o n e ) { System . out . p r i n t l n ( ” Mensaje : ” + e ) ; } } }
C´ odigo 14: Aplicaci´on CrearArchivoVacioEmpleados. En las l´ıneas 8—9 del c´ odigo 14, un String que representa un registro por defecto es declarado. El n´ umero de empleado de tres d´ıgitos es puesto a ceros, el apellido consiste de veinte blancos, y el salario es 000.00, adem´ as la cadena termina con el valor del separador de l´ınea del sistema. Un arreglo byte es construido del String y envuelto en un b´ ufer. Luego un archivo es abierto en modo CREATE y un BufferedWriter es establecido. En las l´ıneas 16—17 del c´ odigo 14, un ciclo se ejecuta mil veces. Dentro del ciclo, la cadena por defecto empleado es pasada al m´etodo write() del objeto BufferedWriter. Los campos por defecto en el archivo base de acceso aleatorio se pueden inicializar con los valores que sean m´as convenientes para la organizaci´on. El u ´nico requisito en la inicializaci´on es que los registros por defecto sean reconocidos como tales. Despu´es de crear el archivo por defecto base, se puede reemplazar cualquiera de sus registros con datos para un empleado actual. La aplicaci´on CrearUnRegistroAccesoAleatorio, c´odigo 15, crea un s´olo registro empleado definido en la sentencia de las l´ıneas 9—10. El registro es para empleado 002 con apellidos Silvano Aureoles y un salario de 987.65. En la l´ınea 11, el tama˜ no de la cadena es asignado a TAM REG. En este caso el tama˜ no es 32, ya que cada caracter, incluyendo las comas delimitadoras, y un byte para el valor del separador de l´ınea dado por System.getProperty(). Despu´es el FileChannel es establecido, el registro es escrito al archivo en la posici´on que inicia en dos veces el tama˜ no del registro. El valor dos es codificado en esta aplicaci´on porque el n´ umero identicador de empleados es 002. 24
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
import j a v a . i o . ∗ ; import j a v a . n i o . B y t e B u f f e r ; import j a v a . n i o . c h a n n e l s . F i l e C h a n n e l ; import j a v a . n i o . f i l e . ∗ ; import s t a t i c j a v a . n i o . f i l e . StandardOpenOption . ∗ ; public c l a s s C r e a r U n R e g i s t r o A c c e s o A l e a t o r i o { public s t a t i c void main ( S t r i n g [ ] a r g s ) { Path a r c h i v o = Paths . g e t ( ” e m p l e a d o s a l e a t o r i o . t x t ” ) ; String s = ” 002 , Silvano Aureoles ,987.65 ” + System . g e t P r o p e r t y ( ” l i n e . s e p a r a t o r ” ) ; f i n a l int TAM REG = s . l e n g t h ( ) ; byte [ ] d a t o s = s . g e t B y t e s ( ) ; B y t e B u f f e r s a l i d a = B y t e B u f f e r . wrap ( d a t o s ) ; F i l e C h a n n e l f c = null ; try { f c = ( F i l e C h a n n e l ) F i l e s . newByteChannel ( a r c h i v o , READ, WRITE) ; f c . p o s i t i o n ( 2 ∗ TAM REG) ; fc . write ( salida ) ; fc . close (); } catch ( E x c e p t i o n e ) { System . out . p r i n t l n ( ” Mensaje : ” + e ) ; } } }
C´ odigo 15: Aplicaci´on CrearUnRegistroAccesoAleatorio. Un programa que inserte un registro de empleado codificado en un archivo de datos no es muy u ´til. El programa CrearEmpleadosAccesoAleatorio, c´odigo 16, acepta cualquier cantidad de registros ingresados por el usuario y los escribe a un archivo dentro de un ciclo. En la l´ınea 26 cada valor de dato de empleado es aceptado desde el teclado como un String y convertido a un entero usando el m´etodo parseInt(). Luego, en la sentencia de la l´ınea 36 la posici´on del registro deseado es calculada multiplicando el n´ umero identificador por el tama˜ no del registro. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
import j a v a . i o . ∗ ; import j a v a . n i o . B y t e B u f f e r ; import j a v a . n i o . c h a n n e l s . F i l e C h a n n e l ; import j a v a . n i o . f i l e . ∗ ; import s t a t i c j a v a . n i o . f i l e . StandardOpenOption . ∗ ; import j a v a . u t i l . Scanner ; public c l a s s C r e a r E m p l e a d o s A c c e s o A l e a t o r i o { public s t a t i c void main ( S t r i n g [ ] a r g s ) { Scanner e n t r a d a = new Scanner ( System . i n ) ; Path a r c h i v o = Paths . g e t ( ” e m p l e a d o s a l e a t o r i o . t x t ” ) ; String s = ” 000 , ,000.00 ” + System . g e t P r o p e r t y ( ” l i n e . s e p a r a t o r ” ) ; f i n a l int TAM REG = s . l e n g t h ( ) ; F i l e C h a n n e l f c = null ; S t r i n g d e l i m i t a d o r=” , ” ; S t r i n g idCadena ; int i d ; S t r i n g nombre ;
25
String salario ; f i n a l S t r i n g SALIR = ” 999 ” ; try { f c = ( F i l e C h a n n e l ) F i l e s . newByteChannel ( a r c h i v o , READ, WRITE) ; System . out . p r i n t ( ” I n g r e s a r n´ u mero i d e n t i f i c a d o r ( 3 l u g a r e s ) >> ” ) ; idCadena = e n t r a d a . n e x t L i n e ( ) ; while ( ! ( idCadena . e q u a l s ( SALIR ) ) ) { i d = I n t e g e r . p a r s e I n t ( idCadena ) ; System . out . p r i n t ( ” I n g r e s a r a p e l l i d o s para empleado #” + i d + ” >> ( 2 0 l u g a r e s ) ” ) ; nombre = e n t r a d a . n e x t L i n e ( ) ; System . out . p r i n t ( ” I n g r e s a r s a l a r i o >> ( 6 l u g a r e s ) ” ) ; s a l a r i o = entrada . nextLine ( ) ; s = idCadena + d e l i m i t a d o r + nombre + d e l i m i t a d o r + s a l a r i o + System . g e t P r o p e r t y ( ” l i n e . s e p a r a t o r ” ) ; byte [ ] d a t o s = s . g e t B y t e s ( ) ; B y t e B u f f e r s a l i d a = B y t e B u f f e r . wrap ( d a t o s ) ; f c . p o s i t i o n ( i d ∗ TAM REG) ; fc . write ( salida ) ; System . out . p r i n t ( ” I n g r e s a r s i g u i e n t e n´ u mero i d e n t i f i c a d o r o ” + SALIR + ” para t e r m i n a r >> ” ) ; idCadena = e n t r a d a . n e x t L i n e ( ) ; } fc . close (); } catch ( E x c e p t i o n e ) { System . out . p r i n t l n ( ” Mensaje : ” + e ) ; }
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
} }
C´ odigo 16: La clase CrearEmpleadosAccesoAleatorio. Para que la aplicaci´ on CrearEmpleadosAccesoAleatorio, c´odigo 16, se mantenga simple y enfocada en la escritura del archivo de acceso aleatorio realiza varias suposiciones: Un registro de empleado contiene un s´olo n´ umero identificador, apellidos, y un salario. En una aplicaci´ on de la vida real, cada empleado podr´ıa requerir mas campos de datos, como direcci´ on, n´ umero tel´efonico, fecha de contrataci´on, etc. Cada n´ umero identificador de empleado es de tres d´ıgitos. En muchas aplicaciones de la vida real, estos n´ umeros podr´ıan ser m´as grandes para asegurar valores u ´nicos. El usuario deber´ a ingresar n´ umeros identificadores y salarios v´alidos. En una aplicaci´on profesional, esta es una suposici´ on incorrecta porque el usuario podr´ıa meter m´as d´ıgitos o caracteres no num´ericos. En la aplicaci´on no se hace validaci´on de entrada para mantener el c´odigo simple. El usuario no deber´ a duplicar los n´ umeros identificadores. En una aplicaci´on real, un campo llave deber´ a ser revisado contra todos los campos llave existentes para asegurar que un registro es u ´nico antes de agregarlo al archivo.
26
Todos los apellidos ingresados deben tener 20 caracteres para que cada registro sea del mismo tama˜ no. Solo cuando los tama˜ nos de los registros son uniformes pueden ser usados para calcular las posiciones de desplazamiento aritm´eticamente. En una aplicaci´on real, se deber´ an rellenar con espacios los apellidos de menos de 20 caracteres y recortar los que excedan el tama˜ no anterior. Cada registro de empleado es colocado en la posici´on del archivo de acceso aleatorio que es una menos que el n´ umero identificador de empleado. En una aplicaci´on real, el c´ alculo realizado en un campo llave para determinar el desplazamiento es m´as complicada.
8.
Lectura de registros en archivos de acceso aleatorio
Si un archivo es creado como archivo de acceso aleatorio, este puede ser procesado tambi´en en forma secuencial
Acceso secuencial de un archivo aleatorio El archivo empleados aleatorio.txt creado en la secci´on 7 contiene mil registros. Sin embargo, s´olo unos cuantos contienen datos u ´tiles. Mostrar cada registro del archivo podr´ıa resultar en muchas l´ıneas de salida irrelevantes. Tiene m´ as sentido mostrar s´olo aquellos registros para los cuales un n´ umero identificador ha sido insertado. La aplicaci´on LeerEmpleadosSecuencialmente, c´ odigo 17, lee los mil registros secuencialmente con un ciclo while. En la l´ınea 28 se revisar por n´ umeros identificadores v´ alidos. En este ejemplo se supone que un empleado no tiene un n´ umero identificador igual a 000, as´ı la aplicaci´ on s´ olo mostrar´a aquellos registros cuando el n´ umero identificador sea diferente de 000. Si 000 es un n´ umero identificador v´alido entonces se deber´ıa revisar que el nombre est´e en blanco, y tenga un salario de cero. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
import j a v a . i o . ∗ ; import j a v a . n i o . f i l e . ∗ ; import s t a t i c j a v a . n i o . f i l e . AccessMode . ∗ ; public c l a s s L e e r E m p l e a d o s S e c u e n c i a l m e n t e { public s t a t i c void main ( S t r i n g [ ] a r g s ) { Path a r c h i v o = Paths . g e t ( ” e m p l e a d o s a l e a t o r i o . t x t ” ) ; S t r i n g [ ] a r r e g l o = new S t r i n g [ 3 ] ; S t r i n g s = ”” ; String delimitador = ” ,” ; int i d ; S t r i n g cadenaId ; String apellidos ; double s a l a r i o ; double n e t o ; f i n a l double HORAS SEMANA = 4 0 . ; double t o t a l = 0 . 0 ; try { InputStream e n t r a d a = new B u f f e r e d I n p u t S t r e a m ( F i l e s . newInputStream ( a r c h i v o ) ) ; B u f f e r e d R e a d e r l e c t o r = new B u f f e r e d R e a d e r ( new InputStreamReader ( e n t r a d a ) ) ; System . out . p r i n t l n ( ) ;
27
s = l e c t o r . readLine ( ) ; while ( s != null ) { arreglo = s . s p l i t ( delimitador ) ; cadenaId = a r r e g l o [ 0 ] ; i d = I n t e g e r . p a r s e I n t ( cadenaId ) ; i f ( i d != 0 ) { apellidos = arreglo [ 1 ] ; s a l a r i o = Double . p a r s e D o u b l e ( a r r e g l o [ 2 ] ) ; n e t o = s a l a r i o ∗ HORAS SEMANA; System . out . p r i n t l n ( ”# i d e n t i f i c a d o r ” + cadenaId + ” ” + apellidos + ” $” + s a l a r i o + ” $” + neto ) ; t o t a l += n e t o ; } s = l e c t o r . readLine ( ) ; } lector . close ();
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
} catch ( E x c e p t i o n e ) { System . out . p r i n t l n ( ” Mensaje : ” + e ) ; } System . out . p r i n t l n ( ” El pago t o t a l de l a n´o mina e s $ ” + t o t a l ) ;
39 40 41 42 43 44 45
} }
C´ odigo 17: Aplicaci´on LeerEmpleadosSecuencialmente.
Acceso arbitrario de un archivo aleatorio Si solo se quiere mostrar registros usando su campo llave, no es necesario crear un archivo de acceso aleatorio y desperdiciar espacio no necesitado. Se podr´ıa ordenar los registros usando algunas de las t´ecnicas vistas en el cap´ıtulo arreglos avanzados. El beneficio de usar un archivo de acceso aleatorio es la habilidad para recuperar un registro espec´ıfico de un archivo directamente, sin tener que pasar a trav´es de otros registros para localizar el registro deseado. La aplicaci´on LeerEmpleadosAleatoriamente, c´odigo 18, permite al usuario ingresar un n´ umero identificador de empleado. La aplicaci´ on calcula la posici´on del registro en el archivo de datos, uno menos que el n´ umero identificador, y pone el apuntador de archivo en la localidad para iniciar la lectura. Al usuario se le pide un n´ umero identificador, el cual es convertido a un entero con el m´etodo parseInt(). La aplicaci´ on no revisa que el n´ umero identificador sea v´alido. En las l´ıneas 27—28 de la aplicaci´ on la posici´ on del registro es calculada multiplicando el n´ umero identificador por el tama˜ no del registro y luego posicionando el apuntador de archivo en la localidad deseada. Para mantener el ejemplo peque˜ no, el n´ umero identificador no es revisado para asegurarse que 999 o menos. El registro de empleado es recuperado del archivo de datos y mostrado, y luego al usuario se pide por el siguiente n´ umero identificador deseado. 1 2 3 4 5 6
import import import import import import
java . io . ∗ ; java . nio . ByteBuffer ; java . nio . channels . FileChannel ; java . nio . f i l e . ∗ ; s t a t i c j a v a . n i o . f i l e . StandardOpenOption . ∗ ; j a v a . u t i l . Scanner ;
28
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
public c l a s s L e e r E m p l e a d o s A l e a t o r i a m e n t e { public s t a t i c void main ( S t r i n g [ ] a r g s ) { Scanner t e c l a d o = new Scanner ( System . i n ) ; Path a r c h i v o = Paths . g e t ( ” e m p l e a d o s a l e a t o r i o . t x t ” ) ; String s = ” 000 , ,000.00 ” + System . g e t P r o p e r t y ( ” l i n e . s e p a r a t o r ” ) ; f i n a l int TAM REG = s . l e n g t h ( ) ; byte [ ] d a t o s = s . g e t B y t e s ( ) ; B y t e B u f f e r s a l i d a = B y t e B u f f e r . wrap ( d a t o s ) ; F i l e C h a n n e l f c = null ; S t r i n g cadenaId ; int i d ; f i n a l S t r i n g SALIR = ” 999 ” ; try { f c = ( F i l e C h a n n e l ) F i l e s . newByteChannel ( a r c h i v o , READ, WRITE) ; System . out . p r i n t ( ” I n g r e s a r n´ u mero i d e n t i f i c a d o r >> ” ) ; cadenaId = t e c l a d o . n e x t L i n e ( ) ; while ( ! cadenaId . e q u a l s ( SALIR ) ) { i d = I n t e g e r . p a r s e I n t ( cadenaId ) ; s a l i d a = B y t e B u f f e r . wrap ( d a t o s ) ; f c . p o s i t i o n ( i d ∗ TAM REG) ; f c . read ( s a l i d a ) ; s = new S t r i n g ( d a t o s ) ; System . out . p r i n t l n ( ”# i d ” + s ) ; System . out . p r i n t ( ” I n g r e s a r n´ u mero i d e n t i f i c a d o r o ” + SALIR + ” para s a l i r >> ” ) ; cadenaId = t e c l a d o . n e x t L i n e ( ) ; } fc . close (); } catch ( E x c e p t i o n e ) { System . out . p r i n t l n ( ” Mensaje : ” + e ) ; } } }
C´ odigo 18: Aplicaci´on LeerEmpleadosAleatoriamente. Actividad 1. Crear una aplicaci´ on que pida al usuario datos de clientes y los asigne a uno de dos archivos dependiendo del estado de residencia del cliente. La aplicaci´on asume que los registros de la Ciudad de M´exico (CDMX) son asignados a un archivo dentro de la CDMX y los otros registros son asignados a un archivo fuera de la CDMX. Primero se crear´an archivos vac´ıos para guardar los registros, y luego se escribir´ a el c´ odigo que coloca cada registro en el archivo correcto.
29