POI 多线程多Sheet导出Excel,Csv

2018/8/20 Javapoi多线程

因项目特殊,最近遇到了Excel导出大数据量的问题,所以就趁着解决这个问题的机会,把poi导出Excel再整理一遍,方便自己日后总结。

# 概要

Excel:POI 多线程多sheet导出数据

  • Excel2003版最大行数是65536,Excel2007最大行数是1048576
  • Excel2003版最大列数是256,Excel2007最大列数是16384

# 问题原因

  1. 导出大数据量,可能遇到的第一个问题就是后台报:java.lang.OutOfMemoryError: Java heap space 内存溢出这个问题

    分析:poi导出时,会先把内容写到内存中,最后浏览器从内存中下载,但是数据量太大,会导致内存溢出

# 解决办法:

# 1. 调整jvm/tomcat大小

# 2. 通过新版的SXSSFWorkbook来解决

# 3. 转csv导出

  • CSV是一种通用的、相对简单的文件格式,其文件以纯文本形式存储表格数据(数字和文本)。
  • Excel和CSV格式文件的不同之处 => 传送门 (opens new window)

手写一个 csv,可先直接运行main()查看结果

package com.zpj.electric.utils;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletResponse;

import com.alibaba.druid.util.StringUtils;

/**
 * @className: CSVUtils
 * @date: 2018/7/16
 * @version: 1.0
 */

public class CSVUtils {


    /**
     * 功能说明:获取UTF-8编码文本文件开头的BOM签名。
     * BOM(Byte Order Mark),是UTF编码方案里用于标识编码的标准标记。例:接收者收到以EF BB BF开头的字节流,就知道是UTF-8编码。
     * @return UTF-8编码文本文件开头的BOM签名
     */
    public static String getBOM() {

        byte b[] = {(byte)0xEF, (byte)0xBB, (byte)0xBF};
        return new String(b);
    }

    /**
     * 生成CVS文件
     * @param exportData
     *       源数据List
     * @param map
     *       csv文件的列表头map
     * @param outPutPath
     *       文件路径
     * @param fileName
     *       文件名称
     * @return
     */
    @SuppressWarnings("rawtypes")
    public static File createCSVFile(List exportData, LinkedHashMap map, String outPutPath,
                                     String fileName) {
        File csvFile = null;
        BufferedWriter csvFileOutputStream = null;
        try {
            File file = new File(outPutPath);
            if (!file.exists()) {
                file.mkdirs();
            }
            //定义文件名格式并创建
            csvFile =new File(outPutPath+fileName+".csv");
            file.createNewFile();
            // UTF-8使正确读取分隔符","
            //如果生产文件乱码,windows下用gbk,linux用UTF-8
            csvFileOutputStream = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(
                    csvFile), "UTF-8"), 1024);

