Анализ файла .csv с использованием Java 8 Stream

У меня есть файл .csv с данными о более чем 500 компаниях. Каждая строка в файле относится к конкретному набору данных компаний. Мне нужно проанализировать этот файл и экстраполировать данные каждого из них для вызова 4 разных веб-служб.

Первая строка файла .csv содержит имена столбцов. Я пытаюсь написать метод, который принимает строковый параметр, и это относится к заголовку столбца, найденному в файле .csv.

На основе этого параметра я хочу, чтобы метод анализировал файл с использованием функциональности Java 8 Stream и возвращал список данных, взятых из заголовка столбца для каждой строки / компании.

Я чувствую, что делаю это более сложным, чем нужно, но не могу придумать более эффективного способа достижения своей цели.

Приветствуются любые мысли или идеи.

Просматривая stackoverflow, я нашел следующий пост, похожий, но не совсем такой же. Разбор CSV-файла для уникальной строки с использованием нового 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;
}

Спасибо

в вашем коде много проблем, вы его скомпилировали?

Andrew Tobilko 04.04.2018 23:41

Да, скомпилировал в eclipse, ошибок компиляции не было. В настоящее время у меня нет доступа к файлу csv, поэтому я еще не смог правильно протестировать.

Michael Heneghan 04.04.2018 23:49
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
7
2
33 044
4

Ответы 4

Не стоит изобретать велосипед и использовать обычную библиотеку парсера csv. Например, вы можете просто использовать CSV-файл Apache Commons.

Он сделает многое за вас и гораздо более читабелен. Существует также OpenCSV, который является еще более мощным и поставляется с сопоставлениями на основе аннотаций с классами данных.

 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");
                // (..)
          }

Редактировать: В любом случае, если вы действительно хотите сделать это самостоятельно, взгляните на пример это.

Всегда. Не изобретать велосипед - обязательно! +1

Jorge Campos 05.04.2018 00:52

Полностью согласен, мне не следует даже пытаться изобретать заново, поскольку это сделали люди лучше меня. К сожалению, я работаю над проектом, который на самом деле не позволяет мне импортировать внешние библиотеки и ограничен использованием предустановленных библиотек. Я не знал, что у Apache есть библиотека для файлов csv, она пригодится в будущем. Спасибо за информацию :)

Michael Heneghan 05.04.2018 17:20

Если вам нужна скорость, взгляните на это сравнение парсера CSV. однозначные парсеры обрабатывает крайние случаи лучше, чем другие библиотеки.

Jeronimo Backes 07.04.2018 16:16

Используется ли потоковая передача? Что делать, если у меня очень большой файл?

Md Faraz 14.12.2019 14:31

@MdFaraz Apache Commons CSV CSVParser реализует Iterable<CSVRecord>, который можно расширить с помощью потокового API. Ключевые слова для дальнейшего исследования: итерация для потоковой передачи

aff 04.09.2020 12:22

1) Вы не можете запускать несколько терминальных операций в Stream.
Но вы вызываете два из них: findFirst() для получения имен столбцов и затем collect() для сбора значений строк. Вторая операция терминала, вызванная в Stream, вызовет исключение.

2) Вместо Stream<String> lines = Files.lines(path)), который считывает все строки в потоке, вы должны сделать это в два раза, используя Files.readAllLines(), который возвращает список строк. Используйте первый элемент для получения имени столбца и используйте весь список для получения значения каждой строки, соответствующей критериям.

3) Вы разделяете извлечение на несколько маленьких шагов, которые можно сократить за счет обработки одного потока, который будет перебирать все строки, сохранять только те из них, которые соответствуют критериям, и собирать их.

Это дало бы что-то вроде:

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 - Конечно, глупая ошибка меня. Ура 2 - Я подумал об этом, но эта функция будет повторно использоваться для других файлов csv, которые могут содержать тысячи записей, поэтому беспокоился о OutOfMemoryError 3 - это еще один хороший вариант для реализации при использовании readAllLines. Спасибо!

Michael Heneghan 05.04.2018 17:15

Мне удалось немного сократить ваш фрагмент.

Если я вас правильно понял, вам нужны все значения определенного столбца. Дано имя этого столбца.

Идея та же, но я улучшил чтение из файла (читается один раз); убрано дублирование кода (например, line.split(",")), ненужные обертки в 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());

I've got 2 tips not related to the issue:

1. You have hardcoded a URI, it's better to move the value to a constant or add a method param.
2. You could move the main part out of the if clause if you checked the opposite condition !Files.exists(path) and threw an exception.

Как обычно, вы должны использовать Джексон! Ознакомьтесь с документами

Если вы хотите, чтобы Джексон использовал первую строку в качестве информации заголовка:

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();
    }
}

или вы можете определить свою схему как объект 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();
    }
}

К сожалению, я работаю над проектом, который позволит мне импортировать ограниченные библиотеки, поэтому я надеялся сделать это только с помощью Java JDK, но это полезно отметить для других проектов. Спасибо

Michael Heneghan 05.04.2018 17:14

Другие вопросы по теме