成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

快手二面:敢不敢說(shuō)說(shuō)為啥POI會(huì)導(dǎo)致內(nèi)存溢出?

開發(fā) 開發(fā)工具
在POI中,提供了SXSSFWorkbook,通過(guò)將部分?jǐn)?shù)據(jù)寫入磁盤上的臨時(shí)文件來(lái)減少內(nèi)存占用。但是SXSSFWorkbook只能用于文件寫入,但是文件讀取還是不行的,就像我們前面分析過(guò)的,Excel的文件讀取還是會(huì)存在內(nèi)存溢出的問(wèn)題的。?

Apache POI,是一個(gè)非常流行的文檔處理工具,通常大家會(huì)選擇用它來(lái)處理Excel文件。但是在實(shí)際使用的時(shí)候,經(jīng)常會(huì)遇到內(nèi)存溢出的情況,那么,為啥他會(huì)導(dǎo)致內(nèi)存溢出呢?

Excel并沒(méi)看到的那么小

我們通常見到的xlsx文件,其實(shí)是一個(gè)個(gè)壓縮文件。它們把若干個(gè)XML格式的純文本文件壓縮在一起,Excel就是讀取這些壓縮文件的信息,最后展現(xiàn)出一個(gè)完全圖形化的電子表格。

所以,如果我們把xlsx文件的后綴更改為.zip或.rar,再進(jìn)行解壓縮,就能提取出構(gòu)成Excel的核心源碼文件。解壓后會(huì)發(fā)現(xiàn)解壓后的文件中有3個(gè)文件夾和1個(gè)XML格式文件:

圖片圖片

_rels 文件夾 看里面數(shù)據(jù)像是一些基礎(chǔ)的配置信息,比如 workbook 文件的位置等信息,一般不會(huì)去動(dòng)它. 

docProps 文件夾下重要的文件是一個(gè) app.xml,這里面主要存放了 sheet 的信息,如果想添加或編輯 sheet 需要改這個(gè)文件.其他文件都是一些基礎(chǔ)信息的數(shù)據(jù),比如文件所有者,創(chuàng)建時(shí)間等.

 xl 文件夾是最重要的一個(gè)文件夾,里面存放了 Sheet 中的數(shù)據(jù),行和列的格式,單元格的格式,sheet 的配置信息等等信息.

所以,實(shí)際上我們處理的xlsx文件實(shí)際上是一個(gè)經(jīng)過(guò)高度壓縮的文件格式,背后是有好多文件支持的。所以,我們看到的一個(gè)文件可能只有2M,但是實(shí)際上這個(gè)文件未壓縮情況下可能要比這大得多。

圖片圖片

也就是說(shuō),POI在處理的時(shí)候,處理的實(shí)際上并不只是我們看到的文件大小,實(shí)際上他的大小大好幾倍。(本文節(jié)選自我的《java面試寶典》)

這是為什么明明我們處理的文件只有100多兆,但是實(shí)際卻可能占用1G內(nèi)存的其中一個(gè)原因。當(dāng)然這只是其中一個(gè)原因,還有一個(gè)原因,我們就需要深入到POI的源碼中來(lái)看了。

POI溢出原理

我們拿POI的文件讀取來(lái)舉例,一般來(lái)說(shuō)文件讀取出現(xiàn)內(nèi)存溢出的情況更多一些。以下是一個(gè)POI文件導(dǎo)出的代碼示例:

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;


import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;


public class ExcelReadTest {