            //写入前段字节流,防止乱码
            csvFileOutputStream.write(getBOM());
            // 写入文件头部
            for (Iterator propertyIterator = map.entrySet().iterator(); propertyIterator.hasNext();) {
                java.util.Map.Entry propertyEntry = (java.util.Map.Entry) propertyIterator.next();
                csvFileOutputStream.write((String) propertyEntry.getValue() != null ? (String) propertyEntry.getValue() : "" );
                if (propertyIterator.hasNext()) {
                    csvFileOutputStream.write(",");
                }
            }
            csvFileOutputStream.newLine();
            // 写入文件内容
            for (Iterator iterator = exportData.iterator(); iterator.hasNext();) {
                Object row = (Object) iterator.next();
                for (Iterator propertyIterator = map.entrySet().iterator(); propertyIterator
                        .hasNext();) {
                    java.util.Map.Entry propertyEntry = (java.util.Map.Entry) propertyIterator
                            .next();
                    String str=row!=null?((String)((Map)row).get( propertyEntry.getKey())):"";

                    if(StringUtils.isEmpty(str)){
                        str="";
                    }else{
                        str=str.replaceAll("\"","\"\"");
                        if(str.indexOf(",")>=0){
                            str="\""+str+"\"";
                        }
                    }
                    csvFileOutputStream.write(str);
                    if (propertyIterator.hasNext()) {
                        csvFileOutputStream.write(",");
                    }
                }
                if (iterator.hasNext()) {
                    csvFileOutputStream.newLine();
                }
            }
            csvFileOutputStream.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                csvFileOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return csvFile;
    }

    /**
     *     生成并下载csv文件
     * @param response
     * @param exportData
     * @param map
     * @param outPutPath
     * @param fileName
     * @throws IOException
     */
    @SuppressWarnings("rawtypes")
    public static void exportDataFile(HttpServletResponse response,List exportData, LinkedHashMap map, String outPutPath,String fileName) throws IOException{
        File csvFile = null;
        BufferedWriter csvFileOutputStream = null;
        try {
            File file = new File(outPutPath);
            if (!file.exists()) {
                file.mkdirs();
            }
            //定义文件名格式并创建
            csvFile =new File(outPutPath+fileName+".csv");
            if(csvFile.exists()){
                csvFile.delete();
            }
            csvFile.createNewFile();
            // UTF-8使正确读取分隔符","
            //如果生产文件乱码,windows下用gbk,linux用UTF-8
            csvFileOutputStream = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(csvFile), "UTF-8"), 1024);
            //写入前段字节流,防止乱码
            csvFileOutputStream.write(getBOM());
            // 写入文件头部
            for (Iterator propertyIterator = map.entrySet().iterator(); propertyIterator.hasNext();) {
                java.util.Map.Entry propertyEntry = (java.util.Map.Entry) propertyIterator.next();
                csvFileOutputStream.write((String) propertyEntry.getValue() != null ? (String) propertyEntry.getValue() : "" );
                if (propertyIterator.hasNext()) {
                    csvFileOutputStream.write(",");
                }
            }
            csvFileOutputStream.newLine();
            // 写入文件内容
            for (Iterator iterator = exportData.iterator(); iterator.hasNext();) {
                Object row = (Object) iterator.next();
                for (Iterator propertyIterator = map.entrySet().iterator(); propertyIterator
                        .hasNext();) {
                    java.util.Map.Entry propertyEntry = (java.util.Map.Entry) propertyIterator
                            .next();
                    String str=row!=null?((String)((Map)row).get( propertyEntry.getKey())):"";
                    if(StringUtils.isEmpty(str)){
                        str="";
                    }else{
                        str=str.replaceAll("\"","\"\"");
                        if(str.indexOf(",")>=0){
                            str="\""+str+"\"";
                        }
                    }
                    csvFileOutputStream.write(str);
                    if (propertyIterator.hasNext()) {
                        csvFileOutputStream.write(",");
                    }
                }
                if (iterator.hasNext()) {
                    csvFileOutputStream.newLine();
                }
            }
            csvFileOutputStream.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                csvFileOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }




        InputStream in = null;
        try {
            in = new FileInputStream(outPutPath+fileName+".csv");
            int len = 0;
            byte[] buffer = new byte[1024];

            OutputStream out = response.getOutputStream();
            response.reset();

            response.setContentType("application/csv;charset=UTF-8");
            response.setHeader("Content-Disposition","attachment; filename=" + URLEncoder.encode(fileName+".csv", "UTF-8"));
            response.setCharacterEncoding("UTF-8");
            while ((len = in.read(buffer)) > 0) {
                out.write(new byte[] { (byte) 0xEF, (byte) 0xBB, (byte) 0xBF });
                out.write(buffer, 0, len);
            }
            out.close();
        } catch (FileNotFoundException e) {
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }

    }

    /**
     * 删除该目录filePath下的所有文件
     * @param filePath
     *      文件目录路径
     */
    public static void deleteFiles(String filePath) {
        File file = new File(filePath);
        if (file.exists()) {
            File[] files = file.listFiles();
            for (int i = 0; i < files.length; i++) {
                if (files[i].isFile()) {
                    files[i].delete();
                }
            }
        }
    }

    /**
     * 删除单个文件
     * @param filePath
     *     文件目录路径
     * @param fileName
     *     文件名称
     */
    public static void deleteFile(String filePath, String fileName) {
        File file = new File(filePath);
        if (file.exists()) {
            File[] files = file.listFiles();
            for (int i = 0; i < files.length; i++) {
                if (files[i].isFile()) {
                    if (files[i].getName().equals(fileName)) {
                        files[i].delete();
                        return;
                    }
                }
            }
        }
    }

    /**
     * 测试数据
     * @param args
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static void main(String[] args) {
        List exportData = new ArrayList<Map>();
        Map row1 = new LinkedHashMap<String, String>();
        row1.put("1", "11");
        row1.put("2", "12");
        row1.put("3", "13");
        row1.put("4", "14");
        exportData.add(row1);
        row1 = new LinkedHashMap<String, String>();
        row1.put("1", "21");
        row1.put("2", "22");
        row1.put("3", "23");
        row1.put("4", "24");
        exportData.add(row1);
        LinkedHashMap map = new LinkedHashMap();

        //设置列名
        map.put("1", "第一列名称");
        map.put("2", "第二列名称");
        map.put("3", "第三列名称");
        map.put("4", "第四列名称");
        //这个文件上传到路径,可以配置在数据库从数据库读取,这样方便一些!
        String path = "E:/";

        //文件名=生产的文件名称+时间戳
        String fileName = "文件导出";
        File file = CSVUtils.createCSVFile(exportData, map, path, fileName);
        String fileName2 = file.getName();
        System.out.println("文件名称:" + fileName2);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304

# 4. 本地90万数据:输入输出流+poi多线程多sheet导出Excel

可直接运行main()查看导出结果,注意poi版本3.7+

package com.zpj.electric.util.excel;

import com.zpj.electric.po.Student;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;

import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadExcelUtils {

    /**
     * 定义没个 sheet 最多数量
     */
    public static final Integer EXCEL_MAX_CNT = 50000;

    private Workbook wb;

    private String fileName;

    private String filePath;

    private String[] hearders;

    private String[] fields;

    public Workbook getWb() {
        return wb;
    }

    public String getFileName() {
        return fileName;
    }

    public String getFilePath() {
        return filePath;
    }

    public String[] getHearders() {
        return hearders;
    }

    public String[] getFields() {
        return fields;
    }

    /**
     * @param fileName 文件名称
     * @param filePath 文件路径
     * @param hearders 文件头
     * @param fields   字段属性
     * @Author maochuang.li
     * @Date Create in 13:54 2017/12/28 0028
     */
    public ThreadExcelUtils(String fileName, String filePath, String[] hearders, String[] fields) {
        this.wb = new SXSSFWorkbook(10000);
        this.fileName = fileName;
        this.filePath = filePath;
        this.hearders = hearders;
        this.fields = fields;
    }

    public static void main(String[] args) throws Exception {
        String[] header = {"姓名", "年龄"};
        String[] fileNames = {"name", "age"};
        ThreadExcelUtils utils = new ThreadExcelUtils("测试Excel1", "D:\\exceltext\\wer\\sd", header, fileNames);
        List list = new ArrayList<>();
        System.out.println("开始造数据.......");
        for (int i = 0; i < 900000; i++) {
            Student student = new Student();
            student.setName("name->" + i);
            student.setAge(i);
            list.add(student);
        }
        System.out.println(getDate(new Date()) + "开始写入文件.......");
        long startTime = System.currentTimeMillis();
        utils.exportExcelToFilePath(list);
        long endTime = System.currentTimeMillis();
        System.out.println("耗时:" + (endTime - startTime));
        //耗时:29230
        //耗时:25226

    }

    /**
     * @param list 数据
     * @Author maochuang.li
     * @Date Create in 10:52 2017/12/28 0028
     */
    public void exportExcelToFilePath(List<Object> list) throws Exception {
        int excelSize = EXCEL_MAX_CNT;    //每个Excel文件条数
        int totalCount = list.size();    //查询结果总条数
        int pageCount = 0;//总sheet页个数
        int numPage = totalCount % excelSize;    //是否整页数
        if (numPage > 0)
            pageCount = totalCount / excelSize + 1;
        else
            pageCount = totalCount / excelSize;
        //创建线程池 多sheet多线程写入 线程数 为sheet页的 1/4
        Integer threadNumber = pageCount / 4;
        if (threadNumber == 0)
            threadNumber = 1;
//        ExecutorService threadPool = Executors.newFixedThreadPool(threadNumber);
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(threadNumber, threadNumber, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(50));
        //创建栅栏 等待任务完成
        CountDownLatch countDownLatch = new CountDownLatch(pageCount);
        //循环遍历投递任务
        for (int i = 1; i <= pageCount; i++) {
            ThraedExcel thraedExcel = new ThraedExcel(list, i, pageCount, numPage, this);
            thraedExcel.setCountDownLatch(countDownLatch);
            threadPool.submit(thraedExcel);
        }
        countDownLatch.await(10L,TimeUnit.SECONDS);
        System.err.println("超时拉=====================================");
        Workbook wb = getWb();
        File file = new File(filePath);
        if (!file.exists() && !file.isDirectory()) {
            file.mkdirs();
        }
        FileOutputStream fout = new FileOutputStream(new File(file, fileName + ".xls"));
        try {
            wb.write(fout);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("文件写入完成");
        //立即销毁线程池
        threadPool.shutdownNow();
    }


    private static String getDate(Date date) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        String format = simpleDateFormat.format(date);
        return format;
    }

    /**
     * JavaBean转Map
     *
     * @param obj
     * @return
     */
    public static Map<String, Object> beanToMap(Object obj) {
        Map<String, Object> params = new HashMap<String, Object>(0);
        try {
            PropertyUtilsBean propertyUtilsBean = new PropertyUtilsBean();
            PropertyDescriptor[] descriptors = propertyUtilsBean.getPropertyDescriptors(obj);
            int length = descriptors.length;
            for (int i = 0; i < length; i++) {
                String name = descriptors[i].getName();
                if (!StringUtils.equals(name, "class")) {
                    params.put(name, propertyUtilsBean.getNestedProperty(obj, name));
                }
            }
        } catch (Exception e) {
            System.err.println("bean 转Map出错");
            e.printStackTrace();
        }
        return params;
    }

    /***
     * 线程写入sheet
     */
    private static class ThraedExcel implements Runnable {

        private List<Object> list;//数据

        private Integer sheetNumber;//当前sheet页

        private Integer totalSheetCount;//总数据

        private int numPage;    //是否整页数

        private Integer excelSize;

        private ThreadExcelUtils threadExcelUtils;
        //栅栏对象
        private CountDownLatch countDownLatch;

        /**
         * @param list            总数据
         * @param sheetNumber     当前sheet页
         * @param totalSheetCount 总sheet页
         * @param numPage         是否整数
         * @Author maochuang.li
         * @Date Create in 11:12 2017/12/28 0028
         */
        public ThraedExcel(List<Object> list, Integer sheetNumber, Integer totalSheetCount, Integer numPage, ThreadExcelUtils threadExcelUtils) {
            this.list = list;//总数据
            this.sheetNumber = sheetNumber;//当前sheet页
            this.totalSheetCount = totalSheetCount;//总sheet页
            this.numPage = numPage;//是否整除
            this.excelSize = ThreadExcelUtils.EXCEL_MAX_CNT;//没个sheet最大数量
            this.threadExcelUtils = threadExcelUtils;//当前线程对象
        }

        public void setCountDownLatch(CountDownLatch countDownLatch) {
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            System.out.println("第"+sheetNumber+"个sheet开始");
            List<Object> sheetList = null;
            if (totalSheetCount > 1) {
                if (numPage == 0) {
                    sheetList = list.subList((sheetNumber - 1) * excelSize, excelSize * sheetNumber);
                } else {
                    if (sheetNumber == totalSheetCount) {
                        sheetList = list.subList((sheetNumber - 1) * excelSize, list.size());
                    } else {
                        sheetList = list.subList((sheetNumber - 1) * excelSize, excelSize * (sheetNumber));
                    }
                }
            } else
                sheetList = list;
            //开始写入数据
            createWorkBook(sheetList);
            if (this.countDownLatch != null)
                this.countDownLatch.countDown();
            System.out.println("第"+sheetNumber+"个sheet  结束");
        }

        /***
         * 写出数据
         */
        private void createWorkBook(List<Object> sheetList) {
            Sheet sheet = null;
            Row row = null;
            synchronized (ThreadExcelUtils.class) {
                String fileName = threadExcelUtils.getFileName();
                Workbook wb = threadExcelUtils.getWb();
                sheet = wb.createSheet(fileName + "_" + this.sheetNumber);
                row = sheet.createRow(0);
            }
            String[] header = threadExcelUtils.getHearders();
            String[] fields = threadExcelUtils.getFields();
            //设置标题
            for (int i = 0; i < header.length; i++) {
                row.createCell(i).setCellValue(header[i]);
            }
            //开始写入数据
            if (sheetList != null && sheetList.size() > 0) {
                int dataLength = sheetList.size();
                for (int i = 0; i < dataLength; i++) {
                    Row row1 = sheet.createRow(i + 1);
                    Object obj = sheetList.get(i);
                    Map<String, Object> map = (obj instanceof Map) ? (Map<String, Object>) obj : beanToMap(obj);
                    int length = fields.length;
                    for (int j = 0; j < length; j++) {
                        String key = fields[j];
                        Object value = map.get(key);
//                        if ((StringUtil.isNotBlank(value))) {
                        if (!(org.springframework.util.StringUtils.isEmpty(value))) {
                            //不晓得 此处为啥有线程安全问题
                            synchronized (ThreadExcelUtils.class) {
                                if (value instanceof Date) {
                                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                                    String format = sdf.format(value);
                                    row1.createCell(j).setCellValue(format);
                                } else {
                                    try {
                                        row1.createCell(j).setCellValue(value.toString());
                                    } catch (Exception e) {
                                        e.printStackTrace();
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290

# 5. 浏览器:多线程多sheet页下载

  • 先引入依赖
<!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.9</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.9</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.4</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/log4j/log4j -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  • 创建下载工具类(我写的太low,可以自己优化)
package com.zpj.electric.util;

import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFClientAnchor;
import org.apache.poi.hssf.usermodel.HSSFFont;
import org.apache.poi.hssf.usermodel.HSSFPatriarch;
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFFont;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

public class ExcelUtil {

	private static Logger log = Logger.getLogger(ExcelUtil.class);

	private static SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
	private final static String EXCE_VERSION_03 = "03";
	private final static String EXCE_VERSION_07 = "07";

	/**
	 * @param resp
	 * @param fileName
	 *            文件名,一级表头
	 * @param useFirstTitle
	 *            是否需要一级表头,true:是,false:否
	 * @param version
	 *            导出版本,可选值为 07,统一用07版
	 * @param titles
	 *            excel列名
	 * @param data
	 *            需要导出的数据集合
	 */
	public static void exportExcel(HttpServletResponse resp, String fileName,
			boolean useFirstTitle, String version, String[] titles,
			List<Object[]> data) {
		// 如果传入的文件名为空,则使用当前时间作为文件名
		String excelFileName = "";
		if ("".equals(fileName) || fileName == null) {
			excelFileName = sdf.format(new Date());
		}
		// (data.size() >= 0) 当列表中没有数据时,导出的excel加上标题等。
		if (titles.length >= 0) {
			try {
				if (EXCE_VERSION_03.equals(version)) {
					excelFileName = fileName + ".xls";
				} else if (EXCE_VERSION_07.equals(version)) {
					excelFileName = fileName + ".xlsx";
				}

				resp.setContentType("application/x-msdownload");
				excelFileName = new String(excelFileName.getBytes("gb2312"), "iso8859-1");
				resp.addHeader("Content-Disposition", "attachment;filename="
						+ excelFileName);
				OutputStream out = resp.getOutputStream();
				if (EXCE_VERSION_03.equals(version)) {
					exprotExcel_03(fileName, titles, useFirstTitle, data, out);
				} else if (EXCE_VERSION_07.equals(version)) {
					exprotExcel_07_plus(excelFileName, titles, useFirstTitle, data, out);
				}
			} catch (Exception e) {
				log.error("", e);
			}
		}
	}

	private static void exprotExcel_07_plus(String fileName, String[] titles,
			boolean useFirstTitle, List<Object[]> data, OutputStream out) {
		XSSFWorkbook wb = new XSSFWorkbook();

		//把要下载的数据分为多个sheet下载
		List<List<Object[]>> dataList = createList(data, 50000);
		Long start = System.currentTimeMillis();
		System.out.println("下载开始时间:" + start);

		//使用多线程下载
		ExecutorService es = Executors.newFixedThreadPool(dataList.size());
		//使用计步器
		// final CountDownLatch doneSignal = new CountDownLatch(dataList.size());
		CountDownLatch doneSignal = new CountDownLatch(dataList.size());

		try {

		for(int x = 0;x<dataList.size();x++){
			PoiWriter poiWriter = new PoiWriter(x, wb, fileName, titles, data, dataList, useFirstTitle, doneSignal);
			Future future = es.submit(poiWriter);
			 /*try {
				 	//如果Future.get()返回null,任务完成
		            if(future.get()==null){
		                System.out.println("第"+(x+1)+"个sheet页,任务完成");
		            }
		        } catch (InterruptedException e) {
		        } catch (ExecutionException e) {
		            //失败
		            System.out.println("第"+(x+1)+"个sheet页,任务失败:"+e.getCause().getMessage());
		        }*/
			//new PoiWriter(x, wb, fileName, titles, data, dataList, useFirstTitle, null).run();
		}
		doneSignal.await();//阻塞,直到计数器的值为0,才让主线程往下执行
	    es.shutdown();//关闭线程池
		Long end = System.currentTimeMillis();
		System.out.println("总计下载时间:" + ((end-start)/1000) + "s");
			wb.write(out);
			out.flush();
			out.close();
		} catch (Exception e) {
			log.error("", e);
		} finally {
			IOUtils.closeQuietly(out);
		}

	}


	 public static List<List<Object[]>>  createList(List<Object[]> targe,int size) {
		        List<List<Object[]>> listArr = new ArrayList<List<Object[]>>();
		        //获取被拆分的数组个数
		        int arrSize = targe.size()%size==0?targe.size()/size:targe.size()/size+1;
		        for(int i=0;i<arrSize;i++) {
		            List<Object[]>  sub = new ArrayList<Object[]>();
		            //把指定索引数据放入到list中
		            for(int j=i*size;j<=size*(i+1)-1;j++) {
		                if(j<=targe.size()-1) {
		                	//得到拆分后的集合
		                    sub.add(targe.get(j));
		                }
		            }
		            //拆分的集合可以做点什么
		            //sub.dosomething();
		            //将拆分后的集合综合为一个集合
		            listArr.add(sub);
		        }
		        return listArr;
	 }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
  • 线程类
package com.zpj.electric.util;

import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.*;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class PoiWriter implements Runnable {

	public int x;//第几个sheet页
	public XSSFWorkbook wb;//poi Workbook
	public String fileName;//要导出的文件名字(含后缀)
	public String[] titles;//标题
	public List<Object[]> data;//全部数据
	public List<List<Object[]>> dataList;//包含了各个sheet页的数据
	public boolean useFirstTitle;//是否需要表头
	public  CountDownLatch doneSignal;//多线程栅栏计步器


	public PoiWriter(int x, XSSFWorkbook wb, String fileName, String[] titles,
			List<Object[]> data, List<List<Object[]>> dataList,
			boolean useFirstTitle, CountDownLatch doneSignal) {
		super();
		this.x = x;
		this.wb = wb;
		this.fileName = fileName;
		this.titles = titles;
		this.data = data;
		this.dataList = dataList;
		this.useFirstTitle = useFirstTitle;
		this.doneSignal = doneSignal;
	}

	public PoiWriter() {

	}

	@Override
	public void run() {
		Long start1 = System.currentTimeMillis();
		System.out.println(fileName+":第"+(x+1)+"个sheet开始时间:" + start1);
		// 创建一sheet页
		XSSFSheet sheet = wb.createSheet("sheet"+x);
		// 设置格式 在单元格中右排放
		XSSFCellStyle style = wb.createCellStyle();
		style.setAlignment(XSSFCellStyle.ALIGN_RIGHT);

		int firstRow = 0;
		if (useFirstTitle) {
			XSSFRow fr = sheet.createRow(firstRow);
			XSSFFont font = wb.createFont();
			font.setFontName("宋体");
			font.setFontHeightInPoints((short) 16);
			XSSFCellStyle cs = wb.createCellStyle();
			cs.setAlignment(XSSFCellStyle.ALIGN_CENTER);
			cs.setVerticalAlignment(XSSFCellStyle.VERTICAL_CENTER);
			cs.setFont(font);
			fr.setHeight((short) 700);
			fr.setHeight((short) 700);
			//excel大标题和并列多了一列,当列表中数据为空时,导出的excel中加上标题等信息
			sheet.addMergedRegion(new CellRangeAddress(0, 0, 0,
					titles.length-1));
			for (int i = 0; i < titles.length; i++) {
				fr.createCell(i);
			}
			/**
			 * 自适应宽度
			 */
			if(null!=data&&data.size()>0){
				Object[] obj=data.get(0);
				if(obj.length==titles.length){
					for(int i=0;i<titles.length;i++)
					{
						int contentLength=titles[i].length();
						if(null!=obj[i]&&obj[i].toString().length()>titles[i].length()){
							contentLength=obj[i].toString().length();
						}
						if(i==0)
						{
							//The maximum column width for an individual cell is 255 characters. 的解决方案
							if((short)50*(50+contentLength)/256>255){
								sheet.setColumnWidth(i, (short)254*256);  //初始列宽+自适应宽度
							}else{
								sheet.setColumnWidth(i, (short)50*(50+contentLength));  //初始列宽+自适应宽度
							}
						}else{
							//The maximum column width for an individual cell is 255 characters. 的解决方案
							if((short)100*(55+contentLength)/256>255){
								sheet.setColumnWidth(i, (short)254*256);  //初始列宽+自适应宽度
							}else{
								sheet.setColumnWidth(i, (short)100*(55+contentLength));  //初始列宽+自适应宽度
							}
						}
					}
				}else{
					for(int i=0;i<titles.length;i++)
					{
						int contentLength=titles[i].length();
						if(i==0)
						{
							sheet.setColumnWidth(i, (short)50*(50+contentLength));  //初始列宽+自适应宽度
						}else{
							sheet.setColumnWidth(i, (short)100*(55+contentLength));  //初始列宽+自适应宽度
						}
					}

				}

			}

			//创建新的样式给列表的大标题加上背景色
			 XSSFCellStyle alignStyle = (XSSFCellStyle)wb.createCellStyle();
			 alignStyle.setAlignment(XSSFCellStyle.ALIGN_CENTER);
			 alignStyle.setVerticalAlignment(XSSFCellStyle.VERTICAL_CENTER);
			 alignStyle.setFillPattern(HSSFCellStyle.SOLID_FOREGROUND);
			 alignStyle.setFillForegroundColor(IndexedColors.SKY_BLUE.getIndex());
			 alignStyle.setFont(font);
			fr.getCell(0).setCellValue(new XSSFRichTextString(fileName));
			fr.getCell(0).setCellStyle(alignStyle);
			firstRow += 1;
		}

		XSSFRow row = sheet.createRow(firstRow);
		for (int i = 0; i < titles.length; i++) {
			row.createCell(i).setCellValue(new XSSFRichTextString(titles[i]));
		}


			for (int i = 0; i < dataList.get(x).size(); i++) {
				XSSFRow r = sheet.createRow(i + firstRow + 1);
				Object[] obj = dataList.get(x).get(i);
				for (int j = 0; j < obj.length; j++) {
					// 创建单元格
					XSSFCell cell = r.createCell(j);
					Object value = obj[j];
					// 判断值的类型后进行强制类型转换
					String textValue = null;
					if (value instanceof Date) {
						Date date = (Date) value;
						SimpleDateFormat sdf = new SimpleDateFormat(
								"yy-MM-dd HH:mm:ss");
						textValue = sdf.format(date);
					} else if (value instanceof byte[]) {
						// 有图片时,设置行高为60px;

					} else {
						if (value == null) {
							// 如果值为空,什么都不做
						} else {
							// 其它数据类型都当作字符串简单处理
							textValue = value.toString();
						}
					}
					// 如果不是图片数据,就利用正则表达式判断textValue是否全部由数字组成
					if (textValue != null) {
						Pattern p = Pattern.compile("^//d+(//.//d+)?$");
						Matcher matcher = p.matcher(textValue);
						if (matcher.matches()) {
							// 是数字当作double处理
							cell.setCellValue(Double.parseDouble(textValue));
						} else {
							XSSFRichTextString richString = new XSSFRichTextString(
									textValue);
							cell.setCellValue(richString);
						}
					} else {// 若果是空值,单元格显示为空
						cell.setCellValue(new XSSFRichTextString(""));
					}
				}
			}

			Long end1 = System.currentTimeMillis();
			System.out.println("第"+(x+1)+"个sheet总计时间:" +((end1-start1)/1000) + "s");
			if(doneSignal!=null){
				doneSignal.countDown(); // 线程计数-1
				System.out.println("第" + (x+1) + "个sheet创建完成," + "计数器减1");
			}
	}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
此生不换
青鸟飞鱼