xufei 2 rokov pred
rodič
commit
ba8c847996

+ 11 - 0
pom.xml

@@ -239,6 +239,17 @@
             <artifactId>commons-compress</artifactId>
             <version>1.19</version>
         </dependency>
+        <dependency>
+            <groupId>org.jumpmind.symmetric</groupId>
+            <artifactId>symmetric-csv</artifactId>
+            <version>3.5.19</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.poi</groupId>
+            <artifactId>poi-ooxml</artifactId>
+            <version>3.17</version>
+        </dependency>
+
     </dependencies>
 
     <build>

+ 47 - 0
src/main/java/com/winhc/task/bean/ExcelBean.java

@@ -0,0 +1,47 @@
+package com.winhc.task.bean;
+
+import com.alibaba.excel.annotation.ExcelProperty;
+import com.alibaba.excel.annotation.format.DateTimeFormat;
+import com.alibaba.excel.annotation.format.NumberFormat;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.util.Date;
+
+/**
+ * @author dz
+ * @date 2021-11-06 上午 9:14
+ */
+@Accessors(chain = true)
+@Data
+public class ExcelBean {
+
+    @ExcelProperty("主键id")
+    private String id;
+
+    @ExcelProperty("姓名")
+    private String name;
+
+    @ExcelProperty("地址")
+    private String address;
+
+    @ExcelProperty("年龄")
+    private Integer age;
+
+    @ExcelProperty("数量")
+    private Integer number;
+
+    @NumberFormat("#.##")
+    @ExcelProperty("身高")
+    private Double high;
+
+    @ExcelProperty("距离")
+    private Double distance;
+
+    @DateTimeFormat("yyyy-MM-dd HH:mm:ss")
+    @ExcelProperty("开始时间")
+    private Date startTime;
+
+    @ExcelProperty("结束时间")
+    private Date endTime;
+}

+ 37 - 0
src/main/java/com/winhc/task/bean/Student.java

@@ -0,0 +1,37 @@
+package com.winhc.task.bean;
+
+/**
+ * 学生类
+ */
+public class Student{
+    /**名字*/
+    private String name;
+    /**年龄*/
+    private int age;
+    /**金钱*/
+    private double money;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int getAge() {
+        return age;
+    }
+
+    public void setAge(int age) {
+        this.age = age;
+    }
+
+    public double getMoney() {
+        return money;
+    }
+
+    public void setMoney(double money) {
+        this.money = money;
+    }
+}

+ 235 - 0
src/main/java/com/winhc/task/util/CsvToXlsxUtil.java

