炎炎夏日最新版Excel导入导出工具类火热出炉
来源:互联网 发布:山东师范大学知乎 编辑:程序博客网 时间:2024/06/07 03:01
辛苦写的,转载请注明来源^_^
一、为什么要写这个Excel工具类
上个项目有个功能点需要导出信息到Excel文件,于是到网上找了工具类xdemo,首先感谢原作者的奉献,使用很简单。但在使用的过程中也发现了几个问题(可能是我用的版本太老导致的):
1.类的属性为空时,没有相应处理,Excel中的单元格会前移一个单元格。
2.不支持Java8的LocalDateTime的日期转换。
3.Excel反序列化List时,不支持自定义日期格式。
4.由于写的时间较早,其中很多POI的API都是标记过时的。
5.代码中大量使用for循环,if判断,对于代码洁癖的我,有点不习惯(对原作者没有任何恶意^_^,不要误解)
二、和老版的Excel工具类对比
三、使用的依赖
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等功能,只需要将不同的分支进行合并就具有了对应的功能,再也不用为搭建框架费心了。目前正在不断扩充其它的功能点,感兴趣的话一块加入进来吧。
- 炎炎夏日最新版Excel导入导出工具类火热出炉
- Excel导入导出工具类
- excel导入导出工具类
- Excel导入导出工具类
- java导出excel,导入excel,导出csv工具类整理
- excel 导入导出 poi工具类
- 导入导出Excel工具类ExcelUtil
- 导入导出Excel工具类ExcelUtil
- 导入导出Excel工具类ExcelUtil
- java 导入导出Excel工具类ExcelUtil
- excel导入导出通用工具类
- Java导入导出Excel工具类ExcelUtil
- Java导入导出Excel工具类ExcelUtil
- 导入导出Excel工具类ExcelUtil
- Excel工具类 导入解析 导出保存
- java 导入导出Excel工具类ExcelUtil
- Excel导入导出工具类(java)
- Excel导入导出封装工具类
- Python两个内置函数——locals 和globals (学习笔记)
- 使用Hibernate 创建数据表时,如何使数据库中的字段的长度和Hibernate映射的长度相等呢?
- ArcGIS Web Adaptor (IIS) 安装指南
- 龟兔赛跑程序模拟
- 双mysql之配置文件my.cnf
- 炎炎夏日最新版Excel导入导出工具类火热出炉
- 求二叉树中两个节点的最近公共祖先
- iOS积累--objectForKey与valueForKey在NSDictionary中的差异
- //实现对输入时间的前后N天的查找
- KVM — 使用 qemu-kvm 原生工具栈实现创建管理虚拟机实例
- 值得推荐的C/C++框架和库
- IOS charts框架 直线图 线性图
- jenkins配置节点无Launch agent via Java Web Start项
- WAMP安装与配置更改