JAVA使用POI如何導(dǎo)出百萬(wàn)級(jí)別數(shù)據(jù)

此文章出自:<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(); 
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末猾封,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子噪珊,更是在濱河造成了極大的恐慌晌缘,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件痢站,死亡現(xiàn)場(chǎng)離奇詭異磷箕,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)瑟押,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門搀捷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人多望,你說(shuō)我怎么就攤上這事嫩舟。” “怎么了怀偷?”我有些...
    開封第一講書人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵家厌,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我椎工,道長(zhǎng)饭于,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任维蒙,我火速辦了婚禮掰吕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘颅痊。我一直安慰自己殖熟,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開白布斑响。 她就那樣靜靜地躺著菱属,像睡著了一般。 火紅的嫁衣襯著肌膚如雪舰罚。 梳的紋絲不亂的頭發(fā)上纽门,一...
    開封第一講書人閱讀 51,443評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音营罢,去河邊找鬼赏陵。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蝙搔。 我是一名探鬼主播候醒,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼杂瘸!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起伙菊,我...
    開封第一講書人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤败玉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后镜硕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體运翼,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年兴枯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了血淌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡财剖,死狀恐怖悠夯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情躺坟,我是刑警寧澤沦补,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站咪橙,受9級(jí)特大地震影響夕膀,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜美侦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一产舞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧菠剩,春花似錦易猫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至嘴办,卻和暖如春瞬场,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背涧郊。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工贯被, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓彤灶,卻偏偏與公主長(zhǎng)得像看幼,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子幌陕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容