    public static void main(String[] args) {
        // 指定要讀取的文件路徑
        String filename = "example.xlsx";


        try (FileInputStream fileInputStream = new FileInputStream(new File(filename))) {
            // 創(chuàng)建工作簿對(duì)象
            Workbook workbook = new XSSFWorkbook(fileInputStream);


            // 獲取第一個(gè)工作表
            Sheet sheet = workbook.getSheetAt(0);


            // 遍歷所有行
            for (Row row : sheet) {
                // 遍歷所有單元格
                for (Cell cell : row) {                   
                    // 根據(jù)不同數(shù)據(jù)類型處理數(shù)據(jù)
                    switch (cell.getCellType()) {
                        case STRING:
                            System.out.print(cell.getStringCellValue() + "\t");
                            break;
                        case NUMERIC:
                            if (DateUtil.isCellDateFormatted(cell)) {
                                System.out.print(cell.getDateCellValue() + "\t");
                            } else {
                                System.out.print(cell.getNumericCellValue() + "\t");
                            }
                            break;
                        case BOOLEAN:
                            System.out.print(cell.getBooleanCellValue() + "\t");
                            break;
                        case FORMULA:
                            System.out.print(cell.getCellFormula() + "\t");
                            break;
                        default:
                            System.out.print(" ");
                    }
                }
                System.out.println();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }


}

這里面用到了一個(gè)關(guān)鍵的XSSFWorkbook類:

public XSSFWorkbook(InputStream is) throws IOException {
    this(PackageHelper.open(is));
}


public static OPCPackage open(InputStream is) throws IOException {
    try {
        return OPCPackage.open(is);
    } catch (InvalidFormatException e){
        throw new POIXMLException(e);
    }
}

最終會(huì)調(diào)用到OPCPackage.open方法,看看這個(gè)方法是咋實(shí)現(xiàn)的:

/**
 * Open a package.
 *
 * Note - uses quite a bit more memory than {@link #open(String)}, which
 * doesn't need to hold the whole zip file in memory, and can take advantage
 * of native methods
 *
 * @param in
 *            The InputStream to read the package from
 * @return A PackageBase object
 *
 * @throws InvalidFormatException
 *         Throws if the specified file exist and is not valid.
 * @throws IOException If reading the stream fails
 */
public static OPCPackage open(InputStream in) throws InvalidFormatException,
        IOException {
    OPCPackage pack = new ZipPackage(in, PackageAccess.READ_WRITE);
    try {
        if (pack.partList == null) {
            pack.getParts();
        }
    } catch (InvalidFormatException | RuntimeException e) {
        IOUtils.closeQuietly(pack);
        throw e;
    }
    return pack;
}

這行代碼的注釋中說(shuō)了:這個(gè)方法會(huì)把整個(gè)壓縮文件都加載到內(nèi)存中。也就是把整個(gè) Excel 文檔加載到內(nèi)存中,可想而知,這在處理大型文件時(shí)是肯定會(huì)導(dǎo)致導(dǎo)致內(nèi)存溢出的。(本文節(jié)選自我的《java面試寶典》,里面有800多道面試常考題目)

也就是說(shuō)我們使用的XSSFWorkbook(包括HSSFWorkbook也同理)在處理Excel的過(guò)程中會(huì)將整個(gè)Excel都加載到內(nèi)存中,在文件比較大的時(shí)候就會(huì)導(dǎo)致內(nèi)存溢出。

如何解決溢出問(wèn)題?

在POI中,提供了SXSSFWorkbook,通過(guò)將部分?jǐn)?shù)據(jù)寫入磁盤上的臨時(shí)文件來(lái)減少內(nèi)存占用。但是SXSSFWorkbook只能用于文件寫入,但是文件讀取還是不行的,就像我們前面分析過(guò)的,Excel的文件讀取還是會(huì)存在內(nèi)存溢出的問(wèn)題的。

那如果要解決這個(gè)問(wèn)題,可以考慮使用EasyExcel?。ū疚墓?jié)選自我的《java面試寶典》,里面有800多道面試??碱}目)

關(guān)于使用XSSFWorkbook和EasyExcel的文件讀取,我這里也做了個(gè)內(nèi)存占用的對(duì)比,讀取一個(gè)27.3?MB的文件:

package excel.read;


import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;


import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;


public class XSSFExcelReadTest {


