Java CSV解析器

时间:2020-02-23 14:34:59  来源:igfitidea点击:

欢迎使用Java CSV解析器教程。
CSV文件是将数据从一个系统传递到另一个系统的最广泛使用的格式之一。
由于Microsoft Excel支持CSV文件,因此非技术人员也可以轻松使用它。

Java CSV解析器

很遗憾,我们没有内置的Java CSV解析器。

如果CSV文件非常简单并且没有任何特殊字符,那么我们可以使用Java Scanner类来解析CSV文件,但在大多数情况下并非如此。
与其编写复杂的逻辑进行解析,不如使用我们拥有的用于解析和编写CSV文件的开源工具。

有三种用于CSV的开源API。

  • OpenCSV
  • Apache Commons CSV
  • 超级CSV

我们将一一研究所有这些Java CSV解析器。

假设我们有一个CSV文件,如下所示:

employees.csv

ID,Name,Role,Salary
1,hyman Kumar,CEO,"5,000USD"
2,Lisa,Manager,500USD
3,David,,1000USD

并且我们想将其解析为Employee对象列表。

package com.theitroad.parser.csv;

public class Employee {

	private String id;
	private String name;
	private String role;
	private String salary;
	
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getRole() {
		return role;
	}
	public void setRole(String role) {
		this.role = role;
	}
	public String getSalary() {
		return salary;
	}
	public void setSalary(String salary) {
		this.salary = salary;
	}
	
	@Override
	public String toString(){
		return "ID="+id+",Name="+name+",Role="+role+",Salary="+salary+"\n";
	}
}

1. OpenCSV

我们将看到如何使用OpenCSV Java解析器将CSV文件读取到Java对象,然后从Java对象写入CSV。
从SourceForge下载OpenCSV库,并将其包含在类路径中。

如果您正在使用Maven,则将其包含在以下依赖项中。

<dependency>
  <groupId>com.opencsv</groupId>
  <artifactId>opencsv</artifactId>
  <version>3.8</version>
</dependency>

对于解析CSV文件,我们可以使用CSVReader解析每一行到对象列表。
CSVParser还提供了一次读取所有数据然后进行解析的选项。

OpenCSV提供了" CsvToBean"类,我们可以将其与" HeaderColumnNameMappingStrategy"对象一起使用,以自动将CSV映射到对象列表。

为了写入CSV数据,我们需要创建String数组列表,然后使用CSVWriter类将其写入文件或者任何其他writer对象。

package com.theitroad.parser.csv;

import java.io.FileReader;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import au.com.bytecode.opencsv.CSVReader;
import au.com.bytecode.opencsv.CSVWriter;
import au.com.bytecode.opencsv.bean.CsvToBean;
import au.com.bytecode.opencsv.bean.HeaderColumnNameTranslateMappingStrategy;

public class OpenCSVParserExample {

	public static void main(String[] args) throws IOException {

		List<Employee> emps = parseCSVFileLineByLine();
		System.out.println("**");
		parseCSVFileAsList();
		System.out.println("**");
		parseCSVToBeanList();
		System.out.println("**");
		writeCSVData(emps);
	}

	private static void parseCSVToBeanList() throws IOException {
		
		HeaderColumnNameTranslateMappingStrategy<Employee> beanStrategy = new HeaderColumnNameTranslateMappingStrategy<Employee>();
		beanStrategy.setType(Employee.class);
		
		Map<String, String> columnMapping = new HashMap<String, String>();
		columnMapping.put("ID", "id");
		columnMapping.put("Name", "name");
		columnMapping.put("Role", "role");
		//columnMapping.put("Salary", "salary");
		
		beanStrategy.setColumnMapping(columnMapping);
		
		CsvToBean<Employee> csvToBean = new CsvToBean<Employee>();
		CSVReader reader = new CSVReader(new FileReader("employees.csv"));
		List<Employee> emps = csvToBean.parse(beanStrategy, reader);
		System.out.println(emps);
	}

	private static void writeCSVData(List<Employee> emps) throws IOException {
		StringWriter writer = new StringWriter();
		CSVWriter csvWriter = new CSVWriter(writer,'#');
		List<String[]> data  = toStringArray(emps);
		csvWriter.writeAll(data);
		csvWriter.close();
		System.out.println(writer);
	}

	private static List<String[]> toStringArray(List<Employee> emps) {
		List<String[]> records = new ArrayList<String[]>();
		//add header record
		records.add(new String[]{"ID","Name","Role","Salary"});
		Iterator<Employee> it = emps.iterator();
		while(it.hasNext()){
			Employee emp = it.next();
			records.add(new String[]{emp.getId(),emp.getName(),emp.getRole(),emp.getSalary()});
		}
		return records;
	}

