(翻译)Java使用POI中的SXSSF处理大数据量Excel文档

SXSSF位于org.apache.poi.xssf.streaming包中,在兼容XSSF的同时,能够应对大数据量和内存空间有限的情况。SXSSF每次获取的行数是在一个数值范围内,这个范围被称为“滑动窗口”,在这个窗口内的数据均存在于内存中,超出这个窗口大小时,数据会被写入磁盘,由此控制内存使用,相比较而言,XSSF则每次都是获取全部行。

这个“滑动窗口”的大小在定义SXSSF实例的时候,可以由构造函数中的参数指定,方法如下:

1 new SXSSFWorkbook(int windowSize)
SXSSFWorkbook.DEFAULT_WINDOW_SIZE是一个默认的窗口大小,其值为100。窗口大小为-1,表示不限制窗口大小,这里普及一下,excel 2003最多只允许存储65536条数据,excel2007以上版本可以支持1048576条数据,单个sheet表就支持近104万条数据了。如果窗口大小=-1,则只有手动调用flushRows()时,数据才会被写入磁盘。
 
createRow()可以创建一个新的行,此时内存中的"窗口"大小就增加,如果超出了窗口限制,索引值最小的行会先被“刷入”磁盘中,一旦某行数据被写入磁盘,则不能使用getRow()方法获取该数据。
需要注意,SXSSF会自动分配临时文件,这些临时文件需要我们手动清除,清除的方式是使用dispose()方法,例如:
SXSSFWorkbook wb = new SXSSFWorkbook(100);
wb.dispose();
SXSSFWorkbook默认使用内联字符(inline strings),而不是共享字符(shared strings),两者的区别在于,如果两个单元格存储了相同的字符串,inline strings把它们当做两个字符串对待,每个单元格都保留此字符串的值,而shared strings则只在内存中保留一个值,单元格中只保留这个字符串的引用。
inline strings的好处是比较高效,因为不需要在内存中保留字符串的内容,坏处是有可能存在兼容性问题。而shared strings的好处是当文档中存在很多重复的字符内容时,该方式能节省空间,产生的文档也会相对更小,坏处是需要把所有字符串内容保存在内存中,这样插入新的字符数据的时候才能知道是否已经存在。因此需要根据内存的实际情况,判断使用哪种string。
除了字符之外,诸如合并单元格、超链接、批注等是直接存储在内存里面的,因此如果文档中大量使用这些特性,需要留意内存的空间。
 
下面这个例子将“窗口”大小设置为100,当行数达到101时,第0行的数据被写入磁盘,然后当行数=102时,第1行的数据被写入磁盘,以此类推:
import junit.framework.Assert;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;

    public static void main(String[] args) throws Throwable {
        SXSSFWorkbook wb = new SXSSFWorkbook(100); // 在内存中保留100行数据,其余的写入磁盘
        Sheet sh = wb.createSheet();
        for(int rownum = 0; rownum < 1000; rownum++){
            Row row = sh.createRow(rownum);
            for(int cellnum = 0; cellnum < 10; cellnum++){
                Cell cell = row.createCell(cellnum);
                String address = new CellReference(cell).formatAsString();
                cell.setCellValue(address);
            }

        }

        // 行号小于900的数据被写入磁盘,在内存中无法访问
        for(int rownum = 0; rownum < 900; rownum++){
          Assert.assertNull(sh.getRow(rownum));
        }

        // 最后的100行数据仍然在内存中
        for(int rownum = 900; rownum < 1000; rownum++){
            Assert.assertNotNull(sh.getRow(rownum));
        }
        
        FileOutputStream out = new FileOutputStream("/temp/sxssf.xlsx");
        wb.write(out);
        out.close();

        // 将此workbook对应的临时文件删除
        wb.dispose();
    }

第二个例子演示,在关闭自动写入磁盘的属性(即将窗口大小设置为-1)时,手动控制何时写入磁盘:

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;

    public static void main(String[] args) throws Throwable {
        SXSSFWorkbook wb = new SXSSFWorkbook(-1); // 关闭自动写入磁盘的功能,所有数据将放在内存中
        Sheet sh = wb.createSheet();
        for(int rownum = 0; rownum < 1000; rownum++){
            Row row = sh.createRow(rownum);
            for(int cellnum = 0; cellnum < 10; cellnum++){
                Cell cell = row.createCell(cellnum);
                String address = new CellReference(cell).formatAsString();
                cell.setCellValue(address);
            }

           // 手动控制写入磁盘的时机
           if(rownum % 100 == 0) {
                ((SXSSFSheet)sh).flushRows(100); // 保留最后100行数据,将其余数据写入磁盘中
                // ((SXSSFSheet)sh).flushRows() 相当于 ((SXSSFSheet)sh).flushRows(0),表示将所有数据写入磁盘
           }

        }

        FileOutputStream out = new FileOutputStream("/temp/sxssf.xlsx");
        wb.write(out);
        out.close();

        // 删除临时文件
        wb.dispose();
   }

 

posted @ 2018-11-08 17:49  游学者  阅读(5366)  评论(0编辑  收藏  举报