Ficheros de Texto

Java con ficheiros de caracteres

Como indica no primeiro apartado introdutorio os ficheiros de caracteres ou tamén chamados de texto están compostos de caracteres alfanuméricos nun formato estándar (ASCII, UNICODE etc…).

Lectura

Para ler caracteres necesitamos a clase FileReader, que se obtén a partir dun obxecto File.
A clase FileReader proporciona diversos métodos de lectura:

  • int read(): lee un carácter e o devolve. clase que define as operacións básicas de entrada de bytes con ficheiros.
  • int read(char [] buf): lee ata buf.lenght caracteres de datos dunha matriz de caracteres (buf). Os caracteres lidos do ficheiro vanse almacenando no buf.
  • int read(char[] buf, int desprazamento, int n): lee ata n caracteres de datos da matriz buf, comezando por buf[ desprazamento ] e devolve o número lido de caracteres. Estes métodos devolven -1 se chegou o final do ficheiro. Exemplo:
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
public class LerCaracteres {    
    public static void main(String args[])throws IOException {     
        File doc = new File("Demo.txt");    
        FileReader freader = new FileReader(doc);
        //segunda forma do metodo
        char [] i = new char[100];
        freader.read(i);
        for(char j : i)
            System.out.print(j);
        freader.close();
    }}

Todo estes métodos de lectura son de carácter a carácter ou agregando un desprazamento (int a int), pero … é a maneira máis práctica? Non, hai outras alternativas que detallamos a continuación.

Usando a clase BufferedReader

Forma máis cómoda de facer lectura de ficheiro de liña a liña.
Podes consultar máis información na API de Java SE 11
A clase BufferedReader ten un método que é readLine(). Este método devolve unha liña completa en String, ata o final do ficheiro que devolve null.
Usando esta clase temos opcións de máis alto nivel de abstracción, que facilitan a vida ó programador.
Para crear un obxecto BufferedReader hai que crealo a partires de FileReader:

File doc = new File("C:\\Drive\\Learn.txt");
BufferedReader obj = new BufferedReader(new FileReader(doc));

Fluxo de comunicacion Exemplo:

public class LerLineas {   
    public static void main(String[] args) throws FileNotFoundException, IOException {
     
        File doc = new File("prueba.txt");
        //Uso do try with resources, non fai falta usar o close 
        try(BufferedReader bufferedReader = new BufferedReader(new FileReader(doc))){
        String line = bufferedReader.readLine();
            while(line != null) {
                System.out.println(line);
                line = bufferedReader.readLine();
            }
}}}

Usando a clase Scanner

Esta clase permítenos ler ficheiros dunha maneira moi práctica (ademais do seu uso moi común de recoller datos do teclado).
Podes consultar máis información na API de Java SE 11.
O que hai que ter en conta é que necesitamos un obxecto tipo File para o construtor de Scanner. Existen outras posibilidades como crear un obxecto Scanner con InputStream. Seguiremos coa primeira forma pero coma sempre en programación… hai múltiples opcións!

File doc = new File(path);
Scanner obj = new Scanner(doc);

A clase Scanner ten dous métodos clave:

  • boolean hasNext(): que devolve verdadeiro se non chega o final do ficheiro, falso en caso contrario.
  • String hasNextLine(): obtemos a liña actual do ficheiro en cadea alfanumérica.

Exemplo:

public class LerLineasScanner {
    public static void main(String[] args) { 
        //obxecto file para Scanner
        File f = new File("demo.txt");
        String cadena;
        Scanner entrada = null;
        try {
            entrada = new Scanner(f);         //crease un Scanner asociado o ficheiro
            while (entrada.hasNext()) {       //mentres non se alcance o final do ficheiro                       
                cadena = entrada.nextLine();  //lese unha liña do ficheiro
                System.out.println(cadena);   //mostrase por pantalla                                           
            }
        } catch (FileNotFoundException e) {
            System.out.println(e.getMessage());
        } finally{
            entrada.close();                                                                                      
        }}}

Escritura

En Java unha das opción que nos permite escribir nun ficheiro é coa clase FileWriter.
Podes consultar máis información na API de Java SE 11
Esta clase nos proporciona unha serie de métodos:

  • void write(int c): escribe un carácter clase que define as operacións básicas de entrada de bytes con ficheiros.
  • void write(char[] buf): escribe un array de caracteres.
  • void write(char[] buf, int desplazamento, int n): escribe n caracteres de datos da matriz buf e comezando por buf[ desplazamento ].
  • void write(String str): escribe unha cadea de caracteres.
  • append(char c): engade un carácter a un ficheiro. Para crear obxectos tipo FileWriter é necesario ter un obxecto File, como pasa con FileReader:
File ficheiro = new File(fich.txt);
FileWriter fic = new FileWriter(ficheiro);

