Trabajar con Ficheros en Java

COMPARTIR EN REDES SOCIALES

En esta entrada vamos a profundizar sobre las diferentes formas que existen de trabajar con ficheros en Java. Veremos como escribir y leer de ficheros de texto, como escribir y leer de ficheros binarios y finalmente hablaremos de la serialización de objetos en archivos binarios. Vamos al lio!

Cabe destacar que existen 2 formas de acceder a un fichero:

  • Acceso secuencial
  • Acceso en modo aleatorio

En esta entrada nos vamos a centrar en ficheros de acceso secuencial. En otra entrada ya veremos como trabajar con ficheros en modo de acceso aleatorio.

Lo primero que vamos a explorar son los ficheros de texto.

¿ Cómo se escribe en un fichero de texto con Java ?

Para escribir en un fichero de texto con Java vamos a utilizar la siguiente clase:

  • La clase «PrintWriter»: Mediante esta clase podemos indicar la ruta del fichero donde queremos escribir y cuenta con los métodos necesarios para poder escribir líneas de texto en nuestro fichero.

Veamos un ejemplo:

PrintWriter writer = null;
try {
	//Esta linea crea el objeto PrintWriter
	writer = new PrintWriter("fichero_texto.txt");
	
	//Escribimos las líneas de texto
	writer.println("Esto es la primera linea de texto");
	writer.println("Esto es otra linea de texto");
	writer.println("Esto es la última línea de texto");
	
} catch (FileNotFoundException e) {
	e.printStackTrace();
}finally {
	if (writer != null) {
		//Cerramos el fichero
		writer.close();
	}
}

Es importante destacar que los recursos en Java siempre se deben cerrar, es por ello que estamos utilizando la cláusula «finally», para que en cualquier caso se cierre el recurso, es decir, si lanza una excepción ejecutará el código que está dentro de la sección «finally» y si todo va bien también la ejecutará.

Otra forma de asegurarse de cerrar siempre un recurso es utilizar el «try with resources», veámoslo en código:

//El try with resources está hecho para que los flujos se cierren automáticamente 
// sin que nos preocupemos como programadores
try (PrintWriter writer = new PrintWriter("fichero_texto.txt")){

	//Escribimos las líneas de texto
	writer.println("Esto es la primera linea de texto");
	writer.println("Esto es otra linea de texto");
	writer.println("Esto es la última línea de texto");

} catch (FileNotFoundException e) {
	e.printStackTrace();
}

Esto nos creará un fichero de texto llamado «fichero_texto.txt» con el contenido que hemos añadido:

Trabajar con Ficheros en Java. Contenido de nuestro fichero de texto
Trabajar con Ficheros en Java. Contenido de nuestro fichero de texto

¿ Y si queremos añadir líneas de texto a un fichero existente ?

Se puede hacer también con «PrintWriter», pero en este caso utilizando el método «append»:

//Es necesario abrir el fichero en modo append
try (PrintWriter writer = new PrintWriter(new FileOutputStream(new File("fichero_texto.txt"), true /* append = true */))){
			
	//Escribimos las líneas de texto
	writer.append("Esto una linea añadida\n");
	writer.append("Esto es otra linea añadida de texto");
			
} catch (FileNotFoundException e) {
	e.printStackTrace();
}

Es importante destacar que es necesario abrir el fichero en modo «append» y que esto no se puede hacer solo con la clase «PrintWriter». Por lo que es necesario utilizar las clases:

  • File: Mediante la cual indicamos la ruta del fichero.
  • FileOutputStream: Mediante la cuál indicamos con el valor «true» que vamos a añadir texto.
  • PrintWriter: La utilizamos para añadir el texto.

Mucho cuidado con utilizar solo el «PrintWriter» o borrareis el contenido del fichero aunque utilicéis el método append.

Esto nos añadirá las línea de texto al fichero:

Trabajar con ficheros en Java. Contenido de nuestro fichero de texto después de añadir líneas.
Trabajar con ficheros en Java. Contenido de nuestro fichero de texto después de añadir líneas.

¿ Cómo se lee un Fichero de Texto línea por línea ?

Para leer de un fichero de texto línea por línea utilizaremos las clases «BufferedReader» y «FileReader».

Lo podemos ver a continuación en el siguiente fragmento de código:

//Utilizamos try with resources
try (BufferedReader br = new BufferedReader(new FileReader("fichero_texto.txt"))){
	//Mediante esta variable almacenaremos las lineas
	String linea = null;
			
	//El while lee linea a linea
	while ((linea = br.readLine()) != null) {
		System.out.println(linea);
	}
			
} catch (FileNotFoundException e) {
	e.printStackTrace();
} catch (IOException e1) {
	e1.printStackTrace();
}

