Lambda con Ficheros

En cada nova versión de Java amplíase o uso de lambda e expresións de programación funcional: actualízanse así as posibilidades de Java para ser unha linguaxe máis directa e menos “verbosa”: resumindo, menos liñas de programación para acadar o mesmo.

icono lambda

Coa API java.NIO temos a clase Files, que se pode utilizar con expresións lambda. A clase Files xunto coa clase Stream crean un binomio moi útil para operacións con ficheiros.

Ler un ficheiro de maneira secuencial

Path file = Paths.get("readFile.txt");
try(Stream<String>lines = Files.lines(file)
    .onClose(() -> System.out.println("Fin de lectura"))) {
        lines.forEach(System.out::println); }

Unha vez que se obtén a ruta do ficheiro utilizamos a método estático lines() de Files. Este método devólvenos un fluxo de traballo (Stream), que ten unha directiva cando se pecha o fluxo (onClose) e un método para percorrer todas as liñas (forEach). A clase Stream pódese “alimentar” de moitas formas: se pretendemos unha maior eficiencia un obxecto BufferedReader debe ser o elixido. Files ten un método no que se obtén un buffer de lectura (newBufferedReader):

try(BufferedReader br = Files.newBufferedReader(file);
    Stream<String> lines = br.lines()
        .onClose(() -> System.out.println("Fin de lectura de ficheiro."));){
            lines.forEach(System.out::println);
}

Ler con procesamento paralelo

Outra opción que nos ofrece Stream e onde non nos ten que importar o tratamento secuencial do ficheiro é o predicado parallel(). O que fai esta opción é crear fíos de execución sobre o contido do Stream (neste caso o ficheiro), mellorando o rendemento:

