我使用以下代码来自动调整电子表格中的列大小:
for (int i = 0; i < columns.size(); i++) {
sheet.autoSizeColumn(i, true);
sheet.setColumnWidth(i, sheet.getColumnWidth(i) + 600);
}
问题是,对于超过 3000 行的大型电子表格,自动调整每列大小需要 10 分钟以上。不过,对于小文档来说,它的速度非常快。有什么可以帮助自动调整大小以更快地工作吗?
对我有用的解决方案:
可以避免合并区域,因此我可以迭代其他单元格,最后自动调整到最大的单元格,如下所示:
int width = ((int)(maxNumCharacters * 1.14388)) * 256;
sheet.setColumnWidth(i, width);
其中 1.14388 是“Serif”字体的最大字符宽度和 256 个字体单元。
自动调整大小的性能从 10 分钟提高到 6 秒。
autoSizeColumn 函数本身并不完美,有些列的宽度并不完全适合内部的数据。所以,我找到了一些适合我的解决方案。
sheet.autoSizeColumn(<columnIndex>);
// 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));
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
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();
不幸的是,我还没有足够的声誉来在答案中添加评论。所以这里有一些注释:
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
sheet.setColumnWidth(i, Math.max(width, 2048));
我遇到的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);
}
}