Cabe destacar que utilizamos un «try with resources» para abrir el fichero que hemos creado en la sección anterior y que leemos línea por línea a través del «while». En cada iteración del «while» en la variable «línea» tendremos una nueva línea del fichero de texto.

La salida de nuestro programa será:

Esto es la primera linea de texto
Esto es otra linea de texto
Esto es la última línea de texto
Esto una linea añadida
Esto es otra linea añadida de texto

¿ Qué es un fichero binario ?

Un fichero binario a diferencia de un fichero de texto es un fichero en el cual se escriben bytes, es decir, se escriben 0’s y 1’s. Para entender la diferencia tenemos que comprender realmente qué es un fichero de texto y para ello tenemos que saber qué es la tabla ASCII.

Caracteres imprimibles en ASCII.
Trabajar con Ficheros en Java. Caracteres imprimibles en ASCII.

La tabla ASCII representa la conversión de decimal a carácter que realiza un editor de texto cuando se abre un fichero de texto, de forma que un fichero de texto no es más que un conjunto de números decimales escritos de forma binaria en un fichero cuya transformación la realiza un editor de texto.

¿ Cómo podemos comprobar que esto es cierto?

Vamos a ver un pequeño ejemplo, vamos a escribir diferentes bytes a un fichero binario y luego los vamos a transformar a carácter «char»:

//Abrimos el fichero binario para escritura
try (DataOutputStream dos = new DataOutputStream (new FileOutputStream (new File("mi_fichero.bin")));
		DataInputStream dis = new DataInputStream(new FileInputStream(new File("mi_fichero.bin")))
	){
	//Escribimos los datos
	dos.writeByte(97);
	dos.writeByte(98);
	dos.writeByte(99);
	dos.writeByte(100);
	dos.writeByte(101);
	//Mostramos las letras
	System.out.println((char)dis.readByte());
	System.out.println((char)dis.readByte());
	System.out.println((char)dis.readByte());
	System.out.println((char)dis.readByte());
	System.out.println((char)dis.readByte());

	
} catch (FileNotFoundException e) {
	e.printStackTrace();
} catch (IOException e1) {
	e1.printStackTrace();
}

La salida de nuestro programa:

a
b
c
d
e

¿ Cómo se ve nuestro fichero en un editor de texto ?

Trabajar con Ficheros en Java. Lectura de nuestro fichero binario representado por un editor.
Trabajar con Ficheros en Java. Lectura de nuestro fichero binario representado por un editor.

Fijaos que nosotros hemos escrito el fichero de forma binaria y además hemos escrito números, pero al escribir caracteres ASCII, el editor de texto los transforma a letras que entendemos los humanos.

¿ Cómo se escribe en un fichero binario ?

Para escribir en un fichero binario utilizaremos las siguientes clases:

  1. La clase «File» para indicar la ruta del fichero donde vamos a escribir.
  2. La clase «FileOutputStream». Se trata de una clase intermedia.
  3. La clase «DataOutputStream». Mediante esta clase escribiremos los datos binarios.

Vamos a ver un ejemplo donde escribimos un entero, un número en coma flotante, un double y un short:

//Abrimos el fichero binario para escritura
try (DataOutputStream dos = new DataOutputStream (new FileOutputStream (new File("mi_fichero.bin")));){
	//Escribimos los datos
	dos.writeInt(45);
	dos.writeFloat((float) 3.5);
	dos.writeDouble(4.7);
	dos.writeShort((int) 2.7);
			
} catch (FileNotFoundException e) {
	e.printStackTrace();
} catch (IOException e1) {
	e1.printStackTrace();
}

¿ Cómo se lee de un fichero binario ?

Para leer de un fichero binario utilizaremos las siguientes clases:

  1. La clase «File» para indicar la ruta del fichero donde vamos a leer.
  2. La clase «FileInputStream». Se trata de una clase intermedia.
  3. La clase «DataInputStream». Mediante esta clase leeremos los datos binarios.

Vamos a leer los datos que hemos escrito anteriormente:

//Abrimos el fichero binario para lectura
try (DataInputStream dis = new DataInputStream(new FileInputStream(new File("mi_fichero.bin")))){
			
	//Leemos los datos de nuestro fichero binario
	System.out.println(dis.readInt());
	System.out.println(dis.readFloat());
	System.out.println(dis.readDouble());
	System.out.println(dis.readShort());
			
} catch (FileNotFoundException e) {
	e.printStackTrace();
} catch (IOException e1) {
	e1.printStackTrace();
}

La salida de nuestro programa:

45
3.5
4.7
2

¿ Se pueden guardar objetos en un fichero binario en Java ?

Sí, y es una de las características más potentes del lenguaje para persistir el estado de nuestros programas. A esto se le llama serialización, y funciona de la siguiente manera. Vamos a guardar nuestra famosa clase coche:

