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.
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.