炎炎夏日最新版Excel导入导出工具类火热出炉

来源:互联网 发布:山东师范大学知乎 编辑:程序博客网 时间:2024/06/07 03:01

辛苦写的,转载请注明来源^_^

一、为什么要写这个Excel工具类

上个项目有个功能点需要导出信息到Excel文件,于是到网上找了工具类xdemo,首先感谢原作者的奉献,使用很简单。但在使用的过程中也发现了几个问题(可能是我用的版本太老导致的):

1.类的属性为空时,没有相应处理,Excel中的单元格会前移一个单元格。
2.不支持Java8的LocalDateTime的日期转换。
3.Excel反序列化List时,不支持自定义日期格式。
4.由于写的时间较早,其中很多POI的API都是标记过时的。
5.代码中大量使用for循环,if判断,对于代码洁癖的我,有点不习惯(对原作者没有任何恶意^_^,不要误解)

二、和老版的Excel工具类对比

功能点 新版ExcelUtils 旧版ExcelUtils 自定义表头样式 支持 不支持 自定义内容样式 支持 不支持 自定义序列化属性转换 支持 支持 自定义反序列属性转换 支持 支持 支持LocalDateTime 支持 不支持 自定义表格合并 不支持(写出来太费劲^_^) 支持

三、使用的依赖

Gradle

//FastJsoncompile ("com.alibaba:fastjson:1.2.35")//POIcompile ("org.apache.poi:poi:3.16")compile ("org.apache.poi:poi-ooxml:3.16")

Maven

//FastJson<dependency>    <groupId>com.alibaba</groupId>    <artifactId>fastjson</artifactId>    <version>1.2.35</version></dependency>//POI<dependency>    <groupId>org.apache.poi</groupId>    <artifactId>poi</artifactId>    <version>3.16</version></dependency><dependency>    <groupId>org.apache.poi</groupId>    <artifactId>poi-ooxml</artifactId>    <version>3.16</version></dependency>

四、使用的技术

1.使用FastJson作为Json转换工具(号称最快的Json解析工具)
2.大量使用Java8的Stream进行链式操作(好像也写了一些for循环……)
3.利用Java8的lambda表达式简化语法
4.利用Java8的Optional排除空指针异常

工具中采用的思想是约定大于配置。必需按照约定好的步骤进行调用。

五、List转Excel的实现思想

1.顺序获取类中所有属性的Excel注解,拿到Excel注解的name属性,设置为表头。
2.逐个顺序获取对象中带有Excel注解的属性值,然后做对应的转换(false->假,true->真等),之后写入到cell中,cell的类型均为String类型。
3.写出文件,搞定了

六、Excel转List的实现思想

可不可以把每行转换成一个对象的Json格式,再用Json工具转换成对象呢?这样效率可能会慢一点,但事实证明并没有慢多少,而且很方便》》》》

1.读取类中的所有带有Excel注解的属性的属性名(个数应和Exel的列的个数对应)。
2.将每一行组成Map<String,String>的数据,代表一个对象。那么多选就是List<Map<String,String>>,然后将其序列化,就类似于这样的Json:

[{    "name":"king1",    "age":"18"},{    "name":"king2",    "age":"19"},......]

组合成了对象数组的Json格式了,这时候只要调用Json工具的List list = JSON.parseArray(json, Class clazz)方法就可以拿到反序列的对象了,这个做法是不是有点骚气==》==》》》》》》

约定:

List中的类中带有Excel注解的属性必需与Excel文件中的数据完全对应,才能反序列成功。说的简单点,就是反序列时类属性上的@Excel注解要和导出为Excel时加的@Excel注解一样。

七、Excel工具类代码

7.1 Excel.java

