使用 Apache 的 commons-csv 实现 CSV 文件导出
CSV全称是:Comma Separated Values (逗号分隔值)或者 Character Separated Values(字符分隔值)。其文件以纯文本形式存储表格数据(数字和文本)。CSV文件由任意数目的记录组成,记录间以某种换行符分隔;每条记录由字段组成,字段间的分隔符是其它字符或字符串,最常见的是逗号或制表符。每一行记录位于一个单独的行上,用回车换行符CRLF(也就是\r\n)分割
1. CSV 简介
CSV全称是:Comma Separated Values (逗号分隔值)或者 Character Separated Values(字符分隔值)。其文件以纯文本形式存储表格数据(数字和文本)。CSV文件由任意数目的记录组成,记录间以某种换行符分隔;每条记录由字段组成,字段间的分隔符是其它字符或字符串,最常见的是逗号或制表符。每一行记录位于一个单独的行上,用回车换行符CRLF(也就是\r\n)分割。
对于excel来说默认使用 ,进行分割数据。
每一行记录最后一个字段后不能跟逗号
每一行一条记录
列为空需要指定 “”
用回车换行符CRLF(\r\n)分割每条记录
纯文本,使用某个字符集,比如ASCII、Unicode、EBCDIC或GB2312
2. 实现 CSV 文件导出
采用 Apache 开源的 commons-csv 包,详细内容参考 官网
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.7</version>
</dependency>
2.1.2 API
print(Object value); // 写入一个单元格数据
printRecord(Iterable<?> values); // 写入一行数据 printRecords(Iterable<?> values);// 写入多行数据
2.2 封装一个导出方法
/**
- 导出 csv 文件
- [@param] out 输出流
- [@param] iter 数据 我这里传 List<List> 类型
- [@param] charset 字符集编码
- [@param] header 表头
*/
public void exportCSVFile(OutputStream out, Iterable<?> iter, String charset, String... header) {
try {
// 写入bom, 防止中文乱码
byte[] bytes = {(byte) 0xEF, (byte) 0xBB, (byte) 0xBF};
out.write(bytes);
OutputStreamWriter osw = new OutputStreamWriter(out, charset);
CSVFormat csvFormat = CSVFormat.EXCEL.withHeader(header);
CSVPrinter csvPrinter = new CSVPrinter(osw, csvFormat);
csvPrinter.printRecords(iter);
csvPrinter.flush();
csvPrinter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
2.3 使用示例
List<List<Object>> lists = new ArrayList<>();
List<Object> list1 = new ArrayList<>();
list1.add("张三");
list1.add(18);
list1.add("男");
lists.add(list1);
List<Object> list2 = new ArrayList<>();
list2.add("李四");
list2.add(20);
list2.add("女");
lists.add(list2);
String[] header = {"姓名", "年龄", "性别"};
// 省略 out
exportCSVFile(out, lists, "UTF-8", header);
文本编辑器打开
张三,18,男
李四,20,女
2.4 可能遇到的问题
使用 Excel 打开出现中文乱码
上面的示例代码采用的是写入 bom ,编码为 UTF-8 的方式解决的,也有其他方案是使用编码为 GBK。
用 Excel 打开后,日期格式会变成 ### 或者 2019/01/01 以及较大数字会变成科学技术法
写数据时加上制表符,一个制表符不够,就 2 个
不要调用 printRecords(); 方法,而是通过遍历数据调用 csvPrinter.print(“\t”+ 数据 +“\t”);
public void exportCSVFile(OutputStream out, Iterable<?> iter, String charset,String... header) {
try {
// 写入bom, 防止中文乱码
byte[] bytes = {(byte) 0xEF, (byte) 0xBB, (byte) 0xBF};
out.write(bytes);
OutputStreamWriter osw = new OutputStreamWriter(out, charset);
CSVFormat csvFormat = CSVFormat.EXCEL.withHeader(header);
CSVPrinter csvPrinter = new CSVPrinter(osw, csvFormat);
Iterator<?> iterator = iter.iterator();
while (iterator.hasNext()) {
Collection list = (Collection)iterator.next();
// 开始写一行数据
list.forEach(c->{
try {
csvPrinter.print("\t" +c.toString() +"\t" );
} catch (IOException e) {
e.printStackTrace();
}
});
// 写完一行,需要换行
csvPrinter.println();
}
csvPrinter.flush();
csvPrinter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
写入的数据包含逗号,双引号等特殊字符时,导出的 csv 文件用文本编辑器打开,数据会用双引号包裹起来, 如下所示:
如果CSV文件不包含标题,或者不确定是否包含标题,则可以使用索引访问记录。由于CSVRecord实现了Java Iterable Interface,因此即使使用Excel和大多数其他应用程序打开CSV索引,其索引也从1开始,但索引是基于0的:
CSVParser csvParser = CSVFormat.DEFAULT.parse(new InputStreamReader(csvFile.getInputStream()));
for (CSVRecord record : csvParser) {
String field_1 = record.get(0);
String field_2 = record.get(1);
...
}
让我们考虑一个带有树数据的示例CSV文件,并将其称为“树数据CSV”,以供本文将来参考:
指数 周长(英寸) 高度(英尺) 体积(英尺)
1个 8.3 70 10.3
要引用数据的每一行,我们可以像上一个示例一样使用索引或列标题:
InputStreamReader input = new InputStreamReader(csvFile.getInputStream());
CSVParser csvParser = CSVFormat.EXCEL.withFirstRecordAsHeader().parse(input);
for (CSVRecord record : csvParser) {
String field_1 = record.get("Index");
String field_2 = record.get("Girth (in)");
String field_3 = record.get("Height (ft)");
String field_4 = record.get("Volume (ft)");
}
如果要读取不包含标题行的文件,想要定义自己的标题或使索引混乱,则Apache Commons还允许定义标题以进行解析。
.withFirstRecordAsHeader()您可以手动定义标题,而不是在定义CSV文件格式时使用该方法。例如,如果要避免在树数据文件的标题中引用度量单位,则可以重新定义标题以使用自己的字符串值:
CSVParser csvParser = CSVFormat.REF4180.withHeader("Index", "Girth", "Height", "Volume");
for (CSVRecord record : csvParser) {
String field_2 = record.get("Girth");
}
如果您的CSV文件包含标题,但是您想定义自己的标题并跳过读取文件中的标题,请使用.readNext()以跳过第一行:
CSVRecord header = csvParser.readNext();
// read the other rows in a loop as usual
public enum treeHeader {
Index, Girth, Height, Volume
}
...
CSVParser csvParser = CSVFormat.DEFAULT.withHeader(treeHeader.class).parse(input);
// read rows
字节流直接操作文件本身,字符流则是针对内存开辟一个缓冲区
什么是缓冲区?为什么要做缓冲区?
缓冲区通俗来说就是一个暂时存储数据的容器,比如你频繁操作一个文件或者查询数据库数据,肯定影响性能,但是如果你开辟一个缓冲区,将之前查到的数据存在内存中,下次直接在内存读取,速度会快很多。缓冲区就跟这个概念差不多,将读取到的数据先全部放在缓冲区内,等到flush()或者close() 在统一输出到文件中去。
InputStream实现一个字节一个字节的读取,而InputStreamReader是升级版实现一个字符一个字符的读取,BufferedReader再次升级实现一行一行的读取;
/**
* 创建字符输出流并且写完后不关闭流
*/
public class demo {
public static void main(String[] args) throws Exception{
//字符流写数据 并且不关闭字符流
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(new File("D:\\tjbh项目\\1111.txt")));
writer.write("12345");
}
}
可以看到并没有在文件中显示内容,说明现在数据还都在缓冲区内,这时候只要close()或者 flush() 就可以将数据存到文件中
/**
* 关闭流
*/
public class demo {
public static void main(String[] args) throws Exception{
//字符流写数据 并且不关闭字符流
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(new File("D:\\tjbh项目\\1111.txt")));
writer.write("12345");
writer.close();
}
现一行一行的读取,那么就用到了BufferedReader
更多推荐
所有评论(0)