↵起因是这样的,用户要下载对账单明细,也就是交易明细,我们公司的数据库的设计是,一天一张表,类似于trace_20190708,trace_20190709  .......  这样的类型,所以设计生产者的时候是,一个线程负责某天的表,线程数不要超过查询的天数总和(例如读取20190720-20190730 这十天的数据,那么线程数不能超过10个,也不能过多,看自己系统内存情况),否则,会有线程被一直阻塞。

注 :其实也可以多线程,读同一张表,这里提供下思路,一般是分页读取,我们可以a线程读取(select * from user limit 0,10  下面就直接用limit x,y代表了)limit 2n,100   ,B线程读取limit 2n+1,100 这样写设计sql读取,这样设计的目的是,不用写锁,若是多个线程用同一个 limit n,100,这样设计,那么会用并发现象,那么就要加锁,比如我在读limit 10,20  ,那么你要读limt30,20 你要等我读完,但是我多线程就是为了读取速度快,并发执行,加锁等待了,多线程的意义何在,所以在sql 上做些文章就好。

package com.example.demo.test;
 * @Author: myc
 * @Description:
 * @Date 下午 4:51 2019/6/26 0026
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 * 测试POI
 * @author alex
public class PoiTest {
    public static void main(String[] args) {
         * 使用线程池进行线程管理。
        ExecutorService es = Executors.newFixedThreadPool(20);
         * 使用计数栅栏
        CountDownLatch doneSignal = new CountDownLatch(1);
        String xlsFile = "d:/xlsx/poiSXXFSBigData" + "2019010" + ".xlsx";
        PoiExcel.multiThreadWrite(es, doneSignal, xlsFile);
        es.shutdown();

读写excel ,这里采用了生产者-消费者模式,将读数据库的数据,与写入excel,两个步骤分离,采用了blockqueue。考虑到一个excel的工作簿数据的容量大概是100W行,所以将其分多个工作簿,处理,这样就可以读写更多数据。但是有个问题是,多线程下,读写的并发问题,在多个消费者的情况下,会有些问题,所以建议开一个消费者就好。

sheet.setRandomAccessWindowSize(windowSize);注意这个设置,这个设置是将数据读到内存中windowSize个,超过这个数后就写入到磁盘零时文件中。若是设置为 -1,那么数据会全部读取到内存中,这样数据过多内存会被撑爆
package com.example.demo.test;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.util.SheetUtil;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import java.io.FileOutputStream;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
 * @Author: myc
 * @Description: 用Poi读写大量数据
 * @Date 下午 2:12 2019/6/28 0028
public class PoiExcel {
    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
    protected static volatile SXSSFWorkbook wb = new SXSSFWorkbook(100);
     * 总行号
    protected static AtomicInteger rowNo = new AtomicInteger(0);
     * 页行号
    protected static AtomicInteger pageRowNo = new AtomicInteger(0);
    protected static AtomicInteger count = new AtomicInteger(0);
    public static BlockingQueue<List<PoiEntity>> queue = new ArrayBlockingQueue(5);
    public static BlockingQueue<String> sqlQueue = new LinkedBlockingQueue();
     * excel 每个sheet大小
    protected static final int MAX_SHEET_PAGE_SIZE = 200000;
     * 查询总记录数
    protected static int TOTAL_COUNT;
     * 判断CountDownLatch,当所有线程运行结束,就写入到文件流中
    protected static AtomicInteger DONESIGNAL_COUNT;
     * 表名数组
    protected static List<String> tableNameArr = new ArrayList<>();
     * 读取数据库中存储的数据行数
    public static int PAGE_SIZE = 50000;
     * 工作表对象
    protected static volatile SXSSFSheet sheet;
     * excel工作簿数组
    protected static ArrayList<SXSSFSheet> sheetList = new ArrayList<>();
     * cell列数
    private static final int cellLength = 10;
    private static final int windowSize = 200;
    public void customerData(CountDownLatch doneSignal) throws Exception {
        List list = queue.take();
        int len = list.size();
        for (int i = 0; i < len; i++) {
            Row row_value = null;
            synchronized (rowNo) {
                if (rowNo.get() % MAX_SHEET_PAGE_SIZE == 0) {
                    if (count.get() < sheetList.size()) {
                        sheet = sheetList.get(count.get());
                        pageRowNo.set(0);        //每当新建了工作表就将当前工作表的行号重置为0
                        createHeader();
                        setColumnWidthByType(sheet, cellLength);
                        count.incrementAndGet();
                row_value = sheet.createRow(pageRowNo.incrementAndGet());
                rowNo.incrementAndGet();
            addToRow(i, row_value, list);
        System.out.println("sheet name " + sheet.getSheetName() + " rowNo " + rowNo + " DONESIGNAL_COUNT " + DONESIGNAL_COUNT);
        DONESIGNAL_COUNT.getAndAdd(-list.size());
        list.clear();
        if (DONESIGNAL_COUNT.get() == 0) {
            doneSignal.countDown();
    protected void addToRow(int i, Row row_value, List _list) {
        List<PoiEntity> list = _list;
        Cell cel0_value = row_value.createCell(0);
        cel0_value.setCellValue(list.get(i).getTraceNo());
        Cell cel2_value = row_value.createCell(1);
        cel2_value.setCellValue(list.get(i).getMerchantNum());
        Cell cel3_value = row_value.createCell(2);
        cel3_value.setCellValue(list.get(i).getTotalFee() + "");
        Cell cel4_value = row_value.createCell(3);
        cel4_value.setCellValue(list.get(i).getMerchantName());
        Cell cel5_value = row_value.createCell(4);
        cel5_value.setCellValue(list.get(i).getDynamicType());
        Cell cel6_value = row_value.createCell(5);
        cel6_value.setCellValue(list.get(i).getTransBegin());
        Cell cel7_value = row_value.createCell(6);
        cel7_value.setCellValue(list.get(i).getTransStatus());
        Cell cel8_value = row_value.createCell(7);
        cel8_value.setCellValue(list.get(i).getRefundFee() + "");
        Cell cel9_value = row_value.createCell(8);
        cel9_value.setCellValue(list.get(i).getOutTransNo());
        Cell cel10_value = row_value.createCell(9);
        cel10_value.setCellValue(list.get(i).getRateFee());
        //Cell cel11_value = row_value.createCell(10);
        // cel11_value.setCellValue(list.get(i).getBankType());
          /*   Cell cel12_value = row_value.createCell(11);
        cel2_value.setCellValue(list.get(i).getTerminalNum());*/
     * 定义表头
    protected void createHeader() {
        Row row = sheet.createRow(0);
        Cell cel0 = row.createCell(0);
        cel0.setCellValue("traceNo");
        Cell cel2 = row.createCell(1);
        cel2.setCellValue("merchantNum");
        Cell cel3 = row.createCell(2);
        cel3.setCellValue("totalFee");
        Cell cel4 = row.createCell(3);
        cel4.setCellValue("merchantName");
        Cell cel5 = row.createCell(4);
        cel5.setCellValue("dynamicType");
        Cell cel6 = row.createCell(5);
        cel6.setCellValue(" transBegin ");
        Cell cel7 = row.createCell(6);
        cel7.setCellValue("transStatus");
        Cell cel8 = row.createCell(7);
        cel8.setCellValue("refundFee");
        Cell cel9 = row.createCell(8);
        cel9.setCellValue("outTransNo");
        Cell cel10 = row.createCell(9);
        cel10.setCellValue("rateFee");
        //Cell cel11 = row.createCell(10);
        // cel11.setCellValue("bankType");
         /*   Cell cel2 = row.createCell(11);
        cel2.setCellValue("terminalNum");*/
    protected static int getListCount(List tableNameArr) throws Exception {
        PreparedStatement stmt = DbUtils.getStm();
        int listCount = 0;
        for (int i = 0; i < tableNameArr.size(); i++) {
            String queryCount = "select count(1) from  " + tableNameArr.get(i);
            ResultSet rsTotal = stmt.executeQuery(queryCount);
            rsTotal.next();
            listCount += rsTotal.getInt(1);
        return listCount;
     * 生成工作簿
     * @param sheet
    protected static void setSheet(SXSSFSheet sheet) {
        int size = 1;
        if (TOTAL_COUNT > MAX_SHEET_PAGE_SIZE) {
            size = TOTAL_COUNT / MAX_SHEET_PAGE_SIZE == 0 ? TOTAL_COUNT / MAX_SHEET_PAGE_SIZE : (TOTAL_COUNT / MAX_SHEET_PAGE_SIZE) + 1;
        for (int i = 0; i < size; i++) {
            /**建立新的sheet对象*/
            sheet = wb.createSheet("我的第" + i + "个工作簿");
            sheet.setRandomAccessWindowSize(windowSize);
            /**动态指定当前的工作表*/
            sheet = wb.getSheetAt(i);
            sheetList.add(sheet);
     * 根据类型指定excel文件的列宽
    private void setColumnWidthByType(SXSSFSheet sheet, int titleLength) {
        sheet.trackAllColumnsForAutoSizing();
        for (int i = 0; i < titleLength; i++) {
            int columnWidth = sheet.getRow(0).getCell(i).getStringCellValue().length();//获取表头的宽度
            int autowidth = (int) SheetUtil.getColumnWidth(sheet, i, false, 1, sheet.getLastRowNum());
            if (columnWidth > autowidth) {
                sheet.setColumnWidth(i, (int) 400.0D * (columnWidth + 1));
            } else {
                sheet.autoSizeColumn(i);
     * 添加表名
     * @param dateList
    private static void addTable(List<String> dateList) {
        dateList.forEach(date -> {
            /**兴业*/
            tableNameArr.add("xingye_bill_download_day_101590267206_" + date);
            tableNameArr.add("xingye_bill_download_day_101540080217_" + date);
            /**汇付*/
            tableNameArr.add("posp_huifu_detail_day_" + date);
     * 添加sql
     * @param dateList
     * @return
    private static List<String> getSqlList(List<String> dateList, String where) {
        List<String> listSql = new ArrayList<>();
        dateList.forEach(date -> {
            /**兴业*/
            String sql1 = "select id,merchant_num merchantNum,merchant_name merchantName,trans_type dynamicType,trans_time transBegin" +
                    "                ,trans_status transStatus,total_fee totalFee,refund_fee refundFee,third_merchant_num outTransNo," +
                    "                rate_fee rateFee ,trace_num traceNo from  xingye_bill_download_day_101590267206_" + date
                    + " WHERE  id >  ownId   " + where + "  ORDER BY id ASC  LIMIT  " + PAGE_SIZE;
            String sql2 = "select id,merchant_num merchantNum,merchant_name merchantName,trans_type dynamicType,trans_time transBegin" +
                    "                ,trans_status transStatus,total_fee totalFee,refund_fee refundFee,third_merchant_num outTransNo," +
                    "                rate_fee rateFee ,trace_num traceNo from  xingye_bill_download_day_101540080217_" + date
                    + " WHERE  id >   ownId   " + where + "   ORDER BY id ASC  LIMIT  " + PAGE_SIZE;
            /**汇付*/
            String sql3 = "select id,merchant_num merchantNum,merchant_name merchantName ,trade_type dynamicType,DATE_FORMAT(trade_date,'%Y-%m-%d-%H-%i-%s') transBegin " +
                    "                ,trade_status transStatus,total_fee totalFee,recorded_money refundFee,outside_order_num outTransNo, " +
                    "                rate_fee rateFee ,trace_num traceNo  from  posp_huifu_detail_day_" + date
                    + " WHERE  id >   ownId   " + where + "   ORDER BY id ASC  LIMIT  " + PAGE_SIZE;
            listSql.add(sql1);
            listSql.add(sql2);
            listSql.add(sql3);
        return listSql;
     * 得到两个日期之间的天数,数组
     * @param dBegin
     * @param dEnd
     * @return
    public static List<String> findDates(Date dBegin, Date dEnd) {
        List lDate = new ArrayList();
        lDate.add(sdf.format(dBegin));
        Calendar calBegin = Calendar.getInstance();
        // 使用给定的 Date 设置此 Calendar 的时间
        calBegin.setTime(dBegin);
        Calendar calEnd = Calendar.getInstance();
        // 使用给定的 Date 设置此 Calendar 的时间
        calEnd.setTime(dEnd);
        // 测试此日期是否在指定日期之后
        while (dEnd.after(calBegin.getTime())) {
            // 根据日历的规则,为给定的日历字段添加或减去指定的时间量
            calBegin.add(Calendar.DAY_OF_MONTH, 1);
            lDate.add(sdf.format(calBegin.getTime()));
        return lDate;
     * 使用多线程进行Excel写操作,提高写入效率。
    public static void multiThreadWrite(ExecutorService es, CountDownLatch doneSignal, String xlsFile) {
        try {
            /**预生产数据*/
            long startTime = System.currentTimeMillis();    //开始时间
            Date begin = sdf.parse("20190602");
            Date end = sdf.parse("20190602");
            List<String> dateList = findDates(begin, end);
            addTable(dateList);
            TOTAL_COUNT = getListCount(tableNameArr);
            DONESIGNAL_COUNT = new AtomicInteger(TOTAL_COUNT);
            setSheet(sheet);
            sheet = sheetList.get(0);
            String where = "";
            getSqlList(dateList, where).forEach(sql -> {
                try {
                    sqlQueue.put(sql);
                } catch (InterruptedException e) {
                    e.printStackTrace();
            /**多线程处理数据*/
            int size = sqlQueue.size();
            /**最多创建10个线程,确保线程不在开始时就被阻塞*/
            if (size > 0) {
                size = size > 12 ? 10 : size;
                for (int i = 0; i < size; i++) {
                    es.submit(new PoiProductor(doneSignal));
            PoiExcel poiExcel = new PoiExcel();
            es.submit(new PoiWriter(doneSignal, poiExcel));
            doneSignal.await();
            System.out.println("read  finish execute time: " + (System.currentTimeMillis() - startTime) / 1000 + " s");
            FileOutputStream os = new FileOutputStream(xlsFile);
            wb.write(os);
            os.flush();
            os.close();
            System.out.println(" outPutStream finish execute time: " + (System.currentTimeMillis() - startTime) / 1000 + " s");
        } catch (Exception e) {
            e.printStackTrace();

从数据库读数 生产者   ↵

package com.example.demo.test;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
 * @Author: myc
 * @Description: 获取对账单表中的数据并放在queue中 ,这里每个线程对应一张表,读完可读剩余的某一张表,保证原子性。
 * 新建的线程个数,不要超过sqlQueue的大小,否则会有线程阻塞消耗内存。
 * @Date 下午 5:37 2019/6/28 0028
public class PoiProductor implements Runnable {
    private final CountDownLatch doneSignal;
    public PoiProductor(CountDownLatch doneSignal) {
        this.doneSignal = doneSignal;
    @Override
    public void run() {
        List list = null;
        String sql = null;
        int id = 0;
        while (true) {
            try {
                //synchronized (doneSignal) {
                if (null == list || list.size() < 1) {
                    id = 0;
                    if (PoiExcel.sqlQueue.size() > 0) {
                        sql = PoiExcel.sqlQueue.take();
                    } else {
                        break;
                if (null != list && list.size() > 0) {
                    id = getId(list);
                list = getListData(sql, id);
                PoiExcel.queue.put(list);
            } catch (Exception e) {
                e.printStackTrace();
    public static int getId(List list) throws Exception {
        List<PoiEntity> _list = list;
        int id = _list.get(_list.size() - 1).getId();
        return id;
    private static void addToList(List _list, ResultSet rs) throws SQLException, ParseException {
        List<PoiEntity> list = _list;
        while (rs.next()) {
            String dynamicType = getDynamicType(rs);
            String transStatus = getTransStatus(rs);
            PoiEntity usera = new PoiEntity();
            usera.setId((rs.getInt("id")));
            usera.setTraceNo((rs.getString("traceNo")));
            usera.setMerchantNum((rs.getString("merchantNum")));
            usera.setTotalFee(rs.getBigDecimal("totalFee"));
            usera.setMerchantName(rs.getString("merchantName"));
            // usera.setTerminalNum(rs.getString("terminalNum"));
            usera.setDynamicType(dynamicType);
            usera.setTransBegin(rs.getString("transBegin"));
            usera.setTransStatus(transStatus);
            usera.setRefundFee(rs.getBigDecimal("refundFee"));
            usera.setOutTransNo(rs.getString("outTransNo"));
            //  usera.setBankType(rs.getString("bankType"));
            usera.setRateFee(rs.getString("rateFee"));
            list.add(usera);
    private static String getTransStatus(ResultSet rs) throws SQLException {
        String transStatus = rs.getString("transStatus");
      /*  if (transStatus.contains("weixin")) {
            transStatus = "微信";
        return transStatus;
    private static String getDynamicType(ResultSet rs) throws SQLException {
        String dynamicType = rs.getString("dynamicType");
      /*  if (dynamicType.contains("weixin")) {
            dynamicType = "微信支付";
        if (dynamicType.contains("alipay")) {
            dynamicType = "支付宝支付";
        return dynamicType;
    public static List getListData(String sql, int id) throws Exception {
        List _list = new ArrayList();
        PreparedStatement stmt = DbUtils.getStm();
        //posp_merchant_account_stats_detail_day_20190607
        sql = sql.replace("ownId", id + "");
        ResultSet rs = stmt.executeQuery(sql);
        if (null != rs) {
            addToList(_list, rs);
            return _list;
        } else {
            _list.clear();
            return null;

将数据写入到excel     --消费者

package com.example.demo.test;
import java.util.concurrent.CountDownLatch;
 * @Author: myc  进行sheet写操作
 * @Description:
 * @Date 上午 9:58 2019/6/28 0028
public class PoiWriter implements Runnable {
    private final CountDownLatch doneSignal;
    private PoiExcel poiExcel;
    public PoiWriter(CountDownLatch doneSignal,PoiExcel poiExcel) {
        this.doneSignal = doneSignal;
        this.poiExcel = poiExcel;
    @Override
    public void run() {
        try {
            while (true) {
                if(doneSignal.getCount() <= 0){
                    break;
                poiExcel.customerData(doneSignal);
        } catch (Exception e) {
            e.printStackTrace();
        } /*finally {
            doneSignal.countDown();
            System.out.println(" Count: " + doneSignal.getCount() + " thread name " + Thread.currentThread().getName());
package com.example.demo.test;
import java.math.BigDecimal;
 * @Author: myc
 * @Description:
 * @Date 上午 9:31 2019/7/1 0001
public class PoiEntity {
    private Integer id;
    private String merchantNum;
    private String merchantName;
    private String terminalNum;
    private String dynamicType;
    private String transBegin;
    private String transStatus;
    private BigDecimal totalFee;
    private BigDecimal refundFee;
    private String outTransNo;
    private String bankType;
    private String rateFee;
    private String traceNo;
    public Integer getId() {
        return id;
    public void setId(Integer id) {
        this.id = id;
    public String getMerchantNum() {
        return merchantNum;
    public void setMerchantNum(String merchantNum) {
        this.merchantNum = merchantNum;
    public String getMerchantName() {
        return merchantName;
    public void setMerchantName(String merchantName) {
        this.merchantName = merchantName;
    public String getTerminalNum() {
        return terminalNum;
    public void setTerminalNum(String terminalNum) {
        this.terminalNum = terminalNum;
    public String getDynamicType() {
        return dynamicType;
    public void setDynamicType(String dynamicType) {
        this.dynamicType = dynamicType;
    public String getTransBegin() {
        return transBegin;
    public void setTransBegin(String transBegin) {
        this.transBegin = transBegin;
    public String getTransStatus() {
        return transStatus;
    public void setTransStatus(String transStatus) {
        this.transStatus = transStatus;
    public BigDecimal getTotalFee() {
        return totalFee;
    public void setTotalFee(BigDecimal totalFee) {
        this.totalFee = totalFee;
    public BigDecimal getRefundFee() {
        return refundFee;
    public void setRefundFee(BigDecimal refundFee) {
        this.refundFee = refundFee;
    public String getOutTransNo() {
        return outTransNo;
    public void setOutTransNo(String outTransNo) {
        this.outTransNo = outTransNo;
    public String getBankType() {
        return bankType;
    public void setBankType(String bankType) {
        this.bankType = bankType;
    public String getRateFee() {
        return rateFee;
    public void setRateFee(String rateFee) {
        this.rateFee = rateFee;
    public String getTraceNo() {
        return traceNo;
    public void setTraceNo(String traceNo) {
        this.traceNo = traceNo;
package com.example.demo.test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
 * @Author: myc
 * @Description:
 * @Date 下午 5:43 2019/6/28 0028
public class DbUtils {
    static String url = "jdbc:mysql://localhost:3306/posp?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull";
    static String user = "root";
    static String password = "root";
    public static PreparedStatement getStm() throws Exception {
        Class.forName("com.mysql.jdbc.Driver").newInstance();
        //获取数据库连接
        Connection conn = null;
        PreparedStatement stmt = null;
        conn = DriverManager.getConnection(url, user, password);
        stmt = conn.prepareStatement("");
        return stmt;
                    ↵起因是这样的,用户要下载对账单明细,也就是交易明细,我们公司的数据库的设计是,一天一张表,类似于trace_20190708,trace_20190709 ....... 这样的类型,所以设计生产者的时候是,一个线程负责某天的表,线程数不要超过查询的天数总和(例如读取20190720-20190730 这十天的数据,那么线程数不能超过10个,也不能过多,看自己系统内存情况),否则,会有线...
				
用easyExcel多线程,生产者-消费者模式excel 阿里easyexcel 插件 easyexcel 项目git地址为: https://github.com/alibaba/easyexcel 官网地址:https://alibaba-easyexcel.github.io excel类 public class WriteExcel { public static voi...
本案例采用的poi读取大数据excel文件 usermodel模式对excel操作前需要将文件全部转入内存,对较大文件来说内存开销很大。但是其使用简单。 eventusermodel模式采用事件模型,对文件边读取边处理,内存消耗较低,效率高,因为不用等待文件全部装入内存。但使用较复杂。 对12万条数据,7M大小的文件也能正常运行。无需设置vm的内存空间
private String prefix = "G:/JAVA资料/"; private String readFilename = prefix + "curblock-笔试-1504_人口、人口密度统计年鉴_20191113.xlsx"; private String writeFilename = prefix + "new.xlsx";
@Test public void test() throws InterruptedException, ExecutionException { String resStr = "{\"languageCode\":\"en-US\",\"categoryType\":\"\",\"categoryLevel\":\"\"}"; GoodsPriceSellListDTO condition = JSON.parseObject(resStr, GoodsPr..
官网介绍及实例 SXSSF通过限制对滑动窗口中的行的访问来实现其低内存占用 默认窗口大小windowSize为100,由SXSSFWorkbook.DEFAULT_WINDOW_SIZE定义。 可以通过新的SXSSFWorkbook(int windowSize)在工作簿构建时指定窗口大小 SXSSFWorkbook wb1 = new SXSSFWorkbook(100); 也可以通过SXSSF
周五的时候,同事突然问我有没有做过多线程Excel数据,看我一时没理解,同事说就是多线程往workbook中数据。说起来Excel的操作之前做的很多了,但是重来没考虑过这么做,不过既然提起了,而且网上也有相关内容,何不自己尝试一下?于是自己便尝试用自己微薄的技术水平来实现下这个逻辑。 首先考虑需要哪些东西: 首先我们需要一个生成和处理数据的类:WriteDataUtils; pa...
今天是2018.03.22,已经很久没有更新博客了。。这段时间一直挺忙的,也收获很多。最近一个excel导出的任务让我搞了好久,想想踩过的坑,就想上来小结一番。 ------------------------------------------------------分割线------------------------------------------------------ public class ExcelExporter implements Runnable { private int startRow; private int endRow; private String fileName; public ExcelExporter(int startRow, int endRow, String fileName) { this.startRow = startRow; this.endRow = endRow; this.fileName = fileName; @Override public void run() { try { Workbook workbook = new XSSFWorkbook(); Sheet sheet = workbook.createSheet("Sheet1"); // 创建表头 Row headerRow = sheet.createRow(); for (int i = ; i < headers.length; i++) { Cell cell = headerRow.createCell(i); cell.setCellValue(headers[i]); // 填充数据 for (int i = startRow; i <= endRow; i++) { Row row = sheet.createRow(i - startRow + 1); for (int j = ; j < data[i].length; j++) { Cell cell = row.createCell(j); cell.setCellValue(data[i][j]); // 导出Excel文件 FileOutputStream outputStream = new FileOutputStream(fileName); workbook.write(outputStream); outputStream.close(); } catch (Exception e) { e.printStackTrace(); // 创建线程池 ExecutorService executorService = Executors.newFixedThreadPool(10); // 分批导出Excel文件 int batchSize = 100000; for (int i = ; i < data.length; i += batchSize) { int startRow = i; int endRow = Math.min(i + batchSize - 1, data.length - 1); String fileName = "data_" + startRow + "_" + endRow + ".xlsx"; executorService.execute(new ExcelExporter(startRow, endRow, fileName)); // 关闭线程池 executorService.shutdown(); while (!executorService.isTerminated()) { Thread.sleep(100);