![](/img/trans.png)
[英]Parsing a CSV file for a multiple row rows using new Java 8 Streams API
[英]Parsing a CSV file for a unique row using the new Java 8 Streams API
我正在嘗試使用新的 Java 8 Streams API(我是一個完整的新手)來解析 CSV 文件中的特定行(名稱列中帶有“Neda”的行)。 使用以下文章作為動機,我修改並修復了一些錯誤,以便我可以解析包含 3 列的文件 - 'name'、'age' 和 'height'。
name,age,height
Marianne,12,61
Julie,13,73
Neda,14,66
Julia,15,62
Maryam,18,70
解析代碼如下:
@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));
}
}
}
有沒有辦法避免在提取標題行后重新讀取文件? 雖然這是一個非常小的示例文件,但我將把這個邏輯應用到一個大的 CSV 文件中。
是否有使用流 API 在提取的列名(在文件的第一次掃描中)到剩余行中的值之間創建映射的技術?
如何僅以List<String>
的形式返回一行(而不是包含所有行的List<List<String>>
)。 我寧願只找到行作為列名與其對應值之間的映射。 (有點像 JDBC 中的結果集)。 我看到一個可能在這里有用的 Collectors.mapMerger 函數,但我不知道如何使用它。
顯式使用BufferedReader
:
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(…)
也Files.lines(…)
BufferedReader.lines(…)
。 唯一的區別是Files.lines
將配置流,以便關閉流將關閉閱讀器,我們在這里不需要,因為顯式try(…)
語句已經確保關閉BufferedReader
。
請注意,在處理lines()
返回的流之后,無法保證讀取器的狀態,但是我們可以在執行流操作之前安全地讀取行。
首先,您擔心此代碼讀取文件兩次是不成立的。 實際上, Files.lines
返回一個惰性填充的行的流。 因此,代碼的第一部分僅讀取第一行,而代碼的第二部分讀取其余部分(盡管它會再次讀取第一行,即使被忽略)。 引用它的文檔:
從文件中讀取所有行作為
Stream
。 與readAllLines
不同,此方法不會將所有行讀入List
,而是在消耗流時延遲填充。
關於只返回一行的第二個問題。 在函數式編程中,您嘗試執行的操作稱為過濾。 Stream API 在Stream.filter
的幫助下提供了這樣的方法。 此方法將Predicate
作為參數,這是一個函數,對於所有應保留的項目返回true
,否則返回false
。
在這種情況下,我們想要一個Predicate
在名稱等於"Neda"
時返回true
。 這可以寫成 lambda 表達式s -> s.equals("Neda")
。
所以在你的代碼的第二部分,你可以有:
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());
但是請注意,這並不能確保只有一個名為"Neda"
,它會將所有可能的項目收集到List<List<String>>
。 根據您的業務需求,您可以添加一些邏輯來查找第一個項目,或者如果沒有找到項目則拋出異常。
仍然請注意,通過直接使用BufferedReader
可以避免調用兩次Files.lines(path)
的回答。
其他答案都很好。 但我建議使用 CSV 處理庫來讀取您的輸入文件。 正如其他人指出的那樣,CSV 格式並不像看起來那么簡單。 首先,這些值可能嵌套在引號中,也可能不嵌套。 CSV 有很多變體,例如在 Postgres、MySQL、Mongo、Microsoft Excel 等中使用的變體。
Java 生態系統提供了幾個這樣的庫。 我使用Apache Commons CSV 。
Apache Commons CSV庫不使用流。 但是,如果使用庫來執行 scut 工作,則您的工作不需要流。 該庫可以輕松地從文件中循環行,而無需將大文件加載到內存中。
在提取的列名(在文件的第一次掃描中)到剩余行中的值之間創建映射?
當您調用withHeader
時, Apache Commons CSV會自動執行此withHeader
。
以 List 的形式只返回一行
是的,很容易做到。
根據您的要求,我們可以使用特定行的 3 個字段值中的每一個來填充List
。 此List
充當元組。
List < String > tuple = List.of(); // Our goal is to fill this list of values from a single row. Initialize to an empty nonmodifiable list.
我們指定我們期望的輸入文件的格式:標准CSV ( RFC 4180 ),第一行由列名填充。
CSVFormat format = CSVFormat.RFC4180.withHeader() ;
我們指定找到輸入文件的文件路徑。
Path path = Path.of("/Users/basilbourque/people.csv");
我們使用 try-with-resources 語法(參見教程)來自動關閉我們的解析器。
當我們閱讀每一行時,我們檢查名稱是否為Neda
。 如果找到,我們將使用該行的字段值報告我們的元組List
。 我們中斷循環。 我們使用List.of
方便地返回某個不可修改的未知具體類的List
對象,這意味着您不能在列表中添加或刪除元素。
try (
CSVParser parser =CSVParser.parse( path , StandardCharsets.UTF_8, format ) ;
)
{
for ( CSVRecord record : parser )
{
if ( record.get( "name" ).equals( "Neda" ) )
{
tuple = List.of( record.get( "name" ) , record.get( "age" ) , record.get( "height" ) );
break ;
}
}
}
catch ( FileNotFoundException e )
{
e.printStackTrace();
}
catch ( IOException e )
{
e.printStackTrace();
}
如果我們發現成功,我們應該在List
看到一些項目。
if ( tuple.isEmpty() )
{
System.out.println( "Bummer. Failed to report a row for `Neda` name." );
} else
{
System.out.println( "Success. Found this row for name of `Neda`:" );
System.out.println( tuple.toString() );
}
跑的時候。
成功。 找到
Neda
名稱的這一行:[內達, 14, 66]
與其使用List
作為元組,我建議您定義一個Person
類來用適當的數據類型表示此數據。 我們這里的代碼將返回一個Person
實例而不是一個List<String>
。
我知道我回復得太晚了,但也許將來會對某人有所幫助
我制作了一個 csv 解析器/編寫器,由於其構建器模式而易於使用
對於您的情況:您可以使用過濾器來過濾要解析的行
csvLineFilter(Predicate<String>)
希望你覺得它很方便,這里是源代碼https://github.com/i7paradise/CsvUtils-Java8/
我加入了一個主類Demo.java來展示它是如何工作的
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.