У меня есть файл .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;
}
Спасибо
Да, скомпилировал в eclipse, ошибок компиляции не было. В настоящее время у меня нет доступа к файлу csv, поэтому я еще не смог правильно протестировать.




Не стоит изобретать велосипед и использовать обычную библиотеку парсера 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
Полностью согласен, мне не следует даже пытаться изобретать заново, поскольку это сделали люди лучше меня. К сожалению, я работаю над проектом, который на самом деле не позволяет мне импортировать внешние библиотеки и ограничен использованием предустановленных библиотек. Я не знал, что у Apache есть библиотека для файлов csv, она пригодится в будущем. Спасибо за информацию :)
Если вам нужна скорость, взгляните на это сравнение парсера CSV. однозначные парсеры обрабатывает крайние случаи лучше, чем другие библиотеки.
Используется ли потоковая передача? Что делать, если у меня очень большой файл?
@MdFaraz Apache Commons CSV CSVParser реализует Iterable<CSVRecord>, который можно расширить с помощью потокового API. Ключевые слова для дальнейшего исследования: итерация для потоковой передачи
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. Спасибо!
Мне удалось немного сократить ваш фрагмент.
Если я вас правильно понял, вам нужны все значения определенного столбца. Дано имя этого столбца.
Идея та же, но я улучшил чтение из файла (читается один раз); убрано дублирование кода (например, 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, но это полезно отметить для других проектов. Спасибо
в вашем коде много проблем, вы его скомпилировали?