我正在尝试使用新的Java8 StreamsAPI(我是一个完全的新手)来解析CSV文件中的特定行(名称列中有“Neda”的行)。使用以下文章作为动机,我修改并修复了一些错误,以便我可以解析包含3列的文件-“名称”、“年龄”和“高度”。
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的形式只返回一行
显式使用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());
}
File.行(…)
也使用BufferedReader.行(…)
。唯一的区别是Files.行
将配置流,以便关闭流将关闭阅读器,这里我们不需要,因为显式的try(…)
语句已经确保关闭BufferedReader
。
请注意,在line()
返回的流被处理后,不能保证阅读器的状态,但我们可以在执行流操作之前安全地读取行。
首先,您担心这段代码会读取文件两次是没有根据的。实际上,Files. line
返回的是惰性填充的行的Stream。因此,代码的第一部分只读取第一行,代码的第二部分读取其余部分(即使被忽略,它也会第二次读取第一行)。引用其留档:
将文件中的所有行作为Stream
读取。与readAllLines
不同,此方法不会将所有行读取到List
中,而是在使用流时延迟填充。
关于仅返回一行的第二个问题。在函数式编程中,您尝试做的称为过滤。StreamAPI在Stream. filter
的帮助下提供了这样一个方法。此方法采用Predicate
作为参数,该函数返回所有应保留的项目的true
,否则返回false
。
在这种情况下,我们需要一个谓词
,当名称等于“Neda”
时,它将返回true
。这可以写成lambda表达式s-
因此,在代码的第二部分中,您可以:
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”
的项目,它将所有可能的项目收集到一个列表中
仍然请注意,可以通过直接使用BufferedReader
来避免调用两次File. line(path)
,如@Holger的回答。
其他答案很好。但是我建议使用CSV处理库来读取您的输入文件。正如其他人所指出的,CSV格式并不像看起来那么简单。首先,值可能嵌套在引号中,也可能不嵌套在引号中。CSV有许多变体,例如Postgres、MySQL、Mongo、Microsoft Excel等中使用的那些。
Java生态系统提供了几个这样的库。我使用Apache CommonsCSV。
Apache CommonsCSV库不使用流。但是,如果使用库来执行scut工作,则您的工作不需要流。该库可以轻松地从文件中循环行,而无需将大文件加载到内存中。
在提取的列名(在文件的第一次扫描中)与其余行中的值之间创建映射?
Apache CommonsCSV在调用with Header
时会自动执行此操作。
以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(RFC4180),第一行由列名填充。
CSVFormat format = CSVFormat.RFC4180.withHeader() ;
我们指定在哪里找到我们的输入文件的文件路径。
Path path = Path.of("/Users/basilbourque/people.csv");
我们使用try with资源语法(请参阅教程)来自动关闭我们的解析器。
当我们在每一行中阅读时,我们检查名称是否为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