Ademais de coñecer os anteriores métodos é necesario ter en conta o seguinte: o obxecto tipo FileWriter por defecto vai sempre sobrescribir o contido. Se o que queremos é engadir debemos poñer no construtor un segundo parámetro a true:

FileWriter fic = new FileWriter (ficheiro, true);

Exemplo:

     //try with resource acepta multiples fontes de datos
try(FileWriter fw=new FileWriter("ficheiro1.txt"); FileReader fr=new FileReader("ficheiro1.txt")){  
            //Escribimos no ficheiro un String, un caracter 97 (a)
            // e array de caracteres
            fw.write("Esto es una prueb");
            fw.write(97);
            char[] buf = {'1','2','1'}; 
            fw.write(buf);
            //Gardamos os cambios do ficheiro, método que forza o guardado
            //para que se poida ler antes de pechar
            fw.flush();
            //Leemos o ficheiro e o amosamos por pantalla
            int valor=fr.read();
            while(valor!=-1){
                System.out.print((char)valor);
                valor=fr.read();
            }
        }catch(IOException e){
            System.out.println("Error E/S: "+e);
}}}

Nota: método flush() para gardar os cambios.

Usando a clase BufferedWriter

Como pasa coa lectura, Java ofrécenos clases para mellorar a eficiencia na escritura é métodos máis sinxelos para escribir liñas. Neste caso o BufferedWriter é unha clase que deriva da clase Write, e que se compón a partir de FileWriter.
Como estades vendo é unha serie de obxectos que “alimentan” o seguinte ata chegar o que precisamos.
Podes consultar máis información na API de Java SE 11.
Na clase BufferedWriter temos os seguintes métodos coma os máis interesantes:

  • void write(String cad): permitenos escribir cadea de alfanuméricos no buffer. Tamén acepta caracteres e arrays.
  • void newLine(): escribe un salto de liña no buffer.

Exemplo:

 // A partir de Java 9 hai unha mellora do try-with-resources
        // Desta forma cerrase o buffer cando termina, reducese a try
        BufferedWriter writer = new BufferedWriter(new FileWriter("test.txt"));
            try (writer) {         
                writer.write("Esto e unha linea");
                writer.newLine();
            }     
   }}

Usando a clase PrintWriter

Por último a clase PrintWriter é moi utilizada xa que ten un método chamado println() moi cómodo de utilizar:

  • void println(String cad): escribe a cadea de caracteres no ficheiro cun salto de liña. Outra das vantaxes desta clase que deriva de Write é que o construtor só precisa a ruta do ficheiro, polo tanto aforramos obxectos e liñas de código: Exemplo:
public class EscribirPrintWriter {    
    public static void main(String[] args) throws FileNotFoundException {      
        try {
            //se non existe se crea
            PrintWriter printWriter = new PrintWriter("test.txt");
            for (int i = 1; i < 4; i++) {
                //se sobreescribe o contido, de necesitar agregar debese usar apend
                printWriter.println("Esta e a linea numero: " + i);          
            }
            printWriter.close();
        } catch (Exception e) {
            e.printStackTrace();
    }}}

Esta clase tamén ten outros métodos como printf() (saída formateada), print() (escritura sen salto de liña) ou append() (para agregar contido).

Exercicio 1

Desenvolve un programa que amañe un ficheiro de texto, quitando espazos baleiros e poñendo a primeira letra de cada línea en maiúscula. Para esta labor crea un ficheiro temporal (investiga o uso do File.createTempFile), que logo sobrescribirá o ficheiro orixinal. Para detectar espazos y letras minúsculas pódese utilizar a clase Character.

Java NIO

Tal como vimos en anteriores apartados a API java.nio.file ofrécenos xeitos máis directos de poder traballar con ficheiros.
Podes consultar máis información na API de Java SE 11.
Neste caso con ficheiros de texto e sempre a partir da clase Files e sus métodos estáticos. Os máis usados son:

  • List < string > readAllLines(Path path, Charset ch): método estático que nos devolve un listado de strings que son as liñas do ficheiro (o charset é opcional). O sinxelo deste método e que péchase e libera o recurso automaticamente ó finalizar a lectura. (existe outra posibilidade con File.lines que amosaremos na sección de lambda)
  • Path write(Path path, Iterable lines, Charset cs, OpenOption… options): esta versión do método escribe liñas ó ficheiro. É necesario un obxecto iterable (cada elemento unha liña), a codificación que se vai usar é o modo de escritura (por defecto se non existe crease, e de existir vaise sobrescribir). Charset: o frecuente é StandardCharsets.UTF_8 OpenOption: para crear o ficheiro StandardOpenOption.CREATE (hai moitas posibilidades próbalas)

Exemplo:

public class EscribirLerNIO {    
    public static void main(String[] args) {      
        EscribirLerNIO eLN = new EscribirLerNIO();
        eLN.lerLineas();
        eLN.escribirLineas();
    }
    
