POIのhssfを使って、巨大なエクセルファイルを読み込むには。
日本企業の皆様におかれましては、エクセルが大好きかと、存じます。
いや、僕も大好きなのだけどもさ、表計算かわいいよ、表計算。
ウルトラハイパフォーマンスで、データストアとしても超一流。
コードの自動生成してもいいし、まかり間違ったりすると、方眼紙としても使えちゃったり…とか。
と、まぁ、色々使い道が多いので、ついJavaでもエクセルを読書きしたくなったりとかするヨネ。
で、そこでApache POIでつよ、となる訳だけど、POIのHSSFは普通に使うと、
ちょっと大きいエクセルファイルを読もうとしただけで、アフォ程メモリを食ってしまうのですな。
例えば、こんなコードで読み込むのが、まぁ普通ダヨネ。
@Override protected void readExcel(File excel) { BufferedInputStream bi = null; try { bi = new BufferedInputStream(new FileInputStream(excel)); HSSFWorkbook workbook = new HSSFWorkbook(bi); for (int i = 0, seetSize = workbook.getNumberOfSheets(); i < seetSize; i++) { HSSFSheet sheet = workbook.getSheetAt(i); LOG.debug(sheet.toString()); for (int j = 0, rowSize = sheet.getPhysicalNumberOfRows(); j < rowSize; j++) { HSSFRow row = sheet.getRow(j); LOG.debug(row.toString()); for (int cellNum = row.getFirstCellNum(), last = row .getLastCellNum() + 1; cellNum < last; cellNum++) { HSSFCell cell = row.getCell(cellNum); LOG.debug(cell); } } } } catch (Exception e) { LOG.error(e.getMessage(), e); } finally { FileUtil.close(bi); } }
このコードだと、実は、100MBとかあるエクセルファイルを読むのに500MB程度のヒープサイズじゃ、全然足りなかったりする。
クマった。
で、どうするのかと言うと、イベントAPIなるものが存在するのでつ。
こやつを使うと、大体、元のエクセルファイルと同じか少し多めのヒープサイズがあれば、エクセルファイルを処理できまつ。
ドキュメント内に丁度いい感じのコードが無かったので、僕も少し書いてみたり。
すげぇでっかくなっちゃったけど、まぁこんな感じ。
謎のインナークラスWorkbookScopeが、味噌だったりする。
何でこんなコードなのかは各自考えてクダサシ。
public class HssfEventMain { static final Log LOG = LogFactory.getLog(HssfEventMain.class); public static void main(String[] args) { File excel = new File("datas/Excel2000/cell/100_256.xls"); new HssfEventMain().readExcel(excel); } class WorkbookScope { FormatTrackingHSSFListener tracker; SheetRecordCollectingListener collector; } protected void readExcel(File excel) { BufferedInputStream in = null; try { final WorkbookScope t = new WorkbookScope(); MissingRecordAwareHSSFListener missingAware = new MissingRecordAwareHSSFListener( new HSSFListener() { @Override public void processRecord(Record record) { handle(record, t); } }); t.tracker = new FormatTrackingHSSFListener(missingAware); t.collector = new SheetRecordCollectingListener(t.tracker); HSSFRequest request = new HSSFRequest(); request.addListenerForAllRecords(t.collector); in = new BufferedInputStream(new FileInputStream(excel)); POIFSFileSystem fs = new POIFSFileSystem(in); HSSFEventFactory factory = new HSSFEventFactory(); factory.processWorkbookEvents(request, fs); } catch (Exception e) { LOG.error(e.getMessage(), e); } finally { FileUtil.close(in); } } protected void handle(Record record, WorkbookScope ws) { switch (record.getSid()) { case BoundSheetRecord.sid: { // Bound Sheet Record (aka BundleSheet) (0x0085) handle((BoundSheetRecord) record, ws); break; } case BOFRecord.sid: { // Beginning Of File (0x0809) handle((BOFRecord) record, ws); break; } case BlankRecord.sid: { // Blank cell record (0x0201) // Represents a column in a row with no value but with styling. handle((BlankRecord) record, ws); break; } case FormulaRecord.sid: { // Formula Record (0x0006) handle((FormulaRecord) record, ws); break; } case StringRecord.sid: { handle((StringRecord) record, ws); break; } case LabelRecord.sid: { handle((LabelRecord) record, ws); break; } case LabelSSTRecord.sid: { handle((LabelSSTRecord) record, ws); break; } case NumberRecord.sid: { handle((NumberRecord) record, ws); break; } case RowRecord.sid: { break; } case -1: { handleDummyRecord(record, ws); break; } default: { LOG.debug(String.format("sid:[%s] record:[%s]", record.getSid(), record.toString())); break; } } } protected void handle(BoundSheetRecord r, WorkbookScope ws) { String fmt = "seetname :[%s] isHidden:[%s] isVeryHidden:[%s]"; LOG.debug(String.format(fmt, r.getSheetname(), r.isHidden(), r .isVeryHidden())); } static final Map<Integer, String> types = new HashMap<Integer, String>(); static { types.put(BOFRecord.TYPE_CHART, "CHART"); types.put(BOFRecord.TYPE_EXCEL_4_MACRO, "EXCEL_4_MACRO"); types.put(BOFRecord.TYPE_VB_MODULE, "VB_MODULE"); types.put(BOFRecord.TYPE_WORKBOOK, "WORKBOOK"); types.put(BOFRecord.TYPE_WORKSHEET, "WORKSHEET"); types.put(BOFRecord.TYPE_WORKSPACE_FILE, "WORKSPACE_FILE"); } protected void handle(BOFRecord r, WorkbookScope ws) { String fmt = "Build:[%s] BuildYear:[%s] RequiredVersion:[%s] Version:[%s] Type:[%s]"; String type = types.get(r.getType()); if (type == null) { type = "UnKnown Type"; } LOG.debug(String.format(fmt, r.getBuild(), r.getBuildYear(), r .getRequiredVersion(), r.getVersion(), type)); } protected void handle(BlankRecord r, WorkbookScope ws) { String fmt = "Column:[%s] Row:[%s] XFIndex:[%s]"; LOG .debug(String.format(fmt, r.getColumn(), r.getRow(), r .getXFIndex())); } protected void handle(FormulaRecord r, WorkbookScope ws) { LOG.debug(ws.tracker.formatNumberDateCell(r)); } protected void handle(StringRecord r, WorkbookScope ws) { LOG.debug(r.getString()); } protected void handle(LabelRecord r, WorkbookScope ws) { LOG.debug(r.getValue()); } protected void handle(LabelSSTRecord r, WorkbookScope ws) { SSTRecord sst = ws.collector.getSSTRecord(); if (sst == null) { LOG.debug("\"(No SST Record, can't identify string)\""); } else { LOG.debug(sst.getString(r.getSSTIndex())); } } protected void handle(NumberRecord r, WorkbookScope ws) { LOG.debug(ws.tracker.formatNumberDateCell(r)); } protected void handle(RowRecord r, WorkbookScope ws) { String fmt = "rowNumber[%s] firstCol[%s] lastCol[%s]"; LOG.debug(String.format(fmt, r.getRowNumber(), r.getFirstCol(), r .getLastCol())); } protected void handleDummyRecord(Record r, WorkbookScope ws) { String fmt = "DummyRecord %s"; LOG.debug(String.format(fmt, r.getClass().getName())); } }
で、OLE2をJavaで読み書きできる様にする為なのか、良く分からないのだけど、
- org.apache.poi.poifs.filesystem.POIFSFileSystem
って奴が、エクセルの中身を全てbyte[]で抱えちゃうんだよねぇ…。
これは、どうにもならんもんかねぇ…。
コンストラクタだけコードを抜粋しとくます。
public POIFSFileSystem(InputStream stream) throws IOException { this(); boolean success = false; HeaderBlockReader header_block_reader; RawDataBlockList data_blocks; try { // read the header block from the stream header_block_reader = new HeaderBlockReader(stream); bigBlockSize = header_block_reader.getBigBlockSize(); // read the rest of the stream into blocks data_blocks = new RawDataBlockList(stream, bigBlockSize); success = true; } finally { closeInputStream(stream, success); } // set up the block allocation table (necessary for the // data_blocks to be manageable new BlockAllocationTableReader(header_block_reader.getBATCount(), header_block_reader.getBATArray(), header_block_reader.getXBATCount(), header_block_reader.getXBATIndex(), data_blocks); // get property table from the document PropertyTable properties = new PropertyTable(header_block_reader.getPropertyStart(), data_blocks); // init documents processProperties( SmallBlockTableReader.getSmallDocumentBlocks( data_blocks, properties.getRoot(), header_block_reader.getSBATStart() ), data_blocks, properties.getRoot().getChildren(), null, header_block_reader.getPropertyStart() ); }