此文章出自:<a > [happyljw]</a>
用過(guò)POI的人都知道院水,在POI以前的版本中并不支持大數(shù)據(jù)量的處理堡距,如果數(shù)據(jù)量過(guò)多還會(huì)常報(bào)OOM錯(cuò)誤,
這時(shí)候調(diào)整JVM的配置參數(shù)
也不是一個(gè)好對(duì)策(注:
jdk在32位系統(tǒng)中支持的內(nèi)存不能超過(guò)2個(gè)G拒名,而在64位中沒有限制,但是在64位的系統(tǒng)中芋酌,性能并不是太好
),好在POI3.8版本新出來(lái)了一個(gè)SXSSFWorkbook對(duì)象增显,它就是用來(lái)解決大數(shù)據(jù)量以及超大數(shù)據(jù)量的導(dǎo)入導(dǎo)出操作的,但是SXSSFWorkbook只支持.xlsx格式脐帝,不支持.xls格式的Excel文件同云。
這里普及一下,在POI中使用HSSF對(duì)象時(shí)堵腹,excel 2003最多只允許存6553數(shù)據(jù)炸站,一般用來(lái)處理較少的數(shù)據(jù)量,這時(shí)對(duì)于百萬(wàn)級(jí)別數(shù)據(jù)疚顷,Excel肯定
容納不了旱易,而且在計(jì)算機(jī)性能稍低的機(jī)器上測(cè)試,就很容易導(dǎo)致堆溢出腿堤。當(dāng)我升級(jí)到XSSF對(duì)象時(shí)阀坏,它可以直接支持excel2007以上版本,因?yàn)樗捎?br>
ooxml格式笆檀。這時(shí)excel可以支持1048576條數(shù)據(jù)忌堂,單個(gè)sheet表就支持近104
萬(wàn)條數(shù)據(jù)了,雖然這時(shí)導(dǎo)出100萬(wàn)數(shù)據(jù)能滿足要求,但使用XSSF測(cè)試后發(fā)現(xiàn)偶爾還是會(huì)發(fā)生堆溢出酗洒,所以也不適合百萬(wàn)數(shù)據(jù)的導(dǎo)出∈啃蓿現(xiàn)在我們知道excel2007及以上版本可以輕松實(shí)現(xiàn)存儲(chǔ)百萬(wàn)級(jí)別的數(shù)據(jù)枷遂,但是系統(tǒng)中的大量數(shù)據(jù)是如何能夠快速準(zhǔn)確的導(dǎo)入到excel中這好像是個(gè)難題,對(duì)于一般的web系統(tǒng)棋嘲,我們?yōu)榱私鉀Q成本酒唉,基本都是使用的入門級(jí)web服務(wù)器tomcat,既然我們不推薦調(diào)整JVM的大小封字,那我們就要針對(duì)我們的代碼來(lái)解決我們要解決的問(wèn)題黔州。在POI3.8之后新增加了一個(gè)類,
SXSSFWorkbook
阔籽,采用當(dāng)數(shù)據(jù)加工時(shí)不是類似前面版本的對(duì)象流妻,它可以控制excel數(shù)據(jù)占用的內(nèi)存,他通過(guò)控制在內(nèi)存中的行數(shù)來(lái)實(shí)現(xiàn)資源管理笆制,即當(dāng)創(chuàng)建對(duì)象超過(guò)了設(shè)定的行數(shù)绅这,它會(huì)自動(dòng)刷新內(nèi)存,將數(shù)據(jù)寫入文件在辆,
這樣導(dǎo)致打印時(shí)证薇,占用的CPU,和內(nèi)存很少匆篓。但有人會(huì)說(shuō)了浑度,我用過(guò)這個(gè)類啊,他好像并不能完全解決鸦概,當(dāng)數(shù)據(jù)量超過(guò)一定量后還是會(huì)內(nèi)存溢出的箩张,而且時(shí)間還很長(zhǎng)。對(duì)你只是用了這個(gè)類窗市,但是你并沒有針對(duì)你的需求進(jìn)行相應(yīng)的設(shè)計(jì)先慷,僅僅是用了,所以接下來(lái)我要說(shuō)的問(wèn)題就是咨察,如何通過(guò)SXSSFWorkbook以及相應(yīng)的寫入設(shè)計(jì)來(lái)實(shí)現(xiàn)百萬(wàn)級(jí)別的數(shù)據(jù)快速寫入论熙。
我先舉個(gè)例子,以前我們[數(shù)據(jù)庫(kù)
中存在大量的數(shù)據(jù)摄狱,我們要查詢脓诡,怎么辦?我們?cè)跊]有經(jīng)過(guò)設(shè)計(jì)的時(shí)候是這樣來(lái)處理的媒役,先寫一個(gè)集合祝谚,然后執(zhí)行jdbc,將返回的結(jié)果賦值給list刊愚,然后再返回到頁(yè)面上,但是當(dāng)數(shù)據(jù)量大的時(shí)候踩验,就會(huì)出現(xiàn)數(shù)據(jù)無(wú)法返回鸥诽,內(nèi)存溢出的情況商玫,于是我們?cè)谟邢薜臅r(shí)間和空間下,通過(guò)分頁(yè)將數(shù)據(jù)一頁(yè)一頁(yè)的顯示出來(lái)牡借,這樣可以避免了[大數(shù)據(jù)
量數(shù)據(jù)對(duì)內(nèi)存的占用拳昌,也提高了用戶的體驗(yàn),在我們要導(dǎo)出的百萬(wàn)數(shù)據(jù)也是一個(gè)道理钠龙,內(nèi)存突發(fā)性占用炬藤,我們可以限制導(dǎo)出數(shù)據(jù)所占用的內(nèi)存,
這里我先建立一個(gè)list容器碴里,list中開辟10000行的存儲(chǔ)空間沈矿,每次存儲(chǔ)10000行,用完了將內(nèi)容清空咬腋,然后重復(fù)利用
羹膳,這樣就可以有效控制內(nèi)存,所以我們的設(shè)計(jì)思路就基本形成了根竿,所以分頁(yè)數(shù)據(jù)導(dǎo)出共有以下3個(gè)步驟:
1陵像、求數(shù)據(jù)庫(kù)中待導(dǎo)出數(shù)據(jù)的行數(shù)
2、根據(jù)行數(shù)求數(shù)據(jù)提取次數(shù)
3寇壳、按次數(shù)將數(shù)據(jù)寫入文件
通過(guò)以上步驟在效率和用戶體驗(yàn)性上都有了很高的提高醒颖,接下來(lái)上代碼
public void exportBigDataExcel(ValueDataDto valueDataDto, String path)
throws IOException {
// 最重要的就是使用SXSSFWorkbook,表示流的方式進(jìn)行操作
// 在內(nèi)存中保持100行壳炎,超過(guò)100行將被刷新到磁盤
SXSSFWorkbook wb = new SXSSFWorkbook(100);
Sheet sh = wb.createSheet(); // 建立新的sheet對(duì)象
Row row = sh.createRow(0); // 創(chuàng)建第一行對(duì)象
// -----------定義表頭-----------
Cell cel0 = row.createCell(0);
cel0.setCellValue("1");
Cell cel2 = row.createCell(1);
cel2.setCellValue("2");
Cell cel3 = row.createCell(2);
cel3.setCellValue("3");
Cell cel4 = row.createCell(3);
// ---------------------------
List<valuedatabean> list = new ArrayList<valuedatabean>();
// 數(shù)據(jù)庫(kù)中存儲(chǔ)的數(shù)據(jù)行
int page_size = 10000;
// 求數(shù)據(jù)庫(kù)中待導(dǎo)出數(shù)據(jù)的行數(shù)
int list_count = this.daoUtils.queryListCount(this.valueDataDao
.queryExportSQL(valueDataDto).get("count_sql"));
// 根據(jù)行數(shù)求數(shù)據(jù)提取次數(shù)
int export_times = list_count % page_size > 0 ? list_count / page_size
+ 1 : list_count / page_size;
// 按次數(shù)將數(shù)據(jù)寫入文件
for (int j = 0; j < export_times; j++) {
list = this.valueDataDao.queryPageList(this.valueDataDao
.queryExportSQL(valueDataDto).get("list_sql"), j + 1,
page_size);
int len = list.size() < page_size ? list.size() : page_size;
到目前
已經(jīng)可以實(shí)現(xiàn)百萬(wàn)數(shù)據(jù)的導(dǎo)出了泞歉,但是當(dāng)我們的業(yè)務(wù)數(shù)據(jù)超過(guò)200萬(wàn),300萬(wàn)了呢冕广?如何解決疏日?
這時(shí),直接打印數(shù)據(jù)到一個(gè)工作簿的一個(gè)工作表是實(shí)現(xiàn)不了的撒汉,必須拆分到多個(gè)工作表沟优,或者多個(gè)工作簿中才能實(shí)現(xiàn)。因?yàn)橐粋€(gè)sheet最多行數(shù)1048576
睬辐。下面就以這種思路提供另外一種解決方案挠阁,直接上代碼(后面會(huì)附上測(cè)試數(shù)據(jù)庫(kù),及案例需要的jar包)
public static void main(String[] args) throws Exception {
Test3SXXFS tm = new Test3SXXFS();
tm.jdbcex(true);
}
public void jdbcex(boolean isClose) throws InstantiationException, IllegalAccessException,
ClassNotFoundException, SQLException, IOException, InterruptedException {
String xlsFile = "f:/poiSXXFSBigData.xlsx"; //輸出文件
//內(nèi)存中只創(chuàng)建100個(gè)對(duì)象溯饵,寫臨時(shí)文件侵俗,當(dāng)超過(guò)100條,就將內(nèi)存中不用的對(duì)象釋放丰刊。
Workbook wb = new SXSSFWorkbook(100); //關(guān)鍵語(yǔ)句
Sheet sheet = null; //工作表對(duì)象
Row nRow = null; //行對(duì)象
Cell nCell = null; //列對(duì)象
//使用jdbc鏈接數(shù)據(jù)庫(kù)
Class.forName("com.mysql.jdbc.Driver").newInstance();
String url = "jdbc:mysql://localhost:3306/bigdata?characterEncoding=UTF-8";
String user = "root";
String password = "123456";
//獲取數(shù)據(jù)庫(kù)連接
Connection conn = DriverManager.getConnection(url, user,password);
Statement stmt = conn.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_UPDATABLE);
String sql = "select * from hpa_normal_tissue limit 1000000"; //100萬(wàn)測(cè)試數(shù)據(jù)
ResultSet rs = stmt.executeQuery(sql);
ResultSetMetaData rsmd = rs.getMetaData();
long startTime = System.currentTimeMillis(); //開始時(shí)間
System.out.println("strat execute time: " + startTime);
int rowNo = 0; //總行號(hào)
int pageRowNo = 0; //頁(yè)行號(hào)
while(rs.next()) {
//打印300000條后切換到下個(gè)工作表隘谣,可根據(jù)需要自行拓展,2百萬(wàn),3百萬(wàn)...數(shù)據(jù)一樣操作寻歧,只要不超過(guò)1048576就可以
if(rowNo%300000==0){
System.out.println("Current Sheet:" + rowNo/300000);
sheet = wb.createSheet("我的第"+(rowNo/300000)+"個(gè)工作簿");//建立新的sheet對(duì)象
sheet = wb.getSheetAt(rowNo/300000); //動(dòng)態(tài)指定當(dāng)前的工作表
pageRowNo = 0; //每當(dāng)新建了工作表就將當(dāng)前工作表的行號(hào)重置為0
}
rowNo++;
nRow = sheet.createRow(pageRowNo++); //新建行對(duì)象
// 打印每行掌栅,每行有6列數(shù)據(jù) rsmd.getColumnCount()==6 --- 列屬性的個(gè)數(shù)
for(int j=0;j<rsmd.getColumnCount();j++){
nCell = nRow.createCell(j);
nCell.setCellValue(rs.getString(j+1));
}
if(rowNo%10000==0){
System.out.println("row no: " + rowNo);
}
// Thread.sleep(1); //休息一下,防止對(duì)CPU占用码泛,其實(shí)影響不大
}
long finishedTime = System.currentTimeMillis(); //處理完成時(shí)間
System.out.println("finished execute time: " + (finishedTime - startTime)/1000 + "m");
FileOutputStream fOut = new FileOutputStream(xlsFile);
wb.write(fOut);
fOut.flush(); //刷新緩沖區(qū)
fOut.close();
long stopTime = System.currentTimeMillis(); //寫文件時(shí)間
System.out.println("write xlsx file time: " + (stopTime - startTime)/1000 + "m");
if(isClose){
this.close(rs, stmt, conn);
}
}
//執(zhí)行關(guān)閉流的操作
private void close(ResultSet rs, Statement stmt, Connection conn ) throws SQLException{
rs.close();
stmt.close();
conn.close();
}