POI实现超大数据的Excel的读写操作

http://thinkgem.iteye.com/blog/2150940


前端时间写了注解方式Excel的读取和写入,它是根据注解完成Excel的操作,虽说支持大数据,但对于超大数据就无能为力了,因为它的读写期间都是将所有数据放入系统内存的,除非你有超大的内存。

 

因项目需要对超大数据的Excel读写操作,于是网上找了个超大数据的读写代码,这个不需要太大内存。并对此进行了简单的修改。

 

原理如下:

 

Excel超大数据读取:抽象Excel2007读取器,excel2007的底层数据结构是xml文件,采用SAX的事件驱动的方法解析 xml,需要继承DefaultHandler,在遇到文件内容时,事件会触发,这种做法可以大大降低内存的耗费,特别使用于大数据量的文件。

 

Excel超大数据写入:抽象excel2007读入器,先构建.xlsx一张模板,改写模板中的sheet.xml, 使用这种方法 写入.xlsx文件,不需要太大的内存。

 

先看调用示例:

  

Java代码   收藏代码
  1. String file = "E:/导入测试数据.xlsx";  
  2.   
  3. ExcelReader reader = new ExcelReader() {  
  4.     public void getRows(int sheetIndex, int curRow, List<String> rowList) {  
  5.           
  6.         System.out.println("Sheet:" + sheetIndex + ", Row:" + curRow + ", Data:" +rowList);  
  7.           
  8.     }  
  9. };  
  10. reader.process(file, 1);  

 

Java代码   收藏代码
  1. String file = "E:/导出测试数据.xlsx";  
  2.   
  3. ExcelWriter writer = new ExcelWriter() {  
  4.     public void generate() throws Exception {  
  5.           
  6.         // 电子表格开始  
  7.         this.beginSheet();  
  8.           
  9.         for (int rownum = 0; rownum < 100; rownum++) {  
  10.             // 插入新行  
  11.             this.insertRow(rownum);  
  12.               
  13.             // 建立新单元格,索引值从0开始,表示第一列  
  14.             this.createCell(0"第 " + rownum + " 行");  
  15.             this.createCell(134343.123456789);  
  16.             this.createCell(2"23.67%");  
  17.             this.createCell(3"12:12:23");  
  18.             this.createCell(4"2014-10-11 12:12:23");  
  19.             this.createCell(5"true");  
  20.             this.createCell(6"false");  
  21.   
  22.             // 结束行  
  23.             this.endRow();  
  24.         }  
  25.           
  26.         // 电子表格结束  
  27.             this.endSheet();  
  28.         }  
  29.     };  
  30.     writer.process(file);  
  31. }  

 这里只展示了对数据的读取和写入,如果正式保存到数据库时建议读取一部分(如100条)再写入一次数据库,尽量不要读取一条就写入一条,这样会非常耗费资源。

 

源代码如下:

 