	private static List<Employee> parseCSVFileLineByLine() throws IOException {
		//create CSVReader object
		CSVReader reader = new CSVReader(new FileReader("employees.csv"), ',');
		
		List<Employee> emps = new ArrayList<Employee>();
		//read line by line
		String[] record = null;
		//skip header row
		reader.readNext();
		
		while((record = reader.readNext()) != null){
			Employee emp = new Employee();
			emp.setId(record[0]);
			emp.setName(record[1]);
			emp.setRole(record[2]);
			emp.setSalary(record[3]);
			emps.add(emp);
		}
		
		reader.close();
		
		System.out.println(emps);
		return emps;
	}
	
	private static void parseCSVFileAsList() throws IOException {
		//create CSVReader object
		CSVReader reader = new CSVReader(new FileReader("employees.csv"), ',');

		List<Employee> emps = new ArrayList<Employee>();
		//read all lines at once
		List<String[]> records = reader.readAll();
		
		Iterator<String[]> iterator = records.iterator();
		//skip header row
		iterator.next();
		
		while(iterator.hasNext()){
			String[] record = iterator.next();
			Employee emp = new Employee();
			emp.setId(record[0]);
			emp.setName(record[1]);
			emp.setRole(record[2]);
			emp.setSalary(record[3]);
			emps.add(emp);
		}
		
		reader.close();
		
		System.out.println(emps);
	}

}

当我们在OpenCSV示例程序上运行时,将得到以下输出。

[ID=1,Name=hyman Kumar,Role=CEO,Salary=5,000USD
, ID=2,Name=Lisa,Role=Manager,Salary=500USD
, ID=3,Name=David,Role=,Salary=1000USD
]
**
[ID=1,Name=hyman Kumar,Role=CEO,Salary=5,000USD
, ID=2,Name=Lisa,Role=Manager,Salary=500USD
, ID=3,Name=David,Role=,Salary=1000USD
]
**
[ID=1,Name=hyman Kumar,Role=CEO,Salary=null
, ID=2,Name=Lisa,Role=Manager,Salary=null
, ID=3,Name=David,Role=,Salary=null
]
**
"ID"#"Name"#"Role"#"Salary"
"1"#"hyman Kumar"#"CEO"#"5,000USD"
"2"#"Lisa"#"Manager"#"500USD"
"3"#"David"#""#"1000USD"

如您所见,在OpenCSV Java解析器中解析或者写入CSV数据时,我们也可以设置定界符。

2. Apache Commmons CSV

您可以下载Apache Commons CSV二进制文件,也可以使用maven包含依赖项,如下所示。

<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-csv</artifactId>
  <version>1.3</version>
</dependency>

Apache Commons CSV解析器易于使用,CSVParser类用于解析CSV数据,CSVPrinter用于写入数据。

下面提供了将上述CSV文件解析为Employee对象列表的示例代码。

package com.theitroad.parser.csv;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.csv.CSVRecord;

public class ApacheCommonsCSVParserExample {

	public static void main(String[] args) throws FileNotFoundException, IOException {
		
		//Create the CSVFormat object
		CSVFormat format = CSVFormat.RFC4180.withHeader().withDelimiter(',');
		
		//initialize the CSVParser object
		CSVParser parser = new CSVParser(new FileReader("employees.csv"), format);
		
		List<Employee> emps = new ArrayList<Employee>();
		for(CSVRecord record : parser){
			Employee emp = new Employee();
			emp.setId(record.get("ID"));
			emp.setName(record.get("Name"));
			emp.setRole(record.get("Role"));
			emp.setSalary(record.get("Salary"));
			emps.add(emp);
		}
		//close the parser
		parser.close();
		
		System.out.println(emps);
		
		//CSV Write Example using CSVPrinter
		CSVPrinter printer = new CSVPrinter(System.out, format.withDelimiter('#'));
		System.out.println("");
		printer.printRecord("ID","Name","Role","Salary");
		for(Employee emp : emps){
			List<String> empData = new ArrayList<String>();
			empData.add(emp.getId());
			empData.add(emp.getName());
			empData.add(emp.getRole());
			empData.add(emp.getSalary());
			printer.printRecord(empData);
		}
		//close the printer
		printer.close();
	}

}

当我们运行上面的程序时,我们得到以下输出。

[ID=1,Name=hyman Kumar,Role=CEO,Salary=5,000USD
, ID=2,Name=Lisa,Role=Manager,Salary=500USD
, ID=3,Name=David,Role=,Salary=1000USD
]

ID#Name#Role#Salary
1#hyman Kumar#CEO#5,000USD
2#Lisa#Manager#500USD
3#David##1000USD

3.超级CSV

在寻找良好的CSV解析器时,我看到很多开发人员建议在Stack Overflow中使用Super CSV。
因此,我想尝试一下。
从SourceForge下载Super CSV库,并将jar文件包含在项目构建路径中。

