Java解析csv文件
2020-07-27 本文已影响0人
砒霜拌辣椒
使用opencsv解析csv文件并进行ORM映射为对象集合,可以指定分隔符,主要有以下几种映射方式。
接口 | 策略 |
---|---|
MappingStrategy | 顶级接口 |
HeaderColumnNameMappingStrategy | 基于DTO属性名或注解与csv头进行映射 |
ColumnPositionMappingStrategy | 基于DTO属性数组顺序或注解指定顺序进行映射 |
HeaderColumnNameTranslateMappingStrategy | 基于csv头和DTO属性的map关系进行映射 |
1、Maven导包
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>4.6</version>
</dependency>
2、HeaderColumnNameMappingStrategy
id,short_name,name,remark,parent_id,type_name,type_id
1,,大型汽车号牌,1.00,,号牌种类,1
2,,小型汽车号牌,2.00,,号牌种类,1
3,,使馆汽车号牌,3.50,,号牌种类,1
4,,领馆汽车号牌,,,号牌种类,1
5,,境外汽车号牌,,,号牌种类,1
6,,外籍汽车号牌,,,号牌种类,1
7,,普通摩托车号牌,,,号牌种类,1
8,,低速车号牌,,,号牌种类,1
9,,拖拉机号牌,,,号牌种类,1
10,,挂车号牌,,,号牌种类,1
11,,教练汽车号牌,,,号牌种类,1
12,,临时行驶车号牌,,,号牌种类,1
13,,警用汽车号牌,,,号牌种类,1
14,,重型普通半挂车,20,,车辆类型,2
15,,重型厢式半挂车,100,,车辆类型,2
@Data
public class CarCsvDTOByName {
@CsvBindByName(column = "id")
private String id;
@CsvBindByName(column = "short_name")
private String shortName;
@CsvBindByName(column = "name")
private String name;
@CsvBindByName(column = "remark")
private String remark;
@CsvBindByName(column = "parent_id")
private String parentId;
@CsvBindByName(column = "type_name")
private String typeName;
@CsvBindByName(column = "type_id")
private String typeId;
}
@PostMapping("/parseByName")
public List parseByName(MultipartFile file) throws IOException {
InputStreamReader inputStream = new InputStreamReader(file.getInputStream(), CharsetUtil.CHARSET_GBK);
// 设置解析策略,csv的头和POJO属性的名称对应,也可以使用@CsvBindByName注解来指定名称
HeaderColumnNameMappingStrategy strategy = new HeaderColumnNameMappingStrategy();
strategy.setType(CarCsvDTOByName.class);
CsvToBean csvToBean = new CsvToBeanBuilder(inputStream)
.withMappingStrategy(strategy)
.build();
List carCsvDTOList = csvToBean.parse();
return carCsvDTOList;
}
- 默认基于DTO类的属性名和csv文件第一行的header进行映射。
- 也可以通过
@CsvBindByName
注解指定映射字段名。
3、ColumnPositionMappingStrategy
1,,大型汽车号牌,1.00,,号牌种类,1
2,,小型汽车号牌,2.00,,号牌种类,1
3,,使馆汽车号牌,3.50,,号牌种类,1
4,,领馆汽车号牌,,,号牌种类,1
5,,境外汽车号牌,,,号牌种类,1
6,,外籍汽车号牌,,,号牌种类,1
7,,普通摩托车号牌,,,号牌种类,1
8,,低速车号牌,,,号牌种类,1
9,,拖拉机号牌,,,号牌种类,1
10,,挂车号牌,,,号牌种类,1
11,,教练汽车号牌,,,号牌种类,1
12,,临时行驶车号牌,,,号牌种类,1
13,,警用汽车号牌,,,号牌种类,1
14,,重型普通半挂车,20,,车辆类型,2
15,,重型厢式半挂车,100,,车辆类型,2
3.1、基于@CsvBindByPosition注解方式的DTO
@Data
public class CarCsvDTOByPosition {
@CsvBindByPosition(position = 0)
private String id;
@CsvBindByPosition(position = 1)
private String shortName;
@CsvBindByPosition(position = 2)
private String name;
@CsvBindByPosition(position = 3)
private String remark;
@CsvBindByPosition(position = 4)
private String parentId;
@CsvBindByPosition(position = 5)
private String typeName;
@CsvBindByPosition(position = 6)
private String typeId;
}
@PostMapping("/parseByPosition")
public List parseByPosition(String filePath) throws IOException {
InputStreamReader inputStream = new InputStreamReader(new FileInputStream(filePath), CharsetUtil.CHARSET_GBK);
// 设置解析策略,使用@CsvBindByPosition注解可以指定字段在csv文件头中的位置,从0开始
ColumnPositionMappingStrategy strategy = new ColumnPositionMappingStrategy();
strategy.setType(CarCsvDTOByPosition.class);
CsvToBean csvToBean = new CsvToBeanBuilder(inputStream)
.withMappingStrategy(strategy)
.build();
List carCsvDTOList = csvToBean.parse();
return carCsvDTOList;
}
3.2、自定义头数组(DTO不需要注解)
@Data
public class CarCsvDTOByMappingArray {
private String id;
private String shortName;
private String name;
private String remark;
private String parentId;
private String typeName;
private String typeId;
}
@PostMapping("/parseByMappingArray")
public List parseByMappingArray(String filePath) throws IOException {
InputStreamReader inputStream = new InputStreamReader(new FileInputStream(filePath), CharsetUtil.CHARSET_GBK);
// 设置解析策略,csv文件不需要头,由程序指定
ColumnPositionMappingStrategy strategy = new ColumnPositionMappingStrategy();
strategy.setType(CarCsvDTOByMappingArray.class);
String headers = "id|shortName|name|remark|parentId|typeName|typeId";
String[] headerArr = headers.split("\\|");
strategy.setColumnMapping(headerArr);
CsvToBean csvToBean = new CsvToBeanBuilder(inputStream)
.withMappingStrategy(strategy)
.build();
List carCsvDTOList = csvToBean.parse();
return carCsvDTOList;
}
- 基于
@CsvBindByPosition
注解指定字段位置来进行映射。 - 也可以通过自定义头数组与csv文件内容按顺序进行映射。
- csv文件中不需要有文件头,如果有则需要手动跳过。
4、HeaderColumnNameTranslateMappingStrategy
id,short_name,name,remark,parent_id,type_name,type_id
1,,大型汽车号牌,1.00,,号牌种类,1
2,,小型汽车号牌,2.00,,号牌种类,1
3,,使馆汽车号牌,3.50,,号牌种类,1
4,,领馆汽车号牌,,,号牌种类,1
5,,境外汽车号牌,,,号牌种类,1
6,,外籍汽车号牌,,,号牌种类,1
7,,普通摩托车号牌,,,号牌种类,1
8,,低速车号牌,,,号牌种类,1
9,,拖拉机号牌,,,号牌种类,1
10,,挂车号牌,,,号牌种类,1
11,,教练汽车号牌,,,号牌种类,1
12,,临时行驶车号牌,,,号牌种类,1
13,,警用汽车号牌,,,号牌种类,1
14,,重型普通半挂车,20,,车辆类型,2
15,,重型厢式半挂车,100,,车辆类型,2
@Data
public class CarCsvDTOByTranslate {
private String id;
private String shortName;
private String name;
private String remark;
private String parentId;
private String typeName;
private String typeId;
}
@PostMapping("/parseByMappingByTranslate")
public List parseByMappingByTranslate(String filePath) throws IOException {
InputStreamReader inputStream = new InputStreamReader(new FileInputStream(filePath), CharsetUtil.CHARSET_GBK);
// 设置解析策略,key-csv的头、value-DTO属性
HeaderColumnNameTranslateMappingStrategy strategy = new HeaderColumnNameTranslateMappingStrategy();
strategy.setType(CarCsvDTOByTranslate.class);
Map<String, String> columnMapping = new HashMap<>();
columnMapping.put("id", "id");
columnMapping.put("short_name", "shortName");
columnMapping.put("name", "name");
columnMapping.put("remark", "remark");
columnMapping.put("parent_id", "parentId");
columnMapping.put("type_name", "typeName");
columnMapping.put("type_id", "typeId");
strategy.setColumnMapping(columnMapping);
CsvToBean csvToBean = new CsvToBeanBuilder(inputStream)
.withMappingStrategy(strategy)
.build();
List carCsvDTOList = csvToBean.parse();
return carCsvDTOList;
}
5、字段类型转换和必输项校验
下面演示将字符串转为BigDecimal
类型,如果解析时id字段为空则抛异常。
id,short_name,name,remark,parent_id,type_name,type_id
1,,大型汽车号牌,1.00,,号牌种类,1
2,,小型汽车号牌,2.00,,号牌种类,1
3,,使馆汽车号牌,3.50,,号牌种类,1
4,,领馆汽车号牌,,,号牌种类,1
5,,境外汽车号牌,,,号牌种类,1
6,,外籍汽车号牌,,,号牌种类,1
7,,普通摩托车号牌,,,号牌种类,1
8,,低速车号牌,,,号牌种类,1
9,,拖拉机号牌,,,号牌种类,1
10,,挂车号牌,,,号牌种类,1
11,,教练汽车号牌,,,号牌种类,1
12,,临时行驶车号牌,,,号牌种类,1
13,,警用汽车号牌,,,号牌种类,1
14,,重型普通半挂车,20,,车辆类型,2
15,,重型厢式半挂车,100,,车辆类型,2
@Data
public class CarCsvDTOConvertAndValid {
@CsvBindByName(column = "id", required = true)
private String id;
@CsvBindByName(column = "short_name")
private String shortName;
@CsvBindByName(column = "name")
private String name;
@CsvCustomBindByName(column = "remark", converter = ConvertToBigDecimal.class)
private BigDecimal remark;
@CsvBindByName(column = "parent_id")
private String parentId;
@CsvBindByName(column = "type_name")
private String typeName;
@CsvBindByName(column = "type_id")
private String typeId;
}
public class ConvertToBigDecimal extends AbstractBeanField {
@Override
protected Object convert(String value) {
if(StringUtils.isNotBlank(value)) {
return new BigDecimal(value);
}
return new BigDecimal(0);
}
}
@PostMapping("/convertAndValid")
public List convertAndValid(String filePath) throws IOException {
InputStreamReader inputStream = new InputStreamReader(new FileInputStream(filePath), CharsetUtil.CHARSET_GBK);
HeaderColumnNameMappingStrategy strategy = new HeaderColumnNameMappingStrategy();
strategy.setType(CarCsvDTOConvertAndValid.class);
// 校验必输项以及做类型转换
CsvToBean csvToBean = new CsvToBeanBuilder(inputStream)
.withMappingStrategy(strategy)
.build();
List carCsvDTOList = csvToBean.parse();
return carCsvDTOList;
}
- 默认的解析规则只能支持DTO中定义简单类型字段,其它类型可以在注解中指定转换的实现类。
- 指定转换类型的注解主要有
@CsvCustomBindByName
和@CsvCustomBindByPosition
这2种,分别对应基于字段名的映射和基于字段位置的映射。 - 同时可以在注解中指定必输项,如果解析时为空则抛出异常。
6、自定义解析
- 解析时跳过首部指定行数。
- 指定分隔符。
- 跳过特定的行不进行解析。
- 指定必输项校验不通过抛出异常或是忽略不进行解析。
=======================跳过此行=======================
=======================跳过此行=======================
id|short_name|name|remark|parent_id|type_name|type_id
1||大型汽车号牌|1.00||号牌种类|1
2||小型汽车号牌|2.00||号牌种类|1
3||使馆汽车号牌|3.50||号牌种类|1
4||领馆汽车号牌|||号牌种类|1
5||境外汽车号牌|||号牌种类|1
6||外籍汽车号牌|||号牌种类|1
7||普通摩托车号牌|||号牌种类|1
8||低速车号牌|||号牌种类|1
9||拖拉机号牌|||号牌种类|1
10||挂车号牌|||号牌种类|1
11||教练汽车号牌|||号牌种类|1
12||临时行驶车号牌|||号牌种类|1
13||警用汽车号牌|||号牌种类|1
14||重型普通半挂车|20||车辆类型|2
15||重型厢式半挂车|100||车辆类型|2
@Data
public class CarCsvDTOConvertAndValid {
@CsvBindByName(column = "id", required = true)
private String id;
@CsvBindByName(column = "short_name")
private String shortName;
@CsvBindByName(column = "name")
private String name;
@CsvCustomBindByName(column = "remark", converter = ConvertToBigDecimal.class)
private BigDecimal remark;
@CsvBindByName(column = "parent_id")
private String parentId;
@CsvBindByName(column = "type_name")
private String typeName;
@CsvBindByName(column = "type_id")
private String typeId;
}
@PostMapping("/parseBySelf")
public List parseBySelf(String filePath) throws IOException {
InputStreamReader inputStream = new InputStreamReader(new FileInputStream(filePath), CharsetUtil.CHARSET_GBK);
HeaderColumnNameMappingStrategy strategy = new HeaderColumnNameMappingStrategy();
strategy.setType(CarCsvDTOConvertAndValid.class);
CsvToBean csvToBean = new CsvToBeanBuilder(inputStream)
.withSkipLines(2) // 跳过行数
.withSeparator('|') // 分隔符
.withFilter(new SkipLineFilter())
.withThrowExceptions(false) // 如果有必输项没有,则不抛异常忽略此行
.withMappingStrategy(strategy)
.build();
List carCsvDTOList = csvToBean.parse();
return carCsvDTOList;
}
跳过特定行
public class SkipLineFilter implements CsvToBeanFilter {
@Override
public boolean allowLine(String[] line) {
// 首列为空的行过滤掉
return StringUtils.isNotBlank(line[0]);
}
}