    public void lerLineas(){ 
        Charset charset = Charset.forName("ISO-8859-1");
        try {
            //non nos preocupamos ni de abrir ni pechar ningun recurso
            List<String> lines = Files.readAllLines(Paths.get("prueba.txt"), charset);
            for (String line : lines) {
                System.out.println(line);
      }
    } catch (IOException e) {
        System.out.println(e);
    }}
      
    public void escribirLineas(){
        Iterable<String> iterable = Arrays.asList("line1", "line2");    
        try {
            Files.write(Paths.get("prueba_escribir.txt"), iterable);
            byte[] bytes = Files.readAllBytes(Paths.get("prueba_escribir.txt"));
            System.out.println(new String(bytes));
    } catch (IOException e) {
            System.out.println(e);
    }}}
String text = "Esto es una cadena de prueba";
Files.write(Paths.get("/examples/writeText.txt"), text.getBytes(StandardCharsets.UTF_8),
        StandardOpenOption.CREATE ...);

Ficheiros de acceso secuencial

O acceso secuencial é o máis frecuente cando se traballa con ficheiros, e xa coñecemos as características e vantaxes do acceso explicadas no primeiro apartado.
Neste tipo de acceso hai te que ter en conta que debemos coñecer o formato ou estrutura do ficheiro de antemán, e ir procesándoo.
Na labor de lectura ou escritura pódese traballar a nivel de bytes ou de caracteres. O seguinte exemplo amosa unha posible aplicación liña a liña de ficheiros de texto.
O exemplo é un pequeno programa de produtos e provedores dunha empresa.

Abrir/crear ficheiro

Xa detallado no apartado de ficheiros de texto.

String fichero = "produc.txt";
File f = new File(fichero);
FileWriter fw = new FileWriter(f);

Gardar

Unha vez aberto unha posibilidade para gardar é usar a clase PrintWriter. Podes consultar máis información na API de Java SE 11
O que temos que ter en conta é que o gardar creamos unha estrutura ríxida de información/rexistros, que logo vamos a utilizar para ler ou engadir máis rexistros.
Un exemplo de gardar os produtos:

PrintWriter escritura = new PrintWriter(fw);
for (int i = 0; i < menu.produtos.size(); i++) {
      escritura.println(menu.produtos.get(i).imprimeProduto());
}
escritura.close();

A información que temos que gardar está nunha colección de obxectos, e a orden de almacenamento dos datos está no método imprimeProduto():

public String imprimeProduto() {
     String saidaProducto = codProd + ":" + tipo + ":" + nome + ":" + prezo + ":" + pais + ":";
        for (int i = 0; i < this.getCodigoProveedor().size(); i++) {
            if (i != 0) {
                saidaProduto = saidaProduto.concat(",");
            }
       saidaProduto= saidaProduto.concat(this.getCodigoProveedor().get(i).getCodProv());
        }
        return saidaProduto;
    }

Ler en ficheiros

Amosaremos a posibilidade directamente sobre este exemplo no que se lee de maneira secuencial e envorca toda esta información en obxectos e estes a súa vez introdúcense nunha colección para xestionalos axeitadamente (neste caso ArrayList):

FileReader fr = null;
            BufferedReader br = null;
            fr = new FileReader("prov.txt");
            br = new BufferedReader(fr);
            String linea;
            String[] items;
            while ((linea = br.readLine()) != null) {
                items = linea.trim().split(":");
                menu.proveedores.add(new Proveedor(items[0], items[1], items[2].toUpperCase()));
            }
            br.close();

Exercicio 2

Disponse de dous ficheiros de texto “proveedores.txt” e “productos.txt”:

  • proveedores.txt: cada liña conten a información dun provedor. Esta componse dos seguintes campos separados por “:” codProv:nome:enderezo
  • productos.txt: cada liña conten a información dun produto. Esta componse dos seguintes campos separados por “:” codProd:tipo:nome:prezo[ :pais ]:codProv,codProv,codProv

Coma se pode observar, o quinto e o sexto campo da liña está formado a súa vez por unha lista que contén o código dos provedores que subministran o devandito produto, e estes códigos están separados polo carácter “,”.
Existen dous tipos de produtos: con “n” é nacional, e con “i” internacional. Se é internacional o producto terá un campo máis que é o país de orixe.
A partires destes ficheiros crear os obxectos precisos, cunha arquitectura O.O. correcta.
Crea un programa con estas funcionalidades:

  • Engadir un produto.
  • Obter un total do facturado por parte dun provedor.
  • Engadir provedor a un determinado produto.
  • Gardar os cambios nos correspondentes ficheiros. Posible interfaz:
void crearProducto(String codProd, String tipo, String nombre, double precio, String pais) throws Exception;
    
void imprimirProveedores();
    
void imprimirProductos();
    
boolean asignarProveedor(String codProd, String codProv) throws Exception;
    
Proveedor getProveedorByCod(String codProv);
    
void facturacion(String codProv) throws Exception; // muestra por pantalla la facturación total del proveedor