public class Coche implements Serializable{
	private int numPuertas;
	private String marca;
	private String modelo;
	private int numeroCaballos;
	private int cilindrada;
	private double precio;
	
	public Coche() {
		
	}
	
	public Coche(int numPuertas, String marca, String modelo, int numeroCaballos, int cilindrada, double precio) {
		super();
		this.numPuertas = numPuertas;
		this.marca = marca;
		this.modelo = modelo;
		this.numeroCaballos = numeroCaballos;
		this.cilindrada = cilindrada;
		this.precio = precio;
	}

	public int getNumPuertas() {
		return numPuertas;
	}
	public void setNumPuertas(int numPuertas) {
		this.numPuertas = numPuertas;
	}
	public String getMarca() {
		return marca;
	}
	public void setMarca(String marca) {
		this.marca = marca;
	}
	public String getModelo() {
		return modelo;
	}
	public void setModelo(String modelo) {
		this.modelo = modelo;
	}
	public int getNumeroCaballos() {
		return numeroCaballos;
	}
	public void setNumeroCaballos(int numeroCaballos) {
		this.numeroCaballos = numeroCaballos;
	}
	public int getCilindrada() {
		return cilindrada;
	}
	public void setCilindrada(int cilindrada) {
		this.cilindrada = cilindrada;
	}
	public double getPrecio() {
		return precio;
	}
	public void setPrecio(double precio) {
		this.precio = precio;
	}
	
}

Cabe destacar que para poder guardar objetos de una determinada clase esta debe implementar la interfaz «Serializable». Esta interface nos permite guardar un objeto de forma binaria. Veamos un ejemplo

¿ Cómo se serializa un Objeto ?

Para serializar un objeto debemos utilizar 3 clases:

  1. La clase «File» que apuntará al fichero donde se guardarán nuestros datos.
  2. La clase «FileOutputStream» que se trata de una clase intermedia.
  3. La clase «ObjectOutputStream» que será la clase que contiene el método necesario para serializar la clase.

Veamos el código fuente:

//Creamos el ObjectOutputStream con un try with resources
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("objetos.bin")))){
	//Creamos las instancias de coche
	Coche c1 = new Coche(5,"Citroen", "C5", 99, 2000, 14000);
	Coche c2 = new Coche(5,"Opel", "Vectra", 82, 1800, 12000);
	
	//Guardamos nuestras instancias de coche
	oos.writeObject(c1);
	oos.writeObject(c2);
} catch (FileNotFoundException e) {
	e.printStackTrace();
} catch (IOException e) {
	e.printStackTrace();
}

Se crea el ObjectOutputStream, se crean las instancias de los coches y se guardan llamando la método «writeObject».

¿ Como se leen los objetos serializados ?

Para leer los objetos serializados se utilizan 3 clases también:

  1. La clase «File» que apuntará al fichero desde donde se leerán nuestros datos.
  2. La clase «FileInputStream» que se trata de una clase intermedia.
  3. La clase «ObjectInputStream» que será la clase que contiene el método necesario para leer nuestros objetos serializados o lo que es lo mismo, deserializar nuestros objetos.

Veamos el código fuente:

//Creamos el ObjectInputStream utilizando un try with resources
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("objetos.bin")))){
	//Leemos nuestros objetos con el método readObject()
	Coche coche1 = (Coche) ois.readObject();
	Coche coche2 = (Coche) ois.readObject();

	//Imprimimos algunas propiedades
	System.out.println(coche1.getMarca() + " - " + coche1.getModelo());
	System.out.println(coche2.getMarca() + " - " + coche2.getModelo());
	
} catch (IOException e) {
	// TODO Auto-generated catch block
	e.printStackTrace();
} catch (ClassNotFoundException e) {
	// TODO Auto-generated catch block
	e.printStackTrace();
}

Se crea el ObjectInputStream, se leen los 2 coches que hemos guardado y se muestran algunas propiedades de los mismos.

La salida de nuestro programa para comprobar la serialización/deserialización:

Citroen - C5
Opel - Vectra

Cabe destacar que igual que hemos guardado 2 coches, podríamos haber guardado cualquier composición de objetos, por poner un ejemplo, podríamos haber guardado:

  1. 2 Coches
  2. Un ArrayList de Personas
  3. Un ArrayList de Productos
  4. Un HashMap.
  5. En general, cualquier objeto de Java que implemente la interface «Serializable».

Recordad que el orden de escritura de los objetos que guardéis tiene que coincidir con el orden de lectura, es decir, que si primero guardáis 2 coches y luego 2 personas, tenéis que leer 2 coches primero y después las 2 personas, y así sucesivamente…

Con esta entrada hemos visto como trabajar con ficheros en Java. Escribiremos otra entrada para entender los ficheros de acceso aleatorio.


COMPARTIR EN REDES SOCIALES

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *