一.问题,CSVWriter生成文件时使用writer.writeRecord();方法保存的文件末尾多一个空行,效果图如下:
目标结果:(去掉末尾空行)
二.关键代码如下(修改前代码):
/**
* 生成CSV文件
* @param filePath 文件保存路径,例如:D:/temp/test.csv
* @param headerBeans 实体对象集合
* @param detailBeans 实体对象集合
* @param trailerBeans 实体对象集合
* @param <T>
*/
public static <T> void createFile(String filePath, List<T> headerBeans, List<T> detailBeans, List<T> trailerBeans) {
CsvWriter writer = null;
try {
// 创建文件对象
File file = createFile(filePath);
// 生成文件
writer = new CsvWriter(filePath, ',', Charset.forName("GBK"));
// 获取内容
List<String[]> contents = new ArrayList<>();
List<String[]> headerContents = getStringArrayFromBean(headerBeans);
List<String[]> detailContents = getStringArrayFromBean(detailBeans);
List<String[]> trailerContents = getStringArrayFromBean(trailerBeans);
contents.addAll(headerContents);
contents.addAll(detailContents);
contents.addAll(trailerContents);
// 写入内容
for (String[] each : contents) {
writer.writeRecord(each);
}
} catch (Exception e) {
LOGGER.error("生成CSV文件失败", e);
} finally {
if (writer != null) {
writer.close();
}
}
}
输出文件就在第26行,writer.writeRecord(each);点进去跳到import com.csvreader.CsvWriter;这个包中,如下截图(序号为程序执行调用顺序):
可以看到执行到endRecord()方法时,总是会执行else中的内容,因为useCustomRecordDeliniter定义的为false...(就是这导致了末尾多一空行)
三.解决问题:
.class文件又不能改, 在这里是换了一种写法 ( 由原来的CSVWriter的writeRecord() 改为用PrintWriter的pw.write() )
/**
* 生成CSV文件
* @param filePath 文件保存路径,例如:D:/temp/test.csv
* @param headerBeans 实体对象集合
* @param detailBeans 实体对象集合
* @param trailerBeans 实体对象集合
* @param <T>
*/
public static <T> void createFile(String filePath, List<T> headerBeans, List<T> detailBeans, List<T> trailerBeans) {
CsvWriter writer = null;
try {
// 创建文件对象
File file = createFile(filePath);
// 生成文件
writer = new CsvWriter(filePath, ',', Charset.forName("GBK"));
// 获取内容
List<String[]> contents = new ArrayList<>();
List<String[]> headerContents = getStringArrayFromBean(headerBeans);
List<String[]> detailContents = getStringArrayFromBean(detailBeans);
List<String[]> trailerContents = getStringArrayFromBean(trailerBeans);
contents.addAll(headerContents);
contents.addAll(detailContents);
contents.addAll(trailerContents);
// 重组内容
String result = "";
for (String[] each : contents) {
for (String s : each){
writer.writeRecord(each);
result += s ;
result += ",";
}
result = result.substring(0,result.length()-1);
result += "\r\n";
}
result = result.substring(0,result.length()-2); writeFileContent(filePath,result);//写入
} catch (Exception e) {
LOGGER.error("生成CSV文件失败", e);
} finally {
if (writer != null) {
writer.close();
}
}
}
调用的writeFileContent()方法如下:
/**
* 向文件中写入内容
*
* @param filepath 文件路径与名称
* @param newstr 写入的内容
* @return
* @throws IOException
*/
public static boolean writeFileContent(String filepath, String newstr) throws IOException {
Boolean bool = false;
String temp = "";
FileInputStream fis = null;
InputStreamReader isr = null;
BufferedReader br = null;
FileOutputStream fos = null;
PrintWriter pw = null;
try {
/* File file = new File(filepath);*///文件路径(包括文件名称)
//将文件读入输入流
fis = new FileInputStream(filepath);
isr = new InputStreamReader(fis);
br = new BufferedReader(isr); // StringBuffer buffer = new StringBuffer();
// //文件原有内容
// for (int i = 0; (temp = br.readLine()) != null; i++) {
// buffer.append(temp);
// // 行与行之间的分隔符 相当于“\n”
// buffer = buffer.append(System.getProperty("line.separator"));
// }
// buffer.append(newstr); fos = new FileOutputStream(filepath);
pw = new PrintWriter(fos);
pw.write(newstr.toCharArray());
pw.flush();
bool = true;
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
throw e;
} finally {
//不要忘记关闭
if (pw != null) {
pw.close();
}
if (fos != null) {
fos.close();
}
if (br != null) {
br.close();
}
if (isr != null) {
isr.close();
}
if (fis != null) {
fis.close();
}
}
return bool;
}
贴上完整的新utils留个备份:
package com.sp.ppms.console.finance.CSV; import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.util.ReflectionUtils;
import com.csvreader.CsvReader;
import com.csvreader.CsvWriter; /**
* CSV工具类
*/
public class CSVUtil {
/**
* 日志对象
**/
private static final Logger LOGGER = Logger.getLogger(CSVUtil.class); /**
* 生成CSV文件
* @param filePath 文件保存路径,例如:D:/temp/test.csv
* @param headerBeans 实体对象集合
* @param detailBeans 实体对象集合
* @param trailerBeans 实体对象集合
* @param <T>
*/
public static <T> void createFile(String filePath, List<T> headerBeans, List<T> detailBeans, List<T> trailerBeans) {
CsvWriter writer = null;
try {
// 创建文件对象
File file = createFile(filePath);
// 生成文件
writer = new CsvWriter(filePath, ',', Charset.forName("GBK"));
// 获取内容
List<String[]> contents = new ArrayList<>();
List<String[]> headerContents = getStringArrayFromBean(headerBeans);
List<String[]> detailContents = getStringArrayFromBean(detailBeans);
List<String[]> trailerContents = getStringArrayFromBean(trailerBeans);
contents.addAll(headerContents);
contents.addAll(detailContents);
contents.addAll(trailerContents);
// 重组内容
String result = "";
for (String[] each : contents) {
for (String s : each){
result += s ;
result += ",";
}
result = result.substring(0,result.length()-1);
result += "\r\n";
}
result = result.substring(0,result.length()-2); writeFileContent(filePath,result);//写入
} catch (Exception e) {
LOGGER.error("生成CSV文件失败", e);
} finally {
if (writer != null) {
writer.close();
}
}
} /**
* 向文件中写入内容
*
* @param filepath 文件路径与名称
* @param newstr 写入的内容
* @return
* @throws IOException
*/
public static boolean writeFileContent(String filepath, String newstr) throws IOException {
Boolean bool = false;
String temp = "";
FileInputStream fis = null;
InputStreamReader isr = null;
BufferedReader br = null;
FileOutputStream fos = null;
PrintWriter pw = null;
try {
/* File file = new File(filepath);*///文件路径(包括文件名称)
//将文件读入输入流
fis = new FileInputStream(filepath);
isr = new InputStreamReader(fis);
br = new BufferedReader(isr); // StringBuffer buffer = new StringBuffer();
// //文件原有内容
// for (int i = 0; (temp = br.readLine()) != null; i++) {
// buffer.append(temp);
// // 行与行之间的分隔符 相当于“\n”
// buffer = buffer.append(System.getProperty("line.separator"));
// }
// buffer.append(newstr); fos = new FileOutputStream(filepath);
pw = new PrintWriter(fos);
pw.write(newstr.toCharArray());
pw.flush();
bool = true;
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
throw e;
} finally {
//不要忘记关闭
if (pw != null) {
pw.close();
}
if (fos != null) {
fos.close();
}
if (br != null) {
br.close();
}
if (isr != null) {
isr.close();
}
if (fis != null) {
fis.close();
}
}
return bool;
} /**
* 读取CSV文件内容
*
* @param filePath 文件存放的路径,如:D:/csv/xxx.csv
* @param bean 类类型
* @return List<T>
*/
public static <T> List<T> readFile(String filePath, Class<T> bean) {
List<String[]> dataList = new ArrayList<String[]>();
CsvReader reader = null;
try {
// 创建CSV读对象 例如:CsvReader(文件路径,分隔符,编码格式);
reader = new CsvReader(filePath, ',', Charset.forName("GBK"));
if (reader != null) {
// 跳过表头,如果需要表头的话,这句可以忽略
//reader.readHeaders();
// 逐行读入除表头的数据
while (reader.readRecord()) {
dataList.add(reader.getValues());
}
if (!dataList.isEmpty()) {
// 数组转对象
return getBeanFromStringArray(dataList, bean);
}
}
} catch (Exception e) {
LOGGER.error("读取CSV文件失败", e);
} finally {
if (reader != null) {
reader.close();
}
}
return Collections.emptyList();
} /**
* 删除该目录下所有文件
*
* @param filePath 文件目录路径,如:d:/test
*/
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()) {
LOGGER.info("删除" + f.getName() + "文件成功");
}
}
return true;
}
}
return false;
} /**
* 删除单个文件
*
* @param filePath 文件目录路径,如:d:/test
* @param fileName 文件名称,如:110.csv
*/
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;
} /**
* 泛型实体转换为数组
*
* @param beans
* @return List<String[]>
*/
private static <T> List<String[]> getStringArrayFromBean(List<T> beans) {
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) {
CSVField anno = field.getAnnotation(CSVField.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(CSVField.class).name();
}
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) {
LOGGER.info("实体对象转数组失败", e);
}
return result;
} /**
* 数组转为对象集合
*
* @param dataList
* @param bean
* @return List<T>
*/
private 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 (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) {
LOGGER.error("创建实体失败", e);
}
return list;
} /**
* 数组标题与值的对应关系
*
* @param dataList
* @return
*/
private 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;
} /**
* 注解名称与字段属性的对应关系
*
* @param clazz 实体对象类类型
* @param <T> 泛型类型
* @return Map<String , Field>
*/
private static <T> Map<String, Field> getFields(Class<T> clazz) {
Map<String, Field> annoMap = new HashMap<>();
Field[] fileds = clazz.getDeclaredFields();
for (Field filed : fileds) {
CSVField anno = filed.getAnnotation(CSVField.class);
if (anno != null) {
// 获取name属性值
if (StringUtils.isNotBlank(anno.name())) {
annoMap.put(anno.name(), filed);
}
}
}
return annoMap;
} /**
* 创建文件对象
*
* @param filePath 文件路径,例如:temp/test.csv
* @return File
*/
private 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()) {
LOGGER.info("创建文件对象成功");
}
} catch (IOException e) {
LOGGER.error("创建文件对象失败", e);
}
return file; }
}
废弃utils:
package com.sp.ppms.console.finance.CSV; import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.util.ReflectionUtils;
import com.csvreader.CsvReader;
import com.csvreader.CsvWriter; /**
* CSV工具类
*/
public class CSVUtil {
/**
* 日志对象
**/
private static final Logger LOGGER = Logger.getLogger(CSVUtil.class); /**
* 生成CSV文件
* @param filePath 文件保存路径,例如:D:/temp/test.csv
* @param headerBeans 实体对象集合
* @param detailBeans 实体对象集合
* @param trailerBeans 实体对象集合
* @param <T>
*/
public static <T> void createFile(String filePath, List<T> headerBeans, List<T> detailBeans, List<T> trailerBeans) {
CsvWriter writer = null;
try {
// 创建文件对象
File file = createFile(filePath);
// 生成文件
writer = new CsvWriter(filePath, ',', Charset.forName("GBK"));
// 获取内容
List<String[]> contents = new ArrayList<>();
List<String[]> headerContents = getStringArrayFromBean(headerBeans);
List<String[]> detailContents = getStringArrayFromBean(detailBeans);
List<String[]> trailerContents = getStringArrayFromBean(trailerBeans);
contents.addAll(headerContents);
contents.addAll(detailContents);
contents.addAll(trailerContents);
// 写入内容
for (String[] each : contents) {
writer.writeRecord(each);
}
} catch (Exception e) {
LOGGER.error("生成CSV文件失败", e);
} finally {
if (writer != null) {
writer.close();
}
}
} /**
* 读取CSV文件内容
*
* @param filePath 文件存放的路径,如:D:/csv/xxx.csv
* @param bean 类类型
* @return List<T>
*/
public static <T> List<T> readFile(String filePath, Class<T> bean) {
List<String[]> dataList = new ArrayList<String[]>();
CsvReader reader = null;
try {
// 创建CSV读对象 例如:CsvReader(文件路径,分隔符,编码格式);
reader = new CsvReader(filePath, ',', Charset.forName("GBK"));
if (reader != null) {
// 跳过表头,如果需要表头的话,这句可以忽略
//reader.readHeaders();
// 逐行读入除表头的数据
while (reader.readRecord()) {
dataList.add(reader.getValues());
}
if (!dataList.isEmpty()) {
// 数组转对象
return getBeanFromStringArray(dataList, bean);
}
}
} catch (Exception e) {
LOGGER.error("读取CSV文件失败", e);
} finally {
if (reader != null) {
reader.close();
}
}
return Collections.emptyList();
} /**
* 删除该目录下所有文件
*
* @param filePath 文件目录路径,如:d:/test
*/
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()) {
LOGGER.info("删除" + f.getName() + "文件成功");
}
}
return true;
}
}
return false;
} /**
* 删除单个文件
*
* @param filePath 文件目录路径,如:d:/test
* @param fileName 文件名称,如:110.csv
*/
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;
} /**
* 泛型实体转换为数组
*
* @param beans
* @return List<String[]>
*/
private static <T> List<String[]> getStringArrayFromBean(List<T> beans) {
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) {
CSVField anno = field.getAnnotation(CSVField.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(CSVField.class).name();
}
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) {
LOGGER.info("实体对象转数组失败", e);
}
return result;
} /**
* 数组转为对象集合
*
* @param dataList
* @param bean
* @return List<T>
*/
private 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 (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) {
LOGGER.error("创建实体失败", e);
}
return list;
} /**
* 数组标题与值的对应关系
*
* @param dataList
* @return
*/
private 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;
} /**
* 注解名称与字段属性的对应关系
*
* @param clazz 实体对象类类型
* @param <T> 泛型类型
* @return Map<String , Field>
*/
private static <T> Map<String, Field> getFields(Class<T> clazz) {
Map<String, Field> annoMap = new HashMap<>();
Field[] fileds = clazz.getDeclaredFields();
for (Field filed : fileds) {
CSVField anno = filed.getAnnotation(CSVField.class);
if (anno != null) {
// 获取name属性值
if (StringUtils.isNotBlank(anno.name())) {
annoMap.put(anno.name(), filed);
}
}
}
return annoMap;
} /**
* 创建文件对象
*
* @param filePath 文件路径,例如:temp/test.csv
* @return File
*/
private 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()) {
LOGGER.info("创建文件对象成功");
}
} catch (IOException e) {
LOGGER.error("创建文件对象失败", e);
}
return file; }
}
另:
严格讲\n是换行,\r是回车。但不同的系统、不同的应用可能做不同的处理。在文本文件中,换行是两者的组合"\r\n"。
感谢:https://bbs.csdn.net/topics/40116687.
https://jingyan.baidu.com/article/0aa223754f1fc288cd0d6479.html
拓展:https://blog.csdn.net/pfm685757/article/details/47806469#commentBox