Java 匯出大批量資料excel(百萬級)(轉載)
參考資料:http://bbs.51cto.com/thread-1074293-1-1.html http://bbs.51cto.com/viewthread.php?tid=1074293&extra=&page=1
目前java框架中能夠生成excel檔案的的確不少,但是,能夠生成大資料量的excel框架,我倒是沒發現,一般資料量大了都會出現記憶體溢位,所以,生成大資料量的excel檔案要返璞歸真,用java的基礎技術,IO流來實現。
如果想用IO流來生成excel檔案,必須要知道excel的檔案格式內容,相當於生成html檔案一樣,用字串拼接html標籤儲存到文字檔案就可以生成一個html檔案了。同理,excel檔案也是可以的。怎麼知道excel的檔案格式呢?其實很簡單,隨便新建一個excel檔案,雙擊開啟,然後點選“檔案”-》“另存為”,儲存的型別為“xml表格”,儲存之後用文字格式開啟,就可以看到excel的字串格式一覽無遺了。
把下面的xml字串複製到文字檔案,然後儲存為xls格式,就是一個excel檔案。
<?xml version="1.0"?> <?mso-application progid="Excel.Sheet"?> <Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:html="http://www.w3.org/TR/REC-html40"> <DocumentProperties xmlns="urn:schemas-microsoft-com:office:office"> <Created>1996-12-17T01:32:42Z</Created> <LastSaved>2000-11-18T06:53:49Z</LastSaved> <Version>11.9999</Version> </DocumentProperties> <OfficeDocumentSettings xmlns="urn:schemas-microsoft-com:office:office"> <RemovePersonalInformation/> </OfficeDocumentSettings> <ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel"> <WindowHeight>4530</WindowHeight> <WindowWidth>8505</WindowWidth> <WindowTopX>480</WindowTopX> <WindowTopY>120</WindowTopY> <AcceptLabelsInFormulas/> <ProtectStructure>False</ProtectStructure> <ProtectWindows>False</ProtectWindows> </ExcelWorkbook> <Styles> <Style ss:ID="Default" ss:Name="Normal"> <Alignment ss:Vertical="Bottom"/> <Borders/> <Font ss:FontName="宋體" x:CharSet="134" ss:Size="12"/> <Interior/> <NumberFormat/> <Protection/> </Style> </Styles> <Worksheet ss:Name="Sheet1"> <Table ss:ExpandedColumnCount="2" ss:ExpandedRowCount="2" x:FullColumns="1" x:FullRows="1" ss:DefaultColumnWidth="54" ss:DefaultRowHeight="14.25"> <Column ss:AutoFitWidth="0" ss:Width="73.5"/> <Row> <Cell><Data ss:Type="String">zhangzehao</Data></Cell> <Cell><Data ss:Type="String">zhangzehao</Data></Cell> </Row> <Row> <Cell><Data ss:Type="String">zhangzehao</Data></Cell> </Row> </Table> <WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel"> <Selected/> <Panes> <Pane> <Number>3</Number> <ActiveRow>5</ActiveRow> <ActiveCol>3</ActiveCol> </Pane> </Panes> <ProtectObjects>False</ProtectObjects> <ProtectScenarios>False</ProtectScenarios> </WorksheetOptions> </Worksheet> <Worksheet ss:Name="Sheet2"> <Table ss:ExpandedColumnCount="0" ss:ExpandedRowCount="0" x:FullColumns="1" x:FullRows="1" ss:DefaultColumnWidth="54" ss:DefaultRowHeight="14.25"/> <WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel"> <ProtectObjects>False</ProtectObjects> <ProtectScenarios>False</ProtectScenarios> </WorksheetOptions> </Worksheet> <Worksheet ss:Name="Sheet3"> <Table ss:ExpandedColumnCount="0" ss:ExpandedRowCount="0" x:FullColumns="1" x:FullRows="1" ss:DefaultColumnWidth="54" ss:DefaultRowHeight="14.25"/> <WorksheetOptions xmlns="urn:schemas-microsoft-com:office:excel"> <ProtectObjects>False</ProtectObjects> <ProtectScenarios>False</ProtectScenarios> </WorksheetOptions> </Worksheet> </Workbook>
如果要生成千萬級別以上的excel,除了這個關鍵點之外,還要控制IO流,如果有1000萬記錄,要迭代1000萬次組裝xml字串,這樣肯定佔用相當大的記憶體,肯定記憶體溢位,所以,必須把組裝的xml字串分批用IO流重新整理到硬盤裡,如果是在web應用中,可以重新整理到response中,web應用會自動把臨時流儲存到客戶端的臨時檔案中,然後再一次性複製到你儲存的路徑。言歸正傳,分批重新整理的話,可以迭代一批資料就flush進硬碟,同時把list,大物件賦值為空,顯式呼叫垃圾回收器,表明要回收記憶體。這樣的話,不管生成多大的資料量都不會出現記憶體溢位的,我曾經試過匯出1億的excel檔案,都不會出現記憶體溢位,只是用了35分鐘。
當然,如果要把實現做的優雅一些,在組裝xml字串的時候,可以結合模板技術來實現,我個人喜好stringtemplate這個輕量級的框架,我給出的DEMO也是採用了模板技術生成的,當然velocity和freemarker都是可以,stringbuilder也行,呵呵。
我為人比較懶,本意不是為了寫個帖子的,只是想多賺點下載豆:lol1 ,這和賺錢一樣誰不想?誰知道就寫了那麼多。同時鄙人知識寡陋,希望可以拋磚引玉。
綜上:使用技術為 stringTemplate
pom.xml:
<dependency> <groupId>antlr</groupId> <artifactId>antlr</artifactId> <version>2.7.7</version> </dependency> <dependency> <groupId>org.antlr</groupId> <artifactId>stringtemplate</artifactId> <version>3.2.1</version> </dependency>
template物件:
class Row{ private List<String> result; public List<String> getResult() { return result; } public void setResult(List<String> result) { this.result = result; } } class Worksheet{ private String sheet; private int columnNum; private int rowNum; private List<String> title; private List<Row> rows; public String getSheet() { return sheet; } public void setSheet(String sheet) { this.sheet = sheet; } public List<Row> getRows() { return rows; } public void setRows(List<Row> rows) { this.rows = rows; } public int getColumnNum() { return columnNum; } public void setColumnNum(int columnNum) { this.columnNum = columnNum; } public int getRowNum() { return rowNum; } public void setRowNum(int rowNum) { this.rowNum = rowNum; } public List<String> getTitle() { return title; } public void setTitle(List<String> title) { this.title = title; } }
推薦:Java大批量匯出資料,格式可以為xml或excel.
[ 最近專案中用到大資料量匯出功能.不能確定到底有多大,最少十來萬條記錄. 100M的excel檔案,excel2003已經不能夠打開了,只能用2007版.不知道後期資料量更大
模版檔案(通用):
excel 頭模板
<?xml version="1.0"?> <?mso-application progid="Excel.Sheet"?> <Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:html="http://www.w3.org/TR/REC-html40"> <DocumentProperties xmlns="urn:schemas-microsoft-com:office:office"> <Created>1996-12-17T01:32:42Z</Created> <LastSaved>2013-08-02T09:21:24Z</LastSaved> <Version>11.9999</Version> </DocumentProperties> <OfficeDocumentSettings xmlns="urn:schemas-microsoft-com:office:office"> <RemovePersonalInformation/> </OfficeDocumentSettings> <ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel"> <WindowHeight>4530</WindowHeight> <WindowWidth>8505</WindowWidth> <WindowTopX>480</WindowTopX> <WindowTopY>120</WindowTopY> <AcceptLabelsInFormulas/> <ProtectStructure>False</ProtectStructure> <ProtectWindows>False</ProtectWindows> </ExcelWorkbook> <Styles> <Style ss:ID="Default" ss:Name="Normal"> <Alignment ss:Vertical="Bottom"/> <Borders/> <Font ss:FontName="宋體" x:CharSet="134" ss:Size="12"/> <Interior/> <NumberFormat/> <Protection/> </Style> </Styles>
body模板:
$worksheet:{ <Worksheet ss:Name="$it.sheet$"> <Table ss:ExpandedColumnCount="$it.columnNum$" ss:ExpandedRowCount="$it.rowNum$" x:FullColumns="1" x:FullRows="1" ss:DefaultColumnWidth="54" ss:DefaultRowHeight="14.25"> <Row> $it.title:{ <Cell><Data ss:Type="String">$it$</Data></Cell> }$ </Row> $it.rows:{ <Row> $it.result:{ <Cell><Data ss:Type="String">$it$</Data></Cell> }$ </Row> }$ </Table> </Worksheet> }$
實際處理類:傳入list物件,利用反射獲取物件屬性名及屬性值
long startTimne = System.currentTimeMillis(); StringTemplateGroup stGroup = new StringTemplateGroup("stringTemplate"); //寫入excel檔案頭部資訊 StringTemplate head = stGroup.getInstanceOf("head"); File file = new File("D:/output2.xls"); PrintWriter writer = new PrintWriter(new BufferedOutputStream(new FileOutputStream(file))); writer.print(head.toString()); writer.flush(); int totalRowNum = listWinningRecordDTOList.size(); int maxRowNum = 60000; int sheets = totalRowNum % 60000 == 0 ? (totalRowNum/maxRowNum) : (totalRowNum/maxRowNum +1); //excel單表最大行數是65535 List record = listWinningRecordDTOList; List<String> title = new ArrayList<String>(); List<Method> getMethods = new ArrayList<Method>(); Class<?> clazz = record.get(0).getClass(); Field[] fields = clazz.getDeclaredFields(); if(fields != null && fields.length > 0){ for(Field field : fields){ if(!"serialVersionUID".equals(field.getName())) { title.add(field.getName()); getMethods.add(clazz.getDeclaredMethod("get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1))); } } } // BeanInfo beanInfo=Introspector.getBeanInfo(clazz,Object.class); // PropertyDescriptor[] proDescrtptors=beanInfo.getPropertyDescriptors(); // for(PropertyDescriptor propertyDescriptor : proDescrtptors){ // title.add(propertyDescriptor.getName()); // getMethods.add(propertyDescriptor.getReadMethod()); // } int columnLength = title.size(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //寫入excel檔案資料資訊 for(int i=0;i<sheets;i++){ StringTemplate body = stGroup.getInstanceOf("body"); Worksheet worksheet = new Worksheet(); worksheet.setTitle(title); worksheet.setSheet(" "+(i+1)+" "); worksheet.setColumnNum(columnLength); worksheet.setRowNum(maxRowNum+1); List<Row> rows = new ArrayList<Row>(); int startIndex = i*maxRowNum; int endIndex = Math.min((i+1)*maxRowNum -1,totalRowNum-1); for(int j=startIndex;j<=endIndex;j++){ Row row = new Row(); List<String> result = new ArrayList<String>(columnLength); for(int n=0;n<columnLength;n++){ Object value = getMethods.get(n).invoke(record.get(j)); if(value == null){ result.add(""); }else{ if(value instanceof Date){ result.add(sdf.format((Date)value)); }else{ result.add(value.toString()); } } } row.setResult(result); rows.add(row); } worksheet.setRows(rows); body.setAttribute("worksheet", worksheet); writer.print(body.toString()); writer.flush(); rows.clear(); rows = null; worksheet = null; body = null; Runtime.getRuntime().gc(); System.out.println("正在生成excel檔案的 sheet"+(i+1)); } //寫入excel檔案尾部 writer.print("</Workbook>"); writer.flush(); writer.close(); System.out.println("生成excel檔案完成"); long endTime = System.currentTimeMillis(); System.out.println("用時="+((endTime-startTimne)/1000)+"秒");
整理後的公用類:
import org.antlr.stringtemplate.StringTemplate; import org.antlr.stringtemplate.StringTemplateGroup; import java.io.*; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * Created by Administrator on 2016/2/25. */ public class ExcelStUtil { public static void export(OutputStream outputStream,List target) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { long startTime = System.currentTimeMillis(); StringTemplateGroup stGroup = new StringTemplateGroup("stringTemplate"); //解決可能發生的中文亂碼 stGroup.setFileCharEncoding("UTF-8"); //寫入excel檔案頭部資訊 StringTemplate head = stGroup.getInstanceOf("st/head"); PrintWriter writer = new PrintWriter(new BufferedOutputStream(outputStream)); writer.print(head.toString()); writer.flush(); int totalRowNum = target.size(); int maxRowNum = 60000; int sheets = totalRowNum % 60000 == 0 ? (totalRowNum/maxRowNum) : (totalRowNum/maxRowNum +1); //excel單表最大行數是65535 List record = target; List<String> title = new ArrayList<String>(); List<Method> getMethods = new ArrayList<Method>(); Class<?> clazz = record.get(0).getClass(); Field[] fields = clazz.getDeclaredFields(); if(fields != null && fields.length > 0){ for(Field field : fields){ if(!"serialVersionUID".equals(field.getName())) { title.add(field.getName()); getMethods.add(clazz.getDeclaredMethod("get" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1))); } } } // BeanInfo beanInfo=Introspector.getBeanInfo(clazz,Object.class); // PropertyDescriptor[] proDescrtptors=beanInfo.getPropertyDescriptors(); // for(PropertyDescriptor propertyDescriptor : proDescrtptors){ // title.add(propertyDescriptor.getName()); // getMethods.add(propertyDescriptor.getReadMethod()); // } int columnLength = title.size(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //寫入excel檔案資料資訊 for(int i=0;i<sheets;i++){ StringTemplate body = stGroup.getInstanceOf("st/body"); Worksheet worksheet = new Worksheet(); worksheet.setTitle(title); worksheet.setSheet(" "+(i+1)+" "); worksheet.setColumnNum(columnLength); worksheet.setRowNum(maxRowNum+1); List<Row> rows = new ArrayList<Row>(); int startIndex = i*maxRowNum; int endIndex = Math.min((i+1)*maxRowNum -1,totalRowNum-1); for(int j=startIndex;j<=endIndex;j++){ Row row = new Row(); List<String> result = new ArrayList<String>(columnLength); for(int n=0;n<columnLength;n++){ Object value = getMethods.get(n).invoke(record.get(j)); if(value == null){ result.add(""); }else{ if(value instanceof Date){ result.add(sdf.format((Date)value)); }else{ result.add(value.toString()); } } } row.setResult(result); rows.add(row); } worksheet.setRows(rows); body.setAttribute("worksheet", worksheet); writer.print(body.toString()); writer.flush(); rows.clear(); rows = null; worksheet = null; body = null; Runtime.getRuntime().gc(); System.out.println("正在生成excel檔案的 sheet"+(i+1)); } //寫入excel檔案尾部 writer.print("</Workbook>"); writer.flush(); writer.close(); System.out.println("生成excel檔案完成"); long endTime = System.currentTimeMillis(); System.out.println("用時="+((endTime-startTime)/1000)+"秒"); } public static void main(String[] args) throws IOException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { System.out.println(Thread.currentThread().getContextClassLoader().getResource("").getPath()); System.out.println(ExcelStUtil.class.getResource("").getPath()); System.out.println(ExcelStUtil.class.getClassLoader().getResource("").getPath()); List<Sample> result = new ArrayList<Sample>(); for(int i=0;i<100;i++){ result.add(new Sample("放大雙方的"+String.valueOf(i),String.valueOf(i))); } //OutputStream outputStream = new FileOutputStream("D:/output2.xls"); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ExcelStUtil.export(byteArrayOutputStream,result); //ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); //解決可能發生的中文亂碼 ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toString().getBytes("UTF-8")); File file = new File("D:/output2.xls"); OutputStream output = new FileOutputStream(file); BufferedOutputStream bufferedOutput = new BufferedOutputStream(output); //bufferedOutput.write(byteArrayOutputStream.toByteArray()); bufferedOutput.write(byteArrayOutputStream.toString().getBytes("UTF-8")); bufferedOutput.flush(); bufferedOutput.close(); } }
推薦:Java poi Excel匯出檔案,Java poi 分批次匯出大批量資料
轉載地址:http://www.itboth.com/d/MjI3Ef/excel-java