Java代码   收藏代码
  1. import java.io.InputStream;  
  2. import java.math.BigDecimal;  
  3. import java.text.SimpleDateFormat;  
  4. import java.util.ArrayList;  
  5. import java.util.Date;  
  6. import java.util.Iterator;  
  7. import java.util.List;  
  8.   
  9. import org.apache.poi.hssf.usermodel.HSSFDateUtil;  
  10. import org.apache.poi.openxml4j.opc.OPCPackage;  
  11. import org.apache.poi.xssf.eventusermodel.XSSFReader;  
  12. import org.apache.poi.xssf.model.SharedStringsTable;  
  13. import org.apache.poi.xssf.usermodel.XSSFRichTextString;  
  14. import org.xml.sax.Attributes;  
  15. import org.xml.sax.InputSource;  
  16. import org.xml.sax.SAXException;  
  17. import org.xml.sax.XMLReader;  
  18. import org.xml.sax.helpers.DefaultHandler;  
  19. import org.xml.sax.helpers.XMLReaderFactory;  
  20.   
  21. /** 
  22.  * Excel超大数据读取,抽象Excel2007读取器,excel2007的底层数据结构是xml文件,采用SAX的事件驱动的方法解析 
  23.  * xml,需要继承DefaultHandler,在遇到文件内容时,事件会触发,这种做法可以大大降低 内存的耗费,特别使用于大数据量的文件。 
  24.  * @version 2014-9-2 
  25.  */  
  26. public abstract class ExcelReader extends DefaultHandler {  
  27.   
  28.     // 共享字符串表  
  29.     private SharedStringsTable sst;  
  30.       
  31.     // 上一次的内容  
  32.     private String lastContents;  
  33.     private boolean nextIsString;  
  34.   
  35.     private int sheetIndex = -1;  
  36.     private List<String> rowList = new ArrayList<String>();  
  37.   
  38.     // 当前行  
  39.     private int curRow = 0;  
  40.     // 当前列  
  41.     private int curCol = 0;  
  42.     // 日期标志  
  43.     private boolean dateFlag;  
  44.     // 数字标志  
  45.     private boolean numberFlag;  
  46.   
  47.     private boolean isTElement;  
  48.   
  49.     /** 
  50.      * 遍历工作簿中所有的电子表格 
  51.      * @param filename 
  52.      * @throws Exception 
  53.      */  
  54.     public void process(String filename) throws Exception {  
  55.         OPCPackage pkg = OPCPackage.open(filename);  
  56.         XSSFReader r = new XSSFReader(pkg);  
  57.         SharedStringsTable sst = r.getSharedStringsTable();  
  58.         XMLReader parser = fetchSheetParser(sst);  
  59.         Iterator<InputStream> sheets = r.getSheetsData();  
  60.         while (sheets.hasNext()) {  
  61.             curRow = 0;  
  62.             sheetIndex++;  
  63.             InputStream sheet = sheets.next();  
  64.             InputSource sheetSource = new InputSource(sheet);  
  65.             parser.parse(sheetSource);  
  66.             sheet.close();  
  67.         }  
  68.     }  
  69.   
  70.     /** 
  71.      * 只遍历一个电子表格,其中sheetId为要遍历的sheet索引,从1开始,1-3 
  72.      * @param filename 
  73.      * @param sheetId 
  74.      * @throws Exception 
  75.      */  
  76.     public void process(String filename, int sheetId) throws Exception {  
  77.         OPCPackage pkg = OPCPackage.open(filename);  
  78.         XSSFReader r = new XSSFReader(pkg);  
  79.         SharedStringsTable sst = r.getSharedStringsTable();  
  80.         XMLReader parser = fetchSheetParser(sst);  
  81.         // 根据 rId# 或 rSheet# 查找sheet  
  82.         InputStream sheet2 = r.getSheet("rId" + sheetId);  
  83.         sheetIndex++;  
  84.         InputSource sheetSource = new InputSource(sheet2);  
  85.         parser.parse(sheetSource);  
  86.         sheet2.close();  
  87.     }  
  88.   
  89.     public XMLReader fetchSheetParser(SharedStringsTable sst)  
  90.             throws SAXException {  
  91.         XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");  
  92.         this.sst = sst;  
  93.         parser.setContentHandler(this);  
  94.         return parser;  
  95.     }  
  96.   
  97.     public void startElement(String uri, String localName, String name,  
  98.             Attributes attributes) throws SAXException {  
  99.   
  100. //      System.out.println("startElement: " + localName + ", " + name + ", " + attributes);  
  101.   
  102.         // c => 单元格  
  103.         if ("c".equals(name)) {  
  104.             // 如果下一个元素是 SST 的索引,则将nextIsString标记为true  
  105.             String cellType = attributes.getValue("t");  
  106.             if ("s".equals(cellType)) {  
  107.                 nextIsString = true;  
  108.             } else {  
  109.                 nextIsString = false;  
  110.             }  
  111.             // 日期格式  
  112.             String cellDateType = attributes.getValue("s");  
  113.             if ("1".equals(cellDateType)) {  
  114.                 dateFlag = true;  
  115.             } else {  
  116.                 dateFlag = false;  
  117.             }  
  118.             String cellNumberType = attributes.getValue("s");  
  119.             if ("2".equals(cellNumberType)) {  
  120.                 numberFlag = true;  
  121.             } else {  
  122.                 numberFlag = false;  
  123.             }  
  124.   
  125.         }  
  126.         // 当元素为t时  
  127.         if ("t".equals(name)) {  
  128.             isTElement = true;  
  129.         } else {  
  130.             isTElement = false;  
  131.         }  
  132.   
  133.         // 置空  
  134.         lastContents = "";  
  135.     }  
  136.   
  137.     public void endElement(String uri, String localName, String name)  
  138.             throws SAXException {  
  139.   
  140. //      System.out.println("endElement: " + localName + ", " + name);  
  141.   
  142.         // 根据SST的索引值的到单元格的真正要存储的字符串  
  143.         // 这时characters()方法可能会被调用多次  
  144.         if (nextIsString) {  
  145.             try {  
  146.                 int idx = Integer.parseInt(lastContents);  
  147.                 lastContents = new XSSFRichTextString(sst.getEntryAt(idx))  
  148.                         .toString();  
  149.             } catch (Exception e) {  
  150.   
  151.             }  
  152.         }  
  153.         // t元素也包含字符串  
  154.         if (isTElement) {  
  155.             String value = lastContents.trim();  
  156.             rowList.add(curCol, value);  
  157.             curCol++;  
  158.             isTElement = false;  
  159.             // v => 单元格的值,如果单元格是字符串则v标签的值为该字符串在SST中的索引  
  160.             // 将单元格内容加入rowlist中,在这之前先去掉字符串前后的空白符  
  161.         } else if ("v".equals(name)) {  
  162.             String value = lastContents.trim();  
  163.             value = value.equals("") ? " " : value;  
  164.             try {  
  165.                 // 日期格式处理  
  166.                 if (dateFlag) {  
  167.                     Date date = HSSFDateUtil.getJavaDate(Double.valueOf(value));  
  168.                     SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");  
  169.                     value = dateFormat.format(date);  
  170.                 }  
  171.                 // 数字类型处理  
  172.                 if (numberFlag) {  
  173.                     BigDecimal bd = new BigDecimal(value);  
  174.                     value = bd.setScale(3, BigDecimal.ROUND_UP).toString();  
  175.                 }  
  176.             } catch (Exception e) {  
  177.                 // 转换失败仍用读出来的值  
  178.             }  
  179.             rowList.add(curCol, value);  
  180.             curCol++;  
  181.         } else {  
  182.             // 如果标签名称为 row ,这说明已到行尾,调用 optRows() 方法  
  183.             if (name.equals("row")) {  
  184.                 getRows(sheetIndex + 1, curRow, rowList);  
  185.                 rowList.clear();  
  186.                 curRow++;  
  187.                 curCol = 0;  
  188.             }  
  189.         }  
  190.   
  191.     }  
  192.   
  193.     public void characters(char[] ch, int start, int length)  
  194.             throws SAXException {  
  195.         // 得到单元格内容的值  
  196.         lastContents += new String(ch, start, length);  
  197.     }  
  198.   
  199.     /** 
  200.      * 获取行数据回调 
  201.      * @param sheetIndex 
  202.      * @param curRow 
  203.      * @param rowList 
  204.      */  
  205.     public abstract void getRows(int sheetIndex, int curRow, List<String> rowList);  
  206.   
  207.     /** 
  208.      * 测试方法 
  209.      */  
  210.     public static void main(String[] args) throws Exception {  
  211.   
  212. String file = "E:/导入测试数据.xlsx";  
  213.   
  214. ExcelReader reader = new ExcelReader() {  
  215.     public void getRows(int sheetIndex, int curRow, List<String> rowList) {  
  216.           
  217.         System.out.println("Sheet:" + sheetIndex + ", Row:" + curRow + ", Data:" +rowList);  
  218.           
  219.     }  
  220. };  
  221. reader.process(file, 1);  
  222.   
  223.     }  
  224.   
  225. }  

 

 

