网上发现这篇文章,这里转载给发家分享一下。原文:https://www.jianshu.com/p/6414185b2f01
Comma-Separated Value ([卡门 赛婆乱提的]
逗号分隔)(CSV),因分隔符没有严格的要求,可以使用逗号,也可以使用其他字符(如制表符\t,分号等),所以CSV也被称为逗号分隔或者其他字符分隔值。csv文件是使用纯文本来存储表格数据(只能存储文本,不能存储二进制)。
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>4.4</version>
</dependency>
CSVReader对象的构造方法
构造器涉及到的三个参数:
CSVWriter.DEFAULT_SEPARATOR
用于分割各列。[寇特]
用于定义各个列的引号,有时候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);
读取数据的read方法
注意:如果先readNext,再readAll,readAll也是从readNext之后的那一行开始了,也就是readNext读了之后就不会再读了。
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();
}
上述的在Opencsv4.0版本以上已经废弃了。采用CSVReaderBuilder来代替。本质上是采用的建造者模式来构建对象,更加优雅。
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();
}
优雅的解析文档中的字段。将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);
}
在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}
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}
在使用opencsv做csv文件导出与导入的时候,由于导入的csv文件带有bom标识,导致老是文件的第一列无法读取,下面贴出解决方法:
使用opencsv进行文件读取时,无法读取到第一列的内容
csv文件带有bom头
方法一:notepad++打开csv文件,在“编码”处选择“使用utf-8编码”,然后导入
方法二:在java后台去掉文件数据流中的bom头,去除方法如下:
/**
* 读取流中前面的字符,看是否有bom,如果有bom,将bom头先读掉丢弃
*
* @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;
}
然后进行读取csv文件的代码:
// 此处的file为multipartfile格式
try {
InputStream inputStream = getInputStream(file.getInputStream());
InputStreamReader reader = new InputStreamReader(inputStream, Charset.forName("UTF-8"));
HeaderColumnNameMappingStrategy strategy = new HeaderColumnNameMappingStrategy();
strategy.setType(TestDTO.class);
if(null != reader) {
CsvToBean csvToBean = new CsvToBeanBuilder<TestDTO>(reader).withMappingStrategy(strategy).build();
List<TestDTO> testDTOLists = csvToBean.parse();
for(int i=0;i<testDTOLists.size();i++){
System.out.println("testDTOList======"+testDTOLists);
}
reader.close();
return true;
}
} catch (IOException e) {
e.printStackTrace();
return false;
}