14. Patrón Decorador en el paquete java.io Este patrón permite añadir de modo dinámico nuevas funciones a objetos individuales (no a clases completas). En vez de usar la herencia tradicional, este patrón encapsula un objeto dentro de un objeto decorador, que se encarga de proporcionar las nuevas funciones.
Objetivos Poder crear, leer, escribir y actualizar archivos. Poder usar la clase File Entender la jerarquía de clases de flujo de Java. Poder usar las clases FileInputStream y FileOutputStream. Familiarizarse con el procesamiento de archivos de acceso secuencial y acceso aleatorio.
La clase File La clase File sirve para encapsular la interacción de nuestros programas con el sistema de archivos. Mediante la clase File no nos limitamos a leer el contenido de un archivo, sino que podemos obtener información adicional, como el tamaño del archivo, su tipo, su fecha de creación, los permisos de acceso que tenemos sobre él, etc.; Además, la clase File es la única forma que tenemos de trabajar con directorios (crearlos, ver los archivos que contienen, cambiar el nombre o borrar los archivos, etc.). La clase File se encuentra definido en el paquete “java.io” Un File es el único objeto del paquete de E/S que referencia a un archivo de disco real. La clase File no especifica cómo se recupera o almacena la información en los archivos; sólo describe las propiedades de un objeto archivo. Los archivos son un origen y un destino primario de los datos dentro de la mayoría de los programas Características : La clase File no permite el acceso a los contenidos de los ficheros (no existen métodos de lectura y escritura) Permite la consulta de atributos y manipulación de directorios. Permite que se cree, elimine y cambie de nombre a los archivos. Ofrecen acceso a la ruta/nombre del archivo y determinan si un objeto es un fichero o directorio. Comprueba los permisos de lectura y escritura.
Métodos de información y operaciones sobre archivos Marca un archivo como sólo lectura boolean setReadOnly() Establece la fecha de modificación del archivo boolean setLastModified(long time) Permite renombrar un archivo boolean renameTo(File dest) Crear un conjunto de directorios boolean mkdirs() Crear un directorio boolean mkdir() Listar archivos dado un filtro de extensiones File[] listFiles(FileFilter filter) Listar instancias File dado un filtro de nombres (si es un directorio) File[] listFiles(FilenameFilter filter) Listar todas las instancias File (si es directorio) File[] listFiles() Listar los nombres de los archivos dado un filtro de nombres (si es un directorio) String[] list(FilenameFilter filter) Listar los nombres de los archivos (si es dir.) String[] list() Elimina el archivo al desaparecer la instancia void deleteOnExit() Elimina un archivo o un directorio vacio boolean delete() Crea un nuevo archivo boolean createNewFile() throws IOException Longitud en bytes long length() Última fecha de modificación en milisegundos long lastModified() true si está marcado como oculto boolean isHidden() true si es un archivo boolean isFile() true si es un directorio boolean isDirectory() true si existe en el sistema de archivos boolean exists() true si no está marcado como sólo lectura boolean canWrite() true si puede ser leído boolean canRead() Retorna una referencia a un Identificador Uniforme de Recurso del tipo java.net.URI URI toURI() Retorna una instancia File en base a la ruta absoluta. ej. new File( this .getAbsolutePath()); File getAbsoluteFile() Retorna la ruta absoluta que depende del sistema operativo String getAbsolutePath() Si la ruta esta escrita de acuerdo algún sistema operativo; ej. “C:\hola.txt” para windows, “/root/hola.txt” para Unix boolean isAbsolute() Retorna la ruta del archivo String getPath() Retorna una instancia de File que representa el directorio que lo contiene. File getParentFile() Retorna la ruta del directorio que lo contiene String getParent() Retorna el nombre del archivo String getName() Descripción Método
Listar los archivos de un directorio Con el método isDirectory podremos comprobar si la ruta especificada corresponde a un directorio, si fuera el caso, el método list , nos permite listar los nombres de los archivos que contenga. FilenameFilter A menudo se desea limitar el número de archivos devueltos por el método list para que se incluyan únicamente aquellos archivos que cumplan un cierto patrón de nombre de archivo. Con este fin, el paquete java.io incluye una interfaz llamada FilenameFilter. Un objeto que implemente FilenameFilter tiene un único método, accept, al que se llama una vez por cada archivo de una lista. El método accept devuelve el valor true si se debiera incluir el archivo en la lista.
El ejemplo siguiente amplía el programa anterior restringiendo la visibilidad de los nombres de archivo devueltos por el método list. La restricción se aplica a archivos con nombres que terminan con la extensión de archivo que se pasa como parámetro cuando se construye le objeto: Modificamos el programa anterior en la línea 13 para que sólo acepte archivos que cumplan con el filtro:
Obtener información de un archivo El método exists verifica la existencia del archivo en el sistema de directorios, al cual podemos complementar con el método isFile , que verifica si es un archivo y no un directorio.
Los streams (flujos) Cualquier programa realizado en Java que necesite llevar a cabo una operación de I/O lo hará a través de un stream . Un stream, cuya traducción literal es "flujo", es una abstracción de todo aquello que produzca o consuma información. Podemos ver a este stream como una entidad lógica. La vinculación de este stream al dispositivo físico la hace el sistema de entrada y salida de Java. Se ve pues la eficacia de esta implementación pues las clases y métodos de I/O que necesitamos emplear son las mismas independientemente del dispositivo con el que estemos actuando, luego, el núcleo de Java, sabrá si tiene que tratar con el teclado, el monitor, un sistema de ficheros o un socket de red liberando a nuestro código de tener que saber con quién está interactuando.
Tipos de streams Byte streams : Nos proporciona un medio adecuado para el manejo de entradas y salidas de bytes y su uso lógicamente está orientado a la lectura y escritura de datos binarios. El tratamiento del flujo de bytes viene gobernado por dos clases abstractas que son InputStream y OutputStream . Cada una de estas clases abstractas tienen varias subclases concretas que controlan las diferencias ente los distintos dispositivos de I/O que se pueden utilizar. Así mismo, estas dos clases son las que definen los métodos que sus subclases tendrán implementados y, de entre todas, destacan las clases read() y write() que leen y escriben bytes de datos respectivamente. Character streams : Proporciona un medio conveniente para el manejo de entradas y salidas de caracteres. Dichos flujos usan codificación Unicode y, por tanto, se pueden internacionalizar. Una observación: Este es un modo que Java nos proporciona para manejar caracteres pero al nivel más bajo todas las operaciones de I/O son orientadas a byte. Al igual que la anterior el flujo de caracteres también viene gobernado por dos clases abstractas: Reader y Writer . Dichas clases manejan flujos de caracteres Unicode. Y también de ellas derivan subclases concretas que implementan los métodos definidos en ellas siendo los más destacados los métodos read() y write() que, en este caso, leen y escriben caracteres de datos respectivamente. Clases abstractas que definen los flujos de bytes y caracteres Caracteres Bytes
Clases orientadas a flujo de bytes de entrada InputStream InputStream es una clase abstracta que define el modelo de Java para el flujo de entrada. Todos los métodos de esta clase lanzarán una IOException si se producen condiciones de error. Este es un breve resumen de los métodos de InputStream: devuelve true si se admiten mark/reset en este flujo boolean markSupported() devuelve el puntero de entrada a la marca establecida previamente void reset() throws IOException coloca una marca en el punto actual del flujo de entrada que seguirá siendo válida hasta que se lean readlimit bytes void mark(int readlimit) cierra el origen de entrada. Los intentos de lectura posteriores generarán una IOException void close() throws IOException devuelve el número de bytes de entrada disponibles actualmente para su lectura int available() throws IOException omite n bytes de la entrada, y devuelve el número de bytes que se han omitido long skip(long n) throws IOException intenta leer hasta len bytes situándolos en b comenzando en b[off], y devuelve el número de bytes que se leyeron con éxito int read(byte b[ ], int off, int len) throws IOException intenta leer hasta b.length bytes situándolos en b y devuelve el número real de bytes que se leyeron con éxito int read(byte b[ ]) throws IOException devuelve una representación como entero del siguiente byte de entrada disponible int read() throws IOException Descripción Método
Clases orientadas a flujo de bytes de salida OutputStream Igual que InputStream, OutputStream es una clase abstracta que define el flujo de salida. Todos los métodos de esta clase devuelven un valor void y lanzan una IOException en caso de error. Esta es una lista de los métodos de OutputStream: cierra el flujo de salida. Los intentos de escritura posteriores generarán una IOException. void close() throws IOException inicializa el estado de la salida de manera que se limpian todos los buffers. void flush() throws IOException escribe len bytes de la matriz b, comenzando a partir de b[off]. void write(byte b[], int off, int len) throws IOException escribe una matriz completa de bytes en un flujo de salida. void write(byte b[]) throws IOException escribe un único byte en un flujo de salida. Observar que el parámetro es un int, lo que permite que se llame a write con expresiones sin tener que convertir su tipo a byte. void write(int b) throws IOException Descripción Método
Clases orientadas a flujo de caracteres de entrada Reader Un Reader permite leer ficheros de caracteres (ficheros textuales, los que normalmente se generan con un editor). Al tratarse de una clase abstracta, no dispone de constructores. Sólo podremos crear objetos de clases descendientes de Reader. devuelve true si se admiten mark/reset en este flujo boolean markSupported() devuelve el puntero de entrada a la marca establecida previamente void reset() throws IOException coloca una marca en el punto actual del flujo de entrada que seguirá siendo válida hasta que se lean readAheadLimit Caracteres void mark(int readAheadLimit) cierra el origen de entrada. Los intentos de lectura posteriores generarán una IOException void close() throws IOException retorna true si se puede seguir con la lectura boolean ready() throws IOException omite n bytes de la entrada, y devuelve el número de bytes que se han omitido long skip(long n) throws IOException intenta leer hasta len caracteres situándolos en cbuf comenzando en cbuf[off], y devuelve el número de caracteres que se leyeron con éxito int read(char cbuf[ ], int off, int len) throws IOException intenta leer hasta cbuf.length caracteres situándolos en cbuf y devuelve el número real de caracteres que se leyeron con éxito int read(char cbuf[ ]) throws IOException devuelve una representación como entero del siguiente caracter de entrada disponible int read() throws IOException Descripción Método
Clases orientadas a flujo de caracteres de salida Writer Los objetos Writer son el contrapunto de los Reader. Los Writer permiten escribir en un fichero de carácteres (textual). Escribe un objeto CharSequence a un flujo de salida desde start hasta end Writer append(CharSequence csq, int start, int end) throws IOException Escribe un objeto CharSequence a un flujo de salida Writer append(CharSequence csq) throws IOException Escribe len caracteres de la cadena str desde el indice off void write(String str, int off, int len) throws IOException Escribe un string en un flujo de salida void write(String str) throws IOException cierra el flujo de salida. Los intentos de escritura posteriores generarán una IOException. void close() throws IOException inicializa el estado de la salida de manera que se limpian todos los buffers. void flush() throws IOException escribe len caracteres de la matriz cbuf, comenzando a partir de cbuf[off]. void write(char cbuf[], int off, int len) throws IOException escribe una matriz completa de caracteres en un flujo de salida. void write(char cbuf[]) throws IOException escribe un único carácter en un flujo de salida. void write(int c) throws IOException Descripción Método
FileInputStream La clase FileInputStream utiliza archivos de datos reales como base del flujo de entrada. El ejemplo siguiente crea dos FileInputStreams que están utilizando el mismo archivo de disco real: Aunque probablemente el primer constructor es el que más se utiliza habitualmente, el segundo permite examinar el archivo más de cerca utilizando sus métodos antes de asignarlo a un flujo de entrada. Cuando se crea un FileInputStream, también se abre para lectura. FileInputStream sobrescribe seis de los métodos de la clase abstracta InputStream. Si se intentan utilizar los métodos mark o reset en un FileInputStream se generará una IOException. FileOutputStream FileOutputStream comparte el mismo estilo de constructores que FileInputStream. Sin embargo, la creación de FileOutputStream no depende de que el archivo ya exista. FileOutputStream creará el archivo antes de abrirlo como salida cuando se crea el objeto. Si se intenta abrir un archivo de sólo lectura como punto final de un FileOutputStream, se lanzará una IOException.
Patrón Decorador en el paquete java.io Las jerarquía de clases cuya raíz es la clase abstracta OutputStream permite la combinación de filtros ( FilterOutputStream ) para combinar funciones diversas: salida sobre ficheros utilizando un buffer manipulando directamente datos básicos Java. De forma análoga se organiza la jerarquía InputStream . Toda clase que derive de FilterOutputStream, FilterInputStream, FilterReader o FilterWriter, constituye un decorador, que añade comportamiento específico a las clases que deriven de InputStream, OutputStream, Reader o Writer, incluyendo a otros decoradores.
Flujos filtrados de escritura
Flujos filtrados de lectura
Serialización Para permitir un proceso de serialización común a todas las clases de objetos Java proporciona la interfaz Serializable. Esta interfaz vacía le indica a los métodos de la clase ObjectOutputStream que pueden actuar sobre ella. La clase ObjectOutputStream define el método writeObject, que permite convertir un objeto serializable en un flujo de datos de salida. Por otro lado, la clase ObjectInputStream permite transformar un flujo de datos de entrada en un objeto gracias al método readObject. Los objetos se serializan llamando a writeObject y se deseriazalizan con el método readObject. Casi todas las clases definidas en los paquetes estándar de Java son Serializables (por ejemplo la clase String y los envoltorios). Así, la serialización y las deserialización de una clase consiste en serializar en el mismo orden las propiedades no estáticas. Para evitar que una propiedad determinada se serialice (por ejemplo una contraseña) recordemos que se puede utilizar en su declaración la etiqueta de prohibición de serialización transient.
Acceso aleatorio a archivos Métodos de desplazamiento Cuenta con una serie de funciones para realizar el desplazamiento del puntero del fichero. Hay que tener en cuenta que cualquier lectura o escritura de datos se realizará a partir de la posición actual del puntero del fichero. Devuelve el descriptor de este fichero. FileDescriptor getFD(); Establece a l el tamaño de este fichero. void setLength( long l); Devuelve la longitud del fichero. long length(); Intenta saltar n bytes desde la posición actual. int skipBytes( int n ); Coloca el puntero del fichero en la posición indicada por l. Un fichero siempre empieza en la posición 0. void seek( long l ); Devuelve la posición actual del puntero del fichero. long getFilePointer(); Descripción Método
Ejemplo de acceso aleatorio Método que escribe un archivo Método que se desplaza por el archivo Ejecución
Práctica 15: Haciendo uso del framework de entrada/salida de Java Objetivo Implementar aplicaciones que permitan la persistencia de información, tanto en archivos de texto como en archivos binarios. Ejercicios: Escriba una aplicación que implemente las operaciones básicas de un editor de Texto (Nuevo, Guardar, Copiar, Contar, Pegar). Escriba un programa que haciendo uso del mecanismo de serialización implemente la persistencia de una Agenda Personal, que contenga los apellidos, nombres, teléfono, celular, e-mail, fecha de nacimiento y dirección de los contactos. El registro de los sucesos de un sistema se realiza a través de sistemas de Log. Implementar un sistema de Log en formato de texto que permita almacenar en ficheros de texto, archivos conteniendo el mensaje de las Excepciones causadas por la ejecución de un programa. Al ejercicio del Tanque de agua, adapte el sistema de Log creado en el ejercicio anterior para almacenar los eventos de apertura y cierre de llaves del Tanque.