工作上有时要整理挺多excel的,好多工作是体力活,所以写了个java的excel工具类来辅助工作.
更新...
pom:

<dependencies>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>3.17</version>
    </dependency>
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>3.17</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.4</version>
        <scope>test</scope>
    </dependency>
</dependencies>

ExcelUtil:

package clearwind;

import java.io.*;
import java.util.*;

import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * @author 晴风
 *
 * Workbook:  excel表格对象,可包含多个Sheet
 * Sheet:     工作表对象,包含多个Row
 * Row:       行对象,包含多个Cell
 * Cell:      单元格对象,最小的对象,有多种单元格数据类型
 */
public class ExcelUtil {
    /***索引
     *
     */

    private static final Logger LOGGER = LoggerFactory.getLogger(ExcelUtil.class);

    public static final String XLS = ".xls";
    public static final String XLSX = ".xlsx";
    public static final String DEFAULT_FONT_NAME = "宋体";
    public static final String CRLF = "\r\n";

    /**
     * 预设的默认的样式
     * 宋体 11号 有边框
     *
     * @param workbook Workbook对象
     * @param isTitle 是否是标题
     * @return CellStyle
     */
    public static CellStyle getDefaultCellStyle(Workbook workbook, boolean isTitle) {
        //创建样式
        Font defaultFont = workbook.createFont();
        CellStyle defaultCellStyle = workbook.createCellStyle();
        //设置字体样式
        defaultFont.setFontName(DEFAULT_FONT_NAME);
//        defaultFont.setFontHeight((short) 11); //这么写并非11号字体
        //标题加粗
        if (isTitle) {
            defaultFont.setBold(true);
        }
        //四边框
        defaultCellStyle.setBorderBottom(BorderStyle.MEDIUM);
        defaultCellStyle.setBorderLeft(BorderStyle.MEDIUM);
        defaultCellStyle.setBorderRight(BorderStyle.MEDIUM);
        defaultCellStyle.setBorderTop(BorderStyle.MEDIUM);
        //应用字体
        defaultCellStyle.setFont(defaultFont);

        return defaultCellStyle;
    }

    /**
     * 预设的默认的样式
     * 宋体 11号 有边框
     *
     * @param workbook Workbook对象
     * @return CellStyle
     */
    public static CellStyle getDefaultCellStyle(Workbook workbook) {
        return getDefaultCellStyle(workbook, false);
    }

