我想使用新的Java 8流API(对此我一个完整的新手)解析为一个特定的行中的CSV文件(其中以"内达"在名称列).使用以下文章获取动机,我修改并修复了一些错误,以便我可以解析包含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 { Mapparams = 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 > values = lines .skip(1) .map((line) -> Arrays.asList(line.split(","))) .collect(Collectors.toList()); values.forEach((l)->System.out.println(l)); } } }
有没有办法避免在提取标题行后重新读取文件?虽然这是一个非常小的示例文件,但我将此逻辑应用于大型CSV文件.
是否有技术使用流API在提取的列名称(在文件的第一次扫描中)与剩余行中的值之间创建映射?
如何以形式返回一行List
(而不是List
包含所有行).我更愿意只将行作为列名与其对应值之间的映射.(有点像JDBC中的结果集).我在这里看到了一个可能有用的Collectors.mapMerger函数,但我不知道如何使用它.>
BufferedReader
明确使用:
Listcolumns; 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
此不同,此方法不会将所有行读入aList
,而是在流消耗时延迟填充.
关于返回一行的第二个问题.在函数式编程中,您要做的是称为过滤.Stream API借助于提供这样的方法Stream.filter
.此方法采用Predicate
as参数,该函数返回true
应保留的所有项,false
否则返回.
在这种情况下,我们希望在名称等于时Predicate
返回.这可以写成lambda表达式.true
"Neda"
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
.您可以添加一些逻辑来查找第一个项目,或者如果找不到任何项目则抛出异常,具体取决于您的业务需求.>
请注意,在@ Holger的答案中Files.lines(path)
直接使用BufferedReader
as 可以避免调用两次.