【问题标题】:java.lang.outofmemory exception while reading excel file (xlsx) using POI使用 POI 读取 excel 文件 (xlsx) 时出现 java.lang.outofmemory 异常
【发布时间】:2012-06-20 11:38:11
【问题描述】:

我正在开发一个从 excel 文件 (xlsx) 读取数据的 Web 应用程序。我正在使用 POI 阅读 excel 表。问题是当我尝试读取 excel 文件时,服务器会抛出以下错误:

我尝试读取的 excel 文件大小接近 80 MB。这个问题有什么解决办法吗?

实际上用户在将文件保存到磁盘后正在上传文件和应用程序尝试读取文件。 我用来测试的代码 sn-p 是:

 File savedFile = new File(file_path);

FileInputStream fis = null;
            try {

                fis = new FileInputStream(savedFile);
                XSSFWorkbook xWorkbook = new XSSFWorkbook(fis);
                XSSFSheet xSheet = xWorkbook.getSheetAt(5);

                Iterator rows = xSheet.rowIterator();
                while (rows.hasNext()) {
                    XSSFRow row = (XSSFRow) rows.next();
                    Iterator cells = row.cellIterator();

                    List data = new ArrayList();
                    while (cells.hasNext()) {
                        XSSFCell cell = (XSSFCell) cells.next();
                        System.out.println(cell.getStringCellValue());
                        data.add(cell);
                    }

                }
            } catch (IOException e) {
                e.printStackTrace();
            } 

【问题讨论】:

  • 看起来您正在尝试读取整个 80MB 的缓冲区,这会引发 OOM。
  • 有什么替代方法可以像片段一样阅读这个?
  • 是的。应该有。可能存在文件大小高达 Gb 的情况。该用户是上传文件还是磁盘文件?可以贴一下文件读取的代码sn-p吗?

标签: java excel apache-poi out-of-memory


【解决方案1】:

在开始时打开文件时会有一点不同。如果你有一个文件,那就把它传进去!使用 InputStream 需要将所有内容缓冲到内存中,这会占用空间。既然你不需要做缓冲,那就不要!

如果您使用 POI 的最新夜间版本运行,那么这非常容易。你的代码变成:

File file = new File(file_path);
OPCPackage opcPackage = OPCPackage.open(file);
XSSFWorkbook workbook = new XSSFWorkbook(opcPackage);

不然很相似:

File file = new File(file_path);
OPCPackage opcPackage = OPCPackage.open(file.getAbsolutePath());
XSSFWorkbook workbook = new XSSFWorkbook(opcPackage);

这会释放你一些内存,这可能就足够了。如果不是这样,并且您无法将 Java 堆空间增加到足以应对的程度,那么您将不得不停止使用 XSSF UserModel。

除了您一直在使用的当前友好的 UserModel 之外,POI 还支持较低级别的文件处理方式。这种较低级别的方式更难使用,因为您周围没有各种需要将整个文件存储在内存中的助手。但是,当您以流式方式处理文件时,它的内存效率要高得多。要开始使用,请参阅 POI 网站上的 XSSF and SAX (Event API) How-To section。尝试一下,并查看各种示例。