@@ -0,0 +1,235 @@
+package com.winhc.task.util;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.poi.xssf.usermodel.XSSFRow;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.jumpmind.symmetric.csv.CsvReader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.*;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * -----------------------maven 依赖----------------------------
+ * CSV解析依赖包
+ * <dependency>
+ * <groupId>org.jumpmind.symmetric</groupId>
+ * <artifactId>symmetric-csv</artifactId>
+ * <version>3.5.19</version>
+ * </dependency>
+ * <p>
+ * APACHE POI包
+ * <dependency>
+ * <groupId>org.apache.poi</groupId>
+ * <artifactId>poi-ooxml</artifactId>
+ * <version>3.17</version>
+ * </dependency>
+ * <p>
+ * -----------------------------------------------------------
+ *
+ * @description
+ */
+public class CsvToXlsxUtil {
+
+    private static final Logger log = LoggerFactory.getLogger(CsvToXlsxUtil.class);
+
+    //CSV常用分隔符,如需动态扩展设置成配置项
+    private static final char[] DELIMITERS = {
+            ',',
+            ';',
+            '\001',
+            ' ',
+            '\t',
+            '|',
+            '#',
+            '&'
+    };
+
+
+    public static void main(String[] args) {
+        String csvFile = "D:\\data\\山西省太原市小店区.csv";
+        System.out.println(csvToXLSX(csvFile));
+    }
+
+    /**
+     * 读取CSV文件并写入到XLSX文件中,默认编码
+     *
+     * @param csvFileAddress
+     * @return
+     */
+    public static String csvToXLSX(String csvFileAddress) {
+        return csvToXLSX(csvFileAddress, "GBK");
+    }
+
+    /**
+     * 读取CSV文件并写入到XLSX文件中,指定CSV文件编码
+     *
+     * @param csvFileAddress
+     * @param charset
+     * @return
+     */
+    public static String csvToXLSX(String csvFileAddress, String charset) {
+        String xlsxFileAddress = "";
+        FileOutputStream fileOutputStream = null;
+        try {
+            //char delimiter = getDelimiter(csvFileAddress);
+            //xlsxFileAddress = csvFileAddress.replace("csv","xlsx"); //xlsx file address
+            xlsxFileAddress = csvFileAddress.split("\\.")[0] + ".xls";
+
+            XSSFWorkbook workBook = new XSSFWorkbook();
+            XSSFSheet sheet = workBook.createSheet(getSheetName(csvFileAddress));
+            int RowNum = -1;
+            CsvReader csvReader = new CsvReader(csvFileAddress, ',', Charset.forName(charset));
+            while (csvReader.readRecord()) {
+                RowNum++;
+                System.out.println(RowNum);
+                XSSFRow currentRow = sheet.createRow(RowNum);
+                for (int i = 0; i < csvReader.getColumnCount(); i++) {
+                    currentRow.createCell(i).setCellValue(csvReader.get(i));
+                }
+            }
+            fileOutputStream = new FileOutputStream(xlsxFileAddress);
+            workBook.write(fileOutputStream);
+            return getFileName(xlsxFileAddress);
+        } catch (Exception e) {
+            log.error("CsvToXlsxUtil exception :", e);
+        } finally {
+            try {
+                fileOutputStream.close();
+            } catch (IOException e) {
+                log.error("CsvToXlsxUtil close FileOutputStream exception :", e);
+            }
+        }
+        return getFileName(xlsxFileAddress);
+    }
+
+
+    public static void csvToXLSxPlus(String csvFilePath, String xlsxFilePath,String sheetName) {
+        EasyExcelUtil easyExcelUtil = new EasyExcelUtil();
+        //easyExcelUtil.init(xlsxFilePath, sheet, head);
+        easyExcelUtil.init(xlsxFilePath, sheetName);
+        try {
+            int RowNum = -1;
+            CsvReader csvReader = new CsvReader(csvFilePath, ',', StandardCharsets.UTF_8);
+            csvReader.setSafetySwitch(false);
+            csvReader.setUseTextQualifier(false);
+            while (csvReader.readRecord()) {
+                RowNum++;
+                System.out.println(RowNum);
+                List<List<String>> sumDataList = new ArrayList<>(1);
+                List<String> dataList = new ArrayList<>(1);
+                for (int i = 0; i < csvReader.getColumnCount(); i++) {
+//                    String col = csvReader.get(i);
+//                    if (StringUtils.isNotBlank(col) && col.length() > 100000L) {
+//                        //break;
+//                        dataList.add(csvReader.get(i).substring(0, 100000));
+//                    }
+                    dataList.add(csvReader.get(i));
+                }
+                sumDataList.add(dataList);
+                easyExcelUtil.doExportExcel(sumDataList);
+            }
+        } catch (Exception e) {
+            log.error("CsvToXlsxUtil exception :", e);
+        } finally {
+            try {
+                easyExcelUtil.finish();
+            } catch (Exception e) {
+                log.error("CsvToXlsxUtil close FileOutputStream exception :", e);
+            }
+        }
+    }
+
+    /**
+     * 设置excel文件的sheet名称
+     * 获取CSV文件名作为Excel文件的sheet名称
+     *
+     * @param path
+     * @return
+     */
+    private static String getSheetName(String path) {
+        try {
+            String[] file = getFileName(path).split("\\.");
+            return file[0];
+        } catch (Exception e) {
+            log.error("CsvToXlsxUtil get sheet name exception : ", e);
+            return "Sheet";
+        }
+    }
+
+
+    /**
+     * 根据资源路径切割获取文件名
+     *
+     * @param path
+     * @return
+     */
+    private static String getFileName(String path) {
+        String[] paths = path.contains("\\") ? path.split("\\\\") : path.split("/");
+        return paths[paths.length - 1];
+    }
+
+    /**
+     * 常用CSV分隔符数组遍历资源第一行,分隔的字段数多的为资源分隔符
+     * 异常情况下默认用’,‘作为分隔符
+     *
+     * @param path 资源路径
+     * @return
+     */
+    private static char getDelimiter(String path) {
+        BufferedReader br = null;
+        char delimiter = ',';
+        try {
+            br = new BufferedReader(new FileReader(path));
+            String line = br.readLine();
+            CsvReader csvReader;
+            int columCount = 0;
+            for (char delimiterTest : DELIMITERS) {
+                csvReader = new CsvReader(getStringStream(line), delimiterTest, Charset.forName("UTF-8"));
+                if (csvReader.readRecord()) {
+                    int newColumnCount = csvReader.getColumnCount();
+                    if (newColumnCount > columCount) {
+                        columCount = newColumnCount;
+                        delimiter = delimiterTest;
+                    }
+                }
+            }
+        } catch (Exception e) {
+            log.error("CsvToXlsxUtil get delimiter exception :", e);
+        } finally {
+            try {
+                br.close();
+            } catch (IOException e) {
+                log.error("CsvToXlsxUtil get delimiter close BufferedReader exception :", e);
+            }
+        }
+        return delimiter;
+    }
+
+
+    /**
+     * 字符串转输入流
+     * 把CSV文件第一行数据转成输入流
+     *
+     * @param sInputString
+     * @return
+     */
+    private static InputStream getStringStream(String sInputString) {
+        if (sInputString != null && !sInputString.equals("")) {
+            try {
+                ByteArrayInputStream tInputStringStream = new ByteArrayInputStream(sInputString.getBytes());
+                return tInputStringStream;
+            } catch (Exception e) {
+                log.error("CsvToXlsxUtil get StringStream exception :", e);
+            }
+        }
+        return null;
+    }
+
+
+}

