使用新的Java 8 Streams API解析唯一行的CSV文件

我正在尝试使用新的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 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 lines = Files.lines(path); List 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> values = lines .skip(1) .map((line) -> Arrays.asList(line.split(","))) .collect(Collectors.toList()); values.forEach((l)->System.out.println(l)); } } } 

有没有办法避免在提取标题行后重新读取文件? 虽然这是一个非常小的示例文件,但我会将此逻辑应用于大型CSV文件。

是否有技术使用流API在提取的列名称(在文件的第一次扫描中)与剩余行中的值之间创建映射?

如何以List的forms返回一行(而不是包含所有行的List<List> )。 我更愿意只将行作为列名与其对应值之间的映射。 (有点像JDBC中的结果集)。 我在这里看到了一个可能有用的Collectors.mapMerger函数,但我不知道如何使用它。

明确使用BufferedReader

 List columns; List> 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(…)也转向BufferedReader.lines(…) 。 唯一的区别是Files.lines将配置流,以便关闭流将关闭读取器,我们在这里不需要,因为显式的try(…)语句已经确保关闭BufferedReader

请注意,在处理了lines()返回的流之后 ,无法保证读取器的状态,但我们可以执行流操作之前安全地读取行。

首先,您对此代码正在两次读取文件的担忧尚未确定。 实际上, Files.lines返回一个延迟填充的行的Stream。 因此,代码的第一部分只读取第一行,代码的第二部分读取其余部分(它确实第二次读取第一行,即使被忽略)。 引用其文档:

从文件中读取所有行作为Stream 。 与readAllLines不同,此方法不会将所有行读入List ,而是在使用流时延迟填充。

关于返回一行的第二个问题。 在函数式编程中,您要做的是称为过滤 。 Stream API在Stream.filter的帮助下提供了这样的方法。 此方法将Predicate作为参数,该函数对应保留的所有项都返回true ,否则返回false

在这种情况下,我们希望Predicate在名称等于"Neda"时返回true 。 这可以写成lambda表达式s -> s.equals("Neda")

因此,在代码的第二部分中,您可以:

 lines = Files.lines(path); List> 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> 。 您可以添加一些逻辑来查找第一个项目,或者如果找不到任何项目则抛出exception,具体取决于您的业务需求。


请注意,通过直接使用BufferedReader可以避免调用两次Files.lines(path) ,就像在Files.lines(path)的答案中一样。

我知道我这么晚才回应,但未来可能会对某人有所帮助

我已经制作了一个csv解析器/编写器,由于它的构建器模式,它易于使用

对于您的情况:您可以过滤要使用的分析行

 csvLineFilter(Predicate) 

希望你觉得它很方便,这里是源代码https://github.com/i7paradise/CsvUtils-Java8/

我加入了一个主类Demo.java来展示它是如何工作的