web-dev-qa-db-ja.com

Java 8 Streamを使用した.csvファイルの解析

500社以上のデータでいっぱいの.csvファイルがあります。ファイルの各行は、特定の企業データセットを参照しています。 4つの異なるWebサービスを呼び出すには、このファイルを解析し、それぞれからデータを推定する必要があります。

.csvファイルの最初の行には、列名が含まれています。文字列パラメーターをとるメソッドを記述しようとしていますが、これは.csvファイルにある列タイトルに関連しています。

このパラメーターに基づいて、Java 8のストリーム機能を使用してファイルを解析し、各行/会社の列タイトルから取得したデータのリストを返すメソッドが必要です。

必要以上に複雑にしていますが、目標を達成するためのより効率的な方法を考えることはできません。

どんな考えやアイデアも大歓迎です。

Stackoverflowを検索すると、似ているがまったく同じではない次の投稿が見つかりました。 新しいJava 8 Streams API を使用して一意の行のCSVファイルを解析する

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

ありがとう

5

ホイールを再発明して、一般的なcsvパーサーライブラリを使用しないでください。たとえば、単に Apache Commons CSV を使用できます。

それはあなたのために多くのものを処理し、はるかに読みやすくなります。 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");
                // (..)
          }

編集:とにかく、あなたが本当に自分でそれをしたい場合は、 this の例を見てください。

13
ixeption

スニペットを少し短くすることができました。

正しく理解できたら、特定の列のすべての値が必要です。その列の名前が与えられます。

考え方は同じですが、ファイルからの読み取りを改善しました(一度読み取ります)。重複したコードの削除(line.split(",")など)、不要なListCollectors.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());
_

問題に関係のない2つのヒントがあります。

1. URIをハードコーディングしました。値を定数に移動するか、メソッドパラメーターを追加することをお勧めします。
2.反対の条件!Files.exists(path)をチェックして例外をスローした場合、if句からメイン部分を移動できます。

3
Andrew Tobilko

1)ストリームで複数の端末操作を呼び出すことはできません。
しかし、それらのうち2つを呼び出します:findFirst()は列名を取得し、次にcollect()は行の値を収集します。 Streamで呼び出される2番目の端末操作は例外をスローします。

2)ストリーム内のすべての行を読み取るStream<String> lines = Files.lines(path))の代わりに、文字列のリストを返すFiles.readAllLines()を使用して2回作成する必要があります。
最初の要素を使用して列名を取得し、リスト全体を使用して基準に一致する各行の値を取得します。

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
davidxxx

いつものように、ジャクソンを使用する必要があります! ドキュメントをご覧ください

Jacksonにヘッダー情報として最初の行を使用させたい場合:

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