有時候我們可能遇到相當復雜的excel,比如表頭的合並等操作,一種簡單的方式就是直接代碼合並(浪費時間),另一種就是寫好模板,動態的向模板中增加行和修改指定單元格數據。
1.一個簡單的根據模板sheet動態修改
原來的excel模板內容如下:
現在的需求是動態的生成生成時間和生成人。並且在第五行開始的數據列表增加5列:
package cn.xm.exam.test; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.xssf.usermodel.XSSFCell; import org.apache.poi.xssf.usermodel.XSSFRow; import org.apache.poi.xssf.usermodel.XSSFSheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; public class DynamicOperateExcelUtils { public static void main(String[] args) throws IOException { // 讀取源文件 FileInputStream fis = new FileInputStream("G:/test.xlsx"); XSSFWorkbook workBook = new XSSFWorkbook(fis); // 進行模板的克隆(接下來的操作都是針對克隆后的sheet) XSSFSheet sheet = workBook.cloneSheet(0); workBook.setSheetName(0, "sheet-0"); // 給sheet命名 // 讀取指定cell的內容 XSSFCell nameCell = sheet.getRow(1).getCell(0); XSSFCell nameCell2 = sheet.getRow(1).getCell(1); System.out.println(nameCell.getStringCellValue()); System.out.println(nameCell2.getStringCellValue()); // 替換單元格內容(注意獲取的cell的下標是合並之前的下標) replaceCellValue(sheet.getRow(1).getCell(2), "xxxxx時間"); replaceCellValue(sheet.getRow(2).getCell(2), "xxxxx人"); // 動態插入數據-增加行 List<Map<String, Object>> datas = new ArrayList<>(); for (int i = 0; i < 5; i++) { Map data = new HashMap<>(); data.put("name", "name" + i); data.put("age", "age" + i); data.put("sex", "sex" + i); datas.add(data); } // 插入行 sheet.shiftRows(4, 4 + datas.size(), datas.size(), true, false);// 第1個參數是指要開始插入的行,第2個參數是結尾行數,第三個參數表示動態添加的行數 for (int i = 0; i < datas.size(); i++) { XSSFRow creRow = sheet.createRow(4 + i); creRow.setRowStyle(sheet.getRow(4).getRowStyle()); creRow.createCell(0).setCellValue(datas.get(i).get("name").toString()); creRow.createCell(1).setCellValue(datas.get(i).get("age").toString()); creRow.createCell(2).setCellValue(datas.get(i).get("sex").toString()); } // 輸出為一個新的Excel,也就是動態修改完之后的excel String fileName = "test" + System.currentTimeMillis() + ".xlsx"; OutputStream out = new FileOutputStream("G:" + "/" + fileName); workBook.removeSheetAt(0); // 移除workbook中的模板sheet workBook.write(out); fis.close(); out.flush(); out.close(); } /** * 替換單元格的內容,單元格的獲取位置是合並單元格之前的位置,也就是下標都是合並之前的下表 * * @param cell * 單元格 * @param value * 需要設置的值 */ public static void replaceCellValue(Cell cell, Object value) { String val = value != null ? String.valueOf(value) : ""; cell.setCellValue(val); } }
結果:
上面需要注意的是:在替換的時候獲取cell的時候獲取的是合並單元格之前的cell位置,在動態增加行的時候行的其實和結束都是包含在內的。
2. 封裝的一個完整的工具類:
此工具類支持xls和xlsx格式(這也是一種常用的思想,用父類引用接受子類對象),完美的支持excel的操作。而且是單個sheet的模板替換以及追加內容、讀取和設置單個cell的值。
代碼中依賴的工具包:Slf4j日志包,IOUtils工具包,commons-collections操作集合包。
package cn.xm.exam.utils; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.List; import java.util.Map; import org.apache.commons.collections.MapUtils; import org.apache.commons.io.IOUtils; import org.apache.poi.hssf.usermodel.HSSFWorkbook; 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.xssf.streaming.SXSSFSheet; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class DynamicOperateExcelUtils { private static final Logger LOGGER = LoggerFactory.getLogger(DynamicOperateExcelUtils.class); private Workbook workBook; private Sheet sheet; public DynamicOperateExcelUtils(String fileFullPath) { this(fileFullPath, null); } public DynamicOperateExcelUtils(String fileFullPath, String sheetName) { // 解決版本問題,HSSFWorkbook是97-03版本的xls版本,XSSFWorkbook是07版本的xlsx try { workBook = new XSSFWorkbook(new FileInputStream(fileFullPath)); } catch (Exception e) { try { workBook = new HSSFWorkbook(new FileInputStream(fileFullPath)); } catch (Exception e1) { LOGGER.error("Excel格式不正確", e1); throw new RuntimeException(e1); } } // 進行模板的克隆(接下來的操作都是針對克隆后的sheet) sheet = workBook.cloneSheet(0); // 移除workbook中的模板sheet workBook.removeSheetAt(0); // 重命名克隆后的sheet workBook.setSheetName(0, sheetName != null ? sheetName : "sheet1"); } public String getCellValue(int rowNum, int colNum) { return getCellValue(rowNum, colNum, ""); } /** * 根據行號列號獲取值 * * @param rowNum * 行號 * @param colNum * 列號 * @param defaultValue * 默認值 * @return */ public String getCellValue(int rowNum, int colNum, String defaultValue) { Row row = sheet.getRow(rowNum); if (row == null) { return defaultValue; } Cell cell = row.getCell(colNum); if (cell == null) { return defaultValue; } return getCellValue(cell, defaultValue); } public String getCellValue(Cell cell) { return getCellValue(cell, ""); } /** * 讀取cell的值 * * @param cell * 需要讀取的cell * @param defaultValue * 默認值 * @return */ public String getCellValue(Cell cell, String defaultValue) { if (cell != null) { cell.setCellType(cell.CELL_TYPE_STRING); return cell.getStringCellValue(); } return defaultValue; } /** * 替換單元格的內容,單元格的獲取位置是合並單元格之前的位置,也就是下標都是合並之前的下表 * * @param cell * 單元格 * @param value * 需要設置的值 */ public void replaceCellValue(Cell cell, Object value) { String val = value != null ? String.valueOf(value) : ""; cell.setCellValue(val); } /** * 根據行號,列號進行替換 * * @param rowNum * 行號 * @param colNum * 列號 * @param value * 值 */ public void replaceCellValue(int rowNum, int colNum, Object value) { Row row = sheet.getRow(rowNum); if (row == null) { return; } Cell cell = row.getCell(colNum); if (cell == null) { return; } replaceCellValue(cell, value); } /** * 向sheet中添加行,后面的行會向后自動移動 * * @param startRowIndex * 起始行 * @param datas * 數據 * @param keys * 數據中Map對應的key */ public void appendRows(int startRowIndex, List<Map<String, Object>> datas, String[] keys) { // 插入行 sheet.shiftRows(startRowIndex, startRowIndex + datas.size(), datas.size(), true, false);// 第1個參數是指要開始插入的行,第2個參數是結尾行數,第三個參數表示動態添加的行數 // 向插入的行中動態的填充數據 for (int i = 0; i < datas.size(); i++) { Map<String, Object> data = datas.get(i); // 創建行 Row row = sheet.createRow(startRowIndex + i); // 添加單元格 Cell cell = null; for (int j = 0, length_2 = keys.length; j < length_2; j++) { String key = keys[j]; String value = MapUtils.getString(data, key, ""); cell = row.createCell(j); cell.setCellType(Cell.CELL_TYPE_STRING); cell.setCellValue(value); } } // 調整列寬 autoResizeColumn(keys.length); } public void exportExcel(File file) { exportExcel(file.getAbsolutePath()); } public void exportExcel(String fileFullPath) { OutputStream outputStream = null; try { outputStream = new FileOutputStream(fileFullPath); workBook.write(outputStream); } catch (IOException e) { LOGGER.error(" exportExcel error", e); } finally { IOUtils.closeQuietly(outputStream); } } private void autoResizeColumn(int colNumber) { // 如果是SXSSFSheet,需要調用trackAllColumnsForAutoSizing方法一次 if (sheet instanceof SXSSFSheet) { SXSSFSheet tmpSheet = (SXSSFSheet) sheet; tmpSheet.trackAllColumnsForAutoSizing(); } for (int i = 0; i < colNumber; i++) { sheet.autoSizeColumn(i, true); } } public Sheet getSheet() { return sheet; } }
測試:
原來excel: myExcel.xlsx
代碼:
public static void main(String[] args) throws IOException { DynamicOperateExcelUtils dynamicOperateExcelUtils = new DynamicOperateExcelUtils("F:/myExcel.xlsx"); // 讀取內容 String cellValue = dynamicOperateExcelUtils.getCellValue(1, 1); System.out.println(cellValue); // 替換單元格內容(注意獲取的cell的下標是合並之前的下標) dynamicOperateExcelUtils.replaceCellValue(1, 1, "updated"); // 動態插入數據-增加行 List<Map<String, Object>> datas = new ArrayList<>(); for (int i = 0; i < 5; i++) { Map data = new HashMap<>(); data.put("name", "name" + i); data.put("age", "age" + i); data.put("sex", "sex" + i); datas.add(data); } dynamicOperateExcelUtils.appendRows(4, datas, new String[] { "name", "age", "sex" }); dynamicOperateExcelUtils.exportExcel(new File("F:/myExcel2.xlsx")); }
結果:
什么鬼
myExcel2.xlsx
補充:有的POI版本如果第index行沒有任何數據,直接getRow(index)的時候會報錯,所以有可能需要先創建行。(這個問題對不同的POI版本情況不一樣)
補充:有時候遇到合並的單元格采用上面自動調整列寬不生效,解決辦法:
sheet.autoSizeColumn(i, true);