Programación 3
Universidad de Alicante, 2024–2025
Sistema de entrada/salida de Java
Java dispone de diversos paquetes para gestionar la entrada/salida de
una aplicación. El que describimos aquí es el paquete
java.io
(Java IO), que se basa en el concepto de
streams (flujos de datos).
Otro paquete dedicado a la gestión de entrada/salida es
java.nio
(Java New IO). Al final de esta página hay una
breve descripción de este paquete.
El paquete Java IO se centra principalmente en la entrada y salida de archivos, conexiones de red, búferes de memoria, o dispositivos como la entrada y salida estándares.
Flujos (Streams)
Los flujos (nos referiremos de ahora en adelante a ellos como streams) son un concepto central en el sistema Java IO. Un stream es, conceptualmente, un flujo de datos sin fin. Podemos leer o escribir en un stream. Dependiendo de si lo usamos para leer o escribir, un stream estará conectado a una fuente de datos o a un destino de datos. Los streams pueden estar basados en bytes o en caracteres, es decir, pueden ser flujos de datos binarios o de texto.
InputStream/Reader, OutputStream/Writer
Las clases InputStream
y OutputStream
son
clases abstractas que representan, respectivamente, un flujo de datos de
entrada binario y un flujo de salida binario. Java IO contiene diversas
subclases de ambas, dependiendo de cual sea la fuente o destino de los
datos: FileInputStream, AudioInputStream,
ByteArrayOutputStream, etc.
Para leer datos de una fuente utilizaremos un
InputStream
. Para escribir datos en un destino usaremos
OutputStream
.
Las clases Reader
y Writer
son equivalentes
a InputStream y OutputStream, pero basadas en
caracteres. Para leer o escribir texto utilizaremos estas clases.
En las secciones Lectura de ficheros y Escritura de ficheros te contamos como hacerlo.
La clase File
File
representa la ruta de acceso a un fichero o
directorio. No se puede usar directamente para leer o escribir a
fichero, sino como fuente o destino de un stream. La clase
File
permite además ciertas operaciones a nivel de sistema
de archivos, como crear ficheros, borrarlos, saber si la ruta apunta a
un directorio, recorrer un directorio, etc.
Una ruta tiene dos componentes:
- Un prefijo opcional, como la letra de una unidad de disco en Windows (“C:") o”/” en sistemas UNIX para denotar la raíz del sistema de archivos.
- Una secuencia de cero o más nombres, separadores por el separador de directorios del sistema (“/” en UNIX, “\” en Windows), de manera que cada nombre denota un directorio, excepto el último que puede se un directorio o un fichero.
Una ruta puede ser absoluta o relativa.
Las rutas absolutas llevan prefijo, mientras que las relativas no. Por
defecto, las clases en java.io
resuelven las rutas
relativas a partir del directorio de usuario actual, que típicamente es
el directorio desde donde se invocó a la máquina virtual de Java.
Las instancias de File
son inmutables. Una vez
creadas no se les puede cambiar la ruta de archivo que representan.
Operaciones con File
Instanciar File
File file = new File("/tmp/my_file.txt");
El constructor toma como parámetro la ruta del archivo al que desea
que apunte la instancia de File
. Ten en cuenta que la ruta
del archivo o directorio en realidad no tiene que hacer referencia a un
archivo o directorio existente. Esto no producirá ninguna excepción. Es
útil cuando queremos, por ejemplo, verificar si existe un archivo o
crear uno nuevo.
Crear un fichero
File file = new File("/tmp/my_file.txt");
boolean fileCreated = file.createNewFile();
File.createNewFile()
devuelve cierto si se pudo crear el
fichero y falso si éste ya existía.
Comprobar si un fichero existe
File file = new File("/tmp/my_file.txt");
if (file.exists()) { ... }
También funciona con directorios.
Crear un directorio
File file = new File("/tmp/my_folder/my_subfolder");
if (file.mkdirs()) { /* path to my_subfolder created */ }
else { /* folder not created for any reason */ }
En este ejemplo, File.mkdirs()
creará el directorio
my_subfolder, pero también tmp y my_folder si
estos no existían previamente. El método File.mkdir()
(sin
‘s’ final) sólo intentaría crear my_subfolder.
Tamaño de un archivo
File file = new File("/tmp/my_file.txt");
long length = file.length();
length()
devuelve el tamaño en bytes del fichero.
Renombrar o mover un fichero o directorio
File file = new File("/tmp/myfile");
if (file.renameTo(new File("/tmp/yourfile"))) { ... }
Ten en cuenta que, incluso si la operación tiene éxito, la instancia
file
sigue representado la ruta original a myfile.
Recuerda que son instancias inmutables, por eso se pasa como argumento
un nuevo objeto File
.
Borrar un fichero o un directorio vacío
File file = new File("/tmp/myfile");
boolean deleted = file.delete();
Comprobar si una ruta es un fichero o un directorio
File file = new File("/tmp/myfile");
boolean isDirectory = file.isDirectory();
Leer el contenido de un directorio
File file = new File("/tmp");
String[] fileNames = file.list();
File[] files = file.listFiles();
Lectura de ficheros
Si dado un objeto File
, queremos leer o escribir de él,
primero tendremos que asociarlo a un stream. La mayoría de
clases que nos permiten leer o escribir ficheros aceptan también
directamente una cadena con la ruta del fichero.
Para leer de ficheros binarios:
File file = new File("/tmp/myfile");
InputStream input = new FileInputStream(file);
int a = input.read();
// leer y guardar en un array:
byte[] bytes = new byte[10];
int bytesRead = input.read(bytes);
...
input.close();
// no olvides cerrar siempre un 'stream' después de usarlo
Para leer ficheros de texto caracter a caracter:
File file = new File("/tmp/myfile");
Reader input = new FileReader(file);
char c = input.read();
...
input.close();
La clase Scanner
Normalmente es más cómodo leer cadenas de ficheros de texto. Para
ello es más conveniente usar la clase java.util.Scanner
.
Además de proporcionar métodos para leer cadenas, esta clase ofrece
métodos específicos para leer ciertos tipos de datos, como números
int, long, double, etc., almacenados en
ficheros de texto.
Scanner ‘trocea’ su entrada en tokens, secuencias de caracteres separados por delimitadores. Por defecto, los delimitadores son los espacios en blanco, tabuladores y saltos de línea.
Tomemos este fichero de texto (“myfile.txt”) como ejemplo:
Linea 1
Linea 2 con mas tokens
Linea 3 1234 12.34
Scanner sc = new Scanner(new File("myfile.txt"));
while (sc.hasNext()) {
String token = sc.next();
System.out.println(token);
}
sc.close(); // no olvides cerrarlo tras usarlo.
producirá la siguiente salida
Linea
1
Linea
2
con
mas
tokens
...
Podemos también leer líneas completas:
Scanner sc = new Scanner(new File("myfile.txt"));
while (sc.hasNextLine()) {
String line = sc.nextLine();
System.out.println(line);
}
sc.close();
El resultado en la salida estándar será
Linea 1
Linea 2 con mas tokens
Linea 3 1234 12.34
Podemos leer tipos específicos de datos numéricos. Supongamos que sabemos que “myfile.txt” tiene esta estructura:
12 12.34
14 1.234
con un entero seguido de un número real en cada línea:
Scanner sc = new Scanner(new File("myfile.txt"));
while (sc.hasNext()) {
int i = sc.nextInt();
double d = sc.nextDouble();
System.out.println("Suman "+(d+i));
}
sc.close();
produce
Suman 24.34
Suman 15.234
Escritura de ficheros
Para escribir ficheros binarios:
File file = new File("/tmp/myfile");
// sobrescribe el fichero si ya existe
OutputStream output = new FileOutputStream(file);
// abre el fichero si ya existe y escribe al final del fichero.
OutputStream output = new FileOutputStream(file, true);
byte b = ...;
output.write(b);
byte[] bytes = new byte[10];
// ...
output.write(bytes);
output.close();
Para escribir ficheros de texto, carácter a carácter:
File file = new File("/tmp/myfile");
Writer output = new FileWriter(file);
char c = 'A';
output.write(c);
output.close();
La clase PrintStream
Normalmente es más cómodo escribir a fichero de texto a partir de
cadenas u otros tipos de datos (siempre que tengan implementado el
método toString()
). Para ello podemos usar la clase
java.io.PrintStream
, que tiene métodos
print/println()
para escribir cadenas y otros tipos de
datos primitivos a un stream en modo texto. De hecho,
System.out
es una instancia de PrintStream.
PrintStream output = new PrintStream(new File("myfile.txt"));
// ó simplemente
// PrintStream output = new PrintStream("myfile.txt");
El fichero se creará vacío y listo para escribir texto en él. Si ya existía se borrará su contenido, por lo que PrintStream no sirve para añadir texto a un fichero ya existente.
El stream así creado usa un buffer interno, por lo
que la escritura a fichero no es inmediata. Debemos usar el método
flush()
si queremos que el contenido del buffer se vuelque
a fichero. Al cerrarlo, se invoca automáticamente a
flush().
output.print("Programacion 3");
output.println(" - entrada/salida");
output.flush(); // write pending text to file
output.println(23.4); // double
output.println(345); // int
// etc...
output.close(); // flush + close
PrintStream
también posee un método
format()
para escribir texto con formato. Aquí
puedes ver más información.
Java NIO
Java también tiene otra API de entrada/salida llamada Java
NIO. Se encuentra en el paquete java.nio
y
contiene clases que hacen casi lo mismo que Java IO, pero Java NIO puede
funcionar en modo no bloqueante. Por ejemplo, un proceso puede pedirle a
un canal que lea datos en un búfer. Mientras el canal lee datos en el
búfer, el proceso puede estar haciendo otra cosa: no tiene que esperar a
que la operación de entrada/salida acabe. Una vez que los datos se leen
en el búfer, el proceso puede continuar procesándolos. Lo mismo ocurre
con la escritura de datos en los canales.
En la API de entrada/salida estándar, trabajamos con secuencias (streams) de bytes y secuencias de caracteres. En NIO trabajamos con canales y buffers. Los datos siempre se leen desde un canal a un búfer, o se escriben desde un búfer a un canal.
Java NIO también tiene el concepto de “selectores”. Un selector es un objeto que puede monitorizar múltiples canales para responder a ciertos eventos (p. ej.: conexión abierta, datos disponibles, etc.). Por lo tanto, un proceso puede monitorizar múltiples canales en busca de datos.
Te dejamos un par de enlaces útiles para usar Java NIO: