如何加快 apache POI 中自动调整列大小的速度?

问题描述 投票:0回答:5

我使用以下代码来自动调整电子表格中的列大小:

for (int i = 0; i < columns.size(); i++) {
   sheet.autoSizeColumn(i, true);
   sheet.setColumnWidth(i, sheet.getColumnWidth(i) + 600);
}

问题是,对于超过 3000 行的大型电子表格,自动调整每列大小需要 10 分钟以上。不过,对于小文档来说,它的速度非常快。有什么可以帮助自动调整大小以更快地工作吗?

java performance excel apache-poi autosize
5个回答
52
投票

对我有用的解决方案:

可以避免合并区域,因此我可以迭代其他单元格,最后自动调整到最大的单元格,如下所示:

int width = ((int)(maxNumCharacters * 1.14388)) * 256;
sheet.setColumnWidth(i, width);

其中 1.14388 是“Serif”字体的最大字符宽度和 256 个字体单元。

自动调整大小的性能从 10 分钟提高到 6 秒。


2
投票

autoSizeColumn 函数本身并不完美,有些列的宽度并不完全适合内部的数据。所以,我找到了一些适合我的解决方案。

  1. 为了避免疯狂的计算,请将其交给 autoSizeColumn() 函数:
   sheet.autoSizeColumn(<columnIndex>);
  1. 现在,我们的列由库自动调整大小,但我们不会在当前列宽度上添加一点点以使表格看起来不错:
   // get autosized column width
   int currentColumnWidth = sheet.getColumnWidth(<columnIndex>);

   // add custom value to the current width and apply it to column
   sheet.setColumnWidth(<columnIndex>, (currentColumnWidth + 2500));
  1. 完整的功能可能如下所示:
   public void autoSizeColumns(Workbook workbook) {
        int numberOfSheets = workbook.getNumberOfSheets();
        for (int i = 0; i < numberOfSheets; i++) {
            Sheet sheet = workbook.getSheetAt(i);
            if (sheet.getPhysicalNumberOfRows() > 0) {
                Row row = sheet.getRow(sheet.getFirstRowNum());
                Iterator<Cell> cellIterator = row.cellIterator();
                while (cellIterator.hasNext()) {
                    Cell cell = cellIterator.next();
                    int columnIndex = cell.getColumnIndex();
                    sheet.autoSizeColumn(columnIndex);
                    int currentColumnWidth = sheet.getColumnWidth(columnIndex);
                    sheet.setColumnWidth(columnIndex, (currentColumnWidth + 2500));
                }
            }
        }
    }

附注感谢 Ondrej Kvasnovsky 的功能 https://stackoverflow.com/a/35324693/13087091


0
投票

autosizeColumn()
功能非常慢且效率低下。甚至 apache POI 的作者在文档中也提到:

对于大纸张,此过程可能相对较慢,...

手动计算和设置单元格的宽度要快得多 - 在我的例子中,我将时间从 ~25,000ms 减少到 ~1-5ms

这就是实现它的方法(我基于Vladimir Shcherbukhin的答案:

Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet();
final int[] maxNumCharactersInColumns = new int[headers.length]; // maximum number of characters in columns. Necessary to calculate the cell width in most efficient way. sheet.autoSizeColumn(...) is very slow.

Row headersRow = sheet.createRow(0);
CellStyle headerStyle = createHeadersStyle(workbook); // createHeadersStyle() is my own function. Create headers style if you want

for (int i = 0; i < headers.length; i++) { // create headers
        Cell headerCell = headersRow.createCell(i, CELL_TYPE_STRING);
        headerCell.setCellValue(headers[i]);
        headerCell.setCellStyle(headerStyle);

        int length = headers[i].length();
        if (maxNumCharactersInColumns[i] < length) { // adjust the columns width
            maxNumCharactersInColumns[i] = length + 2; // you can add +2 if you have filtering enabled on your headers
        }
}

int rowIndex = 1;
    for (List<Object> rowValues : rows) {
        Row row = sheet.createRow(rowIndex);

        int columnIndex = 0;
        for (Object value : rowValues) {
            Cell cell = createRowCell(row, value, columnIndex); // createRowCell() is my own function.

            int length;
            if (cell.getCellType() == Cell.CELL_TYPE_STRING) {
                String cellValue = cell.getStringCellValue();

                // this is quite important part. In some excel spreadsheet you can have a values with line-breaks. It'll be cool to handle that scenario :)
                String[] arr = cellValue.split("\n"); // if cell contains complex value with line breaks, calculate only the longest line
                length = Arrays.stream(arr).map(String::length).max(Integer::compareTo).get();
            } else {
                length = value != null ? value.toString().length() : 0;
            }

            if (maxNumCharactersInColumns[columnIndex] < length) { // if the current cell value is the longest one, save it to an array
                maxNumCharactersInColumns[columnIndex] = length;
            }

            columnIndex++;
        }
        rowIndex++;
    }

    for (int i = 0; i < headers.length; i++) {
        int width = (int) (maxNumCharactersInColumns[i] * 1.45f) * 256; // 1.45f <- you can change this value
        sheet.setColumnWidth(i, Math.min(width, MAX_CELL_WIDTH)); // <- set calculated cell width
    }

    sheet.setAutoFilter(new CellRangeAddress(0, 0, 0, headers.length - 1));

    ByteArrayOutputStream output = new ByteArrayOutputStream();
    workbook.write(output);
    workbook.close();

0
投票

不幸的是,我还没有足够的声誉来在答案中添加评论。所以这里有一些注释:

  • 使用
    Row row = sheet.getRow(sheet.getFirstRowNum());
    时请放心,该行至少包含最后一列中的一个值。否则,cellIterator 将结束得太早,即,如果后续行在此列中有值,则该列将不会自动调整大小。如果
    row
    包含标题(列名),则可以绕过此问题。或者明确使用已知的标题行,例如
    int indexOfHeaderRow = ...;
    ...
    Row row = sheet.getRow(indexOfHeaderRow);
  • 雅库布·斯洛维科夫斯基
    sheet.setColumnWidth(i, Math.min(width, MAX_CELL_WIDTH)); // <- set calculated cellwidth

    我对这一行不太确定,因为没有有关 MAX_CELL_WIDTH 内容的信息 - 也许是总体最大值?所以我改用:
    sheet.setColumnWidth(i, Math.max(width, 2048));

    2048 接缝是默认宽度吗?该值可防止空列的宽度极窄。

0
投票

我遇到的XSSFSheet最大的问题是它没有返回特定列的所有值的功能,你必须按行迭代所有值,所以当你指定列时,它实际上会迭代所有行和列。这可能是缓慢的来源 我做了自己的实现,它只迭代所有列和行一次,然后更改

listOfColumns
中指定的列,我还为处理长文本的情况添加了
maxWidth
,这样你就不会得到疯狂的宽度。添加字体宽度的实现可能是个好主意,就像
Zz'Rot
在他的实现中那样

private static void autoSizeColumns(XSSFSheet sheet, Integer maxWidth, List<Integer> listOfColumns) {
        HashMap<Integer,Integer> sizesArray = new HashMap<>();
        for (Row row : sheet) {
            for (Cell cell : row) {
                switch (cell.getCellType()) {
                    case STRING,FORMULA ->
                            sizesArray.put(cell.getColumnIndex(), Math.max(sizesArray.size() > cell.getColumnIndex() ? sizesArray.get(cell.getColumnIndex()) : 0, cell.getStringCellValue().length()));
                    case NUMERIC ->
                            sizesArray.put(cell.getColumnIndex(), Math.max(sizesArray.size() > cell.getColumnIndex() ? sizesArray.get(cell.getColumnIndex()) : 0, Double.toString(cell.getNumericCellValue()).length()));
                    case BOOLEAN ->
                            sizesArray.put(cell.getColumnIndex(), Math.max(sizesArray.size() > cell.getColumnIndex() ? sizesArray.get(cell.getColumnIndex()) : 0, Boolean.toString(cell.getBooleanCellValue()).length()));
                    case ERROR ->
                            sizesArray.put(cell.getColumnIndex(), Math.max(sizesArray.size() > cell.getColumnIndex() ? sizesArray.get(cell.getColumnIndex()) : 0, Byte.toString(cell.getErrorCellValue()).length()));

                }
            }
        }
        if(listOfColumns == null){
            for (int i = 0; i < sizesArray.size(); i++) {
                int width = sizesArray.get(i)* 256;
                if (maxWidth != null && width > maxWidth) {
                    width = maxWidth;
                }
                sheet.setColumnWidth(i, width);
            }
            return;
        }
        for (Integer column : listOfColumns) {
            Integer size = sizesArray.get(column);
            int width = sizesArray.get(size) * 256;
            if (maxWidth != null && width > maxWidth) {
                width = maxWidth;
            }
            sheet.setColumnWidth(column, width);
        }
    }
© www.soinside.com 2019 - 2024. All rights reserved.