分享一个简单数据导出csv的工具类

直接上代码:

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.*;
import java.util.Map.Entry;

/**
 * CSV导出工具类
 */
public class CsvExportKit {

   public static void main(String[] args) throws Exception {
      LinkedHashMap<String, String> headerMap = new LinkedHashMap<>();
      headerMap.put("key1", "字段1");
      headerMap.put("key2", "字段2");
      headerMap.put("key3", "字段3");
      headerMap.put("key4", "字段4");
      headerMap.put("key5", "字段5");
      List<Map<String, String>> dataList = new ArrayList<>();
      Map<String, String> item;
      for (int i = 0; i < 4000000; i++) {
         item = new HashMap<>();
         item.put("key1", "序号" + i);
         item.put("key2", "订单号2019-" + i);
         item.put("key3", "2019-07-10");
         item.put("key4", "300.15");
         item.put("key5", "备注" + i);
         dataList.add(item);
      }
      System.out.println("数据准备完成,开始导出......");
      /*String fileName = "csv导出";
      HttpServletResponse response = null;
      response.setHeader("Content-disposition", "attachment; filename=" + new String(fileName.getBytes(), "ISO8859-1") + ".csv");
      response.setContentType("text/csv");
      response.setCharacterEncoding("UTF-8");
      OutputStream out = response.getOutputStream();*/
      OutputStream out = new FileOutputStream(new File("D:\\导出测试" + System.currentTimeMillis() + ".csv"));
      long start = System.currentTimeMillis();
      export(out, headerMap, dataList);
      out.flush();
      out.close();
      System.out.println("导出完成, 耗时" + (System.currentTimeMillis() - start) + "毫秒!");
   }
   
   /**
    * 导出CSV文件
    * @param out 输出流
    * @param headerMap 表头与字段对应的有序集合
    * @param dataList 导出的数据
    * @throws Exception
    */
   public static <T> void export(OutputStream out, LinkedHashMap<String, String> headerMap, List<T> dataList) throws Exception {
      String seprator = ",";
      try {
         // 输出表头
         StringBuilder builder = new StringBuilder();
         for (Entry<String, String> i : headerMap.entrySet()) {
            builder.append("\"").append(i.getValue()).append("\"").append(seprator);
         }
         builder.append("\r");
         // 输出内容
         int size = dataList.size();
         T data;
         for (int i = 0; i < size; i++) {
            for (String field : headerMap.keySet()) {
               data = dataList.get(i);
               builder.append("\"").append(getValue(data, field)).append("\"").append(seprator);
            }
            builder.append("\r");
            // 每500000写入一次,防止Java heap space
            if (i > 0 && (i + 1) % 500000 == 0) {
               out.write(builder.toString().getBytes("GB2312"));
               builder = new StringBuilder();
            }
         }
         if (size % 500000 > 0) {
            out.write(builder.toString().getBytes("GB2312"));
         }
      } catch (Exception e) {
         throw new Exception(e.getMessage(), e);
      }
   }
   
   /**
     * 根据属性字段(key)获取对应属性字段(key)的值
     * @param dataObj 数据对象
     * @param field   字段属性(key)
     * @return String 属性(key)值
     */
    private static String getValue(Object dataObj, String field) {
        Object value = null;
        try {
            if (Map.class.isAssignableFrom(dataObj.getClass())) {
                @SuppressWarnings("unchecked")
                Map<String, Object> map = (Map<String, Object>) dataObj;
                value = map.get(field);
            } else {
                Field[] fields = getFields(dataObj.getClass());
                for (Field item : fields) {
                    item.setAccessible(true);
                    if (item.getName().equalsIgnoreCase(field)) {
                        value = item.get(dataObj);
                        break;
                    }
                }
            }
        } catch (IllegalAccessException | IllegalArgumentException | SecurityException e) {
            e.printStackTrace();
        }
        if (value != null) {
            return String.valueOf(value);
        }
        return null;
    }

    /**
     * 获取类属性字段
     * @param clazz
     * @return
     */
    private static Field[] getFields(Class<?> clazz) {
        Set<Field> fieldSet = new HashSet<>();
        Field[] selfFields = clazz.getDeclaredFields();
        Field[] superClassFields = clazz.getSuperclass().getDeclaredFields();
        fieldSet.addAll(Arrays.asList(selfFields));
        fieldSet.addAll(Arrays.asList(superClassFields));
        return fieldSet.toArray(new Field[0]);
    }
}

另外,在本机测试时发现,当数据在400万以内时,导出很快,几秒钟完成,但是当调到500万时,导出却要耗时20多秒甚至半分钟,按数据量级倍数计算,加上其他影响,耗时应该10几秒就能完成,但事实却不是,有点不太明白,知道原因的小伙伴可以解答一下!

评论区

chcode

2019-07-11 12:04

这还用分享,自己一下就写完了

杜福忠

2019-07-11 12:13

我觉得还是 Enjoy Template 导出 xls或者 是 csv 方便... 老爽了, 特别是客户经常改的情况下, 简直就是利器

杜福忠

2019-07-11 12:18

@chcode 我认为, 分享不在于内容有多么好多么牛逼, 重在与 "分享". 社区需要大家一起来多多分享多多鼓励, 哪怕是在开发中遇到的一个梗,然后你发现了并搞定了, 我觉得都可以分享出来

冰雨

2019-07-11 12:58

@chcode 已经写好的东西,分享出来,拿来即用,提高效率,能够帮到他人就好,自己写不还得花时间吗。另外自己写可能遇到乱码问题,还得尝试解决乱码问题,哪如拿来用来的直接!同时,更多的分享,可能会为JFinal社区带来更多的用户,因为总有需要的人,如果对JFinal的生态的发展有利,即使简单,我觉得也有价值!

冰雨

2019-07-11 13:02

@杜福忠 分享也是共同交流和提高的一种方式,并且多数情况是双赢。多谢支持,向你对JFinal的持续支持与回报致敬!

l745230

2019-07-12 08:24

感觉Hutool封装的Excel导出,调用起来会更简单一点. 关键是导出的样式,还挺好看.不用自己在调了

JFinal

2019-07-12 10:04

@杜福忠 没错,用 enjoy 是最方便的,因为这类工作的共同特征就是大量样板式代码中插入少许动态内容,而这正是模板引擎的本质

JFinal

2019-07-12 10:05

@冰雨 感谢分享,赞

冰雨

2019-07-12 11:03

@JFinal 波总,上面代码,当数据200万时很快,当500万的时候,就很慢,时间差别明显与数据差别倍数不成正比。

JFinal

2019-07-12 11:05

@冰雨 用 visualVM 先定位瓶颈,光猜太慢了