接着上一篇博客,我们继续来介绍poi的使用方法,最近的项目里,经常要处理数据量超过五十万条的Excel表格,这里用到poi外接库,poi有两个使用模式。
1是用户模式,这个模式操作比较简单,也不需要驱动代码(poi包内部本身就自带了全部的指令代码,可以直接使用,关于使用方法,请参看我的另一篇博客:http://blog.csdn.net/pai_daxing/article/details/75123579),但是在实际使用过程中,用户模式的性能并不好,尤其是在处理大型文件的时候(超过十万条数据额Excel表格的时候),更是这样,处理速度和写文件的速度都很慢(大约每向文件写入十万行数据需要将近一个小时的时间。笔者亲测,处理过程中数据量超过40万条以后,java的虚拟机还会很容易报错。例如Java heap space堆栈溢出错误,jvm处理超时错误)。这时候用户模式就不再适用了。
2是事件驱动模式,这个模式的读入和写出文件的速度都很快,它是借助Excel的XML格式模型来对文件进行读写的,特点是它只对文件读或者写一遍,并不能很好的支持用户对Excel表的重复或跳跃读取。但是这种方法最大的优点就是运行性能好,可以很好的解决用户模式中的数据量较大时的各种错误,但是poi包并没有提供这种模式下的比较完整地驱动代码。需要使用者自己编译,我之前也是看了很多网上的已经编好的驱动代码,基本都可以运行,例如如下这一份驱动代码示例。是笔者已经测试过,确认可以使用的驱动代码。
package poiTools_Class;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Writer;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
/**
* Excel超大数据写入,抽象excel2007读入器,先构建.xlsx一张模板,改写模板中的sheet.xml,
* 使用这种方法 写入.xlsx文件,不需要太大的内存
* @version 2014-9-2
*/
abstract class ExcelWriter {
private SpreadsheetWriter sw;
/**
* 写入电子表格的主要流程
*
* @param fileName
* @throws Exception
*/
public void process(String fileName) throws Exception {
// 建立工作簿和电子表格对象
XSSFWorkbook wb = new XSSFWorkbook();
XSSFSheet sheet = wb.createSheet("sheet1");
// 持有电子表格数据的xml文件名 例如 /xl/worksheets/sheet1.xml
String sheetRef = sheet.getPackagePart().getPartName().getName();
// 保存模板
FileOutputStream os = new FileOutputStream("template.xlsx");
wb.write(os);
os.close();
// 生成xml文件
File tmp = File.createTempFile("sheet", ".xml");
Writer fw = new FileWriter(tmp);
sw = new SpreadsheetWriter(fw);
generate();
fw.close();
// 使用产生的数据替换模板
File templateFile = new File("template.xlsx");
FileOutputStream out = new FileOutputStream(fileName);
substitute(templateFile, tmp, sheetRef.substring(1), out);
out.close();
// 删除文件之前调用一下垃圾回收器,否则无法删除模板文件
System.gc();
// 删除临时模板文件
if (templateFile.isFile() && templateFile.exists()) {
templateFile.delete();
}
}
/**
* 类使用者应该使用此方法进行写操作
*
* @throws Exception
*/
public abstract void generate() throws Exception;
public void beginSheet() throws IOException {
sw.beginSheet();
}
public void insertRow(int rowNum) throws IOException {
sw.insertRow(rowNum);
}
public void createCell(int columnIndex, String value) throws IOException {
sw.createCell(columnIndex, value, -1);
}
public void createCell(int columnIndex, double value) throws IOException {
sw.createCell(columnIndex, value, -1);
}
public void endRow() throws IOException {
sw.endRow();
}
public void endSheet() throws IOException {
sw.endSheet();
}
/**
*
* @param zipfile
* the template file
* @param tmpfile
* the XML file with the sheet data
* @param entry
* the name of the sheet entry to substitute, e.g.
* xl/worksheets/sheet1.xml
* @param out
* the stream to write the result to
*/
private static void substitute(File zipfile, File tmpfile, String entry,
OutputStream out) throws IOException {
ZipFile zip = new ZipFile(zipfile);
ZipOutputStream zos = new ZipOutputStream(out);
@SuppressWarnings("unchecked")
Enumeration
en = (Enumeration
) zip.entries();
while (en.hasMoreElements()) {
ZipEntry ze = en.nextElement();
if (!ze.getName().equals(entry)) {
zos.putNextEntry(new ZipEntry(ze.getName()));
InputStream is = zip.getInputStream(ze);
copyStream(is, zos);
is.close();
}
}
zos.putNextEntry(new ZipEntry(entry));
InputStream is = new FileInputStream(tmpfile);
copyStream(is, zos);
is.close();
zos.close();
}
private static void copyStream(InputStream in, OutputStream out)
throws IOException {
byte[] chunk = new byte[1024];
int count;
while ((count = in.read(chunk)) >= 0) {
out.write(chunk, 0, count);
}
}
/**
* 在写入器中写入电子表格
*
*/
public static class SpreadsheetWriter {
private final Writer _out;
private int _rownum;
private static String LINE_SEPARATOR = System
.getProperty("line.separator");
public SpreadsheetWriter(Writer out) {
_out = out;
}
public void beginSheet() throws IOException {
_out.write("
"
+ "
");
_out.write("
" + LINE_SEPARATOR);
}
public void endSheet() throws IOException {
_out.write("
");
_out.write("
");
}
/**
* 插入新行
*
* @param rownum
* 以0开始
*/
public void insertRow(int rownum) throws IOException {
_out.write("
" + LINE_SEPARATOR);
this._rownum = rownum;
}
/**
* 插入行结束标志
*/
public void endRow() throws IOException {
_out.write("
" + LINE_SEPARATOR);
}
/**
* 插入新列
*
* @param columnIndex
* @param value
* @param styleIndex
* @throws IOException
*/
public void createCell(int columnIndex, String value, int styleIndex)
throws IOException {
String ref = new CellReference(_rownum, columnIndex)
.formatAsString();
_out.write("
" + encoderXML(value) + "
");
_out.write("
");
}
public void createCell(int columnIndex, String value)
throws IOException {
createCell(columnIndex, value, -1);
}
public void createCell(int columnIndex, double value, int styleIndex)
throws IOException {
String ref = new CellReference(_rownum, columnIndex)
.formatAsString();
_out.write("
" + value + "
");
_out.write("
");
}
public void createCell(int columnIndex, double value)
throws IOException {
createCell(columnIndex, value, -1);
}
public void createCell(int columnIndex, Calendar value, int styleIndex)
throws IOException {
createCell(columnIndex, DateUtil.getExcelDate(value, false),
styleIndex);
}
}
// XML Encode
private static final String[] xmlCode = new String[256];
static {
// Special characters
xmlCode['\''] = "'";
xmlCode['\"'] = "\""; // double quote
xmlCode['&'] = "&"; // ampersand
xmlCode['<'] = "<"; // lower than
xmlCode['>'] = ">"; // greater than
}
/**
*
* Encode the given text into xml.
*
*
* @param string
* the text to encode
* @return the encoded string
*/
public static String encoderXML(String string) {
if (string == null)
return "";
int n = string.length();
char character;
String xmlchar;
StringBuffer buffer = new StringBuffer();
// loop over all the characters of the String.
for (int i = 0; i < n; i++) {
character = string.charAt(i);
// the xmlcode of these characters are added to a StringBuffer
// one by one
try {
xmlchar = xmlCode[character];
if (xmlchar == null) {
buffer.append(character);
} else {
buffer.append(xmlCode[character]);
}
} catch (ArrayIndexOutOfBoundsException aioobe) {
buffer.append(character);
}
}
return buffer.toString();
}
/**
* 测试方法
*/
public static void main(String[] args) throws Exception {
String file = "D:/导出测试数据.xlsx";
ExcelWriter writer = new ExcelWriter() {
public void generate() throws Exception {
// 电子表格开始
this.beginSheet();
for (int rownum = 0; rownum < 100; rownum++) {
// 插入新行
this.insertRow(rownum);
// 建立新单元格,索引值从0开始,表示第一列
this.createCell(0, "第 " + rownum + " 行");
this.createCell(1, 34343.123456789);
this.createCell(2, "23.67%");
this.createCell(3, "12:12:23");
this.createCell(4, "2014-10-11 12:12:23");
this.createCell(5, "true");
this.createCell(6, "false");
// 结束行
this.endRow();
}
// 电子表格结束
this.endSheet();
}
};
writer.process(file);
}
}
但是却普遍存在一个问题,就是当我们向Excel中写入中文内容的时候,就会变成乱码,
如下图:
对此我反复测试,发现是字符编码集的使用错误造成的,我们使用的windows系统普遍使用的是GBK编码格式。但是Excel表格在解释编码的时候,普遍采用UTF-8格式。因此中文会报错,而英文和数字内容却可以正常显示。我找了很多解决方法,这里分享一种已经测试成功的解决方案:
操作如下:
修改编码方式:
新建一个java工程 然后
eclipse上
右键工程–>properties–>Resource–>textfileEncoding
看到这里的GBK了吧,也许你早就改过了,所以前面运行结果不是GBK。
试试改成utf-8,再运行程序。是不是发现运行结果变成utf-8了?
为了统一编码,这里我们通常设置成utf-8
再运行代码:
System.out.println(System.getProperty("file.encoding"));
是不是发现运行结果变成utf-8了?
在这个项目下运行我们之前写好的驱动代码,你会发现,中文内容的乱码恢复正常了~