    /**
     * 读取excel文件创建Workbook对象,过期,使用getWorkbook()代替
     *
     * @param filePath 文件路径
     * @return Workbook
     */
    @Deprecated
    public static Workbook readExcel(String filePath) {
        Workbook wb = null;
        if (filePath == null) {
            return null;
        }
        //提取文件后缀名
        String extString = filePath.substring(filePath.lastIndexOf("."));
        InputStream is;
        try {
            //创建输入流
            is = new FileInputStream(filePath);
            if (XLS.equals(extString)) {
                wb = new HSSFWorkbook(is);
            } else if (XLSX.equals(extString)) {
                wb = new XSSFWorkbook(is);
            }
            //关闭输入流
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return wb;
    }

    /**
     * 创建一个xlsx文件
     *
     * @return Workbook
     */
    public static Workbook createWorkbook() {
        return new XSSFWorkbook();
    }

    /**
     * 使用WorkbookFactory读取excel文件创建Workbook对象
     *
     * @param filePath 文件路径
     * @return Workbook
     */
    public static Workbook getWorkbook(String filePath) {
        Workbook wb = null;
        InputStream is = null;
        if ((filePath.endsWith(XLSX) || filePath.endsWith(XLS)) && !filePath.startsWith("~")) {
            try {

                is = new FileInputStream(filePath);
                wb = WorkbookFactory.create(is);
                //测试:打印文件名
                LOGGER.info("[getWorkbook] " + filePath);

            } catch (IOException | InvalidFormatException e) {
                e.printStackTrace();
            } finally {
                //关闭输入流
                try {
                    assert is != null;
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } else {
            LOGGER.info("[getWorkbook] "+ filePath +": 非xls或者xlsx文件!");
        }
        return wb;
    }

    /**
     * 从文件对象加载Workbook对象
     *
     * @param excelFile excel文件
     * @return Workbook
     */
    public static Workbook getWorkbook(File excelFile) throws IOException, InvalidFormatException {
        if (!excelFile.exists()){
            return null;
        }
        String fileName = excelFile.getName();
        //判断是否是xls或者xlsx文件
        if (fileName.endsWith(XLSX) || fileName.endsWith(XLS)) {
            LOGGER.info("[getWorkbook] " + excelFile.getPath());
            return WorkbookFactory.create(excelFile);
        } else {
            LOGGER.info("[getWorkbook] 非xls或者xlsx文件!");
            return null;
        }
    }

    public static Sheet getSheet(String excelFilePath,String name) {
        return getWorkbook(excelFilePath).getSheet(name);
    }
    public static Sheet getSheetAt(String excelFilePath, int index) {
        return getWorkbook(excelFilePath).getSheetAt(index);
    }


        /**
         * 拿到所有Sheet
         *
         * @return List<Sheet>
         */
    public static List<Sheet> getAllSheet(Workbook workbook) {
        return getSheetsWithRegex(workbook, "[\\s\\S]*");
    }

    /**
     * 根据一个String[]数组拿到多个Sheet,数组内是Sheet名
     *
     * @param workbook   表格对象
     * @param sheetNames Sheet名称数组
     * @return List<Sheet>
     */
    public static List<Sheet> getSheetsWithArray(Workbook workbook, String[] sheetNames) {
        List<Sheet> sheets = new ArrayList<>();
        if (sheetNames.length < 1) {
            return sheets;
        }
        //遍历传入的sheetNames数组
        for (String sheetName : sheetNames) {
            //尝试根据名称拿到sheet
            Sheet sheet = workbook.getSheet(sheetName);
            if (sheet != null) {
                LOGGER.info("[getSheetsWithArray] 找到Sheet:" + sheetName);
                sheets.add(sheet);
            } else {
                LOGGER.warn("[getSheetsWithArray] 未找到Sheet:" + sheetName);
            }
        }
        return sheets;
    }

    /**
     * 拿到sheet名符合正则的sheet
     * 正则语法参考:https://www.runoob.com/regexp/regexp-syntax.html
     *
     * @param pattern 正则表达式
     * @return List<Sheet>
     */
    public static List<Sheet> getSheetsWithRegex(Workbook workbook, String pattern) {
        List<Sheet> sheets = new ArrayList<>();
        //numberOfSheets
        int numberOfSheets = workbook.getNumberOfSheets();
        for (int i = 0; i < numberOfSheets; i++) {
            Sheet sheet = workbook.getSheetAt(i);
            String sheetName = sheet.getSheetName();
            //正则匹配的sheet添加到list
            if (sheetName.matches(pattern)) {
                sheets.add(sheet);
            }
        }
        LOGGER.info("[getSheetWithRegex] numberOfSheets:" + sheets.size());
        return sheets;
    }


    /**
     * 取一个单元格的字符串值,空返回空字符串
     *
     * @param row         cell所在行
     * @param columnIndex 列索引
     * @return Cell value
     */
    public static String getCellStringValue(Row row, int columnIndex) {
        Cell cell;
        if (row != null) {
            try {
                //取第h行,第l列的cell
                cell = row.getCell(columnIndex);
                //取数据
                cell.setCellType(CellType.STRING);
                return cell.getStringCellValue();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return "";
    }

    /**
     * 取一行拼接为String字符串
     *
     * @param row       Row对象
     * @param delimiter 分隔符
     * @return String
     */
    public static String getOneRowToString(Row row, String delimiter) throws Exception {
        StringBuilder line = new StringBuilder();
        //取得迭代器
        if (row != null) {
            Iterator<Cell> cellIterator = row.cellIterator();
            Cell cell;
            while (cellIterator.hasNext()) {
                cell = cellIterator.next();
                //全部设置为字符串类型
                cell.setCellType(CellType.STRING);
                //以分隔符分隔每一个值
                //line += cell.getStringCellValue() + "\t";
                line.append(cell.getStringCellValue()).append(delimiter);
            }
            //空行
            if (line.length() < 1) {
                return "";
            }
            //去除最后的分隔符
            return line.substring(0, line.length() - 1);
        } else {
            LOGGER.info("[getOneRowToString] Row空对象");
            return "";
        }
    }

    /**
     * 取一行拼接为String字符串,以制表符分隔输出字符串
     *
     * @param row Row对象
     * @return String
     */
    public static String getOneRowToString(Row row) throws Exception {
        return getOneRowToString(row, "\t");
    }

    /**
     * 取一行
     *
     * @param sheet    Sheet对象
     * @param rowIndex 行号
     * @return Row
     */
    public static Row getRow(Sheet sheet, int rowIndex) throws RuntimeException {
        if (sheet == null) {
            throw new RuntimeException("[getRow] sheet空对象!");
        }
        if (rowIndex < 0) {
            throw new RuntimeException("[getRow] 行索引不能小0!");
        }
        if (rowIndex > getLastRowNumIndex(sheet)) {
            throw new RuntimeException("[getRow] 行索引不能大于实际表最大索引!");
        }
        return sheet.getRow(rowIndex);
    }

    /**
     * 取范围内行
     *
     * @param sRowIndex 开始索引
     * @param eRowIndex 结束索引
     * @return List<Row>
     */
    public static List<Row> getRow(Sheet sheet, int sRowIndex, int eRowIndex) throws RuntimeException {
        if (sheet == null) {
            throw new RuntimeException("[getRow] sheet空对象!");
        }
        if (sRowIndex < 0 || eRowIndex < sRowIndex) {
            throw new RuntimeException("[getRow] 索引错误!");
        }
        int lastRowNum = getLastRowNumIndex(sheet);
        if (sRowIndex > lastRowNum || eRowIndex > lastRowNum) {
            throw new RuntimeException("[getRow] 行索引不能大于实际表最大索引!");
        }
        //拿row
        ArrayList<Row> rows = new ArrayList<>();
        for (int i = sRowIndex; i < eRowIndex + 1; i++) {
            rows.add(sheet.getRow(i));
        }
        return rows;
    }


    /**
     * Row转为List<String>
     *
     * @param row Row对象
     * @param setRowLength 设置Row长度(非索引),当为-1时不设置
     * @return List<String>
     */
    public static List<String> rowToList(Row row,short setRowLength) {
        short setLastCellNumIndex = (short) (setRowLength-1);
        List<String> dataList = new ArrayList<>();
        //标记是否有数据(有些excel会有很多空行)
        boolean haveData = false;
        short lastCellNum;

        //取得列内真实有数据的单元格个数
        lastCellNum = getLastCellNumIndex(row);
        //固定Row长度
        if (setRowLength!=-1 && setLastCellNumIndex!=lastCellNum) {
            lastCellNum=setLastCellNumIndex;
        }

        //自定义方法getLastCellNumIndex会返回索引,所以改为<=
        Cell cell;
        for (int i = 0; i <= lastCellNum; i++) {
            cell = row.getCell(i);
            if (cell == null) {
                cell = row.createCell(i);
            }
            //类型置为String,不去判断是什么类型的了
            cell.setCellType(CellType.STRING);
            //取数据添加到list
            String stringCellValue = cell.getStringCellValue();
            if (!"".equals(stringCellValue)) {
                haveData = true;
            }
            dataList.add(stringCellValue);

        }
        //确实至少有一列有数据才返回
        if (!haveData) {
            Sheet sheet = row.getSheet();
            int rowNum = row.getRowNum();
            Row row1 = sheet.getRow(0);
            System.out.println(rowToList(row1));
            LOGGER.warn("[rowToList] "+sheet.getSheetName()+":"+rowNum+",无数据!返回null");
            return null;
        }
        return dataList;
    }

    /**
     * Row转为List<String>,默认不去除右空格
     * @param row Row对象
     * @return List<String>
     */
    public static List<String> rowToList(Row row) {
        //不固定列长度
        return rowToList(row, (short) -1);
    }

    /**
     * Row转为List<String>
     *
     * @param rowList 多行
     * @return List<List < String>>
     */
    public static List<List<String>> rowToList(List<Row> rowList, short setRowLength) {
        List<List<String>> dataList = null;
        if (rowList.size() == 0) {
            return dataList;
        }
        try {
            dataList = new ArrayList<>();
            for (Row row : rowList) {
                List<String> rowData = rowToList(row,setRowLength);
                if (rowData != null) {
                    dataList.add(rowData);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dataList;
    }

    public static List<List<String>> rowToList(List<Row> rowList) {
        return rowToList(rowList, (short) -1);
    }


    /**
     * 控制台打印excel
     */
    public static void printSheet(Sheet sheet) throws Exception {
        try {
            //总行数
            int lastRowNum = getLastRowNumIndex(sheet);
            //打印
            for (int i = 0; i <= lastRowNum; i++) {
                LOGGER.info(getOneRowToString(getRow(sheet, i)));
            }
        } catch (NullPointerException e) {
            LOGGER.warn("[printSheet] Sheet对象为NULL.");
        }
    }


    /**
     * 写一行
     *
     * @param row       行对象
     * @param rowIndex  写excel的第几行
     * @param cellStyle 单元格样式
     * @return Row
     */
    public static Row writeALine(Sheet sheet, Row row, int rowIndex, CellStyle cellStyle) {
        return writeALine(sheet, rowToList(row), rowIndex, cellStyle);
    }

    public static Row writeALine(Sheet sheet, Row row, int rowIndex) {
        return writeALine(sheet, row, rowIndex, sheet.getWorkbook().createCellStyle());
    }

    /**
     * 写一行
     *
     * @param data      数据
     * @param rowIndex  写excel的第几行
     * @param cellStyle 单元格样式
     * @return Row
     */
    public static Row writeALine(Sheet sheet, List<String> data, int rowIndex, CellStyle cellStyle) {
        Row row = null;
        try {
            //尝试取得需要写入的行
            row = sheet.getRow(rowIndex);
            if (row == null) {
                //还不存在,新建行
                row = sheet.createRow(rowIndex);
            }
            //取得行的单元格,并写数据
            Cell cell;
            if (data == null) {
                LOGGER.warn("无数据,返回空行");
                return row;
            }
            for (int i = 0; i < data.size(); i++) {
                //从yIndex列开始写
                cell = row.createCell(i);
                //全部设置数据类型为文本
                cell.setCellType(CellType.STRING);
                cell.setCellValue(data.get(i));
                //设置样式
                cell.setCellStyle(cellStyle);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return row;
    }

    /**
     * 写一行,无样式
     *
     * @param data      数据
     * @param rowIndex  写excel的第几行
     * @return Row
     */
    public static Row writeALine(Sheet sheet, List<String> data, int rowIndex) {
        return writeALine(sheet, data, rowIndex, sheet.getWorkbook().createCellStyle());
    }


    /**
     * 写一列
     *
     * @param sheet         Sheet对象
     * @param data          数据
     * @param startRowIndex 从excel第几行开始写
     * @param colIndex      写excel的第几列
     */
    public static void writeAColumn(Sheet sheet, List<String> data, int startRowIndex, int colIndex) {
        try {
            if (sheet == null) {
                throw new RuntimeException("Sheet is null!");
            }
            if (data.size() == 0) {
                throw new RuntimeException("Data is empty!");
            }
            if (startRowIndex < 0 || colIndex < 0) {
                throw new RuntimeException("StartRowIndex and colIndex needs to be greater than 1!");
            }
            //开始写
            for (int i = 0; i < data.size(); i++) {
                LOGGER.info("写入第" + i + "行...");
                Row row = sheet.getRow(i);
                if (row == null) {
                    sheet.createRow(startRowIndex + i).createCell(colIndex).setCellValue(data.get(i));
                } else {
                    row.createCell(colIndex).setCellValue(data.get(i));
                }

            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 写键值对类型的数据
     *
     * @param sheet         sheet
     * @param data          数据,map类型
     * @param startRowIndex 从excel第几行开始写
     * @param colIndex1     key写在excel第几列
     * @param colIndex2     value写在excel第几列
     */
    public static void writeMapTypeData(Sheet sheet, Map<String, String> data, int startRowIndex, int colIndex1, int colIndex2) {
        try {
            Row row;
            if (sheet == null) {
                throw new RuntimeException("Sheet is null!");
            }
            if (data.size() == 0) {
                throw new RuntimeException("Data is empty!");
            }
            if (startRowIndex < 0 || colIndex1 < 0 || colIndex2 < 0) {
                throw new RuntimeException("StartRowIndex and colIndex needs to be greater than 1!");
            }
            if (colIndex1 == colIndex2) {
                throw new RuntimeException("ColIndex1 is equal to colIndex2!");
            }
            //将Map转为list
            List<Map.Entry<String, String>> dataList = new ArrayList<>(data.entrySet());
            for (int i = 0; i < data.size(); i++) {
                row = sheet.createRow(startRowIndex + i);
                row.createCell(colIndex1).setCellValue(dataList.get(i).getKey());
                row.createCell(colIndex2).setCellValue(dataList.get(i).getValue());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    public static void printCollect(Object objs) {
        if (objs instanceof Map) {
            Map maps = (Map) objs;
            Set<Map.Entry> set = maps.entrySet();
            for (Map.Entry entry : set) {
                LOGGER.info(entry.getKey() + "   ->    " + entry.getValue());
            }
        } else if (objs instanceof Collection) {
            for (Object obj : (Collection) objs) {
                LOGGER.info(obj.toString());
            }
        } else if (objs.getClass().isArray()) {
//            List<Object> objects = Arrays.asList(objs);
//            printCollect(objects);
            LOGGER.warn("未实现数组打印...,有空研究...");

        } else {
            LOGGER.info(objs + "不是 Collection 或者 Map 类型!!");
        }
    }

    /**
     * 拿到表最后一行索引
     * @param sheet Sheet对象
     * @return lastRowNum
     */
    public static int getLastRowNumIndex(Sheet sheet){
        int lastRowNum = sheet.getLastRowNum();
        //修正
        //sheet.getLastRowNum(); 取得的是索引,已验证
        for (int i = lastRowNum; i >= 0; i--) {
            Row row;
            try {
                row  = sheet.getRow(i);
            } catch (NullPointerException npe) {
                //若row为空
                //跳过,进行下一次循环
                continue;
            }
            List<String> strings = rowToList(row);
            //从最后一行开始校验,遇到非空行则跳出
            if (strings == null) {
                lastRowNum--;
            } else {
                break;
            }
        }
        return lastRowNum;
    }

    /**
     * 拿到行的最后一列索引
     * @param row Row对象
     * @return lastCellNum
     */
    public static short getLastCellNumIndex(Row row) throws IllegalStateException{
        short lastCellNum = 0;
            lastCellNum = row.getLastCellNum();
            //修正
            for (int i = lastCellNum - 1; i >= 0; i--) {
                //从最后一列开始校验,遇到非空列则跳出

                Cell cell = row.getCell(i);
                if (cell == null) {
                    lastCellNum--;
                } else {
                    String stringCellValue;
//                    stringCellValue = cell.getStringCellValue();
                    try {
                        stringCellValue = cell.getStringCellValue();

                    } catch (IllegalStateException ie) {
                        Sheet sheet = row.getSheet();
                        System.out.println("错误:"+sheet.getSheetName()+","+row.getRowNum());
                        System.out.println(i);
//                        throw ie;
//                        stringCellValue="";
                        stringCellValue=cell.getNumericCellValue()+"";
                    }
                    if ("".equals(stringCellValue.trim())) {
                        lastCellNum--;
                    } else {
                        break;
                    }
                }
            }
            //row.getLastCellNum()拿到的是列数而非索引,所以做-1处理

        return (short)(lastCellNum-1);
    }

    /**
     * 取得一个sheet内的所有行,存到List内,中间空行会被屏蔽
     *
     * @param sheet Sheet对象
     * @return List<List < String>>
     */
    public static List<List<String>> getSheetAllRowsToList(Sheet sheet) throws Exception {
        List<List<String>> rowsList = new ArrayList<>();
        //sheet总行数
        int lastRowNum = getLastRowNumIndex(sheet);
        //打印详情:打印SheetName
        LOGGER.info("[getSheetAllRowsToList] SheetName:" + sheet.getSheetName() + ",lastRowNum:" + lastRowNum);

        if (lastRowNum > 0) {
            Row row;
            //遍历sheet
            for (int i = 0; i <= lastRowNum; i++) {
                //拿到一行
                row = ExcelUtil.getRow(sheet, i);
                //中间空行判断
                String rowString = ExcelUtil.getOneRowToString(row, "");
                if (!"".equals(rowString)) {
                    //拿到一行的list
                    rowsList.add(ExcelUtil.rowToList(row));
                }
            }
        }

        return rowsList;
    }

    /**
     * 不能与write()同名,否则java编译时类型擦除导致两个方法的声明一样,编译不通过
     */
    public static Sheet writeRow(Sheet sheet, List<Row> data, int startIndex, CellStyle cellStyle) throws Exception {
        return write(sheet, rowToList(data), startIndex, cellStyle);
    }

    public static Sheet writeRow(Sheet sheet, List<Row> data, int startIndex) throws Exception {
        return writeRow(sheet, data, startIndex, sheet.getWorkbook().createCellStyle());
    }


    /**
     * 多行写入到sheet内
     *
     * @param data       写入的数据
     * @param startIndex 从第几行开始写入
     */
    public static Sheet write(Sheet sheet, List<List<String>> data, int startIndex, CellStyle cellStyle) throws Exception {
        //遍历数据写入
        for (List<String> oneRow : data) {
            writeALine(sheet, oneRow, startIndex++, cellStyle);
        }
        return sheet;
    }

    public static Sheet write(Sheet sheet, List<List<String>> data, int startIndex) throws Exception {
        return write(sheet, data, startIndex, sheet.getWorkbook().createCellStyle());
    }


    /**
     * 通过sheetName取sheet,不存在则创建
     *
     * @param wb        Workbook对象
     * @param sheetName sheet名
     * @return Sheet
     */
    public static Sheet getOrCreateSheet(Workbook wb, String sheetName) {
        Sheet sheet = null;
        try {
            sheet = wb.getSheet(sheetName);
            if (sheet == null) {
                sheet = wb.createSheet(sheetName);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sheet;
    }


    /**
     * 遍历文件夹
     *
     * @param folderPath 文件夹路径
     * @return List<File>
     */
    public static List<File> fileList(String folderPath) {
        //文件夹
        File folder = new File(folderPath);
        File[] files = folder.listFiles();
        //存文件对象
        ArrayList<File> fileList = new ArrayList<>();
        if (files != null) {
            //递归遍历文件夹内文件
            for (File f : files) {
                if (f.isDirectory()) {
                    //递归
                    fileList.addAll(fileList(f.getAbsolutePath()));
                }
                if (f.isFile()) {
                    fileList.add(f);
                }
            }
        }
        LOGGER.info("[fileList] 共" + fileList.size() + "个文件.");
        return fileList;
    }

    /**
     * 合并多个`表头一致`的sheet,返回新的Workbook对象
     *
     * @return Workbook
     */
    public static Workbook sheetsMerge(List<Sheet> sheets, int titleIndex) {
        return sheetsMerge(sheets, titleIndex, createWorkbook(), "merge");
    }

    /**
     * 合并多个`表头一致`的sheet,写入到指定wb
     *
     * @param sheets         合并文件列表
     * @param titleIndex     表头最大索引
     * @param workbook       写入到的wb
     * @param mergeSheetName 写入数据的新sheet名
     * @return Workbook
     */
    public static Workbook sheetsMerge(List<Sheet> sheets, int titleIndex, Workbook workbook, String mergeSheetName) {

        try {
            if (sheets == null || sheets.size() < 1) {
                return null;
            }
            //创建sheet
            Sheet mergeSheet = workbook.createSheet(mergeSheetName);
            //拿第一个sheet的表头,写到mergeSheet
            List<Row> titleRows = getRow(sheets.get(0), 0, titleIndex);
            for (int i = 0; i < titleRows.size(); i++) {
                writeALine(mergeSheet, rowToList(titleRows.get(i)), i, getDefaultCellStyle(workbook, true));
            }

            //遍历sheets,拿数据写到mergeSheet
            for (int i = 0; i < sheets.size(); i++) {
                String logMSG = "[sheetsMerge] 合并第" + (i + 1) + "个sheet";
                LOGGER.info(logMSG);
                Sheet sheet = sheets.get(i);
                int lastRowNum = getLastRowNumIndex(sheet);
                List<Row> dataRows;
                //有数据才执行
                if (lastRowNum > titleIndex) {
                    //拿数据
                    dataRows = getRow(sheet, titleIndex + 1, lastRowNum);
                    //写数据
                    for (Row dataRow : dataRows) {
                        List<String> strings = rowToList(dataRow);
                        //保险,非空才写,rowToList全空列会返回null
                        if (strings != null) {
                            writeALine(mergeSheet, strings, getLastRowNumIndex(mergeSheet) + 1, getDefaultCellStyle(workbook));
                        }
                    }
                } else {
                    logMSG = "[sheetsMerge] 第" + (i + 1) + "个sheet无数据"+"("+sheet.getSheetName()+")";
                    LOGGER.info(logMSG);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return workbook;
    }

    /**
     * 依据某一列,拆分一个sheet到多个sheet,返回新的Workbook对象
     *
     * @return Workbook
     */
    public static Workbook breakdownIntoSheets(Sheet sheet, int titleIndex, int basisIndex) throws Exception {
        return breakdownIntoSheets(sheet, titleIndex, basisIndex, createWorkbook());
    }

    /**
     * 依据某一列,拆分一个sheet到多个sheet,返回新的Workbook对象
     *
     * @param sourceSheet      需要拆分的sheet
     * @param titleIndex 表头最大索引
     * @param basisIndex 依据列索引,依据列请勿带特殊字符
     * @param workbook   写入到的 workbook
     * @return Workbook
     */
    public static Workbook breakdownIntoSheets(Sheet sourceSheet, int titleIndex, int basisIndex, Workbook workbook) throws Exception {
        LOGGER.info("[breakdownIntoSheets] 拆分:"+sourceSheet.getSheetName()+"\t依据列:"+basisIndex);
        //取得最后一行索引
        int lastRowNumIndex = getLastRowNumIndex(sourceSheet);
        //取表头
        List<Row> titleRow = getRow(sourceSheet, 0, titleIndex);
        //拿到数据行
        List<Row> dataRowList = getRow(sourceSheet, titleIndex + 1, lastRowNumIndex);
        //数据行转list
        List<List<String>> rowToLists = rowToList(dataRowList);
        //封装的各个sheet数据
        Map<String, List<List<String>>> sheetsMap = new LinkedHashMap<String, List<List<String>>>();
        //遍历数据行,依照依据列提取各分sheet行到Map
        for (List<String> rowList : rowToLists) {
            //依据列的值
            String basisValue = rowList.get(basisIndex);
            //sheetsMap内查找,不存在则创建
            List<List<String>> lists = sheetsMap.computeIfAbsent(basisValue, k -> new ArrayList<List<String>>());
            //把当前行加到map里
            lists.add(rowList);
        }
        //上面for结束表示已遍历完数据,下面开始按照分好的数据建sheet,并把数据写进去
        Set<Map.Entry<String, List<List<String>>>> entries = sheetsMap.entrySet();
        for (Map.Entry<String, List<List<String>>> entry : entries) {
            String basisValue = entry.getKey();
            //使用basisValue建sheet
            Sheet basisValueSheet = workbook.createSheet(basisValue);
            LOGGER.info("[breakdownIntoSheets] 新Sheet:" + basisValue + CRLF);
            //写数据到这个sheet里,表头
            writeRow(basisValueSheet,titleRow,0);
            //写数据到这个sheet里,数据
            write(basisValueSheet,entry.getValue(),titleIndex+1);
        }
        LOGGER.info("[breakdownIntoSheets] 拆分出Sheet数:" + sheetsMap.size() + CRLF);
        return workbook;
    }

        /**
         * 列转行
         * <p>
         * ID   F1  F2
         * 1    a   A,B
         * -->>
         * ID   F1  F2
         * 1    a   A
         * 1    a   B
         *
         * @param sheet      存数据的sheet
         * @param titleIndex 表头最大索引
         * @param splitIndex 拆分列
         * @param delimiter  分隔符,注意:分隔符是正则中的特殊字符时,需要带转义
         * @param newWorkbook   合并结果存放的wb
         * @return 合并结果存放的wb
         */
    public static Workbook columnsToRows(Sheet sheet, int titleIndex, int splitIndex, String delimiter, Workbook newWorkbook, String newSheetName) throws Exception {
        //拿到sheet最大行数
        int lastRowNum = getLastRowNumIndex(sheet);
        //拿到表头
        List<Row> titleRows = getRow(sheet, 0, titleIndex);
        //拿到表头的最后一行
        Row titleMaxRow = titleRows.get(titleIndex);
        //根据表头最后一行得到每行长度
        short lastCellNumIndex = getLastCellNumIndex(titleMaxRow);
        //表头转List
        List<List<String>> titleRowsList = ExcelUtil.rowToList(titleRows);
        //表头添加一列
        titleRowsList.get(titleIndex).add("原始位置");
        //拿到数据
        List<Row> dataRows = getRow(sheet, titleIndex + 1, lastRowNum);
        //创建新sheet存放合并后的数据
        Sheet newSheet = newWorkbook.createSheet(newSheetName);
        //新数据Collect    表<行<单元格>>
        ArrayList<List<String>> lists = new ArrayList<>();

        //记录数据行索引,方便找到报错行
        int dataIndex = titleIndex+1;
        //遍历数据  row每一行
        for (Row row : dataRows) {
            //Row转List,固定宽度
            List<String> strings = rowToList(row, (short) (lastCellNumIndex+1));
            assert strings != null;

            String splitStringValue;
            try {
                splitStringValue=strings.get(splitIndex);
            } catch (IndexOutOfBoundsException iobe) {
                LOGGER.warn("Sheet:" + sheet.getSheetName() + ",行:" + dataIndex+1 + ",拆分列索引越界");
                LOGGER.warn(strings.toString());
                //这种情况一般为拆分列无数据,且后面的列也没有数据,这里给处理了
                splitStringValue="";
                // throw iobe;
            }


            //去前后空格
            splitStringValue = splitStringValue.trim();
            //尝试拆分
            String[] splits = splitStringValue.split(delimiter);

            //拆分列直接使用拆分出来的值替换原来的值
            for (String split : splits) {
                //复制一个新的list,并替换列的值
                ArrayList<String> strings_new = new ArrayList<>(strings);
                //添加一列:原始位置
                strings_new.add((dataIndex+1)+"");
                boolean b = Collections.replaceAll(strings_new, splitStringValue, split);
                lists.add(strings_new);
            }

            //更新数据行索引
            dataIndex++;
        }

//        printCollect(lists);
        //存储到newSheet内
        //写表头
        write(newSheet, titleRowsList, 0,getDefaultCellStyle(newSheet.getWorkbook(),true));
        //写数据
        write(newSheet, lists, getLastRowNumIndex(newSheet) + 1,getDefaultCellStyle(newSheet.getWorkbook()));

        return newWorkbook;
    }

    public static Workbook columnsToRows(Sheet sheet, int titleIndex, int splitIndex, String delimiter) throws Exception {
        //默认
        Workbook newWorkbook = sheet.getWorkbook();
        String newSheetName="columnsToRows_" + String.valueOf(System.currentTimeMillis()).substring(8);

        return columnsToRows(sheet, titleIndex, splitIndex, delimiter, newWorkbook, newSheetName);
    }

    /**
     * 写到xlsx文件
     */
    public static void workbookWrite(Workbook workbook, String pathName) {
        try {
            workbook.write(new FileOutputStream(pathName));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 合并多个excel文件的某个sheet(格式,sheetIndex位置要一致),文件夹内请勿包含其它不需要合并的文件
     *
     * @param pathName   输入和输出文件文件夹的地址
     * @param sheetIndex 需要合并的sheetIndex
     */
    public static void workbookMerge(String pathName, int sheetIndex, int titleIndex) throws IOException, InvalidFormatException {
        workbookMerge(pathName, pathName,createWorkbook(), sheetIndex, titleIndex);
    }

    /**
     * 合并多个excel文件的某个sheet(格式,sheetIndex位置要一致),文件夹内请勿包含其它不需要合并的文件
     *
     * @param inPath   输入文件文件夹的地址
     * @param outPath   输出文件文件夹的地址
     * @param newWorkbook   输出到Workbook
     * @param sheetIndex 需要合并的sheetIndex
     * @return Workbook
     */
    public static Workbook  workbookMerge(String inPath, String outPath, Workbook newWorkbook, int sheetIndex, int titleIndex) throws IOException, InvalidFormatException {
        //取得需要合并的文件列表
        List<File> files = fileList(inPath);
        //拿到所有需要合并的sheet
        ArrayList<Sheet> sheets = new ArrayList<>();
        for (File file : files) {
            String fileName = file.getName();
            if (fileName.endsWith(XLSX) || fileName.endsWith(XLS)) {
                Workbook workbook = getWorkbook(file);
                assert workbook != null;
                Sheet sheetAt = workbook.getSheetAt(sheetIndex);
                sheets.add(sheetAt);
            }
        }
        if (sheets.size() != 0) {
            //合并sheet
            newWorkbook = sheetsMerge(sheets, titleIndex,newWorkbook,sheets.get(0).getSheetName());
            //写出
            workbookWrite(newWorkbook, outPath + "\\合并" + XLSX);
        } else {
            LOGGER.info("[workbookMerge] 空Sheets,检查文件夹路径是否有误!");
        }

        return newWorkbook;

    }
    /**
     * 合并多个excel文件的多个sheet(格式,sheetIndex位置要一致),文件夹内请勿包含其它不需要合并的文件
     *
     * @param inPath   输入文件文件夹的地址
     * @param outPath   输出文件文件夹的地址
     * @param sheet_title 需要合并的sheetIndex->titleIndex
     * @return Workbook
     */
    public static Workbook workbookMerge(String inPath, String outPath, Map<Integer,Integer> sheet_title) throws IOException, InvalidFormatException {
        Workbook newWorkbook = createWorkbook();
        //解析map
        Set<Map.Entry<Integer, Integer>> entries = sheet_title.entrySet();
        //遍历map
        for (Map.Entry<Integer, Integer> entry : entries) {
            Integer sheetIndex = entry.getKey();
            Integer titleIndex = entry.getValue();
            // 调用合并
            newWorkbook = workbookMerge(inPath, outPath, newWorkbook, sheetIndex, titleIndex);
        }

        return newWorkbook;
    }

        /**
         * 根据一个String[]数组删除多个Sheet,数组内是Sheet名
         *
         * @param workbook   表格对象
         * @param sheetNames Sheet名称数组
         * @return boolean(全部找到并删除则返回true)
         */
    public static boolean delSheetWithArray(Workbook workbook, String[] sheetNames) {
        LOGGER.info("[delSheetWithArray] " + workbook);
        boolean flag = true;
        if (sheetNames.length < 1) {
            return flag;
        }
        //遍历sheetNames
        for (String sheetName : sheetNames) {
            //尝试通过名称拿到Sheet索引
            int sheetIndex = workbook.getSheetIndex(sheetName);
            if (sheetIndex != -1) {
                workbook.removeSheetAt(sheetIndex);
                LOGGER.info("[delSheetWithArray] 删除Sheet:" + sheetName);
            } else {
                //只要一个未找到则返回false
                flag = false;
                LOGGER.info("[delSheetWithArray] 未找到Sheet:" + sheetName);
            }
        }

        return flag;
    }

    /**
     * 创建sheet,并设置表头
     *
     * @param workbook  工作簿
     * @param sheetName Sheet名
     * @param title     表头,可多行
     * @return Sheet
     */
    public static Sheet createSheet(Workbook workbook, String sheetName, List<List<String>> title) {
        Sheet sheet = null;
        try {
            //创建新的sheet
            sheet = workbook.createSheet(sheetName);
            //拿到默认表头样式
            CellStyle defaultCellStyle = getDefaultCellStyle(workbook, true);
            //写表头
            for (int i = 0; i < title.size(); i++) {
                writeALine(sheet, sheet.getRow(i), i, defaultCellStyle);
            }
        } catch (IllegalArgumentException e) {
            String logMsg = "[createSheet] Sheet'" + sheetName + "'已存在,跳过";
            LOGGER.warn(logMsg);
        }

        return sheet;
    }

    /**
     * 创建sheet,并设置表头
     *
     * @param workbook  工作簿
     * @param sheetName Sheet名
     * @param titleRow  表头,一行
     * @return Sheet
     */
    public static Sheet createSheet(Workbook workbook, String sheetName, Row titleRow) {
        ArrayList<List<String>> title = new ArrayList<>();
        title.add(rowToList(titleRow));
        return createSheet(workbook, sheetName, title);
    }

    /**
     * 同时设置Sheet多列列宽
     *
     * @param sheet  Sheet对象
     * @param widths 变长参数,每一个参数设置一列的列宽
     */
    public static void setColumnWidth(Sheet sheet, int... widths) {
        //根据变长参数的个数和顺序从第一列开始设置每一列的列宽
        for (int i = 0; i < widths.length; i++) {
            //宽计算公式,较接近实际宽度
            int width = 256 * widths[i] + 154;
            sheet.setColumnWidth(i, width);
        }
    }

    public static void setColumnWidthWithMultipleSheets(List<Sheet> sheets, int... widths) {
        //遍历每一个sheet
        for (Sheet sheet : sheets) {
            setColumnWidth(sheet, widths);
        }
    }

    /**
     * 设置Sheet为默认样式
     * (sheet.getLastRowNum拿到的是之后一行索引,row.getLastCellNum()拿到的是单元格个数.)
     *
     * @param sheet      Sheet对象
     * @param titleIndex 表头最后索引
     */
    public static void setAsDefaultCellStyle(Sheet sheet, int titleIndex) {
        LOGGER.info("[setAsDefaultCellStyle] sheetName:" + sheet.getSheetName());
        //取得默认样式
        CellStyle defaultTitleCellStyle = getDefaultCellStyle(sheet.getWorkbook(), true);
        CellStyle defaultCellStyle = getDefaultCellStyle(sheet.getWorkbook());
        //依据表头最后一列取得列数
        Row row = getRow(sheet, titleIndex);
        short lastCellNum = getLastCellNumIndex(row);
        //设置表头样式
        List<Cell> titleCells = getCellInArea(sheet, titleIndex, lastCellNum);
        for (Cell cell : titleCells) {
            cell.setCellStyle(defaultTitleCellStyle);
        }
        //拿到表最大行索引
        int lastRowNum = getLastRowNumIndex(sheet);
        //拿到数据cell,并设置样式
        List<Cell> dataCells = getCellInArea(sheet, titleIndex + 1, 0, lastRowNum, lastCellNum);
        for (Cell cell : dataCells) {
            cell.setCellStyle(defaultCellStyle);
        }

    }

    /**
     * 设置Sheet为默认样式
     *
     * @param sheets      多个Sheet对象
     * @param titleIndex 表头最后索引
     */
    public static void setAsDefaultCellStyle(List<Sheet> sheets, int titleIndex) {
        for (Sheet sheet : sheets) {
            setAsDefaultCellStyle(sheet, titleIndex);
        }
    }

    /**
     * 拿到指定区域内的cell
     *
     * @param sheet    Sheet对象
     * @param indexTLX 左上X索引
     * @param indexTLY 左上Y索引
     * @param indexBRX 右下X索引
     * @param indexBRY 右下Y索引
     * @return List<Cell>
     */
    public static List<Cell> getCellInArea(Sheet sheet, int indexTLX, int indexTLY, int indexBRX, int indexBRY) {
        LOGGER.info("[getCellInArea] 区域[" + indexTLX + "," + indexTLY + ":" + indexBRX + "," + indexBRY + "]");
        ArrayList<Cell> cells = new ArrayList<>();
        //遍历指定区域
        for (int i = indexTLX; i <= indexBRX; i++) {
            //尝试获取行
            Row row = sheet.getRow(i);
            //不存在则创建
            if (row == null) {
                row = sheet.createRow(i);
            }
            for (int j = indexTLY; j <= indexBRY; j++) {
                //尝试获取列
                assert row != null;
                Cell cell = row.getCell(j);
                //不存在则创建
                if (cell == null) {
                    cell = row.createCell(j);
                }
                //添加到list
                cells.add(cell);
            }
        }
        LOGGER.info("[getCellInArea] cell个数:" + cells.size());

        return cells;
    }

    /**
     * 拿到指定区域内的cell,从左上角开始计算
     *
     * @param sheet    Sheet对象
     * @param indexBRX 右下X索引
     * @param indexBRY 右下Y索引
     * @return List<Cell>
     */
    public static List<Cell> getCellInArea(Sheet sheet, int indexBRX, int indexBRY) {
        return getCellInArea(sheet, 0, 0, indexBRX, indexBRY);
    }

    /**
     * 列转行
     * ID   F1  F2
     * 1    a   A
     * 1    a   B
     * -->>
     * ID   F1  F2
     * 1    a   A,B
     *
     * @param sheet
     * @param titleIndex
     * @param basisIndex
     * @param mergeIndex
     * @param separator
     * @return
     * @throws Exception
     */
    public static Sheet rowsToColumn(Sheet sheet,int titleIndex,int basisIndex,int mergeIndex,String separator) throws Exception {
        //存储依据列和合并列
        HashMap<String, List<String>> basis_merge = new HashMap<>();
        //合并之后数据存放的sheet
        Sheet newSheet = sheet.getWorkbook().createSheet(sheet.getSheetName() + "_列转行");
        CellStyle defaultCellStyle = getDefaultCellStyle(newSheet.getWorkbook());
        HashMap<String, List<String>> newSheetData = new HashMap<>();
        //写表头到newSheet
        writeRow(newSheet,getRow(sheet,0,titleIndex),0,defaultCellStyle);
        //遍历来源sheet
        List<List<String>> sheetAllRowsToList = getSheetAllRowsToList(sheet);

        for (int i=titleIndex+1;i<sheetAllRowsToList.size();i++){
            //拿到一行
            List<String> rowList = sheetAllRowsToList.get(i);
            //取出依据列和和并列
            String key = rowList.get(basisIndex);
            String value = rowList.get(mergeIndex);
            //尝试在map里找依据列
            List<String> values = basis_merge.get(key);
//            stringListHashMap.computeIfAbsent(s,
////                    k-> {
////                        ArrayList<String> strings1 = new ArrayList<>();
////                        strings1.add(st);
////                        return strings1;
////                    }
////            );
            //没找到则创建
            if (values==null){
                values=new ArrayList<String>();
                //只放一行到新sheet数据的map内
                newSheetData.put(key, rowList);
            }
            //把值暂时写到list里
            values.add(value);
            //加到map里,key是`依据列`,值是`合并列`
            basis_merge.put(key, values);
        }
        //遍历合并好的结果,替换写到新sheet数据的map内
        Set<Map.Entry<String, List<String>>> entries = basis_merge.entrySet();
        for (Map.Entry<String, List<String>> entry:entries) {
            List<String> rowList = newSheetData.get(entry.getKey());
            //替换掉原始值
//            rowList.remove(mergeIndex);
            rowList.set(mergeIndex, StringUtils.join(entry.getValue().toArray(), separator));
            //写数据到sheet
            writeALine(newSheet,rowList,getLastRowNumIndex(newSheet)+1,defaultCellStyle);
        }

        return newSheet;

    }




    /**
     * 测试
     */
    public static void main(String[] args) {
        System.out.println("123");

    }


}