+ 126 - 0
src/main/java/com/winhc/task/util/EasyExcelUtil.java

@@ -0,0 +1,126 @@
+package com.winhc.task.util;
+
+import com.alibaba.excel.EasyExcelFactory;
+import com.alibaba.excel.ExcelWriter;
+import com.alibaba.excel.write.metadata.WriteSheet;
+
+import java.io.File;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * EasyExcel 操作工具类(写,追加)
+ *
+ * @author lzt
+ * editUser:lizetao
+ * editdate:2021/12/28
+ */
+public class EasyExcelUtil {
+    private ExcelWriter excelWriter = null;
+    private WriteSheet writeSheet = null;
+
+    /**
+     * ===========================================================
+     * <p>
+     * Purpose      :   EasyExcel工具类 初始化(最先调用)
+     *
+     * @return Author       :   lzt
+     * Created Date :   2021-12-28
+     * Update History
+     * Version       Date               Name            Description
+     * --------  ---------------   --------------  --------------------
+     * V1.0       2021-12-28           lzt            Creation
+     * ===========================================================
+     * @params: absFilePath  绝对路径
+     * @params: sheetName 标签页名字
+     * @params: titleList 标题头(第一行)
+     */
+    public void init(String absFilePath, String sheetName, List<List<String>> heads) {
+        if (excelWriter == null && writeSheet == null) {
+            // 这里 需要指定写用哪个标题头去写 可以用class 也可以不用
+            excelWriter = EasyExcelFactory.write(absFilePath).head(heads).build();
+            // 这里注意 如果同一个sheet只要创建一次
+            writeSheet = EasyExcelFactory.writerSheet(sheetName).build();
+        }
+    }
+
+    public void init(String absFilePath, String sheetName) {
+        if (excelWriter == null && writeSheet == null) {
+            // 这里 需要指定写用哪个标题头去写 可以用class 也可以不用
+            excelWriter = EasyExcelFactory.write(absFilePath).build();
+            // 这里注意 如果同一个sheet只要创建一次
+            writeSheet = EasyExcelFactory.writerSheet(sheetName).build();
+        }
+    }
+
+    /**
+     * ===========================================================
+     * <p>
+     * Purpose      :   EasyExcel工具类 写入excel写内容
+     *
+     * @return Author       :   lzt
+     * Created Date :   2021-12-28
+     * Update History
+     * Version       Date               Name            Description
+     * --------  ---------------   --------------  --------------------
+     * V1.0       2021-12-28           lzt            Creation
+     * ===========================================================
+     * @params: dataList  要插入的数据(多行插入)
+     */
+    public void doExportExcel(List<List<String>> dataList) {
+        try {
+            excelWriter.write(dataList, writeSheet);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * ===========================================================
+     * <p>
+     * Purpose      :   EasyExcel工具类 关闭(最后调用 关闭流)
+     *
+     * @return Author       :   lzt
+     * Created Date :   2021-12-28
+     * Update History
+     * Version       Date               Name            Description
+     * --------  ---------------   --------------  --------------------
+     * V1.0       2021-12-28           lzt            Creation
+     * ===========================================================
+     */
+    public void finish() {
+        if (excelWriter != null) {
+            excelWriter.finish();
+        }
+    }
+
+    public static void main(String[] args) {
+        EasyExcelUtil easyExcelUtil = new EasyExcelUtil();
+        //使用原文件名在java的临时文件夹中创建临时文件
+        String tmpdir = System.getProperty("java.io.tmpdir");
+        File file = new File(tmpdir, "测试.xlsx");
+        //若临时文件夹中已经存在该文件,则先删除
+        if (file.exists()) {
+            file.delete();
+        }
+        String absFilePath = file.getAbsolutePath();
+        String sheet = "sheet1";
+        List<List<String>> head = Stream.of("学生", "年龄", "性别")
+                .map(Collections::singletonList)
+                .collect(Collectors.toList());
+        //初始化
+        easyExcelUtil.init(absFilePath, sheet, head);
+        //写入数据
+        List<List<String>> sumDataList = new ArrayList<>(1);
+        List<String> dataList = new ArrayList<>(1);
+        dataList.add("张三");
+        dataList.add("16");
+        dataList.add("女");
+        sumDataList.add(dataList);
+        //我的追加数据 其实是伪追加 因为只要你没有把流关闭 还是持有这个流的 那么你就可以一直调用这个方法 往里面写数据
+        easyExcelUtil.doExportExcel(sumDataList);
+        //关闭流
+        easyExcelUtil.finish();
+    }
+}

+ 225 - 0
src/main/java/com/winhc/task/util/TestExcel.java

@@ -0,0 +1,225 @@
+package com.winhc.task.util;
+
+import com.alibaba.excel.EasyExcel;
+import com.alibaba.excel.ExcelWriter;
+import com.alibaba.excel.write.metadata.WriteSheet;
+import com.google.common.collect.Lists;
+import com.winhc.task.bean.ExcelBean;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.Test;
+
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * @author dengzeng
+ * @date 2021-11-06 上午 8:57
+ *
+ */
+@Slf4j
+public class TestExcel {
+    public static final String FILE_NAME = "D:\\test_" + System.currentTimeMillis() + ".xlsx";
+    // 每个 sheet 写入的数据
+    public static final int NUM_PER_SHEET = 300000;
+    // 每次向 sheet 中写入的数据(分页写入)
+    public static final int NUM_BY_TIMES = 50000;
+
+    /**
+     * 方法一:将数据写入到excel
+     * 直接调用api,适合小数据量
+     * 100W条数据33s
+     */
+    @Test
+    public void writeExcelByApi() {
+        String fileName = FILE_NAME;
+        log.info("导出excel名称={}", fileName);
+        long startTime = System.currentTimeMillis();
+        // 直接调用api
+        List<ExcelBean> date = getDate();
+        EasyExcel.write(fileName, ExcelBean.class).sheet().doWrite(date);
+        log.info("导出excel结束,数据量={},耗时={}ms", date.size(), System.currentTimeMillis() - startTime);
+    }
+
+    /**
+     * 方法二:导出多个sheet
+     * easyExcel 底层是 POI 实现的,POI 单个sheet 最多只能导出 1048576 行,超过该行数,会产生如下异常
+     * java.lang.IllegalArgumentException: Invalid row number (1048576) outside allowable range (0..1048575)
+     * <p>
+     * 11:57:55.541 [main] INFO mytest.TestExcel - 写入sheet=sheet0,数据量300000-0=300000,耗时=6055ms
+     * 11:57:59.701 [main] INFO mytest.TestExcel - 写入sheet=sheet1,数据量600000-300000=300000,耗时=4159ms
+     * 11:58:03.827 [main] INFO mytest.TestExcel - 写入sheet=sheet2,数据量900000-600000=300000,耗时=4126ms
+     * 11:58:05.193 [main] INFO mytest.TestExcel - 写入sheet=sheet3,数据量1000000-900000=100000,耗时=1366ms
+     * 11:58:17.418 [main] INFO mytest.TestExcel - 导出excel结束,总数据量=1000000,耗时=31297ms
+     */
+    @Test
+    public void writeExcelByMulSheet() {
+        String fileName = FILE_NAME;
+        log.info("导出excel名称={}", fileName);
+        long startTime = System.currentTimeMillis();
+        // 获取数据
+        List<ExcelBean> date = getDate();
+        // 获取 sheet 的个数
+        int sheetNum = date.size() % NUM_PER_SHEET == 0 ? date.size() / NUM_PER_SHEET : date.size() / NUM_PER_SHEET + 1;
+        // 指定写入的文件
+        ExcelWriter excelWriter = EasyExcel.write(fileName, ExcelBean.class).build();
+        for (int i = 0; i < sheetNum; i++) {
+            long l = System.currentTimeMillis();
+            // 设置 sheet 的名字(sheet不能相同)
+            String sheetName = "sheet" + i;
+            WriteSheet writeSheet = EasyExcel.writerSheet(i, sheetName).build();
+            int startNum = i * NUM_PER_SHEET;
+            int endNum = i == sheetNum - 1 ? date.size() : (i + 1) * NUM_PER_SHEET;
+            excelWriter.write(date.subList(startNum, endNum), writeSheet);
+            log.info("写入sheet={},数据量{}-{}={},耗时={}ms", sheetName, endNum, startNum, endNum - startNum, System.currentTimeMillis() - l);
+        }
+        // 最好放在 finally中
+        excelWriter.finish();
+        log.info("导出excel结束,总数据量={},耗时={}ms", date.size(), System.currentTimeMillis() - startTime);
+    }
+
+    /**
+     * 方法三:同一个 Sheet,分批多次写入
+     * 当单次读取的 list 数据量过大,会产生 OOM 异常,所以需要分页读取并写入到 excel
+     * 11:55:01.590 [main] INFO mytest.TestExcel - 写入数量50000-0=50000,耗时=2227ms
+     * 11:55:02.429 [main] INFO mytest.TestExcel - 写入数量100000-50000=50000,耗时=838ms
+     * 11:55:03.188 [main] INFO mytest.TestExcel - 写入数量150000-100000=50000,耗时=759ms
+     * 11:55:03.951 [main] INFO mytest.TestExcel - 写入数量200000-150000=50000,耗时=762ms
+     * 11:55:04.708 [main] INFO mytest.TestExcel - 写入数量250000-200000=50000,耗时=757ms
+     * 11:55:05.471 [main] INFO mytest.TestExcel - 写入数量300000-250000=50000,耗时=762ms
+     * 11:55:06.172 [main] INFO mytest.TestExcel - 写入数量350000-300000=50000,耗时=701ms
+     * 11:55:06.921 [main] INFO mytest.TestExcel - 写入数量400000-350000=50000,耗时=749ms
+     * 11:55:07.688 [main] INFO mytest.TestExcel - 写入数量450000-400000=50000,耗时=767ms
+     * 11:55:08.437 [main] INFO mytest.TestExcel - 写入数量500000-450000=50000,耗时=749ms
+     * 11:55:09.141 [main] INFO mytest.TestExcel - 写入数量550000-500000=50000,耗时=704ms
+     * 11:55:09.899 [main] INFO mytest.TestExcel - 写入数量600000-550000=50000,耗时=758ms
+     * 11:55:10.597 [main] INFO mytest.TestExcel - 写入数量650000-600000=50000,耗时=698ms
+     * 11:55:11.353 [main] INFO mytest.TestExcel - 写入数量700000-650000=50000,耗时=756ms
+     * 11:55:12.055 [main] INFO mytest.TestExcel - 写入数量750000-700000=50000,耗时=701ms
+     * 11:55:12.820 [main] INFO mytest.TestExcel - 写入数量800000-750000=50000,耗时=765ms
+     * 11:55:13.576 [main] INFO mytest.TestExcel - 写入数量850000-800000=50000,耗时=756ms
+     * 11:55:14.287 [main] INFO mytest.TestExcel - 写入数量900000-850000=50000,耗时=711ms
+     * 11:55:15.055 [main] INFO mytest.TestExcel - 写入数量950000-900000=50000,耗时=768ms
+     * 11:55:15.773 [main] INFO mytest.TestExcel - 写入数量1000000-950000=50000,耗时=718ms
+     * 11:55:28.016 [main] INFO mytest.TestExcel - 导出excel结束,总数据量=1000000,耗时=31738ms
+     * <p>
+     * Process finished with exit code 0
+     */
+    @Test
+    public void writeExcelByMulWrite() {
+        String fileName = FILE_NAME;
+        log.info("导出excel名称={}", fileName);
+        long startTime = System.currentTimeMillis();
+        // 获取数据
+        List<ExcelBean> date = getDate();
+        ExcelWriter excelWrite = EasyExcel.write(fileName, ExcelBean.class).build();
+        WriteSheet writeSheet = EasyExcel.writerSheet("testSheet").build();
+        // 计算需要写入的次数
+        int times = date.size() % NUM_BY_TIMES == 0 ? date.size() / NUM_BY_TIMES : date.size() / NUM_BY_TIMES + 1;
+        for (int i = 0; i < times; i++) {
+            long l = System.currentTimeMillis();
+            int startNum = i * NUM_BY_TIMES;
+            int endNum = i == times - 1 ? date.size() : (i + 1) * NUM_BY_TIMES;
+            excelWrite.write(date.subList(startNum, endNum), writeSheet);
+            log.info("写入数量{}-{}={},耗时={}ms", endNum, startNum, endNum - startNum, System.currentTimeMillis() - l);
+        }
+        // 需要放入 finally 中
+        if (excelWrite != null) {
+            excelWrite.finish();
+        }
+        log.info("导出excel结束,总数据量={},耗时={}ms", date.size(), System.currentTimeMillis() - startTime);
+    }
+
+
+    /**
+     * 方案四:写入多个 sheet,并且每个 sheet 写入多次数据(结合方案二、三)
+     * 数据量大,导致一个 sheet 存储不下;同时单次读入的数据量太大。可以采用这个方法
+     * 12:02:18.751 [main] INFO mytest.TestExcel - 写入sheet=sheet0,数据量=50000-0=50000,耗时=1558
+     * 12:02:19.542 [main] INFO mytest.TestExcel - 写入sheet=sheet0,数据量=100000-50000=50000,耗时=791
+     * 12:02:20.282 [main] INFO mytest.TestExcel - 写入sheet=sheet0,数据量=150000-100000=50000,耗时=740
+     * 12:02:21.037 [main] INFO mytest.TestExcel - 写入sheet=sheet0,数据量=200000-150000=50000,耗时=755
+     * 12:02:21.781 [main] INFO mytest.TestExcel - 写入sheet=sheet0,数据量=250000-200000=50000,耗时=744
+     * 12:02:22.524 [main] INFO mytest.TestExcel - 写入sheet=sheet0,数据量=300000-250000=50000,耗时=742
+     * 12:02:23.201 [main] INFO mytest.TestExcel - 写入sheet=sheet1,数据量=350000-300000=50000,耗时=677
+     * 12:02:23.852 [main] INFO mytest.TestExcel - 写入sheet=sheet1,数据量=400000-350000=50000,耗时=651
+     * 12:02:24.451 [main] INFO mytest.TestExcel - 写入sheet=sheet1,数据量=450000-400000=50000,耗时=599
+     * 12:02:25.100 [main] INFO mytest.TestExcel - 写入sheet=sheet1,数据量=500000-450000=50000,耗时=649
+     * 12:02:25.753 [main] INFO mytest.TestExcel - 写入sheet=sheet1,数据量=550000-500000=50000,耗时=653
+     * 12:02:26.350 [main] INFO mytest.TestExcel - 写入sheet=sheet1,数据量=600000-550000=50000,耗时=597
+     * 12:02:26.995 [main] INFO mytest.TestExcel - 写入sheet=sheet2,数据量=650000-600000=50000,耗时=645
+     * 12:02:27.588 [main] INFO mytest.TestExcel - 写入sheet=sheet2,数据量=700000-650000=50000,耗时=593
+     * 12:02:28.244 [main] INFO mytest.TestExcel - 写入sheet=sheet2,数据量=750000-700000=50000,耗时=656
+     * 12:02:28.893 [main] INFO mytest.TestExcel - 写入sheet=sheet2,数据量=800000-750000=50000,耗时=648
+     * 12:02:29.506 [main] INFO mytest.TestExcel - 写入sheet=sheet2,数据量=850000-800000=50000,耗时=613
+     * 12:02:30.163 [main] INFO mytest.TestExcel - 写入sheet=sheet2,数据量=900000-850000=50000,耗时=657
+     * 12:02:30.760 [main] INFO mytest.TestExcel - 写入sheet=sheet3,数据量=950000-900000=50000,耗时=597
+     * 12:02:31.419 [main] INFO mytest.TestExcel - 写入sheet=sheet3,数据量=1000000-950000=50000,耗时=659
+     * 12:02:43.235 [main] INFO mytest.TestExcel - 导出excel结束,总数据量=1000000,耗时=28818ms
+     *
+     * Process finished with exit code 0
+     */
+    @Test
+    public void writeExcelByMulSheetAndWriteChange() {
+        String fileName = FILE_NAME;
+        log.info("导出excel名称={}", fileName);
+        long startTime = System.currentTimeMillis();
+        // 获取数据
+        List<ExcelBean> date = getDate();
+        // 获取 sheet 的个数
+        int sheetNum = date.size() % NUM_PER_SHEET == 0 ? date.size() / NUM_PER_SHEET : date.size() / NUM_PER_SHEET + 1;
+        // 获取每个sheet 写入的次数
+        int writeNumPerSheet = NUM_PER_SHEET % NUM_BY_TIMES == 0 ? NUM_PER_SHEET / NUM_BY_TIMES : NUM_PER_SHEET / NUM_BY_TIMES + 1;
+        // 最后一个 sheet 写入的数量
+        int writeNumLastSheet = date.size() - (sheetNum - 1) * NUM_PER_SHEET;
+        // 最后一个 sheet 写入的次数
+        int writeNumPerLastSheet = writeNumLastSheet % NUM_BY_TIMES == 0 ? writeNumLastSheet / NUM_BY_TIMES : writeNumLastSheet / NUM_BY_TIMES + 1;
+        // 指定写入的文件
+        ExcelWriter excelWriter = EasyExcel.write(fileName, ExcelBean.class).build();
+        for (int i = 0; i < sheetNum; i++) {
+            String sheetName = "sheet" + i;
+            WriteSheet writeSheet = EasyExcel.writerSheet(i, sheetName).build();
+            int writeNum = i == sheetNum - 1 ? writeNumPerLastSheet : writeNumPerSheet; // 每个sheet 写入的次数
+            int endEndNum = i == sheetNum - 1 ? date.size() : (i + 1) * NUM_PER_SHEET; // 每个sheet 最后一次写入的最后行数
+            for (int j = 0; j < writeNum; j++) {
+                long l = System.currentTimeMillis();
+                int startNum = i * NUM_PER_SHEET + j * NUM_BY_TIMES;
+                int endNum = j == writeNum - 1 ? endEndNum : i * NUM_PER_SHEET + (j + 1) * NUM_BY_TIMES;
+                excelWriter.write(date.subList(startNum, endNum), writeSheet);
+                log.info("写入sheet={},数据量={}-{}={},耗时={}", sheetName, endNum, startNum, endNum - startNum, System.currentTimeMillis() - l);
+            }
+        }
+        // 需要放入 finally 中
+        if (excelWriter != null) {
+            excelWriter.finish();
+        }
+        log.info("导出excel结束,总数据量={},耗时={}ms", date.size(), System.currentTimeMillis() - startTime);
+    }
+
+
+    /**
+     * 获取excel 导出的数据
+     *
+     * @return list 集合
+     */
+    private List<ExcelBean> getDate() {
+        log.info("开始生成数据");
+        Date date = new Date();
+        long startTime = System.currentTimeMillis();
+        List<ExcelBean> list = Lists.newArrayList();
+        for (int i = 0; i < 1000000; i++) {
+            ExcelBean bean = new ExcelBean();
+            bean.setId(UUID.randomUUID().toString()).
+                    setName("隔壁老樊" + i).
+                    setAddress("北京市朝阳区酒仙桥" + i + "路").
+                    setAge(i).
+                    setNumber(i + 10000).
+                    setHigh(1.234 * i).
+                    setDistance(1.234 * i).
+                    setStartTime(date).
+                    setEndTime(date);
+            list.add(bean);
+        }
+        log.info("数据生成结束,数据量={},耗时={}ms", list.size(), System.currentTimeMillis() - startTime);
+        return list;
+    }
+}

+ 136 - 0
src/main/java/org/apache/poi/ss/SpreadsheetVersion.java

@@ -0,0 +1,136 @@
+/* ====================================================================
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+==================================================================== */
+
+package org.apache.poi.ss;
+
+import org.apache.poi.ss.util.CellReference;
+
+/**
+ * This enum allows spreadsheets from multiple Excel versions to be handled by the common code.
+ * <p>Properties of this enum correspond to attributes of the <i>spreadsheet</i> that are easily
+ * discernable to the user.  It is not intended to deal with low-level issues like file formats.
+ */
+public enum SpreadsheetVersion {
+    /**
+     * Excel97 format aka BIFF8
+     * <ul>
+     * <li>The total number of available rows is 64k (2^16)</li>
+     * <li>The total number of available columns is 256 (2^8)</li>
+     * <li>The maximum number of arguments to a function is 30</li>
+     * <li>Number of conditional format conditions on a cell is 3</li>
+     * <li>Number of cell styles is 4000</li>
+     * <li>Length of text cell contents is 32767</li>
+     * </ul>
+     */
+    EXCEL97(0x10000, 0x0100, 30, 3, 4000, Integer.MAX_VALUE),
+
+    /**
+     * Excel2007
+     *
+     * <ul>
+     * <li>The total number of available rows is 1M (2^20)</li>
+     * <li>The total number of available columns is 16K (2^14)</li>
+     * <li>The maximum number of arguments to a function is 255</li>
+     * <li>Number of conditional format conditions on a cell is unlimited
+     * (actually limited by available memory in Excel)</li>
+     * <li>Number of cell styles is 64000</li>
+     * <li>Length of text cell contents is 32767</li>
+     * <ul>
+     */
+    EXCEL2007(0x100000, 0x4000, 255, Integer.MAX_VALUE, 64000, Integer.MAX_VALUE);
+
+    private final int _maxRows;
+    private final int _maxColumns;
+    private final int _maxFunctionArgs;
+    private final int _maxCondFormats;
+    private final int _maxCellStyles;
+    private final int _maxTextLength;
+
+    private SpreadsheetVersion(int maxRows, int maxColumns, int maxFunctionArgs, int maxCondFormats, int maxCellStyles, int maxText) {
+        _maxRows = maxRows;
+        _maxColumns = maxColumns;
+        _maxFunctionArgs = maxFunctionArgs;
+        _maxCondFormats = maxCondFormats;
+        _maxCellStyles = maxCellStyles;
+        _maxTextLength = maxText;
+    }
+
+    /**
+     * @return the maximum number of usable rows in each spreadsheet
+     */
+    public int getMaxRows() {
+        return _maxRows;
+    }
+
+    /**
+     * @return the last (maximum) valid row index, equals to <code> getMaxRows() - 1 </code>
+     */
+    public int getLastRowIndex() {
+        return _maxRows - 1;
+    }
+
+    /**
+     * @return the maximum number of usable columns in each spreadsheet
+     */
+    public int getMaxColumns() {
+        return _maxColumns;
+    }
+
+    /**
+     * @return the last (maximum) valid column index, equals to <code> getMaxColumns() - 1 </code>
+     */
+    public int getLastColumnIndex() {
+        return _maxColumns - 1;
+    }
+
+    /**
+     * @return the maximum number arguments that can be passed to a multi-arg function (e.g. COUNTIF)
+     */
+    public int getMaxFunctionArgs() {
+        return _maxFunctionArgs;
+    }
+
+    /**
+     * @return the maximum number of conditional format conditions on a cell
+     */
+    public int getMaxConditionalFormats() {
+        return _maxCondFormats;
+    }
+
+    /**
+     * @return the maximum number of cell styles per spreadsheet
+     */
+    public int getMaxCellStyles() {
+        return _maxCellStyles;
+    }
+
+    /**
+     *
+     * @return the last valid column index in a ALPHA-26 representation
+     *  (<code>IV</code> or <code>XFD</code>).
+     */
+    public String getLastColumnName() {
+        return CellReference.convertNumToColString(getLastColumnIndex());
+    }
+
+    /**
+     * @return the maximum length of a text cell
+     */
+    public int getMaxTextLength() {
+        return _maxTextLength;
+    }
+}

+ 30 - 0
src/test/java/com/winhc/task/TestExcel.java

@@ -0,0 +1,30 @@
+package com.winhc.task;
+
+import com.alibaba.excel.EasyExcel;
+import com.winhc.task.bean.Student;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author π
+ * @Description:
+ * @date 2022/11/8 9:09
+ */
+public class TestExcel {
+    public static void main(String[] args) {
+        // 文件路径
+        String fileName = "d://tmp//temp.xlsx";
+        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
+        //dataList 就是要插入的数据
+        List<Student> dataList = new ArrayList<>(1);
+        Student student = new Student();
+        student.setName("张三");
+        student.setAge(15);
+        student.setMoney(100);
+        dataList.add(student);
+        //Student.class 就是写到表格数据类型的class对象
+        //一行代码 把数据写入
+        EasyExcel.write(fileName, Student.class).sheet("模板").doWrite(dataList);
+    }
+}

+ 43 - 0
src/test/java/com/winhc/task/TransToExcel.java

@@ -0,0 +1,43 @@
+package com.winhc.task;
+
+import com.winhc.task.util.CsvToXlsxUtil;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * @author π
+ * @Description:
+ * @date 2022/11/7 16:10
+ */
+public class TransToExcel {
+    public static void main(String[] args) {
+
+//        String csvFile = "D:\\data\\山西省太原市小店区.csv";
+//        String xlsxFile = "D:\\data\\split_out\\山西省太原市小店区.csv";
+//        //System.out.println(CsvToXlsxUtil.csvToXLSX(csvFile));
+
+        String head = "企业名称,法定代表人,注册资本,成立日期,注吊销日期,登记状态,工商注册号,机构组织代码,统一社会信用代码,纳税人识别号,企业类型,所属行业,营业期限,核准日期,登记机关,省份代码,所属地区,详细地址,注册地址城市,经营范围,电话,更多电话,邮箱";
+        List<List<String>> headList = Arrays.stream(head.split(","))
+                .map(Collections::singletonList)
+                .collect(Collectors.toList());
+
+        String path = "D:\\data\\split_out";        //要遍历的路径
+        File file = new File(path);        //获取其file对象
+        File[] fs = file.listFiles();    //遍历path下的文件和目录,放在File数组中
+        for (File f : fs) {                    //遍历File[]数组
+            String csvFile = f.getAbsolutePath();
+            if (!f.isDirectory()) {
+                String fileName = f.getName();
+                System.out.println(csvFile);
+                CsvToXlsxUtil.csvToXLSxPlus(csvFile, csvFile.replace(".csv", ".xlsx"), fileName.replaceAll(".csv",""));
+            }
+        }
+
+    }
+}
+
+