web-dev-qa-db-fra.com

Analyse du fichier .csv à l'aide de Java 8 Stream

J'ai un fichier .csv plein de données sur plus de 500 entreprises. Chaque ligne du fichier fait référence à un ensemble de données d'entreprises particulier. J'ai besoin d'analyser ce fichier et d'extrapoler les données de chacun pour appeler 4 services Web différents.

La première ligne du fichier .csv contient les noms des colonnes. J'essaie d'écrire une méthode qui prend un paramètre de chaîne et cela se rapporte au titre de la colonne trouvée dans le fichier .csv.

Sur la base de ce paramètre, je veux que la méthode analyse le fichier en utilisant Java 8 et retourne une liste des données tirées du titre de la colonne pour chaque ligne/entreprise.

J'ai l'impression de le rendre plus compliqué que nécessaire, mais je ne peux pas penser à un moyen plus efficace d'atteindre mon objectif.

Toute pensée ou idée serait grandement appréciée.

En cherchant dans stackoverflow, j'ai trouvé le message suivant qui est similaire mais pas tout à fait le même. Analyse d'un fichier CSV pour une ligne unique à l'aide de la nouvelle Java 8 Streams API

    public static List<String> getData(String titleToSearchFor) throws IOException{
    Path path = Paths.get("arbitoryPath");
    int titleIndex;
    String retrievedData = null;
    List<String> listOfData = null;

    if(Files.exists(path)){ 
        try(Stream<String> lines = Files.lines(path)){
            List<String> columns = lines
                    .findFirst()
                    .map((line) -> Arrays.asList(line.split(",")))
                    .get();

            titleIndex = columns.indexOf(titleToSearchFor);

            List<List<String>> values = lines
                    .skip(1)
                    .map(line -> Arrays.asList(line.split(",")))
                    .filter(list -> list.get(titleIndex) != null)
                    .collect(Collectors.toList());

            String[] line = (String[]) values.stream().flatMap(l -> l.stream()).collect(Collectors.collectingAndThen(
                    Collectors.toList(), 
                    list -> list.toArray()));
            String value = line[titleIndex];
            if(value != null && value.trim().length() > 0){
                retrievedData = value;
            }
            listOfData.add(retrievedData);
        }
    }
    return listOfTitles;
}

Merci

5
Michael Heneghan

Vous ne devez pas réinventer la roue et utiliser une bibliothèque d'analyseur csv commune. Par exemple, vous pouvez simplement utiliser Apache Commons CSV .

Il gérera beaucoup de choses pour vous et est beaucoup plus lisible. Il y a aussi OpenCSV , qui est encore plus puissant et est livré avec des mappages basés sur des annotations aux classes de données.

 try (Reader reader = Files.newBufferedReader(Paths.get("file.csv"));
            CSVParser csvParser = new CSVParser(reader, CSVFormat.DEFAULT
                    .withFirstRecordAsHeader()        
        ) {
            for (CSVRecord csvRecord : csvParser) {
                // Access
                String name = csvRecord.get("MyColumn");
                // (..)
          }

Edit: Quoi qu'il en soit, si vous voulez vraiment le faire par vous-même, jetez un œil à l'exemple this .

13
ixeption

J'ai réussi à raccourcir un peu ton extrait.

Si je vous comprends bien, vous avez besoin de toutes les valeurs d'une colonne particulière. Le nom de cette colonne est donné.

L'idée est la même, mais j'ai amélioré la lecture du fichier (il lit une fois); suppression de la duplication de code (comme line.split(",")), renvois inutiles dans List (Collectors.toList()).

// read lines once
List<String[]> lines = lines(path).map(l -> l.split(","))
                                  .collect(toList());

// find the title index
int titleIndex = lines.stream()
                      .findFirst()
                      .map(header -> asList(header).indexOf(titleToSearchFor))
                      .orElse(-1);

// collect needed values
return lines.stream()
            .skip(1)
            .map(row -> row[titleIndex])
            .collect(toList());

J'ai 2 conseils non liés au problème:

1. Vous avez codé en dur un URI, il est préférable de déplacer la valeur vers une constante ou d'ajouter un paramètre de méthode.
2. Vous pouvez déplacer la partie principale hors de la clause if si vous avez vérifié la condition opposée !Files.exists(path) et levé une exception.

3
Andrew Tobilko

1) Vous ne pouvez pas invoquer plusieurs opérations de terminal sur un flux.
Mais vous en invoquez deux: findFirst() pour récupérer les noms de colonne, puis collect() pour collecter les valeurs de ligne. La deuxième opération de terminal invoquée sur le Stream lèvera une exception.

2) Au lieu de Stream<String> lines = Files.lines(path)) qui lit toutes les lignes d'un Stream, vous devriez faire les choses en deux fois en utilisant Files.readAllLines() qui retourne une liste de chaînes.
Utilisez le premier élément pour récupérer le nom de la colonne et utilisez toute la liste pour récupérer la valeur de chaque ligne correspondant aux critères.

3) Vous divisez la récupération en plusieurs petites étapes que vous pouvez raccourcir en un seul traitement de flux qui itérera toutes les lignes, gardera seulement laquelle d'entre elles où les critères correspondent et les collectera.

Cela donnerait quelque chose comme:

public static List<String> getData(String titleToSearchFor) throws IOException {
    Path path = Paths.get("arbitoryPath");

    if (Files.exists(path)) {
        List<String> lines = Files.readAllLines(path);

        List<String> columns = Arrays.asList(lines.get(0)
                                                  .split(","));

        int titleIndex = columns.indexOf(titleToSearchFor);

        List<String> values = lines.stream()
                                   .skip(1)
                                   .map(line -> Arrays.asList(line.split(",")))
                                   .map(list -> list.get(titleIndex))
                                   .filter(Objects::nonNull)
                                   .filter(s -> s.trim()
                                                 .length() > 0)
                                   .collect(Collectors.toList());

        return values;
    }

    return new ArrayList<>();

}
1
davidxxx

Comme d'habitude, vous devriez utiliser Jackson! Consultez les documents

Si vous souhaitez que Jackson utilise la première ligne comme info d'en-tête:

public class CsvExample {
    public static void main(String[] args) throws IOException {
        String csv = "name,age\nIBM,140\nBurger King,76";
        CsvSchema bootstrapSchema = CsvSchema.emptySchema().withHeader();
        ObjectMapper mapper = new CsvMapper();
        MappingIterator<Map<String, String>> it = mapper.readerFor(Map.class).with(bootstrapSchema).readValues(csv);
        List<Map<String, String>> maps = it.readAll();
    }
}

ou vous pouvez définir votre schéma comme un objet Java:

public class CsvExample {
    private static class Pojo {
        private final String name;
        private final int age;

        @JsonCreator
        public Pojo(@JsonProperty("name") String name, @JsonProperty("age") int age) {
            this.name = name;
            this.age = age;
        }

        @JsonProperty("name")
        public String getName() {
            return name;
        }

        @JsonProperty("age")
        public int getAge() {
            return age;
        }
    }

    public static void main(String[] args) throws IOException {
        String csv = "name,age\nIBM,140\nBurger King,76";
        CsvSchema bootstrapSchema = CsvSchema.emptySchema().withHeader();
        ObjectMapper mapper = new CsvMapper();
        MappingIterator<Pojo> it = mapper.readerFor(Pojo.class).with(bootstrapSchema).readValues(csv);
        List<Pojo> pojos = it.readAll();
    }
}
1
Andbdrew