joh*_*co3 7 java csv java-8 java-stream
我想使用新的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
Run Code Online (Sandbox Code Playgroud)
解析代码如下:
@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));
}
}
}
Run Code Online (Sandbox Code Playgroud)
有没有办法避免在提取标题行后重新读取文件?虽然这是一个非常小的示例文件,但我将此逻辑应用于大型CSV文件.
是否有技术使用流API在提取的列名称(在文件的第一次扫描中)与剩余行中的值之间创建映射?
如何以形式返回一行List<String>(而不是List<List<String>>包含所有行).我更愿意只将行作为列名与其对应值之间的映射.(有点像JDBC中的结果集).我在这里看到了一个可能有用的Collectors.mapMerger函数,但我不知道如何使用它.
Hol*_*ger 11
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());
}
Run Code Online (Sandbox Code Playgroud)
Files.lines(…)也适应BufferedReader.lines(…).唯一的区别是,Files.lines将配置流,以便关闭流将关闭读取器,我们在这里不需要,因为显式try(…)语句已经确保关闭BufferedReader.
请注意,在处理返回的流之后无法保证读取器的状态lines(),但我们可以在执行流操作之前安全地读取行.
首先,您对此代码正在读取文件两次的担忧尚未建立.实际上,Files.lines返回一个懒惰填充的行的Stream.因此,代码的第一部分只读取第一行,代码的第二部分读取其余部分(它确实第二次读取第一行,即使被忽略).引用其文档:
从文件中读取所有行作为
Stream.与readAllLines此不同,此方法不会将所有行读入aList,而是在流消耗时延迟填充.
关于返回一行的第二个问题.在函数式编程中,您要做的是称为过滤.Stream API借助于提供这样的方法Stream.filter.此方法采用Predicateas参数,该函数返回true应保留的所有项,false否则返回.
在这种情况下,我们希望在名称等于时Predicate返回.这可以写成lambda表达式.true"Neda"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());
Run Code Online (Sandbox Code Playgroud)
但请注意,这并不能确保名称中只有一个项目"Neda",它会将所有可能的项目收集到一个项目中List<List<String>>.您可以添加一些逻辑来查找第一个项目,或者如果找不到任何项目则抛出异常,具体取决于您的业务需求.
请注意,在@ Holger的答案中Files.lines(path)直接使用BufferedReaderas 可以避免调用两次.