Summary

This document provides a basic introduction to data access in Java. It covers fundamental concepts like files, directories, and different types of files (standard, directories, special). It also explains the idea of file paths (absolute and relative) and file extensions. A key component is character encoding and how it relates to displaying characters correctly in text files. The document could serve as a good starting point for learning how to handle files and data within Java programs.

Full Transcript

Acceso a datos En los siguientes temas aprenderemos todos los conceptos básicos que hay que tener en cuenta cuando necesitamos tratar con ficheros con lenguaje Java. Para entender mejor qué trataremos en los siguientes apartados, primero deberemos entender bien los concep- tos básicos: ¿qué podemo...

Acceso a datos En los siguientes temas aprenderemos todos los conceptos básicos que hay que tener en cuenta cuando necesitamos tratar con ficheros con lenguaje Java. Para entender mejor qué trataremos en los siguientes apartados, primero deberemos entender bien los concep- tos básicos: ¿qué podemos entender por un fichero? CONCEPTO Un fichero es un archivo que contendrá un conjunto de caracteres o bytes que se almacenarán en el dispositivo en una ruta y con un nombre concretos. Es el archivo que usará nuestro programa para almacenar, leer, escribir o gestionar información sobre el proceso que se esté ejecutando. Existen diferentes tipos de ficheros, como, por ejemplo: Fichero estándar: es un archivo que contiene todo tipo de datos: caracteres, imagen, audio, vídeo, etcétera. Nor- malmente son ficheros que contienen información de cualquier tipo. Directorios o carpetas: son ficheros que albergan más archivos en su interior. Su principal utilidad es mantener un orden o jerarquía en nuestros sistemas. Ficheros especiales: son todos esos ficheros que usa nuestro sistema operativo y que se utilizan para controlar los dispositivos o periféricos de nuestro ordenador. En este tema profundizaremos en el tipo de ficheros están- dar y en los directorios. Como explicaremos más adelante, este tipo de ficheros nos permitirán realizar diferentes ac- ciones para tratar los ficheros y para mantener un orden y jerarquía con las carpetas. Podemos destacar dos tipos de ficheros de datos: Los ficheros de bytes: también conocidos como ficheros binarios, son archivos que usan los programas para leer o escribir información. Los ficheros de caracteres: también conocidos como ficheros de texto, nos permitirán leer o escribir la infor- mación que contengan. Un fichero se caracteriza por estar formado por la ruta en la que está almacenado, el nombre y una extensión, si- guiendo este orden. Además, tenemos que tener en cuenta que no podrán existir ficheros con el mismo nombre, ruta y extensión. Para que sean únicos, el nombre o la extensión en la misma ruta deben ser distintos. 7 Tema 1: Gestión de ficheros Para tener acceso a un fichero determinado, se utiliza una ruta (o también la podemos nombrar path) que indica la ubicación de ese fichero en nuestro sistema. La ruta está compuesta por diferentes niveles jerárquicos (carpetas) separado por un símbolo barra /, Aunque en Windows, para separar los niveles jerárquicos, se utiliza la contrabarra o \. En cambio, en Unix el separador será /. Eclipse admite tanto / como \ cuando definimos la ruta. Si queremos definir la ruta independientemente del siste- ma operativo, podemos realizarlo de este modo: //Ejemplo con la ruta directa al string File archivoNoseguro = new File(“carpeta/ejemplo.txt”); //Ruta que asegura el separador correcto segun plataforma File archivo = new File(“carpeta”+File.separator+”ejemplo.txt”); Existen dos tipos importantes de rutas que nos serán muy útiles en la gestión de ficheros: Ruta absoluta: se conoce como la ruta desde la carpeta padre: C:/Ilerna/accesoDatos/tema1/ejercicio.txt Ruta relativa: es aquella que coge como referencia el di- rectorio actual para dar la ruta. La diferencia entre la ruta absoluta y la relativa es que no se indica la carpeta padre u origen y solo se da la guía desde la carpeta actual. Se in- dica con un punto, una barra y el nombre de los diferentes directorios separados por barras. Teniendo en cuenta que la carpeta actual sea accesoDatos, veamos este ejemplo:. /tema1/ejercicio.txt 8 Acceso a datos La extensión del archivo nos permitirá diferenciar qué pro- grama puede utilizar ese fichero. Se considera extensión todo lo que podemos encontrar después del punto que ponemos al final de nombre. Veamos el ejemplo: El fichero se guardará según la codificación del dispositivo que estemos usando. ejercicio.txt → la extensión será el.txt ejercicio.doc → la extensión será el.doc Los archivos que trataremos, en muchas ocasiones, con- tendrán información de texto o caracteres. Cada lengua utiliza un tipo de carácter distinto de otra, por ejemplo, el ruso utiliza un abecedario diferente que el español, por lo que usará caracteres distintos. Los caracteres se almacenan en nuestro ordenador como uno o más bytes. Básicamente, podemos asumir que todos los caracteres están almacenados en ordenadores usando un código es- pecial, es decir, una codificación de caracteres proporciona una clave para descifrar el código. Es un conjunto de asig- naciones entre los bytes de los ordenadores y los caracteres en el conjunto de caracteres. Sin la clave, cuando el orde- nador descifre los caracteres de ese fichero, aparecerán sin descifrar y se verán raros. Por ejemplo, algo así: H‰ÄTMoÓ@¼ï¯xGûàõ~zרªÔ´©¨(–8¤=Ç) AÔn£Â¿ç½õ¦IH‘‘ƒ³Ÿ3ãyã— Este sería un ejemplo de mala interpretación de un enco- ding. Los caracteres no son legibles y no podemos interpretar la información. CONCEPTO Se denomina encoding al sistema utilizado para transformar los caracteres que usa cada lenguaje en un símbolo que un ordenador pueda interpretar. La codificación de caracteres asigna los caracteres esco- gidos a bytes específicos en la memoria del ordenador, y luego, para mostrar el texto, lee los bytes nuevamente en caracteres. Principalmente, se basa en crear tablas de equivalencias entre caracteres de lenguaje entendible por las personas con su correspondencia al lenguaje que usa un sistema informático. 9 Tema 1: Gestión de ficheros No es necesario saberse todos los que existen, pero aquí os mostraremos los más importantes: Decimal Hex Char Decimal Hex Char 0 0 [NULL] 30 1e [RECORD SEPARATOR] 1 1 [START OF HEADING] 31 1f [UNIT SEPARATOR] 2 2 [START OF TEXT] 32 20 [SPACE] 3 3 [END OF TEXT] 33 21 ! 4 4 [END OF 34 22 " TRANSMISSION] 35 23 # 5 5 [ENQUIRY] 36 24 $ 6 6 [ACKNOWLEDGE] 37 25 % 7 7 [BELL] 38 26 & 8 8 [BACKSPACE] 39 27 ' 9 9 [HORIZONTAL TAB] 40 28 ( 10 a [LINE FEED/NEW LINE] 41 29 ) 11 b [VERTICAL TAB] 42 2a * 12 c [FORM FEED/NEW 43 2b + PAGE] 44 2c , 13 d [CARRIAGE RETURN] 45 2d - 14 e [SHIFT OUT] 46 2e. 15 f [SHIFT IN] 47 2f / 16 10 [DATA LINK ESCAPE] 48 30 0 17 11 [DEVICE CONTROL 1] 49 31 1 18 12 [DEVICE CONTROL 2] 50 32 2 19 13 [DEVICE CONTROL 3] 51 33 3 20 14 [DEVICE CONTROL 4] 52 34 4 21 15 [NEGATIVE ACKNOWLEDGE] 53 35 5 22 16 [SYNCHRONOUS IDLE] 54 36 6 55 37 7 23 17 [END OF TRANSMISSION BLOCK] 56 38 8 24 18 [CANCEL] 57 39 9 25 19 [END OF MEDIUM] 58 3a : 26 1a [SUBSTITUTE] 59 3b ; 27 1b [ESCAPE] 60 3c < 28 1c [FILE SEPARATOR] 61 3d = 29 1d [GROUP SEPARATOR] 62 3e > 10 Acceso a datos Decimal Hex Char Decimal Hex Char 63 3f ? 96 60 ` 64 40 @ 97 61 a 65 41 A 98 62 b 66 42 B 99 63 c 67 43 C 100 64 d 68 44 D 101 65 e 69 45 E 102 66 f 70 46 F 103 67 g 71 47 G 104 68 h 72 48 H 105 69 i 73 49 I 106 6a j 74 4a J 107 6b k 75 4b K 108 6c l 76 4c L 109 6d m 77 4d M 110 6e n 78 4e N 111 6f o 79 4f O 112 70 p 80 50 P 113 71 q 81 51 Q 114 72 r 82 52 R 115 73 s 83 53 S 116 74 t 84 54 T 117 75 u 85 55 U 118 76 v 86 56 V 119 77 w 87 57 W 120 78 x 88 58 X 121 79 y 89 59 Y 122 7a z 90 5a Z 123 7b { 91 5b [ 124 7c | 92 5c \ 125 7d } 93 5d ] 126 7e ~ 94 5e ^ 127 7f [DEL] 95 5f _ Ilustración 1. Tabla ASCII. 11 Tema 1: Gestión de ficheros ASCII Es el conjunto de caracteres creado por la American Na- tional Standard Code for Information (ANSI), en 1967. Codifica caracteres, letras y símbolos que usamos día a día. ISO-8859 Se trata de otro tipo de encoding bastante conocido que se caracteriza por incluir letras, símbolos y caracteres, pero además también los acentos y los símbolos de interroga- ción y exclamación. Este tipo de codificación utiliza 8 bits, por tanto, tiene una capacidad de 256 caracteres, lo que la hace más amplia que ASCII. Incluye los 128 caracteres de ASCII, pero se añaden símbolos matemáticos y letras griegas, entre otros. Con el tiempo, este encoding se ha quedado corto en cuanto a contemplar los diferentes al- fabetos de distintos idiomas. Por este motivo, se han ido creando diferentes especializaciones de esta codificación. Podemos encontrar también estos otros: ISO 8859-1 (Latin-1), para la zona de Europa occidental. ISO 8859-2 (Latin-2), para la zona de Europa occidental y Centroeuropa. ISO 8859-3 (Latin-3), para la zona de Europa occidental y Europa del sur. 12 Acceso a datos ISO 8859-4 (Latin-4), para la zona de Europa occidental y países bálticos (lituano, estonio y lapón). ISO 8859-5, para el alfabeto cirílico. ISO 8859-6, para el alfabeto árabe. ISO 8859-7, para el alfabeto griego. ISO 8859-8, para el alfabeto hebreo. ISO 8859-9 (Latin-5), para la zona de Europa occidental con los caracteres del alfabeto turco. ISO 8859-10 (Latin-6), para la zona de Europa occiden- tal, incluye los caracteres del alfabeto nórdico, lapón y esquimal. ISO 8859-11, incorpora caracteres del alfabeto tailandés. ISO 8859-13 (Latin-7), incorpora caracteres para los idiomas bálticos y el polaco. ISO 8859-14 (Latin-8), incorpora caracteres para los idiomas celtas. ISO 8859-15 (Latin-9), añade el símbolo del euro. ISO 8859-16, incorpora caracteres para los idiomas po- laco, checo, eslovaco, húngaro, albano, rumano, alemán e italiano. Unicode Es una norma de codificación creada en 1991 para unificar los tipos de codificación. Como hemos visto, existen mul- titud de variantes de codificación para abarcar diferentes idiomas. La creación de este encoding pretendía organizar en un mismo estándar los diferentes caracteres dentro de una misma codificación, para poder abarcar diferentes idiomas tanto de alfabetos europeos como de chinos, japoneses, coreanos o lenguas ya extinguidas con alfabe- tos diferentes. Para realizar las tablas de equivalencias, Unicode asigna un identificador numérico a cada carácter, pero también irá acompañado de información como la direccionalidad, la capitalización y otros atributos. Nues- tro ordenador, según su arquitectura, utilizará diferentes bloques de 8, 16 o 32 bits para interpretar y representar los números. Estos tres diferentes bloques han creado dife- rentes codificaciones: UTF-8. UTF-16. UTF-32. Hoy en día, la codificación más usada es la codificación de caracteres UTF-8. 13 Tema 1: Gestión de ficheros Tanto los editores de texto, como los IDE (programa para desarrollar nuestra aplicación) normalmente dan la posibi- lidad de configurar qué tipo de codificación queremos usar. Por ejemplo, en eclipse esta configuración se puede encon- trar dirigiéndonos a Window > Preferences > General > Workspace. Se nos abrirá una ventana y abajo a la derecha podremos escoger la codificación, tal y como se muestra en esta captura: Ilustración 2. Captura de pantalla donde se muestra la configuración de Eclipse para modificar el encoding. Para que se haga efectivo este cambio, tendremos que darle a Apply and Close, y todos los ficheros que creemos a partir de este momento serán con este tipo de codificación. En el caso de que queramos usar diferentes encodings, será necesario usar un fichero binario, es decir, un fichero que almacenará bytes con la información. 14 Acceso a datos 1.1. Clases asociadas a las operaciones de gestión de ficheros (secuenciales, aleatorios) y directorios: creación, borrado, copia, movimiento, entre otros En este apartado, nos centraremos en la gestión de fiche- ros y directorios. Cuando se programa con Java, podemos realizar tareas básicas de gestión de ficheros que nos serán útiles para realizar todo tipo de acciones con ese fichero, desde crear hasta leer, borrar, copiar o mover de sitio car- petas o archivos. Para tratar los diferentes archivos, Java tiene diferentes paquetes que nos pueden ayudar a realizar las operaciones básicas con cualquier tipo de fichero, tal y como veremos a continuación. Los diferentes objetos que veremos en esta sección pertenecen a diferentes paquetes de Java, algunos de la librería propia de Java, pero también hay otros que son necesario importación. Pero antes de complicarnos, mostraremos los más básicos e importantes. El paquete más utilizado en el lenguaje Java es el paquete java.io. Dentro, podremos encontrar diferentes opciones que nos permitirán diferentes acciones con ficheros: crea- ción, borrado, lectura, escritura, movimiento y copia, entre otras. Este paquete nos permitirá tanto la creación de fi- cheros como de carpetas. Para seguir de manera más dinámica los ejemplos del libro, tenéis a vuestra disposición un repositorio en GitHub con todos los ejemplos más básicos que encontraréis en el libro. BUSCA EN LA WEB https://gitlab.com/ilerna/common/java 15 Tema 1: Gestión de ficheros 1.1.1. Creación de directorios o ficheros 1.1.1.1. Creación de ficheros Para la creación de ficheros, Java usa el paquete java.io. La creación de ficheros es una de las tareas más fáciles que nos encontraremos en esta lección. Para la creación de archivos, podemos utilizar diferentes librerías. Las más usadas son: java.io.File Se trata de la librería más básica para la creación de fiche- ros en Java. Nos será útil si solo queremos obtener información de un archivo o de una carpeta. Este paquete está enfocado a la lectura por streams. CONCEPTO Un stream es una librería de java.io que se utiliza para gestionar flujos de datos, ya sea en ficheros, strings o dispositivos. Se encarga de tratar de manera ordenada una secuencia de datos con un origen y un destino. Los datos que se leen desde este paquete no se guardan en ningún sitio, es decir, no se guardan en caché. Si fuera necesario recorrerlos otra vez, un stream no nos sería útil, sino que necesitaríamos cambiar a un búfer, pero amplia- remos información más adelante en el tema. Para crear un nuevo objeto, debemos declarar una nueva instancia del objeto File, al cual le pasaremos el nombre de la ruta, el nombre del fichero que queramos crear y la extensión en un string. En esta tabla tenemos los constructores más importantes que podremos utilizar de la librería File. Constructor Descripción File (File padre, String hijo) Crea una nueva instancia File a partir de una ruta abstracta padre y una ruta abstracta hija. Crea una nueva instancia de File al convertir el nombre File (String ruta) de la ruta dada en un nombre de ruta abstracta. File (String padre, String hijo) Crea una nueva instancia de File desde una ruta padre a una ruta hija. Crea una nueva instancia de File a la cual se le pasará File (URI uri) una URI. Una URI es una secuencia de caracteres utili- zada para la identificación de un recurso en particular. Tabla 1. Tabla con los constructores más importantes de la clase File. 16 Acceso a datos Para la gestión de ficheros, deberemos tener una idea bási- ca de los métodos que existen para cada librería. Para File, tenemos estos como los más destacados y que nos serán muy útiles en nuestros desarrollos: Tipo Método Descripción File createTempFile(String Crea un archivo vacío en un directorio temporal, prefijo, String sufijo) usando un prefijo y un sufijo que le pasaremos por parámetro para definir el nombre. boolean createNewFile() Método que crea un fichero nuevo, vacío y con el nombre que le hayamos pasado al constructor al crear una instancia nueva de File. boolean canWrite() Comprueba si nuestro programa puede escribir datos en el archivo. boolean canExecute() Comprueba si se puede ejecutar el archivo. boolean canRead() Comprueba si se puede leer el archivo. boolean isAbsolute() Comprueba si la ruta del fichero es absoluta. boolean isDirectory() Comprueba si el File que hemos creado es o no una carpeta. boolean isFile() Comprueba si la nueva instancia creada de File es un archivo o no. String getName() Devuelve el nombre del archivo o directorio creado. Nos devuelve un objeto Path con la información de Path toPath() la ruta absoluta del fichero que hemos creado. Construye un objeto URI que representa la ruta URI toURI() abstracta del fichero creado. Crea una carpeta con el nombre que le hemos boolean mkdir() pasado al constructor al crear una nueva instancia File. Borra el fichero siempre que esté en la ruta boolean delete() especificada. Tabla 2. Tabla con los métodos más importantes de java.io.File. A continuación, veremos cómo crear un archivo File pa- sándole como parámetro al constructor la ruta absoluta, el nombre y la extensión del fichero que queremos crear. El constructor, lo que realmente necesita es un string, que será la ruta compuesta. Lo podemos entender mejor en este ejemplo: 17 Tema 1: Gestión de ficheros File archivo = new File(“C:\\AD\\fichero.txt”); archivo.createNewFile(); if(archivo.createNewFile()) System.out.println("Creado el fichero "+ archivo); else { System.out.println("No se ha creado el fichero"); } La c: representa el nombre de nuestro disco duro, archivo será el nombre y la extensión será txt. Para definir dónde acaba el nombre y empieza el archivo, ponemos un punto. El método createNewFile() devuelve un booleano true si se crea el fichero, tal y como hemos visto en la tabla de méto- dos importantes. Si queremos crear el fichero en una carpeta, deberemos ponerlo así: File directorio = new File(“c:\\AD\\Ejercicios”); directorio.mkdir(); File archivo = new File("c:\\AD\\Ejercicios\\ficheroDos.txt"); archivo.createNewFile(); if(archivo.createNewFile()) System.out.println("Creado el fichero "+ archivo); else { System.out.println("No se ha creado el fichero"); } Como vemos, después de la c: pondremos una \ el nombre de la carpeta y otra \. Tendremos que tener en cuenta que la carpeta siempre debe existir antes de crear ese fichero. Si queremos crear una carpeta, veremos en el siguiente apartado cómo crearla. java.nio.file.Files Es una librería importante que encontraremos en la versión de Java 8. Es otra alternativa para la creación de ficheros, una de las más recomendadas. Tiene un gran repertorio de métodos para la creación, copia, borrado, escritura y lectu- ra de datos. 18 Acceso a datos Entrando en detalle de este paquete, nos permite un enfo- que diferente a la hora de gestionar los datos: java.nio.file permite el almacenaje de la información del fichero en un búfer. CONCEPTO Un búfer es un bloque de memoria que permite almacenar temporalmente los datos y recorrerlos tantas veces como se desee para tratarlos. Tal y como hemos comentado en la definición de búfer, los datos que se guardan en él se pueden volver a leer según se necesite. Nos proporciona un poco más de flexibilidad a la hora de tratar con un fichero. Esta librería consta con métodos para la manipulación de ficheros que nos serán realmente útiles. Tipo Método Descripción Path createDirectory(Path ruta) Crea un directorio en la ruta indicada. Path createFile(Path ruta) Crea un fichero en la ruta indicada. copy(Path origen, Path Crea una copia de un fichero origen a una Path destino) ruta destino que le indiquemos. Crea una carpeta temporal en la aplicación createTempDirectory(String Path con el nombre que le pasemos como string prefijo) al llamar al método. Este método se encarga de crear un createTempFile(String fichero temporal en la carpeta temporal del Path prefijo, String sufijo) programa, usando el prefijo y el sufijo que le hemos indicado por parámetro. void delete(Path ruta) Borra un fichero de la ruta indicada. Borra un fichero siempre que exista en la boolean deleteIfExists(Path ruta) ruta indicada. Comprueba si el fichero que le indicamos boolean exists(Path ruta) existe en la ruta indicada. Comprueba si ese fichero indicado en la boolean isDirectory(Path ruta) ruta es una carpeta o no. Devuelve el tamaño del fichero que le long size(Path ruta) indiquemos en la ruta. Este método se encarga de recorrer el árbol Path walkFileTree(Path ruta) de directorios de una ruta recursivamente. Tabla 3. Tabla con los métodos más útiles de la librería java.nio.File. 19 Tema 1: Gestión de ficheros En este ejemplo, veremos mejor cómo crear un fichero con este paquete de Java. Path ruta = Paths.get("src/main/resources/crearFicheros/file.txt"); Path ejemploArchivo = Files.createFile(ruta); System.out.println("Hemos creado un fichero "+ ejemploArchivo); Esta vez, no utilizamos un string para definir la ruta del fichero que queremos crear, sino que utilizamos el obje- to Path. Este es un objeto que se utiliza para localizar un fichero dentro de un sistema de archivos y se encargará de representar una ruta del fichero de nuestro sistema. La librería Files necesitará que se le pase este objeto para crear el fichero. Para crear el fichero, llamaremos al método createFile(), el cual devuelve un path con la ruta del fichero creado. En el caso de que el fichero ya exista, Java lanzará un error. Al definir el path, tenemos que asegurarnos de que la ruta absoluta que utilizamos sea correcta, si no es así, se pro- ducirá un error cuando el programa ejecute esta parte del código porque no encontrará la ruta de carpetas indicada. 1.1.1.2. Creación de directorios Para la creación de directorios se usa la misma clase File que hemos comentado anteriormente, pero el método para crearlo es algo distinto a la creación de ficheros. Si nos fija- mos en el ejemplo: File archivo = new File("src/main/resources/crearFicheros directorio"); if(archivo.mkdir()) { System.out.println("Creado el directorio "+ archivo); } Como podemos ver, para crear un directorio el objeto File tiene un método que permite la creación de directorios: mkdir(). En el caso de que se realice con éxito, el método devuelve un booleano true. Algunos de los problemas que nos podemos encontrar con este método son que, si no existe la carpeta indicada, o si la ruta absoluta que utilizamos para crear nuestro directorio no está bien, dará un error en la ejecución de nuestro pro- grama. Debemos tener en cuenta esta posibilidad y controlar los errores. String nombre = " src/main/resources/crearFicheros carpetaEjemplo"; Path ruta = Paths.get(nombre); Files.createDirectories(ruta); 20 Acceso a datos El objeto java.nio.file.Files también nos ofrece posibilidad de creación de directorios a través del método createDirec- tories(), al cual le tendremos que pasar la ruta, como en otros objetos. Aquí podemos apreciar un ejemplo de cómo usar este objeto con el método de creación de carpetas. Este tipo de tareas son especialmente útiles para diferen- ciar carpetas y tener organización dentro de un ordenador o un SFTP para gestionar archivos que se van generando. CONCEPTO Un SFTP (protocolo de transferencia de archivos) es un programa estandarizado para transferir archivos entre ordenadores de cualquier sistema operativo. Es un protocolo cliente servidor que será útil para transferir archivos que, por ejemplo, un usuario sube en una página web y que queremos transferir a un servidor para que que- den almacenados. Una de las posibles utilidades sería crear carpetas por día, y crear nuevos archivos en esa carpeta. 21 Tema 1: Gestión de ficheros 1.1.2. Borrado Para el borrado de archivos, tenemos métodos disponibles tanto en la librería java.io como en java.nio.Files. Estas dos librerías nos permitirán el borrado de ficheros y de direc- torios. A continuación, veremos los métodos más usados tanto en ficheros como en carpetas a modo de ejemplo. En el objeto File, tenemos dos métodos que nos serán real- mente útiles para el borrado: delete(): borrará el fichero que le indiquemos según la ruta o lanzará un error si no lo encuentra. File archivo = new File(“src/main/resources/crearFicheros/fichero.txt “); if(archivo.delete()) { System.out.println(“Hemos borrado un fichero”); } Como podemos ver, no es necesario crear un nuevo ob- jeto para borrar el fichero en cuestión, solo necesitamos indicarle la ruta y el nombre del fichero, aunque también existe la posibilidad de borrar un fichero ya creado con el mismo método. En Java hay muchas maneras de poder borrar ficheros, os mostramos las más utilizadas. 22 Acceso a datos deleteOnExit(): este método, en cambio, borrará el fi- chero o el directorio que le indiquemos por ruta absoluta cuando la máquina virtual finalice. File archivo = new File(“src/main/resources/crearFicheros fichero4.txt”); archivo.deleteOnExit()) { System.out.println(“Hemos borrado un fichero”); } La librería java.nio.Files también nos ofrece la posibilidad de borrar ficheros, aunque el procedimiento es algo distin- to. También cuenta con un método delete() para borrar los ficheros. Para realizar un borrado, se debe realizar de este modo: //Primero crearemos una carpeta, en este caso se crea porque no existe y sino nos dara error. String nombrePath = “ src/main/resources/crearFicheros/directorio”; File ficheroCarpeta = new File(nombrePath); ficheroCarpeta.mkdir(); //Indicamos la ruta con el directorio y el fichero que queremos crear String nombrefichero = “ src/main/resources/crearFicheros /ejercicio/ fichero.txt”; File ejemplo = new File(nombrefichero); ejemplo.createNewFile(); //Cogemos la ruta y lo borramos. Si no existe dara error Path path = Paths.get(nombrefichero); try { Files.delete(path); }catch (IOException e) { System.out.println(“No existe la carpeta” + ficheroCarpeta); } Para el borrado de directorios, se puede usar los mismos métodos que hemos indicado anteriormente. En el caso de directorios, antes de borrar se debe comprobar que esté vacío, si no, no va a borrarse y se lanzara una excepción NoSuchFileException. Hablaremos de las excepciones más adelante. El objeto java.nio.files.File también ofrece posibilidad de borrado de carpetas. String nombrePath = " src/main/resources/crearFicheros/directorio"; Path carpeta = Paths.get(nombrePath); Files.delete(carpeta); 23 Tema 1: Gestión de ficheros 1.1.3. Copia Otra de las acciones que podemos realizar con los ficheros o directorios es el copiado. Este método nos será útil para copiar ficheros de un origen a un destino, y tratarlos sin modificar los datos de origen. Tal y como hemos mostrado en anteriores temas, veremos ejemplos de los dos objetos Java más utilizados que dispo- nen del método para copiar ficheros. Para empezar, la manera más fácil de copiar un archivo es con la API de java.nio. El objeto Files utiliza el método copy(), el cual necesita que se le pase por parámetro la ruta de origen y la ruta de destino, con un objeto Path. La copia fallará si el fichero destino ya existe, pero si utilizamos la opción REPLACE_EXISTING el fichero se sobrescribirá. Ve- remos en el ejemplo cómo usar esta opción. Este método también se usa para los directorios, pero de- bemos tener en cuenta que solo se copiará la carpeta, no el contenido de esa carpeta. Path origen = Paths.get(“src/main/resources/copiarFicheros/ejemploCopia.txt”); Path ejemploOrigen = Files.createFile(origen); Path destino = Paths.get(“src/main/resources/copiarFicheros/ /destino”); Path ejemploDestino = Files.createFile(destino); Files.copy(origen, destino, StandardCopyOption.REPLACE_EXISTING); 24 Acceso a datos Por otro lado, el objeto Files que se encuentra en la API java.io ofrece también la opción de copiar ficheros. Actúa de una manera parecida, pero se implementa de modo dis- tinto. Para realizar el copiado, es necesario crear búferes. Aquí podemos ver un ejemplo práctico: //Primero se crean los ficheros File archivoOrigen = new File("src/main/resources/copiarrFicheros/ori- gen.txt"); archivoOrigen.createNewFile(); File archivoDestino = new File("src/main/resources/copiarFicheros/des- tino.txt"); archivoDestino.createNewFile(); try { // Se lee el origen InputStream origen = new BufferedInputStream(new FileInput- Stream(archivoOrigen)); // Fichero destino OutputStream destino = new BufferedOutputStream(new FileOutput- Stream(archivoDestino)); byte[] buffer = new byte; int lengthRead; while ((lengthRead = origen.read(buffer)) > 0) { // se escriben los datos de un fichero a otro destino.write(buffer, 0, lengthRead); // Se cierra el proceso destino.flush(); } // Ejemplo de copiado de datos con la api java.nio Path orig = Paths.get("src/main/resources/copiarFicheros/ejemplo- Copia.txt"); Path ejemploOrigen = Files.createFile(orig); Path dest = Paths.get("src/main/resources/copiarFicheros/desti- no"); Path ejemploDestino = Files.createFile(dest); Files.copy(orig, dest, StandardCopyOption.REPLACE_EXISTING); System.out.println("se ha realizado la copia de ficheros"); } catch (Exception e) { e.getCause(); } Tal y como vemos en el ejemplo, primero tendremos que crear un archivo origen y otro destino, con las rutas co- rrespondientes. A continuación, necesitamos crear un InputStream con un nuevo BufferedInputStream, que se 25 Tema 1: Gestión de ficheros encargará de leer los datos del archivo de origen. Profun- dizaremos más adelante en estas clases, pero las clases InputStream y BufferedInputStream se caracterizan por leer archivos, y en este ejemplo nos serán útiles para coger el contenido del fichero origen y traspasarlo al fichero destino. En cambio, las clases OutputStream, BufferedOu- tputStream y FileOutputStream se encargan de escribir ficheros. Para el objeto destino, debemos crear un OutputStream que contendrá un BufferedOutputStream para albergar el resultado de la copia. Con un bucle, recorreremos el fichero origen dato a dato y lo escribiremos al objeto destino. Al finalizar, se llamará al método flush() para terminar con el proceso. Este método se encargará de vaciar el OutputS- tream y se guardarán los archivos de salida en un búfer. Para realizar la copia de los archivos es necesario muchas más líneas de código en comparación con el otro ejemplo, por eso recomendamos usar la API java.nio. 26 Acceso a datos 1.1.4. Movimiento Java proporciona funciones para mover ficheros entre car- petas. Hay diferentes maneras de hacerlo, y veremos las posibilidades que tenemos según la API que queramos usar. La API java.nio es la que ofrece una opción más rápida para mover un fichero. Usa el método move(), al cual le tendre- mos que pasar por parámetro el origen e indicarle un destino. Como se hacía en la copia, tenemos la posibilidad de indicarle por parámetro REPLACE_EXISTING. Este pará- metro le indica al método que, si existe un dichero con ese nombre, lo deberá sobrescribir. Aquí tenemos un ejemplo de cómo se debe implementar: Path destino = Files.move(Paths.get(“c:/ejercicio/ejemplo.txt”), Paths.get(“c:/ejercicio/destinoEjemplo.txt”), StandardCopyOption.REPLACE_EXISTING); Otra manera de realizar esta acción es con el objeto File de la API java.io. Este objeto tiene una forma más rudimen- taria de realizar el movimiento. Básicamente, se encarga de renombrar el fichero a uno nuevo y borrar el fichero de origen. Recomendamos por su rapidez usar el objeto Files. File origenArchivo = new File(“src/main/resources/crearFicheros/carpetaEjemplo/ficheroOrigen.txt”); origenArchivo.createNewFile(); if(origenArchivo.renameTo(new File(src/main/resources/crearFicheros/destino.txt”))){ origenArchivo.delete(); System.out.println(“Se ha movido el fichero.”); } else{ System.out.println(“Se ha producido un error.”); } ponte a prueba ¿Qué método definido dentro de la clase File nos permite diferenciar de un objeto de dicha clase es un directorio? a) isDir() b) isNotFile() c) isDirectory() d) canDirectory() 27 Tema 1: Gestión de ficheros 1.2. Formas de acceso a un fichero de texto en modo de acceso secuencial y aleatorio. Ventajas e inconvenientes de las distintas formas de acceso En este apartado, explicaremos las diferentes maneras de acceder a la información de un fichero. Podemos diferen- ciar dos tipos de acceso: el acceso secuencial y el acceso aleatorio. Para empezar, aprenderemos los conceptos básicos sobre el acceso a ficheros de manera secuencial. CONCEPTO Un archivo con acceso secuencial es un fichero donde se guarda la información en una secuencia de caracteres, de manera que el acceso a ellos se debe realizar en estricto orden, con base en el campo clave de su origen. Para simplificar más la definición, son archivos donde se guardan los registros en orden, con base en el campo clave de origen. Para leer los datos de un fichero secuencial, se debe acceder a los datos uno después de otro y, una vez consultados, no se podrá acceder a ellos si no se sigue el orden. Aquí tenemos la simplificación a modo de esquema para aclarar el concepto. Ilustración 3. Acceso secuencial a ficheros. Como se mencionó anteriormente, estos son archivos en los que los registros se almacenan en orden por el campo clave de registro. Primero demostramos archivos de acceso secuencial utilizando archivos de texto, lo que permite al lector crear y editar rápidamente ficheros fáciles de leer. Más adelante, veremos ejemplos prácticos de lectura y escritura de este tipo de acceso de datos. 28 Acceso a datos Acceso archivo aleatorio CONCEPTO El acceso aleatorio a archivos es un tipo de acceso a datos que permite al programa Java acceder a los datos sin un orden, es decir, a cualquier posición en que se encuentren los datos, sin ningún orden. A diferencia del acceso secuencial, con el acceso aleatorio no es necesario empezar desde la primera línea del fichero. Se asemeja a los arrays de bytes, ya que podemos acceder a cualquier parte de los datos con un puntero de fichero, es decir, indicando al método de acceso en qué posición que- remos empezar a tratar los datos. En la siguiente ilustración, podremos apreciar más fácilmente a qué nos referimos con acceso aleatorio de los datos. Ilustración 4. Acceso aleatorio a ficheros. Como podemos apreciar, el acceso a los datos es total- mente aleatorio y sin ningún orden, y este tipo de acceso es realmente útil en aquellas aplicaciones de baja latencia que necesitan persistencia. Este tipo de acceso a datos es especialmente útil para situaciones en que el programa ha sufrido un error y es necesario acceder otra vez de manera aleatoria a los datos. Java contiene un objeto que permite este tipo de acceso de datos: es el objeto RandomAccesFile. Este objeto tiene la ha- bilidad de leer y escribir cualquier tipo de dato en un fichero de manera aleatoria. Cuando lee el contenido del archivo, comienza con la ubicación actual del puntero del archivo y el puntero avanza más allá de cuántos bytes se leen. De manera similar, cuando escribe datos en un archivo de acceso aleatorio, comienza a escribir desde la ubicación actual del puntero del archivo y luego avanza el puntero del archivo más allá del número de archivos escritos. 29 Tema 1: Gestión de ficheros Los métodos más destacados de este objeto son: seek(): logra configurar el puntero del archivo a cualquier ubicación aleatoria. getFilePointer(): permite obtener la ubicación donde se encuentra en ese momento. Como veremos en los ejemplos más detenidamente: //Paso 1: Crearemos un fichero donde se escribiran los datos File archivoEjemplo = new File("src/main/resources/crearFicheros/ ran- domAccesFileEjemplo.txt"); try { if(archivoEjemplo.createNewFile()) { System.out.println("Se ha creado el fichero"); } } catch (IOException e1) { System.out.println("Se ha producido un error"); e1.printStackTrace(); } //Paso 2: Definiremos el texto que queremos añadir al fichero String texto = "Prueba de informacion acceso a datos"; int posicion = 10; String datos = null; try { RandomAccessFile lectura = new RandomAccessFile(archivoEjemplo, "rw"); //Paso 3: Se mueve a la posición del fichero que le indiquemos lectura.seek(posicion); //Paso 4: Aquí leemos el string de datos desde el objeto RandomAcces- File datos = lectura.readUTF(); lectura.close(); } catch (IOException e) { e.printStackTrace(); } System.out.println("Programa de prueba de acceso de datos aleatorio : " + texto); En el ejemplo, la clase RandomAccesFile, al crear una nueva instancia, necesita un objeto File. Para ello, primero crea- remos un fichero en una ruta de nuestro sistema operativo. Después, también un string con el texto que queramos escribir en ese fichero, como también un int con la posición que queremos empezar a escribir el fichero. Seguida- mente, declararemos una nueva clase RandomAccesFile y crearemos una nueva instancia pasándole el archivo ar- chivoEjemplo y le diremos que queremos permisos de escritura y lectura: para ello, al constructor le pasaremos el 30 Acceso a datos string “rw”. La r equivale a reading (lectura en inglés) y la w a writting (escritura). A continuación, tenemos que realizar la llamada al método seek(), que es el encargado de moverse dentro del fichero, y le tenemos que indicar la posición a la que queremos empezar. Seguidamente, se hace la llamada a readUTF(), que se encargará de leer todos los datos del fichero y me- terlos en un string. Cuando termina el proceso, hacemos la llamada al método close(), que cerrará todos los procesos y se asegurará de que todo queda cerrado sin consumir más recursos dentro del programa. Ventajas e inconvenientes de las distintas formas de acceso Las ventajas del acceso de datos secuencial frente al alea- torio es que este tipo de estructura se puede usar para persistir datos de manera más simple. Por otro lado, las desventajas son evidentes: no es una ma- nera de acceder a los datos de manera eficiente. Nos obliga a acceder a los datos en orden, aunque el resto de datos no nos interesen. En cambio, el acceso de datos aleatorio proporciona mucho más control y eficiencia a la hora de tratar la información. Por un lado, podemos elegir el punto exacto de lectura de datos, permitiendo ser mucho más rápido y ahorrar consu- mo de recursos en la aplicación. Como desventaja podemos destacar que puede ser un método de acceso un poco más lento en comparación con el secuencial. 31 Tema 1: Gestión de ficheros 1.3. Clases para gestión de flujos de datos de un fichero binario desde / hacia archivos En la gestión de ficheros, unas de las principales acciones son la lectura y escritura de datos en ficheros. Antes de adentrarnos en cómo realizarlo, tendremos que identificar qué tipo de datos estamos tratando. En primer lugar, pode- mos tratar ficheros basados en caracteres o basados en bytes (o ficheros binarios). CONCEPTO Un fichero binario es un tipo de archivo que contiene información en cualquier tipo de codificación (o encoding) representado por ceros y unos (conocidamente como binario) para ser tratado y almacenado por un programa o sistema. 1.3.1. Escritura y lectura de datos Una de las acciones más importantes y útiles de los ficheros es la posibilidad de leer el contenido y escribir en él. Para ello, en este apartado aprenderemos como se realiza con las dos API más importantes y detallaremos los objetos y métodos más útiles para poder llevarlo a cabo. CONCEPTO Un fichero de caracteres es aquel que contiene información de texto sin caracteres raros, que se encuentra con la codificación por defecto del sistema que está tratando los datos. 32 Acceso a datos 1.3.1.1. Lectura El lenguaje Java permite más de una manera de implemen- tar una lectura de un fichero: la lectura de ficheros de texto y la lectura de ficheros binarios. Ficheros de texto Ficheros de texto youtu.be/4IhniQyPDzY Para leer un archivo de caracteres en el encoding que venga por defecto, hay diferentes clases que vienen por defecto en Java y que son las que vamos a explicar y utilizar. Todas estas clases están definidas bajo el paquete java.io. FileReader: es una clase muy útil para leer archivos de texto utilizando la codificación del sistema operativo. Los constructores de esta clase usan el búfer con tamaño pre- determinado. //Utilizamos para leer el fichero un archivo en la carpeta resources FileReader fichero = null; try { fichero = new FileReader(“src/main/resources/ejemplo.txt”); } catch (FileNotFoundException e) { System.out.println(“Se ha producido un error”); e.printStackTrace(); } int i; try { while ((i=fichero.read()) != -1) System.out.print((char) i); } catch (IOException e) { System.out.println(“Se ha producido un error”); e.printStackTrace(); } En este ejemplo, vemos que el procedimiento es algo más sencillo. Primero, debemos definir un fichero creando un fichero FileReader y pasándole al constructor la ruta del archivo que queramos leer. A continuación, creamos un bucle while que nos va a permitir leer los datos del fichero y mostrarlos por la consola. Si nos fijamos, en este caso también controlamos la posibilidad de no encontrar el fi- chero y también si se produce un error durante el proceso de lectura. Esta clase lee los datos carácter a carácter has- ta encontrar un -1, que es el número que se utiliza para indicar que no hay más caracteres. Cada lectura recogerá un carácter único, es por este motivo que utilizamos print y no printIn. 33 Tema 1: Gestión de ficheros BufferedReader: lee ficheros de texto desde un stream de entrada de caracteres. Esta clase realiza una lectura de los datos mediante un búfer, lo que lo convierte en un método muy eficiente de lectura. Tiene la peculiaridad que permite definir el tamaño de su búfer o usar el tama- ño predeterminado. En general, cada petición de lectura que realiza un BufferedReader hace que se realice una so- licitud de lectura correspondiente del flujo de bytes. Por ese motivo, es aconsejable envolver un BufferedReader alrededor de cualquier lector cuyos métodos read() pue- dan ser costosos, como FileReaders e InputStreamReaders. //Utilizamos para leer el fichero un archivo en la carpeta resources File file = new File("src/main/resources/ejemplo.txt"); //Creamos el buffer BufferedReader reader; try { //Envolvemos el archivo dentro de un file reader reader = new BufferedReader(new FileReader(file)); String datos; //Imprimimos los datos por consola while ((datos = reader.readLine()) != null) System.out.println(datos); } catch (FileNotFoundException e) { System.out.println("Se ha producido un error"); e.printStackTrace(); } 34 Acceso a datos Primero, definiremos el archivo que queremos leer y su ruta, puede ser la ruta absoluta o la relativa. En este caso, hemos utilizado un fichero que se encuentra dentro del proyecto. A continuación, se define el BufferedReader y se declara una nueva instancia envolviendo el fichero File en un FileReader y pasándole ese último al constructor de BufferedReader. Seguidamente, solo nos hará falta crear un string que contendrá los datos del fichero y recorrer con un while el BufferedReader línea por línea e imprimir- lo por consola. Como vemos, controlamos la posibilidad de error con un FileNotFoundException. Scanner: se trata de una clase que analiza los ficheros de caracteres y permite analizar la información del fichero y clasificarla según su tipo. Esta clase divide los datos que recibe en tokens (un token es el elemento más pequeño de un programa) utilizando un patrón delimitador que por defecto coincide con los espacios en blanco. Los tokens obtenidos se pueden convertir en valores de dife- rentes tipos utilizando los métodos next(). //Utilizamos para leer el fichero un archivo en la carpeta resources File archivo = new File("src/main/resources/ejemplo.txt"); Scanner lector = null; try { lector = new Scanner(archivo); } catch (FileNotFoundException e) { System.out.println("Se ha producido un error"); e.printStackTrace(); } //Usamos \\Z como un delimitador lector.useDelimiter("\\Z"); System.out.println(lector.next()); En este ejemplo, primero, es necesario definir un archivo File con la ruta del fichero que queremos leer. En segundo lugar, vemos que es necesario definir el Scanner y envol- ver en un try-catch la creación de una nueva instancia. Si no se produce ningún error, llamando al método next() podremos visualizar por la consola el contenido del fiche- Ficheros binarios ro. Debemos tener en cuenta que se tiene que controlar si nuestro programa no encuentra el fichero indicado, para youtu.be/X-RzQcinJrA ello lo hemos definido en el catch. Ficheros binarios Para la lectura de ficheros binarios (también conocidos como ficheros de bytes), archivos con caracteres especiales o ficheros de imagen, es necesario usar otro tipo de clases más específicas para ese tipo de acción. Todas estas clases están definidas bajo el paquete java.io. 35 Tema 1: Gestión de ficheros Vamos a explicar los más destacables: InputStream: se caracteriza por ser una superclase abs- tracta que se encarga de leer un stream de bytes. Solo es capaz de leer un byte a la vez, por lo que lo hace una op- ción bastante lenta. Al ser una superclase, no es útil por sí misma, es por ese motivo que se usan sus subclases. CONCEPTO Se considera una superclase a aquella clase padre de la que derivan diferentes clases, también conocidas como subclases. Las subclases derivan de la clase padre y heredan todas sus propiedades y métodos. CONCEPTO BUSCA EN LA WEB Una clase abstracta es un tipo de clase que permite la declaración de métodos, pero no su Si queréis refrescar implementación. La implementación será realizada conceptos, podéis con- por las clases que implementen esa clase. sultar la documentación de Java, donde se explica mucho más detallado: InputStream https://bit.ly/3nvDgKJ FileInputStream BufferedInputStream Ilustración 5. Esquema de la estructura de InputStream. Esta clase tiene diferentes métodos que serán útiles si que- remos leer datos de un stream de datos: Método Descripción avaliable() Este método se encarga de devolver los bytes disponibles en el InputStream. close() Es el método que se encarga de cerrar el stream. mark() Este método se encarga de marcar la posición de los bytes leídos del stream. read() Se encarga de leer los bytes de datos uno a uno. read(byte[] array) Se encarga de leer los bytes del stream y los almacena en un array. reset() Método que se encarga de quitar la marca de los bytes leídos por el método mark(). skips() Este método se encarga de descartar un numero especificado de bytes del stream que este leyendo en ese momento. Tabla 4. Tabla de métodos importantes de InputStream. 36 Acceso a datos FileInputStream: es una subclase de InputStream que se utiliza para leer streams de bytes. Está enfocada al trata- miento de bytes sin procesar, como datos de imágenes, vídeo o audio. También puede leer ficheros de caracteres, pero no es el más adecuado, es mejor FileReader. public static void lecturaFileInputStream() throws IOException{ byte[] array = new byte; try { //Utilizamos para leer el fichero un archivo en la carpeta resources InputStream archivo = new FileInputStream(“src/main/resources/ejemplo.txt”); //Lectura de bytes desde el Input Stream archivo.read(array); // Convierte los bytes en un string String datos = new String(array); System.out.println(datos); // Cerramos el inputStream archivo.close(); } catch (FileNotFoundException e) { System.out.println(“No se ha encontrado el archivo”); e.printStackTrace(); } } En el ejemplo de arriba, primero hemos creado un InputStream con una nueva instancia de su subclase Fi- leInputStream con la ruta relativa del fichero que se encuentra en nuestro proyecto, pero puede ser cual- quier fichero de bytes. Para leer los datos del fichero, hemos implementado el método read(), que irá guar- dando los bytes en el array de bytes que hemos creado al principio. Para imprimir por consola los bytes, hemos creado un string pasándole el array de bytes. Una vez realizado todo esto, tendremos que cerrar el stream con el método close(). 37 Tema 1: Gestión de ficheros BufferedInputStream: es una clase que extiende (es de- cir, que implementa todos los métodos) de InputStream y que se utiliza para leer streams de datos en bytes de ma- nera más eficiente. Esta clase se caracteriza por tener un búfer interno de 8192 bytes. Durante la operación de lec- tura, BufferedInputStream se encargará de leer una porción de bytes del fichero que se encuentra en el disco y almacenará los bytes en el búfer interno. Una vez alma- cenados en el búfer interno, se leerán los bytes individualmente. try { // creamos un InputStream para coger el fichero de la ruta de nuestro proyecto FileInputStream fichero = new FileInputStream("src/main/resources/ ejemplo.txt"); // Creamos un BufferedInputStream y le pasamos el archivo al construc- tor BufferedInputStream bufer = new BufferedInputStream(fichero); // Se encarga de leer el primer byte del fichero int i = bufer.read(); while (i != -1) { System.out.print((char) i); // Va leyendo cada byte del buffer i = bufer.read(); } bufer.close(); } catch (Exception e) { System.out.println("Se ha producido un error"); e.getStackTrace(); } En este ejemplo, podemos ver que hemos creado en pri- mer lugar un fichero con una nueva instancia FileInputStream a la cual le pasaremos la ruta del fichero que queremos leer. A continuación, creamos un búfer Bu- fferedInputStream, al cual le pasaremos el fichero creado en la línea anterior a través del constructor. Seguidamen- te, tendremos que procesar el fichero, para ello utilizaremos el método read() del búfer e iremos leyendo byte a byte el contenido. Mientras el byte no equivalga a -1 se irá recorriendo el fichero e imprimiendo por consola el contenido. Una vez terminado el proceso, cerraremos el búfer. Todo el ejercicio está envuelto en un try-catch para controlar los posibles errores que puedan ocurrir, como que no encuentre el fichero o se produzca un error leyen- do el búfer de bytes. Tenemos que tener en cuenta que siempre se deben controlar los errores. 38 Acceso a datos 1.3.1.2. Escritura de datos Para la escritura de datos, también podemos diferenciar dos grupos: el de escritura de ficheros de caracteres y el de escritura de ficheros de bytes. Primero detallaremos las clases de escritura de ficheros de caracteres. El proceso es muy parecido al de lectura de datos, pero se usarán las clases que detallaremos a continuación: Ficheros de texto Para la escritura de ficheros de texto, usaremos las clases que podemos encontrar en el paquete java.io. Writer: es una superclase abstracta que no se usa sola por sí misma, sino que siempre va acompañada de sus subclases, como hemos visto con InputStream. BufferedWriter CharArrayWriter PrintWriter Writer FilterWriter OutputStreamWriter FileWriter PipedWriter StringWriter Ilustración 6. Esquema de las subclases de la clase Writer. 39 Tema 1: Gestión de ficheros Estos son los métodos de la clase más destacables: Método Descripción append(char c) Este método añade el carácter que le indiquemos close() El método cierra el stream que está en uso. El método obliga escribir toda la información que tenga el objeto flush() OutputStreamWriter al destino correspondiente. Este método nos indica qué encoding se está utilizando para getEncoding() escribir los datos en el fichero. Este método escribe en un fichero un carácter que le pasemos por write(char c) parámetro. Este método escribe en un fichero el array que le pasemos por write(char[] array) parámetro. Este método escribe en un fichero el string que le pasemos por write(String data) parámetro. Tabla 5. Métodos más destacables de Writer. OutputStreamWriter: es una subclase de Writer que se usa para convertir streams de caracteres, pero también se utiliza para streams de bytes. Como es capaz de tratar tanto caracteres como bytes, se usa como puente entre estos dos tipos de datos. A continuación, veremos un ejemplo de escritura de da- tos. Primero, es necesario la creación de un nuevo fichero FileOutputStream, creando una nueva instancia y pasán- dole al constructor la ruta del fichero que queremos 40 Acceso a datos escribir. Este objeto será un auxiliar que pasarle al cons- tructor para poder crear un OutputStreamWriter. Seguidamente, para escribir información, deberemos te- ner un string con los caracteres que escribir y hacer una llamada al método write() pasándole por parámetro el string. Una vez finalizado el proceso, deberemos cerrar el stream con el método close(). String data = "Ejemplo de escritura de datos con FileOutputStream"; try { //Creamos un FileOutputStream FileOutputStream archivo = new FileOutputStream("src/main/resources/ outputStream.txt"); // Creamos el stream que nos va a ayudar a escribir los datos en el fichero indicado OutputStreamWriter escribirDatos = new OutputStreamWriter(archivo); // Con este método escribiremos datos en el fichero escribirDatos.write(data); // Cerramos la el writer escribirDatos.close(); }catch (Exception e) { System.out.println("Se ha producido un error"); e.getStackTrace(); } En este ejemplo, tendremos que realizar la operación con un try-catch para poder controlar los posibles errores que puedan producirse. Los más usuales siempre son un Fi- leNotFoundException cuando no encuentra la ruta del fichero indicado o un error en el proceso de escritura de los datos. Este método lanza un IOException, por lo tanto, tendremos que tenerlo controlado en un catch. FileWriter: es una clase que nos permitirá escribir carac- teres en un archivo. Es una subclase de OutputStreamWriter. String data = "Ejemplo de escritura de datos con FileWriter"; try { // Creamos un FileWriter FileWriter output = new FileWriter("src/main/resources/fileWriter. txt"); // Con este método escribiremos datos en el fichero output.write(data); // Cerramos la el writer output.close(); }catch (Exception e) { System.out.println("Se ha producido un error"); e.getStackTrace(); } 41 Tema 1: Gestión de ficheros El funcionamiento para escribir datos con esta clase es muy parecido al anterior, usan los mismos métodos, ya que las dos clases heredan métodos de la clase padre Wri- ter. En primer lugar, deberemos definir la información que queremos escribir en el fichero. Crearemos un FileWriter definiendo la ruta del fichero que escribir. Seguidamente, haciendo uso del método write(), pasaremos por pará- metro el string con la frase que hemos definido. Una vez escrito, cerraremos el Writer con el método close(). BufferedWriter: es una subclase de Writer que también permite almacenar datos en el búfer. Esta es la clase más eficiente para escribir datos en un archivo, ya que per- mite escribir los datos en el búfer y no en el disco. Una vez que el búfer esté lleno o se cierre, los datos se escri- ben en el disco. Como vamos a ver en el siguiente ejemplo, en primer lu- gar, deberemos crear un FileWriter creando una nueva instancia y pasándole al constructor la ruta del fichero que queremos usar. A continuación, crearemos el Bu- fferedWriter y le pasaremos el archivo creado en la línea anterior. Seguidamente, podremos escribir los datos en el fichero y al acabar cerraremos el búfer. Como se puede apreciar, el proceso es igual al ser subclases de Writer. String data = “Ejemplo de escritura de datos con BufferedWriter”; try { // Creamos un FileWriter FileWriter file = new FileWriter(“src/main/resources/output.txt”); // Creates a BufferedWriter BufferedWriter output = new BufferedWriter(file); // Con este método escribiremos datos en el fichero output.write(data); // Cerramos la el writer output.close(); }catch (Exception e) { System.out.println(“Se ha producido un error”); e.getStackTrace(); } 42 Acceso a datos Ficheros binarios La escritura de ficheros binarios consiste en escribir datos de tipo byte en un fichero. Este tipo de fichero podrá ser un fichero de datos codificado, un archivo de audio, uno de vídeo, una foto, etcétera. Para la escritura de ficheros binarios, usaremos las clases que podemos encontrar en el paquete java.io. OutputStream: pertenece al paquete java.io y es una su- perclase abstracta que se utiliza para escribir streams de bytes. OutputStream FileOutputStream Ilustración 7. Esquema de las subclases de la clase OutputStream. Estos son los métodos de la clase más destacables: Método Descripción close() El método cierra el stream que está en uso. flush() Este método libera los datos del stream. write(int b) Este método se encarga de escribir un byte indicado al fichero. write(byte[] array) Este método escribe todo el array en un fichero. Tabla 6. Métodos más importantes de la clase OutputStream. FileOutputStream: esta clase se encargará de escribir streams de bytes en los ficheros. Es una subclase que he- reda de OutputStream. String data = "Ejemplo de escritura de datos con FileOutputStream"; try { FileOutputStream output = new FileOutputStream("src/main/resources/ escrituraBytes/fileOutput.txt"); byte[] array = data.getBytes(); //Escribimos los datos en el archivo output.write(array); // Cerramos el writer output.close(); } catch (Exception e) { System.out.println("Se ha producido un error"); e.getStackTrace(); } 43 Tema 1: Gestión de ficheros El procedimiento para usar las clases y sus métodos si- gue la dinámica explicada ya en diferentes apartados. En primer lugar, crearemos una nueva instancia de la clase FileOutputStream y le pasaremos al constructor la ruta del fichero que queremos escribir. Como el método wri- te() necesita un array de bytes para escribir los datos, transformaremos el string que queremos escribir en un array de bytes con el método getBytes() que tiene la cla- se String. A continuación, si no se produce ningún error, cerraremos el stream con el método close(). BufferedOutputStream: es una clase que se utiliza para envolver OutputStream para dar soporte a las capacida- des del búfer. Se trata de una de las clases más eficientes de escritura de datos. Estos son solo algunas de las más importantes, pero en realidad existen muchas más clases para escribir datos en archivos de texto. Aquí solo mostramos una muestra de lo que podemos llegar a hacer con Java, pero existen infinidad de posibilidades. String data = "Ejemplo de escritura de datos con BufferedOutputStream"; try { BufferedOutputStream bufer = new BufferedOutputStream(new FileOutputS- tream("src/main/resources/escrituraBytes/output.txt")); bufer.write(data.getBytes()); bufer.close(); } catch (IOException e) { System.out.println("Se ha producido un error"); e.getStackTrace(); } ponte a prueba El método close() nos permite cerrar el flujo abierto desde nuestro programa a un fichero binario aleatorio, aunque actualmente está en desuso y no es obliga- torio usarlo puesto que el flujo se cierra al finalizar la operación de lectura o escritura. a) Verdadero b) Falso 44 Acceso a datos 1.4. Trabajo con archivos XML En este tema, trabajaremos con archivos XML. Para refres- car la memoria, los XML (lenguaje de marcas extensible) es un fichero de texto simple de metalenguaje extensible que consta de diferentes etiquetas que contienen los datos. CONCEPTO Podemos entender el concepto metalenguaje como el código que utilizaremos para describir la información que queremos transmitir en un fichero XML. Es un lenguaje especializado para describir nuestro lenguaje natural en código: mediante símbolos, iremos representando la estructura de lo que se quiera representar. Un XML se compone de la declaración del XML: En esta parte se declara el XML, la versión del documen- to y se define el encoding que se utilizará en el fichero. Debemos definir esta línea como nuestra primera línea de cualquier fichero XML. Los campos versión y encoding deben estructurarse en este orden para considerarse una estructura correcta. De todos modos, las declaraciones son opcionales, pero si se establece encoding se deberá añadir también la declaración versión. La versión nos servirá para indicar en qué momento se realizó el documento y seguir una evolución del estándar si se modifica el archivo en un futuro. El encoding, como hemos explicado en apartados anteriores, nos permitirá definir qué caracteres vamos a utilizar. Por defecto, utilizaremos UTF-8. A continuación, añadiremos el cuerpo del XML, que es la parte más importante del fichero. Este documento adquiere una estructura de árbol, compuesto por un ele- mento raíz o principal dentro del cual añadiremos el resto de los elementos. Como vemos en este ejemplo de cuerpo, podemos ver que se estructura por un elemento padre “raíz” del cual se des- prenden diferentes hijos, en este caso, el elemento “tronco”, 45 Tema 1: Gestión de ficheros o subhijos, que serían los elementos “rama”. Podrá tener tantos hijos como sea necesario, pero el elemento padre no se podrá repetir. Dentro de cada etiqueta, se podrá encon- trar la información de cada elemento. Para finalizar, nuestro XML debería parecerse a algo más o menos así: Seat Ibiza rojo 2019 Ford Focus gris 2014 Este tipo de fichero se utiliza en muchos programas para comunicarse los unos con los otros y transportar diferentes datos entre ellos. 1.4.1. Analizadores sintácticos (parser) y vinculación (binding). Analizadores sintácticos (parser DOM y SAX) y vinculación El soporte XML en Java tiene diferentes API que nos permi- tirán trabajar con la información de estos archivos. Un analizador sintáctico es básicamente un objeto que permitirá leer la información del XML y acceder a ella para extraerla. Como veremos en este tema, hay diferentes tipos que nos ofrecen diferentes ventajas frente a otros. DOM SAX JAXB Eficiente NO SÍ SÍ Navegación bidireccional SÍ NO SÍ Manipulación del XML SÍ NO SÍ binding NO NO SÍ Tabla 7. Esquema de las características de los analizadores sintácticos. 46 Acceso a datos Como vemos en esta tabla, Java soporta diferentes API para gestionar XML. A continuación, analizaremos más profundamente cada una de ellas y mostraremos algunos ejemplos. 1.4.1.1. Acceso datos DOM CONCEPTO Un parser es un analizador sintáctico para XML que se encarga de verificar que la estructura de ese fichero de texto es correcta. La API DOM se caracteriza por ser un analizador basado en modelos de carga de documentos con estructuras en árbol, el cual guarda en memoria la información del XML. CONCEPTO DOM es una plataforma e interfaz de lenguaje estándar que permite a los programas y a los scripts acceder y actualizar dinámicamente el contenido, la estructura y el estilo de un documento. Se caracteriza por tener una estructura en forma de árbol. 47 Tema 1: Gestión de ficheros Entre las características principales de este analizador po- demos destacar que nos permitirá tener los datos en orden, navegar por ellos en ambas direcciones y disponer de una API de lectura y escritura de datos, así como también la manipulación del fichero XML. Lo único negativo que cabe destacar es que el parser DOM tiene un procesado de información bastante lento, lo que provoca que consuma y ocupe mucho espacio en memoria del programa al cargar o tratar el fichero XML. Si observamos el ejemplo, podemos ver que, para empe- zar, declaramos un fichero XML. Tendremos que indicarle la ruta del fichero. El fichero tendrá esta estructura con estos datos: Seat Ibiza rojo 2019 Si nos fijamos, en esta ocasión le pasamos la ruta relativa del fichero. Este fichero estará en una carpeta creada den- tro de nuestro proyecto, dentro de la carpeta main/ resources/xml, aunque puede usarse la ruta absoluta, solo tendréis que cambiar esa ruta por la de vuestro fichero. A continuación, es necesario crear una nueva instancia del objeto DOM que cargará el archivo XML en la memoria. 48 Acceso a datos try{ //Indicaremos la ruta del fichero xml //Src es el nombre raiz de nuestro proyecto, main es la primera capreta, resources la siguiente, dentro de xml encontraremos el fichero //Esta es la ruta relativa. File arxXml = new File(“src/main/resources/xml/coches.xml”); //Creamos los objetos que nos permitiran leer el fichero DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); //Le pasamos el XML Document doc = db.parse(arxXml); doc.getDocumentElement().normalize(); System.out.println(“Elemento raiz:” + doc.getDocumentElement(). getNodeName()); NodeList nodeList = doc.getElementsByTagName(“coche”); //Creamos un bucle para leer los datos del xml y los mostramos en la consola for (int itr = 0; itr < nodeList.getLength(); itr++) { Node node = nodeList.item(itr); if (node.getNodeType() == Node.ELEMENT_NODE){ Element eElement = (Element) node; System.out.println(“Marca: “+ eElement. getElementsByTagName(“marca”).item(0).getTextContent()); System.out.println(“Modelo: “+ eElement. getElementsByTagName(“modelo”).item(0). getTextContent()); System.out.println(“Color: “+ eElement. getElementsByTagName(“color”).item(0).getTextContent()); System.out.println(“Matriculacion: “+ eElement. getElementsByTagName(“matriculacion”).item(0). getTextContent()); } } } catch (Exception e) { e.printStackTrace(); } En primer lugar, vamos a utilizar una nueva instancia de DocumentBuilderFactory, esta clase nos va a permitir ob- tener un analizador sintáctico que produce la jerarquía de objetos DOM a partir de documentos XML. Una vez creada la instancia de esta clase, necesitaremos definir una clase DocumentBuilder. Esta es necesaria para posteriormente definir el documento que nos permitirá parsear el XML. 49 Tema 1: Gestión de ficheros Después crearemos una nueva instancia de la clase Do- cument, que nos permitirá almacenar nuestro documento XML. La clase Document representa un documento XML y nos proporcionará el acceso al contenido del documento XML. Seguidamente, necesitaremos obtener el nodo raíz a través del método getDocumentElement(), así obtendremos los datos de esa etiqueta. A continuación, tenemos que detectar cuántos elementos contiene el XML, es decir, cuántos nodos hay definidos. Para ello, utilizamos el método getElementByTagName() al cual le pasaremos el nombre del nodo que queremos sustraer, en este caso, “coche”. Para almacenar los datos obtenidos, definiremos el NodeList, que almacenará todos los datos encontrados haciendo la llamada al método ge- tElementByTagName(). Si necesitamos acceder a todos los nodos desde el inicio del fichero, podemos llamar recursi- vamente a este método: getChildElement(). En el ejemplo vemos también que definimos

Use Quizgecko on...
Browser
Browser