package com.kingboy.common.utils.excel;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * @author kingboy--KingBoyWorld@163.com * @date 2017/7/26 下午1:23 * @desc Excel注解,用以生成Excel表格文件. */@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.FIELD, ElementType.TYPE})public @interface Excel {    //列名    String name() default "";    //宽度    int width() default 25;    //忽略该字段    boolean skip() default false;    //日期格式    String dateFormat() default "yyyy年MM月dd日 HH:mm";}

7.2 ExcelStyle.java

package com.kingboy.common.utils.excel;import org.apache.poi.ss.usermodel.BorderStyle;import org.apache.poi.ss.usermodel.HorizontalAlignment;/** * @author kingboy--KingBoyWorld@163.com * @date 2017/7/26 下午4:58 * @desc 设置Excel的样式.使用了build模式,链式赋值 */public class ExcelStyle {    public ExcelStyle() { }    //对齐方式    private HorizontalAlignment align;    //字体  "黑体"等    private String fontName;    //是否加粗,对表头不生效    private boolean isBold;    //字体大小    private short size;    //字体颜色    private short fontColor;    //边框粗细    private BorderStyle borderStyle;    //背景颜色    private short backColor;    public ExcelStyle setAlign(HorizontalAlignment align) {        this.align = align;        return this;    }    public ExcelStyle setFontName(String fontName) {        this.fontName = fontName;        return this;    }    public ExcelStyle setBold(boolean bold) {        isBold = bold;        return this;    }    public ExcelStyle setSize(short size) {        this.size = size;        return this;    }    public ExcelStyle setFontColor(short fontColor) {        this.fontColor = fontColor;        return this;    }    public ExcelStyle setBorderStyle(BorderStyle borderStyle) {        this.borderStyle = borderStyle;        return this;    }    public ExcelStyle setBackColor(short backColor) {        this.backColor = backColor;        return this;    }    public HorizontalAlignment getAlign() {        return align;    }    public String getFontName() {        return fontName;    }    public boolean isBold() {        return isBold;    }    public short getSize() {        return size;    }    public short getFontColor() {        return fontColor;    }    public BorderStyle getBorderStyle() {        return borderStyle;    }    public short getBackColor() {        return backColor;    }}

7.3 ExcelUtils.java

package com.kingboy.common.utils.excel;import com.alibaba.fastjson.JSON;import com.alibaba.fastjson.JSONObject;import org.apache.poi.hssf.util.HSSFColor;import org.apache.poi.ss.usermodel.*;import org.apache.poi.xssf.streaming.SXSSFWorkbook;import org.apache.poi.xssf.usermodel.XSSFWorkbook;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Path;import java.text.SimpleDateFormat;import java.time.LocalDateTime;import java.time.format.DateTimeFormatter;import java.util.*;import java.util.stream.Collectors;import java.util.stream.Stream;/** * @author kingboy--KingBoyWorld@163.com * @date 2017/7/26 下午1:25 * @desc Excel工具类. 只针对单个类内的属性进行转换。 * 额外说明:本工具类支持设置数据行的格式,但由于这个功能占用执行时间过多(大约十倍),所以在205行注释掉了,有需要可以打开 * 使用说明-博客:http://blog.csdn.net/kingboyworld/article/details/76253785 */public final class ExcelUtils {    private ExcelUtils() { }    /**     * 将List转换成Excel     * @param list 数据集合     * @param filePath java7中的文件操作  创建方式:Paths.get("文件地址");     * @param <T> bean类型     * @return     * @throws Exception     */    public static <T> void listToExcel(List<T> list, Path filePath, Map<String, Map<String, String>> fieldMapper,                                          ExcelStyle headStyle, ExcelStyle contentStyle) throws Exception {        //List为null或者empty抛出异常        Optional.ofNullable(list)                .filter(l -> !l.isEmpty())                .orElseThrow(NullPointerException::new);        //1. 获取类的字符串属性        List<Excel> excelList = getExcelAnnoList(list.get(0).getClass());        //2. 创建工件薄,工件表        Workbook workbook = new SXSSFWorkbook();        Sheet sheet = workbook.createSheet();        //3. 设置表头        setHead(excelList, sheet, headStyle);        //4. 设置内容        setContent(list, sheet, fieldMapper, contentStyle);        //5. 写文件        workbook.write(Files.newOutputStream(filePath));    }    //每次转换多少行    private static Integer size = 1_000;    /**     * excel转换为List,基本思路是拼接成Json,然后用Json工具转换为List     * 必需保证类里面的带有Excel注解属性的顺序和Excel文件中标题的顺序相对应     * @param filePath 文件路径    创建方式:Paths.get("文件地址");     * @param clazz 类     * @param <T>     * @return     */    public static<T> List<T> excelToList(Path filePath, Class<T> clazz, Map<String, Map<String, String>> fieldMapper)            throws Exception {        Workbook workbook = new XSSFWorkbook(Files.newInputStream(filePath));        Sheet sheet = workbook.getSheetAt(0);        //1. 获取类和Excel表格对应的属性,有序放入ArrayList中        ArrayList<String> fieldList = getFieldNameByExcelAnno(clazz);        //2. 获取结果        ArrayList<T> resultList = getClazzArrayList(sheet, clazz, fieldList, fieldMapper);        return resultList;    }    /*------------------------------ExcelToList的调用方法------------------------------------*/    /**     *     * @param sheet 工作表     * @param fieldList 获取类中有映射关系的属性名     * @param <T>     * @return     */    private static <T> ArrayList<T> getClazzArrayList(Sheet sheet, Class<T> clazz, ArrayList<String> fieldList,                                                      Map<String, Map<String, String>> fieldMapper) {        //数据行数        int rowNumber = sheet.getPhysicalNumberOfRows() - 1;        //读取次数        int times = rowNumber / size + 1;        //结果集        ArrayList<T> result = new ArrayList<T>(rowNumber);        Stream.iterate(1, i -> i + 1)                .limit(times - 1)                .forEach(i -> {                        result.addAll(getArrayListFromMap(                              readExcel((i - 1) * size + 1, i == times ? rowNumber : size, sheet, fieldList),                              clazz,                              fieldMapper)                        );                });        return result;    }    /**     * 解析Json字符串     * @param mapList Excel表中的数据     * @param clazz     * @param fieldMapper 属性转换容器     * @param <T>     * @return     */    private static <T> List<T> getArrayListFromMap(List<Map<String, String>> mapList, Class<T> clazz,                                                   Map<String, Map<String, String>> fieldMapper) {        //记录默认的日期格式        String tempDateFormat = JSONObject.DEFFAULT_DATE_FORMAT;        //设置日期编码        getFieldWithExcel(clazz)                .filter(field -> field.getType() == Date.class || field.getType() == LocalDateTime.class)                .findFirst()                .ifPresent(field ->                        JSONObject.DEFFAULT_DATE_FORMAT = field.getAnnotation(Excel.class).dateFormat());        //转换属性        if (fieldMapper != null) {            mapList.stream().forEach(map -> {                map.forEach((key, value) -> {                    if (fieldMapper.get(key) != null) {                        //真实的属性值                        String realValue = fieldMapper.get(key).get(value);                        //如果有对应的值,就设置对应的值                        map.put(key, realValue == null ? value : realValue);                    }                });            });        }        List<T> list = JSON.parseArray(JSON.toJSONString(mapList), clazz);        //还原默认的日期格式        JSONObject.DEFFAULT_DATE_FORMAT = tempDateFormat;        return list;    }    /**     * 读取指定数量的数据,每行都拼接成一个Map<String, String>的集合     * @param start 开始行     * @param size 长度     * @param sheet     * @param fieldList 获取类中有映射关系的属性名     * @return     */    private static <T> ArrayList<Map<String, String>> readExcel(int start, int size, Sheet sheet, ArrayList<String> fieldList) {        ArrayList<Map<String, String>> listMap = new ArrayList<>();        for (int i = start; i < start + size; i++) {            Map<String, String> map = new TreeMap<>();            for (int j = 0; j < fieldList.size(); j++) {                map.put(fieldList.get(j), sheet.getRow(i).getCell(j).getStringCellValue());            }            listMap.add(map);        }        return listMap;    }    /**     * 获取属性名     * @param clazz     * @param <T>     * @return     * @throws Exception     */    private static <T> ArrayList<String> getFieldNameByExcelAnno(Class<T> clazz) throws Exception {        return getFieldWithExcel(clazz)                .map(field -> field.getName())                .collect(Collectors.toCollection(ArrayList::new));    }    /*------------------------------ListToExcel的调用方法------------------------------------*/    /**     * 写入内容     * @param list 数据内容     * @param sheet     * @param fieldMapper 属性转换     * @param <T>     */    private static <T> void setContent(List<T> list, Sheet sheet, Map<String, Map<String, String>> fieldMapper, ExcelStyle contentStyle) {        Stream.iterate(0, item -> item + 1).limit(list.size())                .forEach(item -> {                    //当前行                    Row row = sheet.createRow(item + 1);                    //每个对象的属性值                    List<String> fieldValue = getFiledValueIfIsExcel(list.get(item), fieldMapper);                    Stream.iterate(0, i -> i + 1)                            .limit(fieldValue.size())                            .forEach(i -> {                                Cell cell = row.createCell(i);                                cell.setCellType(CellType.STRING);                                //cell.setCellStyle(getContentStyle(sheet.getWorkbook(), contentStyle));                                cell.setCellValue(fieldValue.get(i));                            });                });    }    /**     * 写入标题     * @param excelList 标题     * @param sheet     */    private static void setHead(List<Excel> excelList, Sheet sheet, ExcelStyle headStyle) {        Row row = sheet.createRow(0);        Stream.iterate(0, item -> item + 1)                .limit(excelList.size())                .forEach(item -> {                    Cell cell = row.createCell(item);                    cell.setCellType(CellType.STRING);                    cell.setCellValue(excelList.get(item).name());                    cell.setCellStyle(getTitleStyle(sheet.getWorkbook(), headStyle));                    sheet.setColumnWidth(item, excelList.get(item).width() * 2 << 6);                });    }    /**     * 设置头样式     * @param workbook     * @return     */    private static CellStyle getTitleStyle(Workbook workbook, ExcelStyle headStyle) {        CellStyle cellStyle = workbook.createCellStyle();        headStyle = headStyle == null ? new ExcelStyle() : headStyle;        //对齐方式        cellStyle.setAlignment(headStyle.getAlign() == null ? HorizontalAlignment.CENTER : headStyle.getAlign());        //设置字体        Font font = workbook.createFont();        String fontName = headStyle.getFontName();        font.setFontName(null == fontName ? "黑体" : fontName);        //字体大小        font.setFontHeightInPoints(headStyle.getSize() <= 0 ? 14 : headStyle.getSize());        font.setBold(true);        font.setColor(headStyle.getFontColor() <= 0 ? HSSFColor.BLACK.index : headStyle.getFontColor());        cellStyle.setFont(font);        //背景        cellStyle.setFillForegroundColor(headStyle.getBackColor() <= 0                ? HSSFColor.SEA_GREEN.index                : headStyle.getBackColor());        cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);        BorderStyle border = headStyle.getBorderStyle() == null ? BorderStyle.MEDIUM : headStyle.getBorderStyle();        //边框        cellStyle.setBorderLeft(border);        cellStyle.setBorderTop(border);        cellStyle.setBorderRight(border);        cellStyle.setBorderBottom(border);        //自动换行        //cellStyle.setWrapText(true);        return cellStyle;    }    /**     * 设置内容样式,占用过大,并没有开启这个功能     * @param workbook     * @return     */    private static CellStyle getContentStyle(Workbook workbook, ExcelStyle contentStyle) {        contentStyle = contentStyle == null ? new ExcelStyle() : contentStyle;        CellStyle cellStyle = workbook.createCellStyle();        //对齐方式        cellStyle.setAlignment(contentStyle.getAlign() == null ? HorizontalAlignment.LEFT : contentStyle.getAlign());        //设置字体        Font font = workbook.createFont();        String fontName = contentStyle.getFontName();        font.setFontName(null == fontName ? "黑体" : fontName);        //字体大小        font.setFontHeightInPoints(contentStyle.getSize() <= 0 ? 12 : contentStyle.getSize());        font.setBold(contentStyle.isBold());        font.setColor(contentStyle.getFontColor() <= 0 ? HSSFColor.BLACK.index : contentStyle.getFontColor());        cellStyle.setFont(font);        //背景        cellStyle.setFillForegroundColor(contentStyle.getBackColor() <= 0                ? HSSFColor.WHITE.index                : contentStyle.getBackColor());        cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);        BorderStyle border = contentStyle.getBorderStyle() == null ? BorderStyle.THIN : contentStyle.getBorderStyle();        //边框        cellStyle.setBorderLeft(border);        cellStyle.setBorderTop(border);        cellStyle.setBorderRight(border);        cellStyle.setBorderBottom(border);        //自动换行        //cellStyle.setWrapText(true);        return cellStyle;    }    /**     * 拿到一个类中属性上的Excel注解     * @param clazz     * @param <T>     * @return     * @throws Exception     */    private static <T> List<Excel> getExcelAnnoList(Class<T> clazz) throws Exception {        return  getFieldWithExcel(clazz)                .map(field -> field.getAnnotation(Excel.class))                .collect(Collectors.toCollection(ArrayList::new));    }    /**     * 获取带有Excel注解的属性     * @param clazz     * @param <T>     * @return     */    private static <T> Stream<Field> getFieldWithExcel(Class<T> clazz) {        Field[] fields = clazz.getDeclaredFields();        return Arrays.stream(fields)                .filter(field -> {                    Excel excel = field.getAnnotation(Excel.class);                    return excel != null && !excel.skip();                });    }    /**     * 获取一个类包含Excel注解的属性的值     * @param t     * @param <T>     * @return     */    private static  <T> List<String> getFiledValueIfIsExcel(T t, Map<String, Map<String, String>> fieldMapper) {        return getFieldWithExcel(t.getClass()).map(field -> {            field.setAccessible(true);            Object o = null;            try {                 o = field.get(t);            } catch (IllegalAccessException e) {                e.printStackTrace();            }            //如果属性值为空,返回空字符串            if (Objects.isNull(o)) {                o =  "";            }            //如果是Date类型            if (o instanceof Date) {                o =  new SimpleDateFormat(field.getAnnotation(Excel.class).dateFormat()).format((Date) o);            }            //如果是LocalDateTime类型,支持Java8LocalDateTime            if (o instanceof LocalDateTime) {                o =  ((LocalDateTime) o).format(DateTimeFormatter.ofPattern(field.getAnnotation(Excel.class).dateFormat()));            }            //bool和int属性转换,感觉最常用这两个,其它的需要再扩充            if (fieldMapper != null && (o instanceof Boolean || o instanceof Integer)) {                Map<String, String> map = fieldMapper.get(field.getName());                o =  map == null ? o : map.get(o.toString()) == null ? o : map.get(o.toString());            }            //其它的返回toString            return o.toString();        }).collect(Collectors.toCollection(ArrayList::new));    }}

八、使用方法

//把List转换为Excelpublic static <T> void listToExcel(List<T> list, Path filePath, Map<String, Map<String, String>> fieldMapper, ExcelStyle headStyle, ExcelStyle contentStyle) {}

参数说明:
1. list: 要转换的类(类中需要转换的属性需要加上Excel注解)
2. filePath: Java7中的文件操作类。Excel导出的地址
3. fieldMapper: 属性转换(可以为null)
4. headStyle: 头部样式(可以为null),为空时使用默认样式。
5. contentStyle:内容体的样式(可以为null),这个默认不生效(由于这个功能占用执行时间过多(大约十倍),所以在205行注释掉了,有需要可以打开)

//把Excel转换为List

excelToList(Path filePath, Class<T> clazz, Map<String, Map<String, String>> fieldMapper){}

参数说明:
1. filePath: Java7中的文件操作类。Excel导出的地址
2. clazz: 要转换的类
3. fieldMapper: 属性转换(可以为null)
4. 如果有日期等,需要在类属性的Excel注解上给dateFormat属性赋值。

九、测试用例

//基础类public class ExcelDTO {    @Excel(name = "姓名", width = 30)    private String name;    @Excel(name = "年龄", width = 30)    private Integer age;    @Excel(skip = true)    private String password;    @Excel(name = "生日", dateFormat = "yyyy年MM/dd HH:mm", width = 50)    private Date birth;    @Excel(name = "帐户")    private Double money;    @Excel(name = "冻结")    private Boolean lock;//getter,setter}

测试

/** * @author kingboy--KingBoyWorld@163.com * @date 2017/7/26 下午2:04 * @desc 测试我写的Excel工具类. */public class MyExcelTest {    /**     * 导出为Excel,将属性进行转换。     * @throws Exception     */    @Test    public void ListToExcel_test() throws Exception {        long start = System.nanoTime();        //属性转换        Map<String, Map<String, String>> map = new HashMap<>();        //age的转换        Map<String, String> value = new HashMap<>();        value.put("12", "12岁");        value.put("15", "15岁");        value.put("18", "18岁");        map.put("age", value);        //lock的转换        Map<String, String> lock = new HashMap<>();        lock.put("true", "锁");        lock.put("false", "正常");        map.put("lock", lock);        ExcelUtils.listToExcel(getExcelDTOList(), Paths.get("/Users/kingboy/Desktop/My.xlsx"), map, null, null);        System.out.println("-----my-------" + (System.nanoTime() - start) / 1000_000 + "毫秒");    }    /**     * 导出为Excel,没有将属性进行转换。     * @throws Exception     */    @Test    public void ListToExcelNoMapper_test() throws Exception {        //没有转换属性测试        ExcelUtils.listToExcel(getExcelDTOList(), Paths.get("/Users/kingboy/Desktop/My1.xlsx"), null, new ExcelStyle().setBackColor(HSSFColor.WHITE.index),new ExcelStyle().setAlign(HorizontalAlignment.CENTER));    }    /**     * 导入为List,将属性进行转换。     * 注意属性转换是反过来的。     * @throws Exception     */    @Test    public void ExcelToList_test() throws Exception {        long start = System.nanoTime();        Map<String, Map<String, String>> map = new HashMap<>();        Map<String, String> value = new HashMap<>();        value.put("小", "12");        value.put("中", "15");        value.put("大", "18");        Map<String, String> lock = new HashMap<>();        lock.put("锁", "true");        lock.put("正常", "false");        map.put("age", value);        map.put("lock", lock);        List<ExcelDTO> excelDTOS = ExcelUtils.excelToList(Paths.get("/Users/kingboy/Desktop/My.xlsx"), ExcelDTO.class, map);        System.out.println("----my--------" + (System.nanoTime() - start) / 1000_000 + "毫秒");    }    public List<ExcelDTO> getExcelDTOList() {        List<ExcelDTO> list = new ArrayList<>();        Stream.iterate(1, item -> item + 1).limit(10000)                .forEach(item -> list.add(new ExcelDTO("king1", 15, "1123", new Date(), 111d, true)));        return list;    }}

导出的效果图:

有属性转换的效果:

有属性转换的效果

无属性转换的效果:

无属性转换的效果

十、推荐下自己写的项目

github项目:https://github.com/KingBoyWorld/aurora.git

这个Excel工具类的代码文件就在这个项目的utils_feature分支中,utils分支还包含了各种个人整理与重写的非常实用的工具类。

这个框架是基于Springboot搭建的多模块多功能的框架,目前项目中各个分支包含了redis,resttemplate,springcache,actuator,utils等功能,只需要将不同的分支进行合并就具有了对应的功能,再也不用为搭建框架费心了。目前正在不断扩充其它的功能点,感兴趣的话一块加入进来吧。

原创粉丝点击