You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Apache POI的shiftRows按列排序Excel遇XmlValueDisconnectedException问题

解决Apache POI 3.17按第一列排序XSSFWorksheet的问题

嘿,我懂你遇到的糟心事了——shiftRows在POI 3.17版本里确实容易踩坑,行移动时经常因为行引用、样式关联或者隐藏行/合并单元格的问题抛出异常。与其死磕这个不稳定的方法,不如换个更可靠的思路:先把所有数据行提取出来排序,再重新写入工作表,既避免了移动行的各种幺蛾子,排序逻辑也更清晰可控。

下面是具体的实现步骤和代码:

核心思路

  1. 提取表头之外的所有数据行(包含单元格值和样式信息)
  2. 按第一列的值对数据行进行排序(适配不同数据类型)
  3. 清空原工作表的数据行(保留表头)
  4. 将排序后的行重新写入工作表

完整代码示例

首先需要两个辅助方法,用来处理不同类型单元格的读写:

private static Object getCellValue(XSSFCell cell) {
    if (cell == null) return "";
    switch (cell.getCellTypeEnum()) {
        case STRING:
            return cell.getStringCellValue();
        case NUMERIC:
            if (DateUtil.isCellDateFormatted(cell)) {
                return cell.getDateCellValue();
            } else {
                return cell.getNumericCellValue();
            }
        case BOOLEAN:
            return cell.getBooleanCellValue();
        case FORMULA:
            try {
                return cell.getStringCellValue();
            } catch (Exception e) {
                return cell.getNumericCellValue();
            }
        default:
            return "";
    }
}

private static void setCellValue(XSSFCell cell, Object value) {
    if (value instanceof String) {
        cell.setCellValue((String)value);
    } else if (value instanceof Double) {
        cell.setCellValue((Double)value);
    } else if (value instanceof Date) {
        cell.setCellValue((Date)value);
    } else if (value instanceof Boolean) {
        cell.setCellValue((Boolean)value);
    } else {
        cell.setCellValue(value.toString());
    }
}

接下来是排序的核心逻辑:

// 获取目标工作表
XSSFSheet sheet = workbook.getSheetAt(0);
int headerRowNum = 0; // 假设第0行是表头,可根据实际调整
int startDataRow = headerRowNum + 1;
int lastRowNum = sheet.getLastRowNum();

// 1. 收集所有数据行的内容和样式
List<Map<String, Object>> rowDataList = new ArrayList<>();
for (int i = startDataRow; i <= lastRowNum; i++) {
    XSSFRow row = sheet.getRow(i);
    if (row == null) continue;

    List<Object> cellValues = new ArrayList<>();
    int columnCount = sheet.getRow(headerRowNum).getLastCellNum();
    // 遍历所有列,收集单元格值
    for (int j = 0; j < columnCount; j++) {
        XSSFCell cell = row.getCell(j);
        cellValues.add(getCellValue(cell));
    }

    // 保存行样式(如果有设置的话)
    rowDataList.add(Map.of(
        "values", cellValues,
        "rowStyle", row.getRowStyle()
    ));
}

// 2. 按第一列的值排序
rowDataList.sort((rowA, rowB) -> {
    Object valA = ((List<Object>)rowA.get("values")).get(0);
    Object valB = ((List<Object>)rowB.get("values")).get(0);

    // 根据第一列的实际数据类型调整排序逻辑
    if (valA instanceof String && valB instanceof String) {
        return ((String)valA).compareToIgnoreCase((String)valB);
    } else if (valA instanceof Double && valB instanceof Double) {
        return ((Double)valA).compareTo((Double)valB);
    } else if (valA instanceof Date && valB instanceof Date) {
        return ((Date)valA).compareTo((Date)valB);
    } else {
        // 其他类型统一转字符串进行比较
        return valA.toString().compareToIgnoreCase(valB.toString());
    }
});

// 3. 清空原数据行(从后往前删避免索引混乱)
for (int i = lastRowNum; i >= startDataRow; i--) {
    XSSFRow row = sheet.getRow(i);
    if (row != null) {
        sheet.removeRow(row);
    }
}

// 4. 写入排序后的行
int currentRow = startDataRow;
for (Map<String, Object> rowData : rowDataList) {
    XSSFRow newRow = sheet.createRow(currentRow++);
    // 复制原行的样式
    newRow.setRowStyle((XSSFCellStyle)rowData.get("rowStyle"));

    List<Object> values = (List<Object>)rowData.get("values");
    int columnCount = values.size();
    for (int j = 0; j < columnCount; j++) {
        XSSFCell newCell = newRow.createCell(j);
        setCellValue(newCell, values.get(j));
        // 如果需要保留单元格单独样式,可以在这里添加复制逻辑
        // 比如从原行单元格获取样式,设置到新单元格
    }
}

额外注意事项

  • 如果工作表存在合并单元格,需要先记录所有合并区域的位置和内容,排序完成后再重新创建合并区域,否则合并单元格会丢失或错位。
  • 若需要保留单元格的单独样式(而非行样式),可以在收集数据时一并保存每个单元格的样式,写入时复制到新单元格中。
  • 排序逻辑可以根据需求灵活调整,比如改成倒序、多列组合排序,只需要修改sort方法里的比较器即可。

这种方法比直接调用shiftRows稳定得多,在POI 3.17版本中可以完全正常运行,而且扩展性更强,后续调整排序规则也很方便。

内容的提问来源于stack exchange,提问作者Leo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 06:35:49