1. 什么叫做CSV
Comma-Separated Value ([卡門 賽婆亂提的]
逗號分隔)(CSV),因分隔符沒有嚴格的要求,可以使用逗號,也可以使用其他字符(如制表符\t,分號等),所以CSV也被稱為逗號分隔或者其他字符分隔值。csv文件是使用純文本來存儲表格數據(只能存儲文本,不能存儲二進制)。
2. CSV解析的API方法
2.1. Maven依賴
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>4.4</version>
</dependency>
2.2 CSVReader對象
構造器涉及到的三個參數:
- reader:讀取文件的流對象,常有的是BufferedReader,InputStreamReader。
- separator:用于定義前面提到的分割符,默認為逗號
CSVWriter.DEFAULT_SEPARATOR
用于分割各列。 - quotechar:
[寇特]
用于定義各個列的引號,有時候csv文件中會用引號或者其它符號將一個列引起來,例如一行可能是:"1","2","3",如果想讀出的字符不包含引號,就可以把參數設為:"CSVWriter.NO_QUOTE_CHARACTER "
注:若是設置解析的編碼,需要在InputStreamReader對象中設置。
定義一個以逗號為分隔符、讀取時忽略引號的CSVReader就是:
CSVReader reader = new CSVReader(
new InputStreamReader(new FileInputStream(csvFile), "GBK"),
CSVWriter.DEFAULT_SEPARATOR,
CSVWriter.NO_QUOTE_CHARACTER);
2.3 read方法
- readAll():讀取全部;
- readNext():讀取一行;
注意:如果先readNext,再readAll,readAll也是從readNext之后的那一行開始了,也就是readNext讀了之后就不會再讀了。
2.4 CsvWriter對象
CSVWriter writer= null;
try {
writer = new CSVWriter(
new OutputStreamWriter(new FileOutputStream(fileName),"utf-8"),
'\t',CSVWriter.NO_QUOTE_CHARACTER);
String[] strings={"第一行","001","sds"};
writer.writeNext(strings);
writer.flush();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
3. CSV新版本
上述的在Opencsv4.0版本以上已經廢棄了。采用CSVReaderBuilder來代替。本質上是采用的建造者模式來構建對象,更加優雅。
3.1 構建CSVReader對象
try {
InputStreamReader is = new InputStreamReader(new FileInputStream(fileName), "utf-8");
CSVParser csvParser = new CSVParserBuilder().withSeparator('\t').build();
CSVReader reader = new CSVReaderBuilder(is).withCSVParser(csvParser).build();
List<String[]> strings = reader.readAll();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
3.2 CsvToBeanBuilder
優雅的解析文檔中的字段。將CSV文件轉換為Bean對象。
在上面我們可以使用readNext或者readAll進行逐行解讀。但是opencsv提供了基于"策略"的映射,將CSV綁定到bean。
接口名 | 策略 |
---|---|
MappingStrategy | 頂級的映射接口 |
headerColumnNameMappingStrategy | 基于列名的映射策略,讀取csv文件的第一行作為header,比如header1,header2,header3 然后調用bean的setHeader1方法,setHeader2方法,setHeader3方法分別設置值。**所以這種策略要求,列名與bean中的屬性名完全一致,如果不一致,則值為空,不會出錯。使用注解時,注解名字必須與csv中列名一致。 |
ColumnPositionMappingStrategy | 列位置映射策略,他沒有header的概念,所以會輸出取所有行。在columnMapping數組中指定bean的屬性,第一個值對應csv的第一列,第二個值對應csv的第二列…… |
HeaderColumnNameTranslateMappingStrategy | 列頭名字翻譯映射策略,與HeaderColumnNameMappintStrategy相比,bean的屬性名可以與csv列頭不一樣。通過指定map來映射。 |
注:bean的類型只能為基本數據類型以及String類型,若是BigDecimal類型,那么將會拋出異常。
解析后的bean類:
public class Stu {
private String header1;
private String header2;
private String header3;
//省略getter、setter、tostring方法
}
csv文件:
header1,header2,header3
哈哈,你好,不錯
第二行,不好,不是
基于列索引的映射
通俗點就是列位置映射,csv文件中列位置對應到bean中的列。
需要注意的是,該策略會輸出所有的行,故,我們需要跳過某些行。
- 非注解方式(使用數組)
public static <T> List<T> parseCsvToBean(Class<T> clazz, String fileName, String params, char csvSeparator, int skipLineNum) {
//獲取列位置數組
String[] columnMapping = params.split("\\|", -1);
ColumnPositionMappingStrategy<T> mapper = new ColumnPositionMappingStrategy<>();
mapper.setColumnMapping(columnMapping);
mapper.setType(clazz);
List<T> parse;
try {
CsvToBean csvToBean = new CsvToBeanBuilder(new FileReader(fileName))
.withMappingStrategy(mapper)
.withSeparator(csvSeparator)
.withSkipLines(skipLineNum)
.build();
parse = csvToBean.parse();
} catch (FileNotFoundException e) {
logger.error("【fileName:{}地址異常!】", fileName);
throw e;
}
}
測試方法:
@Test
public void test(){
String fileName = "D:\\app\\share\\download\\20190605\\testOfComma.csv";
String params="header1|header2|header3";
List<Stu> stus = CsvUtil.parseCsvToBean(Stu.class, fileName, params, ',', 1);
for (Stu stu:stus){
System.out.println(stu);
}
}
測試結果:
Stu{header1='哈哈', header2='你好', header3='不錯'}
Stu{header1='第二行', header2='不好', header3='不是'}
Process finished with exit code 0
- 注解方式
public class Stu {
@CsvBindByPosition(position = 0)
private String header1;
@CsvBindByPosition(position = 1)
private String header2;
@CsvBindByPosition(position = 2)
private String header3;
}
基于列名的映射
- 非注解方式:
CSVReader reader = new CSVReader(new InputStreamReader(new FileInputStream("test.csv"),"gbk"));
HeaderColumnNameMappingStrategy<SimpleBeanInfo> mapper = new
HeaderColumnNameMappingStrategy<SimpleBeanInfo>();
mapper.setType(SimpleBeanInfo.class);
CsvToBean<SimpleBeanInfo> csvToBean = new CsvToBean<SimpleBeanInfo>();
List<SimpleBeanInfo> list = csvToBean.parse(mapper, reader);
for(SimpleBeanInfo e : list){
System.out.println(e);
}
- 注解方式
csv文件:
編號 姓名 性別
001 李明 男
002 tom 男
public class Person {
@CsvBindByName(column = "性別")
private String sex;
@CsvBindByName(column = "姓名")
private String name;
@CsvBindByName(column = "編號")
private String id;
}
//省略getter和setter方法
@Test
public void testHeaderColumn() throws IOException {
String fileName = "D:\\app\\share\\eaglewood-client\\download\\20190605\\testofTab.csv";
InputStreamReader is = new InputStreamReader(CsvUtil.getInputStream(new FileInputStream(fileName)), "utf-8");
HeaderColumnNameMappingStrategy<Person> mappingStrategy = new HeaderColumnNameMappingStrategy<>();
mappingStrategy.setType(Person.class);
CsvToBean<Person> build = new CsvToBeanBuilder<Person>(is).withMappingStrategy(mappingStrategy).withSeparator(',').build();
List<Person> personList = build.parse();
for(Person person:personList){
System.out.println(person);
}
}
/**
* 讀取流中前面的字符,看是否有bom,如果有bom,將bom頭先讀掉丟棄
* (opencsv 按列名獲取bean對象,第一列缺失的情況)
* InputStreamReader is = new InputStreamReader(
* CsvUtil.getInputStream(new FileInputStream(fileName)), "utf-8");
* @param in
* @return
* @throws IOException
*/
public static InputStream getInputStream(InputStream in) throws IOException {
PushbackInputStream testin = new PushbackInputStream(in);
int ch = testin.read();
if (ch != 0xEF) {
testin.unread(ch);
} else if ((ch = testin.read()) != 0xBB) {
testin.unread(ch);
testin.unread(0xef);
} else if ((ch = testin.read()) != 0xBF) {
throw new IOException("錯誤的UTF-8格式文件");
} else {
}
return testin;
}
測試結果:
Person{id='001', sex='男', name='李明'}
Person{id='002', sex='男', name='tom'}
基于列名轉換映射
CSVReader reader = new CSVReader(new InputStreamReader(new FileInputStream("test.csv"),"gbk"));
/*
* 基于列名轉換,映射成類
*/
HeaderColumnNameTranslateMappingStrategy<SimpleBeanInfo> mapper =
new HeaderColumnNameTranslateMappingStrategy<SimpleBeanInfo>();
mapper.setType(SimpleBeanInfo.class);
Map<String,String> columnMapping = new HashMap<String,String>();
columnMapping.put("header1", "header1");//csv中的header1對應bean的header1
columnMapping.put("header2", "header2");
columnMapping.put("header3", "header3");
mapper.setColumnMapping(columnMapping);
CsvToBean<SimpleBeanInfo> csvToBean = new CsvToBean<SimpleBeanInfo>();
List<SimpleBeanInfo> list = csvToBean.parse(mapper, reader);
for(SimpleBeanInfo e : list){
System.out.println(e);
}
4. 轉換器
在csv獲取的都是字符串,這種情況下應該使用轉換器。將csv中的字段轉換為對應的bean中的字段類型。
public class Cluster {
@CsvBindByName
private String cluster;
@CsvCustomBindByName(converter = ConvertSplitOnWhitespace.class)
private String[] nodes;
@CsvCustomBindByName(converter = ConvertGermanToBoolean.class)
private boolean production;
// Getters and setters go here.
}
opencsv為我們提供了上面的兩個轉換器(我們可以參考,來實現自定義轉換器)。使用AbstractBeanField<T>
類來實現轉換器。
csv文件
編號,姓名,性別,金額
001,李明,男,0.23
002,tom,男,3.00
public class ConvertGermanToBigDecimal<Person> extends AbstractBeanField<Person> {
@Override
protected Object convert(String value) throws CsvDataTypeMismatchException, CsvConstraintViolationException {
Field field = getField();
System.out.println(field);
return new BigDecimal(value);
}
}
注:若是列映射策略,則要使用@CsvCustomBindByPosition()注解。
public class Person {
@CsvBindByName(column = "性別")
private String sex;
@CsvBindByName(column = "姓名")
private String name;
@CsvBindByName(column = "編號")
private String id;
@CsvCustomBindByName(column = "金額",converter = ConvertGermanToBigDecimal.class)
private BigDecimal amount;
}
測試結果:
Person{sex='男', name='李明', id='001', amount=0.23}
Person{sex='男', name='tom', id='002', amount=3.00}
5. 過濾器
opencsv提供了過濾器,可以過濾某些行,比如page header、page footer等
所有的過濾器必須實現CsvToBeanFilter 接口
public class MyCsvToBeanFilter implements CsvToBeanFilter {
@Override
public boolean allowLine(String[] line) {
//若是第二列為tom,則過濾掉
if ("tom".equals(line[1])) {
return false;
}
return true;
}
}
@Test
public void testHeaderColumn() throws IOException {
String fileName = "D:\\app\\share\\download\\20190605\\testofTab.csv";
InputStreamReader is = new InputStreamReader(CsvUtil.getInputStream(new FileInputStream(fileName)), "utf-8");
HeaderColumnNameMappingStrategy<Person> mappingStrategy = new HeaderColumnNameMappingStrategy<>();
mappingStrategy.setType(Person.class);
CsvToBean<Person> build = new CsvToBeanBuilder<Person>(is)
.withMappingStrategy(mappingStrategy)
.withFilter(new MyCsvToBeanFilter()) //增加過濾器,將tom過濾掉
.withSeparator(',')
.build();
List<Person> personList = build.parse();
for(Person person:personList){
System.out.println(person);
}
}
測試結果
Person{sex='男', name='李明', id='001', amount=0.23}
文章參考:
(官網)CSVParserBuilder——CSVParser的目的是獲取單個字符串并根據分隔符,引號和轉義字符將其解析為其元素。