    public static void main(String[] args) {
        // 指定要讀取的文件路徑
        String filename = "example.xlsx";


        try (FileInputStream fileInputStream = new FileInputStream(new File(filename))) {
            // 創(chuàng)建工作簿對(duì)象
            Workbook workbook = new XSSFWorkbook(fileInputStream);


            // 獲取第一個(gè)工作表
            Sheet sheet = workbook.getSheetAt(0);


            // 遍歷所有行
            for (Row row : sheet) {
                // 遍歷所有單元格
                for (Cell cell : row) {
                    // 根據(jù)不同數(shù)據(jù)類型處理數(shù)據(jù)
                    switch (cell.getCellType()) {
                        case STRING:
                            System.out.print(cell.getStringCellValue() + "\t");
                            break;
                        case NUMERIC:
                            if (DateUtil.isCellDateFormatted(cell)) {
                                System.out.print(cell.getDateCellValue() + "\t");
                            } else {
                                System.out.print(cell.getNumericCellValue() + "\t");
                            }
                            break;
                        case BOOLEAN:
                            System.out.print(cell.getBooleanCellValue() + "\t");
                            break;
                        case FORMULA:
                            System.out.print(cell.getCellFormula() + "\t");
                            break;
                        default:
                            System.out.print(" ");
                    }
                }
                System.out.println();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } 
    }


}

使用Arthas查看內(nèi)存占用情況:

圖片圖片

占用內(nèi)存在1000+M。

改成使用EasyExcel同樣讀取同一份文件:

package excel.read;


import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;


public class EasyExcelReadTest {


    public static void main(String[] args) {
        // 指定要讀取的文件路徑
        String filename = "example.xlsx";


        EasyExcel.read(filename, new PrintDataListener()).sheet().doRead();
    }


}


// 監(jiān)聽器,用于處理讀取到的數(shù)據(jù)
class PrintDataListener implements ReadListener<Object> {
    @Override
    public void invoke(Object data, AnalysisContext context) {
        // 處理每一行的數(shù)據(jù)
        System.out.println(data);
    }


    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 所有數(shù)據(jù)解析完成后的操作
    }


    @Override
    public void onException(Exception exception, AnalysisContext context) throws Exception {
        // 處理讀取過(guò)程中的異常
    }
}

同樣使用Arthas查看內(nèi)存占用情況:

圖片圖片

內(nèi)存占用只有不到100M。

責(zé)任編輯:武曉燕 來(lái)源: Hollis
相關(guān)推薦

2014-01-23 16:53:49

2018-10-08 10:18:13

2015-02-09 17:38:56

愛(ài)情保鮮期平安WiFi

2010-11-10 12:38:50

10G網(wǎng)絡(luò)以太網(wǎng)

2009-05-04 09:26:39

2021-02-28 20:52:41

5G自動(dòng)駕駛數(shù)據(jù)

2022-10-28 12:18:18

AI繪畫自拍

2013-03-28 13:33:39

魅族MEIZU招聘

2020-12-27 10:44:55

微信支付寶互聯(lián)網(wǎng)應(yīng)用

2020-12-28 06:20:04

微信支付寶移動(dòng)應(yīng)用

2022-10-18 08:38:16

內(nèi)存泄漏線程

2024-10-28 11:07:33

磁盤目錄文件

2020-01-17 20:00:25

SQL函數(shù)數(shù)據(jù)庫(kù)

2019-10-10 09:34:19

Python網(wǎng)絡(luò)爬蟲GitHub

2024-05-24 10:15:36

2024-10-24 16:51:08

2024-03-25 12:38:00

MySQL內(nèi)存參數(shù)

2024-07-04 17:22:23

2021-08-26 05:00:44

生產(chǎn)環(huán)境內(nèi)存

2024-03-11 08:22:40

Java內(nèi)存泄漏
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 国产精品久久久久久久久 | 欧美日韩福利 | 国产精品成人一区二区 | 免费午夜剧场 | 一区二区三区日韩精品 | 老熟女毛片 | 成人午夜电影网 | 在线免费观看视频你懂的 | 国产99久久久国产精品 | 国产精品美女久久久 | 少妇黄色 | 久久日本 | 国产精品一区二区福利视频 | 日本a∨视频| 亚洲人成人一区二区在线观看 | 国产视频一区二区 | 手机看片169| 久草在线在线精品观看 | 久久成人精品一区二区三区 | 99免费看 | 国产欧美日韩综合精品一 | 亚洲在线中文字幕 | 国产精品视频久久 | 九九亚洲 | 日韩精品在线观看视频 | 亚洲九九精品 | 亚洲一区二区三区观看 | 日韩视频 中文字幕 | 国产精品自产拍 | 日韩欧美在线观看 | 在线看中文字幕 | 国产高清在线观看 | 国产成人亚洲精品 | www.奇米| 国产99视频精品免视看9 | 国产一区二区精品在线观看 | 中文字幕 国产精品 | 日本电影一区二区 | 剑来高清在线观看 | 国产精品美女在线观看 | 亚洲精品免费视频 |