try(Stream<String>lines = Files.lines(file)
        .parallel()
        .onClose(() -> System.out.println("Fin de lectura de ficheiro."))) {
        //modificamos esta parte para poder observar os fíos que se xeran
        lines.forEach(s ->{
        System.out.println(s +" " + Thread.currentThread().getName());});

Exemplo:

import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.stream.Stream; 
public class LerFicheiroLambda { 
    public static void main(String[] args) throws IOException {    
        Path file = Paths.get("probas.txt");  
        // Uso do try-with-resources con lambda
        try(Stream<String>lines = Files.lines(file)
                        .onClose(() -> System.out.println("Fin de lectura de ficheiro."))) {
            lines.forEach(System.out::println);
        }  
        //versión más eficiente utilizando BufferedReader
        System.out.println("Version mais eficiente....");   
        try(BufferedReader br = Files.newBufferedReader(file);
            Stream<String> lines = br.lines()
                    .onClose(() -> System.out.println("Fin de lectura de ficheiro."));){  
            lines.forEach(System.out::println);
        }       
        //versión de procesamento pararelo
        System.out.println("Version con procesamento paralelo....");
         try(Stream<String>lines = Files.lines(file)
                        .parallel()
                        .onClose(() -> System.out.println("Fin de lectura de ficheiro."))) {    
            //modificamos esta parte para poder observar os fíos que se xeran
             lines.forEach(s ->{
                    System.out.println(s +" " + Thread.currentThread().getName());});
        }      
    }
}

Lectura condicional

Stream ten un predicado que é filter(): permítenos filtrar e quedar coas liñas que nos interesan do ficheiro. Ten múltiples opcións; a continuación amosamos un exemplo sinxelo:

try (Stream<String> lines = Files.lines(Path.of("probas.txt"))) {
    long i = lines.filter(line -> line.startsWith("T"))
    .count();

Filter necesita un predicado lóxico booleano, ou utilizar métodos de Stream coma os seguintes:

  • contentEquals(String st)
  • endsWith(String sufix)
  • startsWith(String prefix)
  • matches(String red): expresións regulares.
  • … Exemplo:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;
public class LerFilter {        
    public static void main(String[] args) throws IOException {      
        //Exemplos do uso do filter     
        try (Stream<String> lines = Files.lines(Path.of("probas.txt"))) {
            long i = lines.filter(line ->line.startsWith("T"))
            .count();
        System.out.println("Numero de lineas que empezar por 'T' e " + i);
    }                
        try (Stream<String> lines = Files.lines(Path.of("probas.txt"))) {
            long i = lines.filter(line ->line.isEmpty())
            .count();
        System.out.println("Número de lineas vacias: " + i);
    }            
    try (Stream<String> lines = Files.lines(Path.of("probas.txt"))) {  
            lines.filter(line ->line.endsWith("11"))
            .forEach(System.out::println);
    }
}}

Uso de lambda para operacións de directorios e arquivos de sistema

Para listar os directorios dunha ruta, temos a directiva isDirectory de Files:

try (Stream<Path> paths = Files.list(Path.of(folderPath))) {
    paths.filter(Files::isDirectory)
    .forEach(System.out::println);
}

Fíxate que devolve un Stream de path é dicir rutas de ficheiros. Para listar os ficheiros dunha ruta:

Para percorrer todo a árbore de directorios

Coma se fora de xeito recursivo Files ten un método estático chamado walk() que devolve un stream con todos os elementos da carpeta, das subcartafoles etc. ata chegar a final da árbore. O stream estará formado por todos os camiños que forma esa árbore.

Stream<Path> miStream = Files.walk(Paths.get("./src"));
miStream.forEach(System.out::println);

Búsqueda de ficheiros

A clase Files ten un método de busca de ficheiros. O uso máis frecuente é con tres argumentos de entrada:

  • Path p: obxecto que representa a ruta onde se vai buscar.
  • int maxDepth: enteiro que representa que nivel de busca se precisa na árbore de directorios. Se indicamos Integer.Maxvalue sería ata o final da árbore de ficheiros, se indicamos “1” sería o primeiro nivel.
  • BiPredicate <Path, BasicFileAttributes> matcher: é unha interfaz funcional que serve para indicar as condicións de busca.
try (Stream<Path> pathStream = Files.find(path, Integer.MAX_VALUE,
    (p, basicFileAttributes) ->
    p.getFileName().toString().equalsIgnoreCase(fileName))

Exemplo:

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class BusquedaFicheiroLambda { 
    public static void main(String[] args) throws IOException {    
        Path path = Paths.get("/Users/jmor/NetBeansProjects/AccesoDatos_licencia");
        List<Path> result = busquedaPorNombre(path, "pom.xml");
        result.forEach(x -> System.out.println(x));    
        List<Path> result2 = busquedaPorExtension(path, ".txt");
        result2.forEach(x -> System.out.println(x));     
    }   
    //Metodo de busca usando .find con Stream
    public static List<Path> busquedaPorNombre(Path path, String fileName)
            throws IOException {
        //listado que xenera a busqueda
        List<Path> result;    
        //find ten tres atributos de entrada
        //a interfaz funcional fai ignorar as maiúsculas y minúsculas
        try (Stream<Path> pathStream = Files.find(path,
                Integer.MAX_VALUE,
                (p, basicFileAttributes) ->
                        p.getFileName().toString().equalsIgnoreCase(fileName))
        ) {
            result = pathStream.collect(Collectors.toList());
        }
        return result;
    }   
    //método de busca por extensión 
    public static List<Path> busquedaPorExtension(Path path, String ext)
            throws IOException {
        //listado que xera a busca
        List<Path> result;      
        //a interfaz funcional filtra por ficheiros (no directorios) y que
        // tena a extension
        try (Stream<Path> pathStream = Files.find(path,
                Integer.MAX_VALUE,
                (p, attr) -> 
                   attr.isRegularFile() && p.toString().endsWith(ext))
        )
        {
            result = pathStream.collect(Collectors.toList());
        }
        return result;
    }    
}

Exercicio 1

  • Crea un método de buscade de arquivos con Files.find a partires dunha ruta cun determinado tamaño (Files.size) usando expresións lambda. Mellora o teu programa detectando se un ficheiro é un directorio (Files.isDirectory), e devolvendo falso.
  • Crea un método que busque un ficheiro nun directorio e en todas os seus subcartafois (ata o final da árbore de directorios) usando lambda. Para ser máis eficiencia usa un filtro con Files::isRegularFile.