funciones - java 8 lambda explicacion
Analizando un archivo CSV para una fila Ășnica usando la nueva API Java 8 Streams (2)
Estoy intentando usar la nueva API de Java 8 Streams (para la cual soy un novato completo) para analizar una fila en particular (la que tiene ''Neda'' en la columna de nombre) en un archivo CSV. Usando el siguiente artículo como motivación, modifiqué y corrigí algunos errores para poder analizar el archivo que contenía 3 columnas: ''nombre'', ''edad'' y ''altura''.
name,age,height
Marianne,12,61
Julie,13,73
Neda,14,66
Julia,15,62
Maryam,18,70
El código de análisis es el siguiente:
@Override
public void init() throws Exception {
Map<String, String> params = getParameters().getNamed();
if (params.containsKey("csvfile")) {
Path path = Paths.get(params.get("csvfile"));
if (Files.exists(path)){
// use the new java 8 streams api to read the CSV column headings
Stream<String> lines = Files.lines(path);
List<String> columns = lines
.findFirst()
.map((line) -> Arrays.asList(line.split(",")))
.get();
columns.forEach((l)->System.out.println(l));
// find the relevant sections from the CSV file
// we are only interested in the row with Neda''s name
int nameIndex = columns.indexOf("name");
int ageIndex columns.indexOf("age");
int heightIndex = columns.indexOf("height");
// we need to know the index positions of the
// have to re-read the csv file to extract the values
lines = Files.lines(path);
List<List<String>> values = lines
.skip(1)
.map((line) -> Arrays.asList(line.split(",")))
.collect(Collectors.toList());
values.forEach((l)->System.out.println(l));
}
}
}
¿Hay alguna manera de evitar volver a leer el archivo después de la extracción de la línea de encabezado? Aunque este es un archivo de ejemplo muy pequeño, aplicaré esta lógica a un gran archivo CSV.
¿Existe alguna técnica para usar la API de secuencias para crear un mapa entre los nombres de las columnas extraídas (en el primer escaneo del archivo) y los valores en las filas restantes?
¿Cómo puedo devolver solo una fila en la forma de List<String>
(en lugar de List<List<String>>
contiene todas las filas). Preferiría encontrar la fila como un mapeo entre los nombres de las columnas y sus valores correspondientes. (un poco como un conjunto de resultados en JDBC). Veo una función Collectors.mapMerger que podría ser útil aquí, pero no tengo idea de cómo usarla.
Primero, su preocupación de que este código esté leyendo el archivo dos veces no está fundado. En realidad, Files.lines
devuelve una secuencia de las líneas que está llena de vagos. Entonces, la primera parte del código solo lee la primera línea y la segunda parte del código lee el resto (sin embargo, lee la primera línea una segunda vez, incluso si se ignora). Citando su documentación:
Lee todas las líneas de un archivo como un
Stream
. A diferencia dereadAllLines
, este método no lee todas las líneas en unaList
, sino que se rellena de forma diferida a medida que se consume la secuencia.
En su segunda preocupación acerca de regresar solo una fila. En la programación funcional, lo que estás tratando de hacer se llama filtrado . Stream API proporciona dicho método con la ayuda de Stream.filter
. Este método toma un Predicate
como argumento, que es una función que devuelve true
para todos los elementos que se deben conservar, y de lo contrario es false
.
En este caso, queremos un Predicate
que devuelva true
cuando el nombre es igual a "Neda"
. Esto podría escribirse como la expresión lambda s -> s.equals("Neda")
.
Entonces, en la segunda parte de tu código, podrías tener:
lines = Files.lines(path);
List<List<String>> values = lines
.skip(1)
.map(line -> Arrays.asList(line.split(",")))
.filter(list -> list.get(0).equals("Neda")) // keep only items where the name is "Neda"
.collect(Collectors.toList());
Sin embargo, tenga en cuenta que esto no garantiza que solo haya un solo elemento donde el nombre sea "Neda"
, sino que recoge todos los elementos posibles en una List<List<String>>
. Puede agregar algo de lógica para encontrar el primer elemento o lanzar una excepción si no se encuentran elementos, según los requisitos de su empresa.
Tenga en cuenta que se puede evitar llamar dos veces a Files.lines(path)
utilizando directamente un BufferedReader
como en la respuesta de @ Holger.
Use un BufferedReader
explícitamente:
List<String> columns;
List<List<String>> values;
try(BufferedReader br=Files.newBufferedReader(path)) {
String firstLine=br.readLine();
if(firstLine==null) throw new IOException("empty file");
columns=Arrays.asList(firstLine.split(","));
values = br.lines()
.map(line -> Arrays.asList(line.split(",")))
.collect(Collectors.toList());
}
Files.lines(…)
también recurre a BufferedReader.lines(…)
. La única diferencia es que Files.lines
configurará la secuencia para que al cerrar la secuencia se cierre el lector, que no necesitamos aquí, ya que la declaración explícita try(…)
ya asegura el cierre de BufferedReader
.
Tenga en cuenta que no hay garantía sobre el estado del lector después de que se haya procesado la secuencia devuelta por lines()
, pero podemos leer líneas de manera segura antes de realizar la operación de transmisión.