场景: 系统需要导出大数据文件。目标导出梳理在500w。
通常的excel,旧版大约是在63w-65w。 新版在100w左右。500w远超excel导出要求,所以决定采用csv文件进行处理。
原先导出csv文件通常是利用io流进行编写,本次学习使用opencsv来进行项目使用。
原先是因为有历史项目是参考opencsv实现,对应版本在4.0,属于较为早期项目,本次前期也是使用4.0进行了简单demo实现,但是发现有部分功能没办法很好实现。所以在网上也查找了很多资料,对应csdn、gitee、github都进行查阅。但是找到的版本都极为有限,没办法很好配合自己的需求。
后续决定投入时间进行知识储备学习,对应查阅到opencsv官网,对应版本已经升级到5.6,升级时间为2022.2.20,距离目前时间也很接近,而且还持续更新,下一个6.0的版本也值得期待。
学习之路:
1、查阅博客: 较好博客推荐
csv文件处理-opencsv 对应网址 https://www.jianshu.com/p/6414185b2f01
opencsv官网 对应网址 http://opencsv.sourceforge.net/#skipping_filtering_and_verifying
opencsv接口 对应网址 http://opencsv.sourceforge.net/apidocs/overview-summary.html
opencsv源码地址 对应地址 https://sourceforge.net/p/opencsv/source/ci/master/tree/
maven官网 https://mvnrepository.com/
对应maven下载jar地址 https://mvnrepository.com/artifact/com.opencsv/opencsv/5.6
对应接口文档 http://opencsv.sourceforge.net/xref-test/index.html
2、翻阅对应gitee项目跟github项目。 搜索关键词 opencsv / csv 自行搜索
3、搞项目 - 遇到问题解决问题
本次参考4.0模板 后续追加追加填充对应代码进行完善
util - CsvUtils.java
package com.fuhua.csvdemo.util;
import com.opencsv.CSVWriter;
import com.opencsv.bean.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.input.BOMInputStream;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.ReflectionUtils;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
/**
* opencsv 5.6 最新2022.2.20版本进行修正
* <br/>
* @author zhouyiwei
* @date 2022/3/24 16:26
*/
@Slf4j
public class CsvUtils {
private static final String CSV = "csv";
/**
* 实体类写入CSV文件,全部一起写入。对应生成csv文件默认是UTF-8格式。
* 推荐使用的bean数量 在1000行或者1000以内。
*
* withQuotechar(CSVWriter.DEFAULT_QUOTE_CHARACTER) ==> 默认bean对应数据不存在以""代替
* withSeparator(CSVWriter.DEFAULT_SEPARATOR) ==> 对应csv文件以,拼接
* withEscapechar('\\') ==> 转义符设置
*
* Writer 为普通写入
* CSVWriter 是opencsv特有写入
*
* <br/>
* @param filePath 待生成的文件路径
* @param tClass bean对应实体类
* @param writeList 具体的beanList 数据
* @return [filePath, tClass, writeList]
* @author zhouyiwei
* @date 2022/3/26 10:05
*/
public static <T> void writeCsv(String filePath, Class<T> tClass, List<T> writeList) {
Writer writer = null;
try{
writer = Files.newBufferedWriter(Paths.get(filePath));
StatefulBeanToCsv<T> beanToCsv = new StatefulBeanToCsvBuilder<T>(writer)
.withQuotechar(CSVWriter.DEFAULT_QUOTE_CHARACTER)
.withSeparator(CSVWriter.DEFAULT_SEPARATOR)
.withEscapechar('\\')
.build();
beanToCsv.write(writeList);
} catch (Exception e) {
log.error("写入csv文件失败!"+e.getMessage());
}finally {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* List<T> 写入csv文件
* <br/>
* @param filePath 待生成的文件路径
* @param writeList bean对应实体类
* @param headtitle 表头
* @param codingFormat 对应格式 UTF-8 / gbk
* @param additional 追加 true是追加。 false 是新建
* @param time 数据循环遍数(该参数是测试使用)
* @return [filePath, tClass, writeList, geshi]
* @author zhouyiwei
* @date 2022/3/24 19:48
*/
public static <T> void writeCsvAdd(String filePath, List<T> writeList , List<String[]> headtitle ,String codingFormat ,Boolean additional ,Integer time) {
CharsetEncoder encoder = Charset.forName(codingFormat).newEncoder();
CSVWriter csvWriter = null;
Writer writer = null;
//获取开始时间
long startTime = System.currentTimeMillis();
log.info("写入开始,时间为"+startTime);
try{
if(additional !=null && additional){
writer = Files.newBufferedWriter(Paths.get(filePath),encoder.charset());
}else{
writer = new FileWriter(filePath,true);
}
// 普通writer转csvWriter
csvWriter = new CSVWriter(writer);
//写入表头。
if(! headtitle.isEmpty()){
for (String[] title:headtitle){
//默认都是加引号的 boolean applyQuotesToAll 设置成 true ; 如果不加引号设置成false
csvWriter.writeNext(title,false);
}
}
StatefulBeanToCsv<T> beanToCsv = new StatefulBeanToCsvBuilder<T>(csvWriter)
// withQuotechar(CSVWriter.DEFAULT_QUOTE_CHARACTER) 配置应用符号, 用单引号括起来
.withQuotechar(CSVWriter.NO_QUOTE_CHARACTER)
.withSeparator(CSVWriter.DEFAULT_SEPARATOR)
// withApplyQuotesToAll(false) 设置最后文件是携带引号的
.withApplyQuotesToAll(false)
.withEscapechar('\\')
.build();
for(int i = 0; i<time ;i ++){
beanToCsv.write(writeList);
}
} catch (Exception e) {
log.error("写入csv文件失败!"+e.getMessage());
}finally {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//获取结束时间
long endTime = System.currentTimeMillis();
log.info("写入结束,时间为"+endTime);
log.info("开始到结束,时间为"+startTime + "--到--" + endTime);
//输出程序运行时间
log.info("写入程序运行时间:" + (endTime - startTime) + "ms;" + (endTime - startTime) /1000 + "s;");
}
/**
* 读取csv转List<T>
* */
public static <T> List<T> readCsv(File file, Class<T> clazz,String codingFormat) throws IOException {
CsvBaseUtils.checkFile(file,CSV);
InputStreamReader in = null;
CsvToBean<T> csvToBean = null;
try {
in = new InputStreamReader(new BOMInputStream(new FileInputStream(file)), codingFormat);
HeaderColumnNameMappingStrategy<T> strategy = new HeaderColumnNameMappingStrategy<>();
strategy.setType(clazz);
csvToBean = new CsvToBeanBuilder<T>(in)
.withSeparator(',')
.withMappingStrategy(strategy)
.build();
} catch (Exception e) {
log.error("读取csv文件失败!" + e.getMessage());
}finally {
in.close();
}
return csvToBean.parse();
}
/**
* 读取csv转List<T> 通过迭代器处理
* */
public static <T> void readCsvIterator(File file, Class<T> clazz,String codingFormat) throws IOException {
//获取开始时间
long startTime = System.currentTimeMillis();
log.info("读取开始,时间为"+startTime);
CsvBaseUtils.checkFile(file,CSV);
InputStreamReader in = null;
in = new InputStreamReader(new BOMInputStream(new FileInputStream(file)), codingFormat);
HeaderColumnNameMappingStrategy<T> strategy = new HeaderColumnNameMappingStrategy<>();
strategy.setType(clazz);
CsvToBean<T> csvToBean = null;
try {
csvToBean = new CsvToBeanBuilder<T>(in)
.withSeparator(',')
.withQuoteChar('\"')
.withMappingStrategy(strategy)
.withOrderedResults(false)
.build();
int size = 0;
Iterator iterator = csvToBean.iterator();
for (Iterator it = iterator; it.hasNext(); ) {
Object nextLine = it.next();
// nextLine[] is an array of values from the line
// log.info("展示数据为" + nextLine.toString());
size++;
}
log.info("总数量" + size);
} catch (Exception e) {
log.error("读取csv文件失败!" + e.getMessage());
}finally {
in.close();
}
//获取结束时间
long endTime = System.currentTimeMillis();
log.info("读取结束,时间为"+endTime);
log.info("读取到结束,时间为"+startTime + "--到--" + endTime);
//输出程序运行时间
log.info("读取程序运行时间:" + (endTime - startTime) + "ms;" + (endTime - startTime) /1000 + "s;");
}
}
后续也参考网上 进行了基本版本的添加
util - CsvBaseUtils
package com.fuhua.csvdemo.util;
import com.opencsv.bean.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.ReflectionUtils;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
/**
* opencsv 文件操作基本类
* <br/>
* @author zhouyiwei
* @date 2022/3/24 16:26
*/
@Slf4j
public class CsvBaseUtils {
/**
* 泛型实体转换为数组
* <br/>
* @param beans 泛型实体
* @param needTitle 返回数组是否是否需要标题
* @return [beans]
* @author zhouyiwei
* @date 2022/3/26 10:45
*/
public static <T> List<String[]> getStringArrayFromBean(List<T> beans , Boolean needTitle) {
List<String[]> result = new ArrayList<String[]>();
Class<? extends Object> cls = beans.get(0).getClass();
Field[] declaredFields = cls.getDeclaredFields();
List<Field> annoFields = new ArrayList<Field>();
// 筛选出标有注解的字段
for (Field field : declaredFields) {
CsvBindByName anno = field.getAnnotation(CsvBindByName.class);
if (anno != null) {
annoFields.add(field);
}
}
// 获取注解的值,即内容标题
String[] title = new String[annoFields.size()];
if(needTitle){
for (int i = 0; i < annoFields.size(); i++) {
title[i] = annoFields.get(i).getAnnotation(CsvBindByName.class).toString();
}
result.add(title);
}
try {
// 获取内容
for (T t : beans) {
String[] item = new String[annoFields.size()];
int index = 0;
for (Field field : annoFields) {
String fieldName = field.getName();
String methodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
Method method = ReflectionUtils.findMethod(t.getClass(), methodName);
if (method != null) {
Object value = ReflectionUtils.invokeMethod(method, t);
if (value == null) {
item[index] = "";
} else {
item[index] = value.toString();
}
}
index++;
}
result.add(item);
}
} catch (Exception e) {
log.info("实体对象转数组失败", e);
}
return result;
}
/**
* 注解转换为返回数据的首行数据.
* <br/>
* @param clazz 泛型实体
* @return [beans]
* @author zhouyiwei
* @date 2022/3/26 10:45
*/
public static <T> String[] getStringArrayFromtitle(Class<T> clazz ) {
Class<? extends Object> cls = clazz;
Field[] declaredFields = cls.getDeclaredFields();
List<Field> annoFields = new ArrayList<Field>();
// 筛选出标有注解的字段
for (Field field : declaredFields) {
CsvBindByName anno = field.getAnnotation(CsvBindByName.class);
if (anno != null) {
annoFields.add(field);
}
}
// 获取注解的值,即内容标题
String[] title = new String[annoFields.size()];
for (int i = 0; i < annoFields.size(); i++) {
title[i] = annoFields.get(i).getAnnotation(CsvBindByName.class).column();
}
return title;
}
/**
* 数组转为对象集合
* <br/>
* @param dataList
* @param bean
* @return [dataList, bean]
* @author zhouyiwei
* @date 2022/3/26 11:05
*/
public static <T> List<T> getBeanFromStringArray(List<String[]> dataList, Class<T> bean) {
List<T> list = new ArrayList<>();
List<Map<String, String>> titles = getTitles(dataList);
Map<String, Field> fields = getFields(bean);
try {
for (Map<String, String> map : titles) {
T t = bean.newInstance();
for (Map.Entry<String, String> entry : map.entrySet()) {
if (fields.containsKey(entry.getKey())) {
Field field = fields.get(entry.getKey());
Class<?> valType = field.getType();
String fieldName = field.getName();
String methodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
Method method = ReflectionUtils.findMethod(bean, methodName, valType);
if (method != null) {
ReflectionUtils.invokeMethod(method, t, entry.getValue());
}
}
}
list.add(t);
}
}catch (Exception e) {
log.error("创建实体失败", e);
}
return list;
}
/**
* 数组标题与值的对应关系
* <br/>
* @param dataList
* @return [dataList]
* @author zhouyiwei
* @date 2022/3/26 11:06
*/
public static <T> List<Map<String, String>> getTitles(List<String[]> dataList) {
List<Map<String, String>> list = new ArrayList<>();
String[] titles = dataList.get(0);
dataList.remove(0);
for (String[] values : dataList) {
Map<String, String> titleMap = new HashMap<>();
for (int i = 0; i < values.length; i++) {
titleMap.put(titles[i], values[i]);
}
list.add(titleMap);
}
return list;
}
/**
* 注解名称与字段属性的对应关系
* <br/>
* @param clazz 实体对象类类型
* @param <T> 泛型类型
* @return [clazz] Map<String , Field>
* @author zhouyiwei
* @date 2022/3/26 11:15
*/
public static <T> Map<String, Field> getFields(Class<T> clazz) {
Map<String, Field> annoMap = new HashMap<>();
Field[] fileds = clazz.getDeclaredFields();
for (Field filed : fileds) {
CsvBindByName anno = filed.getAnnotation(CsvBindByName.class);
if (anno != null) {
// 获取name属性值
if (StringUtils.isNotBlank(anno.column())) {
annoMap.put(anno.column(), filed);
}
}
}
return annoMap;
}
/**
* 创建文件对象
* <br/>
* @param filePath 文件路径,例如:temp/test.csv
* @return [filePath]
* @author zhouyiwei
* @date 2022/3/26 11:17
*/
public static File createFile(String filePath) {
File file = null;
try {
// 创建文件目录
file = new File(filePath.substring(0, filePath.lastIndexOf('/')));
if (!file.exists()) {
file.mkdirs();
}
// 创建文件对象
file = new File(filePath);
if (!file.exists() && file.createNewFile()) {
log.info("创建文件对象成功");
}
}catch (IOException e) {
log.error("创建文件对象失败", e);
}
return file;
}
/**
* 删除该目录下所有文件
* <br/>
* @param filePath 文件目录路径,如:d:/test
* @return [filePath]
* @author zhouyiwei
* @date 2022/3/26 10:33
*/
public static boolean deleteFiles(String filePath){
File file = new File(filePath);
if (file.exists()) {
File[] files = file.listFiles();
if (files != null && files.length > 0) {
for (File f : files) {
if (f.isFile() && f.delete()) {
log.info("删除" + f.getName() + "文件成功");
}
}
return true;
}
}
return false;
}
/**
* 删除单个文件
* <br/>
* @param filePath 文件目录路径,如:d:/test
* @param fileName 文件名称,如:110.csv
* @return [filePath, fileName]
* @author zhouyiwei
* @date 2022/3/26 10:35
*/
public static boolean deleteFile(String filePath, String fileName) {
File file = new File(filePath);
if (file.exists()) {
File[] files = file.listFiles();
if (files != null && files.length > 0) {
for (File f : files) {
if (f.isFile() && f.getName().equals(fileName)) {
return f.delete();
}
}
}
}
return false;
}
/**
* 校验文件是否存在
* <br/>
* @param file
* @param fileSuffix 文件后缀
* @return [file, fileSuffix]
* @author zhouyiwei
* @date 2022/3/26 11:21
*/
public static void checkFile(File file , String fileSuffix) throws IOException {
// 判断文件是否存在
if (null == file) {
throw new FileNotFoundException("文件不存在");
}
// 获得文件名
String fileName = file.getName();
// 判断文件是否是相同文件后缀文件 。比如是csv
if (!fileName.endsWith(fileSuffix.toLowerCase()) && !fileName.endsWith(fileSuffix.toUpperCase())) {
throw new IOException(fileName + "不是"+ fileSuffix +"文件");
}
}
}
其次是实体类的构建
entity - User.java
package com.fuhua.csvdemo.entity;
import com.opencsv.bean.CsvBindByName;
import com.opencsv.bean.CsvBindByPosition;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* CSV文件测试实体类 - 超过30个字段
* 原先 可以有参数是 Integer,或者其他的
* 但是如果没设置值就是null, 本次对接需要对应参数 如果没有参数就是"" ,所以设置对应参数 统一设置为String,默认值为""
* <br/>
* @author zhouyiwei
* @date 2022/3/26 9:47
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@CsvBindByName(column = "id")
@CsvBindByPosition(position=0)
private String id ;
@CsvBindByName(column = "name")
@CsvBindByPosition(position=1)
private String name;
/**
* 原先 对应参数是 Integer,但是如果没设置值就是null, 本次对接需要对应参数 如果没有参数就是"" ,所以设置对应参数 统一设置为String,默认值为""
* */
@CsvBindByName(column = "age")
@CsvBindByPosition(position=2)
private String age ;
@CsvBindByName(column = "department")
@CsvBindByPosition(position=3)
private String department;
@CsvBindByName(column = "qq")
@CsvBindByPosition(position=4)
private String qq;
@CsvBindByName(column = "weixin")
@CsvBindByPosition(position=5)
private String weixin;
@CsvBindByName(column = "dingding")
@CsvBindByPosition(position=6)
private String dingding;
@CsvBindByName(column = "email")
@CsvBindByPosition(position=7)
private String email;
@CsvBindByName(column = "qq1")
@CsvBindByPosition(position=8)
private String qq1;
@CsvBindByName(column = "weixin1")
@CsvBindByPosition(position=9)
private String weixin1;
@CsvBindByName(column = "dingding1")
@CsvBindByPosition(position=10)
private String dingding1 ;
@CsvBindByName(column = "email1")
@CsvBindByPosition(position=11)
private String email1;
@CsvBindByName(column = "qq2")
@CsvBindByPosition(position=12)
private String qq2;
@CsvBindByName(column = "weixin2")
@CsvBindByPosition(position=13)
private String weixin2;
@CsvBindByName(column = "dingding2")
@CsvBindByPosition(position=14)
private String dingding2 ;
@CsvBindByName(column = "email2")
@CsvBindByPosition(position=15)
private String email2;
@CsvBindByName(column = "qq3")
@CsvBindByPosition(position=16)
private String qq3;
@CsvBindByName(column = "weixin3")
@CsvBindByPosition(position=17)
private String weixin3;
@CsvBindByName(column = "dingding3")
@CsvBindByPosition(position=18)
private String dingding3;
@CsvBindByName(column = "email3")
@CsvBindByPosition(position=19)
private String email3;
@CsvBindByName(column = "qq4")
@CsvBindByPosition(position=20)
private String qq4;
@CsvBindByName(column = "weixin4")
@CsvBindByPosition(position=21)
private String weixin4;
@CsvBindByName(column = "dingding4")
@CsvBindByPosition(position=22)
private String dingding4 ;
@CsvBindByName(column = "email4")
@CsvBindByPosition(position=23)
private String email4;
@CsvBindByName(column = "qq5")
@CsvBindByPosition(position=24)
private String qq5;
@CsvBindByName(column = "weixin5")
@CsvBindByPosition(position=25)
private String weixin5;
@CsvBindByName(column = "dingding5")
@CsvBindByPosition(position=26)
private String dingding5 ;
@CsvBindByName(column = "email5")
@CsvBindByPosition(position=27)
private String email5;
@CsvBindByName(column = "qq6")
@CsvBindByPosition(position=28)
private String qq6;
@CsvBindByName(column = "weixin6")
@CsvBindByPosition(position=29)
private String weixin6;
@CsvBindByName(column = "dingding6")
@CsvBindByPosition(position=30)
private String dingding6 ;
@CsvBindByName(column = "email6")
@CsvBindByPosition(position=31)
private String email6;
}
进行test
test - CSVTest.java
package com.fuhua.csvdemo.test;
import com.fuhua.csvdemo.entity.User;
import com.fuhua.csvdemo.util.CsvBaseUtils;
import com.fuhua.csvdemo.util.CsvUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* 进行测试
* <br/>
* @author zhouyiwei
* @date 2022/3/26 11:24
*/
@Slf4j
public class CSVTest {
private CsvBaseUtils csvBaseUtils;
public static void main(String[] args) throws IOException {
// 写入csv
String path = "D:\\success80.csv";
List<User> csvData = new ArrayList<>();
User csv1= new User("哈皮","453454","10","100",
"哈皮","453454","\"","100","哈皮","453454","","100",
"哈皮","453454","","100","哈皮","453454","","100",
"哈皮","453454","","100","哈皮","453454","","100",
"哈皮","453454","","100");
User csv2= new User("237869","傻逼","10","200",
"哈皮","453454","","100","哈皮","453454","","100",
"哈皮","453454","","100","哈皮","453454","","100",
"哈皮","453454","","100","哈皮","453454","","100",
"哈皮","453454","","100");
csvData.add(csv1);
csvData.add(csv2);
// String codingFormat = "gbk";
String codingFormat = "UTF-8";
File file = new File("E:\\success100.csv");
// CsvUtils.readCsvIterator(file, User.class,codingFormat);
List<String[]> headtitle = new ArrayList<>();
String[] title = CsvBaseUtils.getStringArrayFromtitle(User.class);
headtitle.add(title);
log.info("展示" + StringUtils.join(title, ","));
CsvUtils.writeCsvAdd(path,csvData,headtitle,codingFormat,true ,3);
}
}
其实test 比较凌乱,也是对应记录自我调试的过程。
4、测试数据以及结果。
写入效率。写入通过工作台打印,对应使用多线程,打印日志类型是debug
10条记录, 大概时间1350毫秒,1s;
100条数据, 大概时间1497 毫秒,1s;
1000条数据,大概时间3622毫秒,3s;
10000条数据,大概时间11833毫秒,11s;
100000,10w条数据, 耗时46435毫秒,46s,在数据相对简单情况下,大小为21095kb;
100w条数据,耗时 429s, 在数据相对简单情况下,大小为210938kb,是 206MB;
600w条数据,耗时 2572194ms。2572s。对应大小 为100w的 近6倍,即1236mb左右,1.5G
读取效率,相对写入数据速度要快。不过读取采用单线程。
600w条数据读取成bean,1352s,大约23分钟。
5、遇到困难,以及如何解决?
A、对应生成文件格式,默认是 UTF-8,但是之前的老项目实现对应格式是GBK?
关键代码是在文件生成时候,赋值对应编码格式类型。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-unpUdys6-1648295412400)(C:\Users\qijian\AppData\Roaming\Typora\typora-user-images\image-20220326193755939.png)]](https://i-blog.csdnimg.cn/blog_migrate/574abe77796101a25413475afc9311d0.png)
进行处理,从而代码为
// 处理对应 方法外传入的追加格式。 格式目前仅区分 UTF-8/gbk[需要小写]
CharsetEncoder encoder = Charset.forName(codingFormat).newEncoder();
//后续生成文件进行赋值
writer = Files.newBufferedWriter(Paths.get(filePath),encoder.charset());
B、原生的写法 参照 对应方法writeCsv 即可实现。也就是有一个list bean 整个进行转换生成csv文件,并无表头设置。 后续优化方法 为 writeCsvAdd。 可以人工进行是否表头写入,对应表头信息根据对应 实体类中主键字段进行自动生成,以及通过csvWriter 进行追加,使用 csvWriter.writeNest实现,其中applyQuotesToAll为false是特意不加引号,因为业务需求。
C、如何进行中间信息的追加?
答: 在创建好文件之后,可以一直处理对应具体业务,不要关闭写的过程即可。demo中使用了for循环来代替中间处理业务逻辑过程。暂时没有很好的所谓本次方法调用结束,再对着已经存在文件进行后续编写。可能是对应方法找的不对吧。看后续需求。
D、目前仍有难题是,对应部分bean如果没有值,跟业务约定是传值"",目前仅实现生成"""",解析是可以解析成null,但是这个数据格式未找到很好方法处理。 目前已经将对应数据赋值处理成 “/”"。
20220327。该问题解决,对应字段赋值""。 同时 csvWriter = new CSVWriter(writer,CSVWriter.DEFAULT_SEPARATOR,CSVWriter.NO_QUOTE_CHARACTER,CSVWriter.NO_QUOTE_CHARACTER,CSVWriter.DEFAULT_LINE_END); 已经同步更新到gitee
以上代码跟官网5.6对应的文章整合成了中英 pdf,也会在gitee中统一放置。中文是根据谷歌翻译直接生成,所以部分细节翻译存在不准确性。以及5.x-4.x,4.x-3.x 变化中因为英文太多,所以页面翻译死机,直接用有道翻译复制直译,存在很大出入。需要自己辨别。
2264

被折叠的 条评论
为什么被折叠?