如果您使用的是Maven,则只需添加以下依赖项即可。

<dependency>
  <groupId>net.sf.supercsv</groupId>
  <artifactId>super-csv</artifactId>
  <version>2.4.0</version>
</dependency>

为了将CSV文件解析为对象列表,我们需要创建CsvBeanReader的实例。
我们可以使用CellProcessor数组设置特定于单元的规则。
我们可以使用它直接从CSV文件读取到Java bean,反之亦然。

如果必须写入CSV数据,则过程类似,并且必须使用CsvBeanWriter类。

package com.theitroad.parser.csv;

import java.io.FileReader;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;

import org.supercsv.cellprocessor.Optional;
import org.supercsv.cellprocessor.constraint.NotNull;
import org.supercsv.cellprocessor.constraint.UniqueHashCode;
import org.supercsv.cellprocessor.ift.CellProcessor;
import org.supercsv.io.CsvBeanReader;
import org.supercsv.io.CsvBeanWriter;
import org.supercsv.io.ICsvBeanReader;
import org.supercsv.io.ICsvBeanWriter;
import org.supercsv.prefs.CsvPreference;

public class SuperCSVParserExample {

	public static void main(String[] args) throws IOException {

		List<Employee> emps = readCSVToBean();
		System.out.println(emps);
		System.out.println("**");
		writeCSVData(emps);
	}

	private static void writeCSVData(List<Employee> emps) throws IOException {
		ICsvBeanWriter beanWriter = null;
		StringWriter writer = new StringWriter();
		try{
			beanWriter = new CsvBeanWriter(writer, CsvPreference.STANDARD_PREFERENCE);
			final String[] header = new String[]{"id","name","role","salary"};
			final CellProcessor[] processors = getProcessors();
          
			//write the header
          beanWriter.writeHeader(header);
          
          //write the bean's data
          for(Employee emp: emps){
          	beanWriter.write(emp, header, processors);
          }
		}finally{
			if( beanWriter != null ) {
              beanWriter.close();
			}
		}
		System.out.println("CSV Data\n"+writer.toString());
	}

	private static List<Employee> readCSVToBean() throws IOException {
		ICsvBeanReader beanReader = null;
		List<Employee> emps = new ArrayList<Employee>();
		try {
			beanReader = new CsvBeanReader(new FileReader("employees.csv"),
					CsvPreference.STANDARD_PREFERENCE);

			//the name mapping provide the basis for bean setters 
			final String[] nameMapping = new String[]{"id","name","role","salary"};
			//just read the header, so that it don't get mapped to Employee object
			final String[] header = beanReader.getHeader(true);
			final CellProcessor[] processors = getProcessors();

			Employee emp;
			
			while ((emp = beanReader.read(Employee.class, nameMapping,
					processors)) != null) {
				emps.add(emp);
			}

		} finally {
			if (beanReader != null) {
				beanReader.close();
			}
		}
		return emps;
	}

	private static CellProcessor[] getProcessors() {
		
		final CellProcessor[] processors = new CellProcessor[] { 
              new UniqueHashCode(), //ID (must be unique)
              new NotNull(), //Name
              new Optional(), //Role
              new NotNull() //Salary
      };
		return processors;
	}

}

当我们在Super CSV示例程序上方运行时,将得到以下输出。

[ID=1,Name=hyman Kumar,Role=CEO,Salary=5,000USD
, ID=2,Name=Lisa,Role=Manager,Salary=500USD
, ID=3,Name=David,Role=null,Salary=1000USD
]
**
CSV Data
id,name,role,salary
1,hyman Kumar,CEO,"5,000USD"
2,Lisa,Manager,500USD
3,David,,1000USD

如您所见,"角色"字段设置为"可选",因为对于第三行,该字段为空。
现在,如果将其更改为NotNull,则会得到以下异常。

Exception in thread "main" org.supercsv.exception.SuperCsvConstraintViolationException: null value encountered
processor=org.supercsv.cellprocessor.constraint.NotNull
context={lineNo=4, rowNo=4, columnNo=3, rowSource=[3, David, null, 1000USD]}
	at org.supercsv.cellprocessor.constraint.NotNull.execute(NotNull.java:71)
	at org.supercsv.util.Util.executeCellProcessors(Util.java:93)
	at org.supercsv.io.AbstractCsvReader.executeProcessors(AbstractCsvReader.java:203)
	at org.supercsv.io.CsvBeanReader.read(CsvBeanReader.java:206)
	at com.theitroad.parser.csv.SuperCSVParserExample.readCSVToBean(SuperCSVParserExample.java:66)
	at com.theitroad.parser.csv.SuperCSVParserExample.main(SuperCSVParserExample.java:23)

因此,SuperCSV为我们提供了对其他CSV解析器不可用的字段具有条件逻辑的选项。