Java代码   收藏代码
  1. import java.io.File;  
  2. import java.io.FileInputStream;  
  3. import java.io.FileOutputStream;  
  4. import java.io.FileWriter;  
  5. import java.io.IOException;  
  6. import java.io.InputStream;  
  7. import java.io.OutputStream;  
  8. import java.io.Writer;  
  9. import java.util.Calendar;  
  10. import java.util.Enumeration;  
  11. import java.util.zip.ZipEntry;  
  12. import java.util.zip.ZipFile;  
  13. import java.util.zip.ZipOutputStream;  
  14.   
  15. import org.apache.poi.hssf.util.CellReference;  
  16. import org.apache.poi.ss.usermodel.DateUtil;  
  17. import org.apache.poi.xssf.usermodel.XSSFSheet;  
  18. import org.apache.poi.xssf.usermodel.XSSFWorkbook;  
  19.   
  20. /** 
  21.  * Excel超大数据写入,抽象excel2007读入器,先构建.xlsx一张模板,改写模板中的sheet.xml, 
  22.  * 使用这种方法 写入.xlsx文件,不需要太大的内存 
  23.  * @version 2014-9-2 
  24.  */  
  25. public abstract class ExcelWriter {  
  26.   
  27.     private SpreadsheetWriter sw;  
  28.   
  29.     /** 
  30.      * 写入电子表格的主要流程 
  31.      *  
  32.      * @param fileName 
  33.      * @throws Exception 
  34.      */  
  35.     public void process(String fileName) throws Exception {  
  36.           
  37.         // 建立工作簿和电子表格对象  
  38.         XSSFWorkbook wb = new XSSFWorkbook();  
  39.         XSSFSheet sheet = wb.createSheet("sheet1");  
  40.           
  41.         // 持有电子表格数据的xml文件名 例如 /xl/worksheets/sheet1.xml  
  42.         String sheetRef = sheet.getPackagePart().getPartName().getName();  
  43.   
  44.         // 保存模板  
  45.         FileOutputStream os = new FileOutputStream("template.xlsx");  
  46.         wb.write(os);  
  47.         os.close();  
  48.   
  49.         // 生成xml文件  
  50.         File tmp = File.createTempFile("sheet"".xml");  
  51.         Writer fw = new FileWriter(tmp);  
  52.         sw = new SpreadsheetWriter(fw);  
  53.         generate();  
  54.         fw.close();  
  55.   
  56.         // 使用产生的数据替换模板  
  57.         File templateFile = new File("template.xlsx");  
  58.         FileOutputStream out = new FileOutputStream(fileName);  
  59.         substitute(templateFile, tmp, sheetRef.substring(1), out);  
  60.         out.close();  
  61.         // 删除文件之前调用一下垃圾回收器,否则无法删除模板文件  
  62.         System.gc();  
  63.         // 删除临时模板文件  
  64.         if (templateFile.isFile() && templateFile.exists()) {  
  65.             templateFile.delete();  
  66.         }  
  67.     }  
  68.   
  69.     /** 
  70.      * 类使用者应该使用此方法进行写操作 
  71.      *  
  72.      * @throws Exception 
  73.      */  
  74.     public abstract void generate() throws Exception;  
  75.   
  76.     public void beginSheet() throws IOException {  
  77.         sw.beginSheet();  
  78.     }  
  79.   
  80.     public void insertRow(int rowNum) throws IOException {  
  81.         sw.insertRow(rowNum);  
  82.     }  
  83.   
  84.     public void createCell(int columnIndex, String value) throws IOException {  
  85.         sw.createCell(columnIndex, value, -1);  
  86.     }  
  87.   
  88.     public void createCell(int columnIndex, double value) throws IOException {  
  89.         sw.createCell(columnIndex, value, -1);  
  90.     }  
  91.   
  92.     public void endRow() throws IOException {  
  93.         sw.endRow();  
  94.     }  
  95.   
  96.     public void endSheet() throws IOException {  
  97.         sw.endSheet();  
  98.     }  
  99.   
  100.     /** 
  101.      *  
  102.      * @param zipfile 
  103.      *            the template file 
  104.      * @param tmpfile 
  105.      *            the XML file with the sheet data 
  106.      * @param entry 
  107.      *            the name of the sheet entry to substitute, e.g. 
  108.      *            xl/worksheets/sheet1.xml 
  109.      * @param out 
  110.      *            the stream to write the result to 
  111.      */  
  112.     private static void substitute(File zipfile, File tmpfile, String entry,  
  113.             OutputStream out) throws IOException {  
  114.         ZipFile zip = new ZipFile(zipfile);  
  115.         ZipOutputStream zos = new ZipOutputStream(out);  
  116.   
  117.         @SuppressWarnings("unchecked")  
  118.         Enumeration<ZipEntry> en = (Enumeration<ZipEntry>) zip.entries();  
  119.         while (en.hasMoreElements()) {  
  120.             ZipEntry ze = en.nextElement();  
  121.             if (!ze.getName().equals(entry)) {  
  122.                 zos.putNextEntry(new ZipEntry(ze.getName()));  
  123.                 InputStream is = zip.getInputStream(ze);  
  124.                 copyStream(is, zos);  
  125.                 is.close();  
  126.             }  
  127.         }  
  128.         zos.putNextEntry(new ZipEntry(entry));  
  129.         InputStream is = new FileInputStream(tmpfile);  
  130.         copyStream(is, zos);  
  131.         is.close();  
  132.         zos.close();  
  133.     }  
  134.   
  135.     private static void copyStream(InputStream in, OutputStream out)  
  136.             throws IOException {  
  137.         byte[] chunk = new byte[1024];  
  138.         int count;  
  139.         while ((count = in.read(chunk)) >= 0) {  
  140.             out.write(chunk, 0, count);  
  141.         }  
  142.     }  
  143.   
  144.     /** 
  145.      * 在写入器中写入电子表格 
  146.      *  
  147.      */  
  148.     public static class SpreadsheetWriter {  
  149.         private final Writer _out;  
  150.         private int _rownum;  
  151.         private static String LINE_SEPARATOR = System  
  152.                 .getProperty("line.separator");  
  153.   
  154.         public SpreadsheetWriter(Writer out) {  
  155.             _out = out;  
  156.         }  
  157.   
  158.         public void beginSheet() throws IOException {  
  159.             _out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"  
  160.                     + "<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">");  
  161.             _out.write("<sheetData>" + LINE_SEPARATOR);  
  162.         }  
  163.   
  164.         public void endSheet() throws IOException {  
  165.             _out.write("</sheetData>");  
  166.             _out.write("</worksheet>");  
  167.         }  
  168.   
  169.         /** 
  170.          * 插入新行 
  171.          *  
  172.          * @param rownum 
  173.          *            以0开始 
  174.          */  
  175.         public void insertRow(int rownum) throws IOException {  
  176.             _out.write("<row r=\"" + (rownum + 1) + "\">" + LINE_SEPARATOR);  
  177.             this._rownum = rownum;  
  178.         }  
  179.   
  180.         /** 
  181.          * 插入行结束标志 
  182.          */  
  183.         public void endRow() throws IOException {  
  184.             _out.write("</row>" + LINE_SEPARATOR);  
  185.         }  
  186.   
  187.         /** 
  188.          * 插入新列 
  189.          *  
  190.          * @param columnIndex 
  191.          * @param value 
  192.          * @param styleIndex 
  193.          * @throws IOException 
  194.          */  
  195.         public void createCell(int columnIndex, String value, int styleIndex)  
  196.                 throws IOException {  
  197.             String ref = new CellReference(_rownum, columnIndex)  
  198.                     .formatAsString();  
  199.             _out.write("<c r=\"" + ref + "\" t=\"inlineStr\"");  
  200.             if (styleIndex != -1)  
  201.                 _out.write(" s=\"" + styleIndex + "\"");  
  202.             _out.write(">");  
  203.             _out.write("<is><t>" + encoderXML(value) + "</t></is>");  
  204.             _out.write("</c>");  
  205.         }  
  206.   
  207.         public void createCell(int columnIndex, String value)  
  208.                 throws IOException {  
  209.             createCell(columnIndex, value, -1);  
  210.         }  
  211.   
  212.         public void createCell(int columnIndex, double value, int styleIndex)  
  213.                 throws IOException {  
  214.             String ref = new CellReference(_rownum, columnIndex)  
  215.                     .formatAsString();  
  216.             _out.write("<c r=\"" + ref + "\" t=\"n\"");  
  217.             if (styleIndex != -1)  
  218.                 _out.write(" s=\"" + styleIndex + "\"");  
  219.             _out.write(">");  
  220.             _out.write("<v>" + value + "</v>");  
  221.             _out.write("</c>");  
  222.         }  
  223.   
  224.         public void createCell(int columnIndex, double value)  
  225.                 throws IOException {  
  226.             createCell(columnIndex, value, -1);  
  227.         }  
  228.   
  229.         public void createCell(int columnIndex, Calendar value, int styleIndex)  
  230.                 throws IOException {  
  231.             createCell(columnIndex, DateUtil.getExcelDate(value, false),  
  232.                     styleIndex);  
  233.         }  
  234.     }  
  235.   
  236.     // XML Encode  
  237.     private static final String[] xmlCode = new String[256];  
  238.   
  239.     static {  
  240.         // Special characters  
  241.         xmlCode['\''] = "'";  
  242.         xmlCode['\"'] = "\""// double quote  
  243.         xmlCode['&'] = "&"// ampersand  
  244.         xmlCode['<'] = "<"// lower than  
  245.         xmlCode['>'] = ">"// greater than  
  246.     }  
  247.   
  248.     /** 
  249.      * <p> 
  250.      * Encode the given text into xml. 
  251.      * </p> 
  252.      *  
  253.      * @param string 
  254.      *            the text to encode 
  255.      * @return the encoded string 
  256.      */  
  257.     public static String encoderXML(String string) {  
  258.         if (string == null)  
  259.             return "";  
  260.         int n = string.length();  
  261.         char character;  
  262.         String xmlchar;  
  263.         StringBuffer buffer = new StringBuffer();  
  264.         // loop over all the characters of the String.  
  265.         for (int i = 0; i < n; i++) {  
  266.             character = string.charAt(i);  
  267.             // the xmlcode of these characters are added to a StringBuffer  
  268.             // one by one  
  269.             try {  
  270.                 xmlchar = xmlCode[character];  
  271.                 if (xmlchar == null) {  
  272.                     buffer.append(character);  
  273.                 } else {  
  274.                     buffer.append(xmlCode[character]);  
  275.                 }  
  276.             } catch (ArrayIndexOutOfBoundsException aioobe) {  
  277.                 buffer.append(character);  
  278.             }  
  279.         }  
  280.         return buffer.toString();  
  281.     }  
  282.   
  283.     /** 
  284.      * 测试方法 
  285.      */  
  286.     public static void main(String[] args) throws Exception {  
  287.   
  288.     String file = "E:/导出测试数据.xlsx";  
  289.       
  290.     ExcelWriter writer = new ExcelWriter() {  
  291.         public void generate() throws Exception {  
  292.               
  293.             // 电子表格开始  
  294.             this.beginSheet();  
  295.               
  296.             for (int rownum = 0; rownum < 100; rownum++) {  
  297.                 // 插入新行  
  298.                 this.insertRow(rownum);  
  299.                   
  300.                 // 建立新单元格,索引值从0开始,表示第一列  
  301.                 this.createCell(0"第 " + rownum + " 行");  
  302.                 this.createCell(134343.123456789);  
  303.                 this.createCell(2"23.67%");  
  304.                 this.createCell(3"12:12:23");  
  305.                 this.createCell(4"2014-10-11 12:12:23");  
  306.                 this.createCell(5"true");  
  307.                 this.createCell(6"false");  
  308.   
  309.                 // 结束行  
  310.                 this.endRow();  
  311.             }  
  312.               
  313.             // 电子表格结束  
  314.             this.endSheet();  
  315.         }  
  316.     };  
  317.     writer.process(file);  
  318. }  
  319.           

猜你喜欢

转载自blog.csdn.net/dufufd/article/details/79918164