csv是最通用的一种文件格式,它可以非常容易地被导入各种PC表格及数据库中。 此文件,一行即为数据表的一行。生成数据表字段用半角逗号隔开。csv文件用记事本和excel都能打开。
1.CSV是纯文本文件,excel不是纯文本,excel包含很多格式信息在里面。
2.CSV文件的体积会更小,创建分发读取更加方便,适合存放结构化信息,比如记录的导出,流量统计等等。
3.CSV文件在windows平台默认的打开方式是excel,但是它的本质是一个文本文件。
de.siegmar
fastcsv
2.0.0
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CsvField {
/**
* 字段的标题
* @return
*/
String value() default "";
/**
* 是否忽略此字段
* @return
*/
boolean ignore() default false;
/**
* 转换器,按需生成结果
* @return
*/
Class extends CsvConvertHandler> using() default CsvConvertHandler.None.class;
}
public abstract class CsvConvertHandler implements CsvConvertVisitable {
@Override
public String convert(Object value) {
return this.get((T) value);
}
protected abstract String get(T value);
public abstract static class None extends CsvConvertHandler {
public None() {
}
}
}
@EqualsAndHashCode()
@Data
public class TestBean {
@CsvField(value = "字段1")
private String name1;
@CsvField(value = "字段2")
private String name2;
@CsvField(value = "字段3")
private String name3;
@CsvField(value = "字段4")
private String name4;
}
注解中删除了转换类
public class FastCsvHelper {
private final static String charset = "GBK";
private static class FieldData {
Field field;
CsvConvertVisitable converter;
public FieldData(Field field, CsvConvertVisitable converter) {
this.field = field;
this.converter = converter;
}
}
/**
* 读取CSV文件
* @param inputStream
* @param clazz
* @param
* @return
* @throws Exception
*/
public static List readCsv(InputStream inputStream, Class clazz) throws Exception {
if (inputStream == null){
return new ArrayList<>();
}
if (clazz == null){
throw new Exception("解析模板为空");
}
// 筛选出拥有注解的字段
LinkedHashMap stringFieldDataLinkedHashMap = initCsvFields(clazz);
if (stringFieldDataLinkedHashMap.size() < 1) {
return new ArrayList<>();
}
List items = readData(inputStream, stringFieldDataLinkedHashMap, clazz);
return items;
}
/**
* 读取数据
* @param inputStream
* @param stringFieldDataLinkedHashMap
* @param clazz
* @param
* @return
* @throws Exception
*/
private static List readData(InputStream inputStream, LinkedHashMap stringFieldDataLinkedHashMap, Class clazz)
throws Exception {
List items = new ArrayList<>();
try {
final InputStreamReader inputStreamReader = new InputStreamReader(inputStream, charset);
List titles = null;
List fieldDataList = null;
try (CsvReader csvReader = CsvReader.builder().skipEmptyRows(true).build(inputStreamReader)) {
for (final Iterator iterator = csvReader.iterator(); iterator.hasNext();){
final CsvRow csvRow = iterator.next();
//读取表头
if (csvRow.getOriginalLineNumber() == 1) {
titles = csvRow.getFields();
if (titles != null){
// 获取列对应赋值方法
fieldDataList = getFieldDataList(stringFieldDataLinkedHashMap, titles);
} else {
throw new Exception("文件标题为空");
}
continue;
}
// 读取数据
if (!CollectionUtils.isEmpty(fieldDataList)){
T instance = null;
//创建实例
try {
instance = clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
//创建实例失败,直接结束循环
throw new Exception("创建实例失败");
}
for (int i = 0; i < fieldDataList.size(); i++) {
//属性赋值
setInstanceField(instance,fieldDataList.get(i),csvRow.getField(i));
}
items.add(instance);
}
}
}
} catch (final IOException e) {
throw new UncheckedIOException(e);
}
return items;
}
/**
* 实例属性赋值
* @param instance
* @param fieldData
* @param value
* @throws IllegalAccessException
*/
private static void setInstanceField(Object instance, FieldData fieldData, String value) throws IllegalAccessException {
if (!StringUtils.isEmpty(value)){
fieldData.field.setAccessible(true);
ObjectMapper objectMapper = new ObjectMapper();
// 转化为对应类型
Object rightTypeValue = objectMapper.convertValue(value, fieldData.field.getType());
fieldData.field.set(instance,rightTypeValue);
}
}
/**
* 获取csv标题与转换类属性对应关系列表(顺序存取)
* @param stringFieldDataLinkedHashMap
* @param titles
* @return
*/
private static List getFieldDataList(LinkedHashMap stringFieldDataLinkedHashMap, List titles) {
List fieldDataArrayList = new ArrayList<>();
for (int i = 0; i < titles.size(); i++) {
if (stringFieldDataLinkedHashMap.get(titles.get(i)) != null){
// key 为列号,value为对应赋值方法
fieldDataArrayList.add(stringFieldDataLinkedHashMap.get(titles.get(i)));
}
}
return fieldDataArrayList;
}
/**
* 导出列表 CSV
* @param items 要导出的数据列表
* @param os 输出到的流
* @param res HttpServletResponse(可选),如果指定了就会添加文件下载的头部
* @param fileName 可选,文件名,用户下载的文件名,传入 res 有效
* @param
* @throws IOException
*/
public static void writeCsv(List items,
Class clazz,
OutputStream os,
HttpServletResponse res,
String fileName) throws IOException, IllegalAccessException {
if (res != null) {
setHttpHeader(res, fileName);
if (os == null){
os = res.getOutputStream();
}
}
if (os == null){
return;
}
if (items == null || items.size() < 1) {
os.flush();
return;
}
// 筛选出拥有注解的字段
LinkedHashMap stringFieldDataLinkedHashMap = initCsvFields(clazz);
if (stringFieldDataLinkedHashMap.size() < 1) {
os.flush();
return;
}
// 写入数据
writeData(items, stringFieldDataLinkedHashMap, os);
}
/**
* 设置下载用的 Http 响应头部
* @param res
* @param fileName
*/
private static void setHttpHeader(HttpServletResponse res, String fileName) {
fileName = StringUtils.isEmpty(fileName) ? (generateRandomFileName() + ".csv") : (fileName.contains(".csv") ? fileName : fileName + ".csv");
res.setHeader("content-type", "application/octet-stream; charset=" + charset);
res.setContentType("application/octet-stream");
res.setHeader("Content-Disposition", "attachment; filename=" + fileName);
}
/**
* 生成uudi随机文件名
* @return
*/
private static String generateRandomFileName() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
/**
* 初始化要输出的CSV字段
* @param clazz
* @param
* @return
*/
private static LinkedHashMap initCsvFields(Class clazz) {
Field[] fields = clazz.getDeclaredFields();
// 筛选出拥有注解的字段与字段属性作为map
LinkedHashMap stringFieldDataLinkedHashMap = new LinkedHashMap<>();
for(int i=0;i< fields.length;i++){
CsvField item = fields[i].getAnnotation(CsvField.class);
if (item != null && !item.ignore()) {
CsvConvertVisitable converter = null;
if (item.using() != null && item.using() != CsvConvertHandler.None.class) {
converter = ClassUtil.createInstance(item.using(), true);
}
if (StringUtils.isEmpty(item.value())){
stringFieldDataLinkedHashMap.put(fields[i].getName(), new FieldData(fields[i], converter));
} else {
stringFieldDataLinkedHashMap.put(item.value(), new FieldData(fields[i], converter));
}
}
}
return stringFieldDataLinkedHashMap;
}
/**
* 写入数据
* @param items
* @param stringFieldDataLinkedHashMap
* @param os
* @param
* @throws IOException
* @throws IllegalAccessException
* @throws JsonProcessingException
*/
private static void writeData(List items, LinkedHashMap stringFieldDataLinkedHashMap, OutputStream os)
throws IOException, IllegalAccessException, JsonProcessingException {
final OutputStreamWriter outputStreamWriter = new OutputStreamWriter(os, charset);
try (CsvWriter csv = CsvWriter.builder().build(outputStreamWriter)) {
String[] strings = new String[stringFieldDataLinkedHashMap.size()];
String[] textArray = stringFieldDataLinkedHashMap.keySet().toArray(strings);
// 写入标题
csv.writeRow(textArray);
// 写入内容
for (T item : items) {
textArray = itemToStringArray(item, stringFieldDataLinkedHashMap, strings);
if (textArray == null || textArray.length < 1){
continue;
}
csv.writeRow(textArray);
}
} finally {
os.flush();
}
}
/**
* 将对象属性转化为字符串数组
* @param item
* @param stringFieldDataLinkedHashMap
* @param values
* @return
* @throws IllegalAccessException
*/
private static String[] itemToStringArray(Object item, LinkedHashMap stringFieldDataLinkedHashMap, String[] values)
throws IllegalAccessException{
int i = 0;
for (Map.Entry stringFieldDataEntry : stringFieldDataLinkedHashMap.entrySet()) {
FieldData fieldData = stringFieldDataEntry.getValue();
fieldData.field.setAccessible(true);
Object val = fieldData.field.get(item);
if (fieldData.converter != null) {
values[i] = fieldData.converter.convert(val);
} else if (val == null) {
values[i] = "";
i += 1;
continue;
} else {
values[i] = val.toString();
}
if (values[i] == null || values[i].isEmpty()){
i += 1;
continue;
}
if (!values[i].isEmpty() && (values[i].startsWith("{") || values[i].startsWith("["))){
values[i] = "\"" + values[i].replace("\"", "\"\"") + "\"";
} else {
values[i] = values[i].replace("\\\"", "\"\"");
}
i += 1;
}
return values;
}
}
/** 导出excel */
@GetMapping("/export2")
@ResponseBody
Object getExcelFile2(HttpServletResponse res) throws Exception {
try {
List items = getList();
long startTime = System.currentTimeMillis();
FastCsvHelper.writeCsv(items, TestBean.class, res.getOutputStream(), res, "test");
System.out.println("查询耗时:"+(System.currentTimeMillis()-startTime)/1000+"s");
} catch (IOException e) {
return null;
}
return null;
}
/** 读取excel */
@PostMapping("/import")
@ResponseBody
Object importExcelFile(@RequestPart(name = "file",required = true) MultipartFile file) throws Exception {
try {
long startTime = System.currentTimeMillis();
FastCsvHelper.readCsv(file.getInputStream(), TestBean.class);
System.out.println("查询耗时:"+(System.currentTimeMillis()-startTime)/1000+"s");
} catch (IOException e) {
return null;
}
return null;
}
本机实测,5w行一百字段(每一字段约30字符)
读取:耗时6s-9s,文件大小200m
写入:耗时24s-26s,文件大小200m