【讨论】:

    【解决方案2】:

    您可能应该更改 JVM 的设置。尝试将-Xmx1024 -Xms1024 添加到启动器。

    【讨论】:

    • Xms 设置为等于Xmx 被认为是不好的做法。这样做,您基本上不允许 JVM 调整其内存池的大小,并恶化内存管理。
    • 1024 字节?也许您的意思是 -Xmx1024m-mx1g 相同
    • @npe:你是对的。如果您的应用程序无论如何都会使用大量内存,这不是问题。但我不知道这里是不是这样。更一般地说,这个想法是将 Xmx 设置为更高的值。这实际上取决于可用内存和数据大小。
    • 还有一件事:默认值是64M,所以对于大量数据的复杂处理,它很快就会失败。
    • 在 32 位 windows JVM 上默认为 64M。在其他平台上,其 1/4 的主内存高达 32 GB。
    【解决方案3】:

    您可以尝试增加您的 Java 堆大小。

    【讨论】:

      【解决方案4】:

      我认为你必须增加堆的大小。 您可以通过编辑 catalina.bat 文件来完成。将-Xms1024m -Xmx1024m 添加到CATALINA_OPTS 变量中。

      • Xms = 初始 Java 堆大小
      • Xmx = 最大 Java 堆大小

      编辑: 来自 Catalina.bat

      
      rem   CATALINA_OPTS   (Optional) Java runtime options used when the "start",
      rem                   "run" or "debug" command is executed.
      rem                   Include here and not in JAVA_OPTS all options, that should
      rem                   only be used by Tomcat itself, not by the stop process,
      rem                   the version command etc.
      rem                   Examples are heap size, GC logging, JMX ports etc.
      

      【讨论】:

      • 我打开了 catalina.bat 文件。我为 JAVA_POSTS 找到了两到三个条目。编辑哪一个?
      • 我实际上做了以下更改并粘贴了文件段: ------ 如果不存在 "%CATALINA_BASE%\conf\logging.properties" goto noJuliConfig set LOGGING_CONFIG=-Djava.util.logging .config.file="%CATALINA_BASE%\conf \logging.properties" :noJuliConfig 设置 JAVA_OPTS=%JAVA_OPTS% %LOGGING_CONFIG% 如果不是 "%LOGGING_MANAGER%" == "" 转到 noJuliManager 设置 LOGGING_MANAGER=-Djava.util.logging。 manager=org.apache.juli.ClassLoaderLogManager :noJuliManager set JAVA_OPTS=%-Xms1024m -Xmx1024m% %LOGGING_MANAGER% rem ----- 执行请求的命令 ----- ---------------------- 可以吗?
      • 应该有一行set JAVA_OPTS=%JAVA_OPTS% %LOGGING_CONFIG%改为set JAVA_OPTS=%JAVA_OPTS% -Xmx1024m - Xms1024m %LOGGING_CONFIG%
      • 谢谢,但有同样的问题。即使我将这些值增加到 2048。我的 excel 文件大小为 80MB。
      • 你重启tomcat服务了吗?
      【解决方案5】:

      我已经通过改变实施解决了这个问题。实际上,首先我从 Excel 文件中获取所有数据,并且数据存储在 ArrayList 类型中。之后我将数据插入数据库,这才是真正的问题。现在我根本不存储数据。当我从 ResultSet 中获得一条记录时,我立即将其插入 DB,而不是将其存储到 arraylist 中。我知道这个一个一个的插入不是一个好方法,但我暂时正在使用这种方法。以后如果我找到更好的,我肯定会切换到那个。谢谢大家。

      【讨论】:

        【解决方案6】:

        对您当前方法的改进可能是从 excel 中读取大约 100 行(用这个数字进行实验以获得最佳值)并在数据库中进行批量更新。这会更快。

        您还可以在代码中执行一些优化,将列表创建移出外部循环(用于读取行数据的循环)

        列表数据 = new ArrayList();

        读取字符串缓冲区中一行中所有单元格的内容(可能用“逗号”分隔),然后将其添加到arraylist “data”

        您正在将XSSFRow 类型的对象添加到数组列表中。存储excel单元格的整个对象是没有意义的。取出其内容并丢弃该对象。

        稍后在将内容插入数据库之前,您可以拆分分隔的单元格内容并执行插入。

        希望这会有所帮助!

        【讨论】:

          【解决方案7】:

          您最好将它们存储在文件中,然后尝试将它们加载到数据库中。 这将避免单插入一个

          【讨论】:

          • 这应该是一条评论。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-03-24
          • 2011-11-11
          • 1970-01-01
          相关资源
          最近更新 更多