关于本类线程安全性的解释:

多数工具方法不涉及共享变量问题,至于添加合并单元格方法addMergeArea,使用ThreadLocal变量存储合并数据,ThreadLocal内部借用Thread.ThreadLocalMap以当前ThreadLocal为key进行存储,设置一次变量,则其他线程也会有上次数据的残留,因此在addMergeArea方法中进行清空的操作。为了保证原子性, 采用ReentrantLock确保一次只有一个线程可以进行添加合并数据的操作。

线程安全性从以上两个方面保证。

水平有限,难免会有疏漏,敬请谅解。

主要使用Apache POI进行Excel的导入、导出

使用

读取Excel中的数据

原始数据如下:

方法:public static List<List<String>> readFile(InputStream ins, int headRowNum) throws Exception

使用方式:

        String filePath = "excel文件路径";
File file = new File(filePath);
InputStream ins = new FileInputStream(file);
List<List<String>> lists = ExcelUtil.readFile(ins, 2);
System.out.println(lists);

返回结果:

[[序号, 部门, 姓名, 上岗证号, 岗职, 入职时间], [, , , , , ], [1, 财务部, 赵六, 001, 出纳, 1540915200000], [1, 财务部, 张三, 002, 会计, 1517328000000]]

注:对于Date类型,读取时读的是long类型参数

将Excel中的数据转换为对应的实体Entity

  • public static <T> List<T> getListFromExcel(InputStream ins, int headRowNum, Class<T> cls, int parseIndex, String... excludeAttr) throws Exception
  • public static <T> List<T> getListFromExcel(MultipartFile file, int headRowNum, Class<T> cls, int parseIndex, String... excludeAttr) throws Exception


    两种方法本质上没有什么区别,可变参数excludeAttr配置实体不匹配的属性,例如:id

    注:转换的时候,需要保证excel中属性的顺序与实体中属性的顺序对应,例如excel中部门-姓名-上岗证号...这样,则实体也应该按照这样的顺序定义属性。

此方法通常用于获取对应的excel数据,并批量插入数据库中。

导出Excel

根据List数据导出excel
  • public static void exportExcel(String title, String[] headers, List<?> list, HttpServletResponse response, boolean useXSSF, String sheetName, List<String> includeAttr)


    title:导出名字

    headers:表头数组,list:数据, useXSSF:是否使用2007Excel, sheetName:创建sheet名字,includeAttr:展示实体的哪些属性

    public static void exportExcel(String title, String[] headers, List<?> list, HttpServletResponse response, boolean useXSSF, String sheetName, String... excludeAttr)主要差别在最后一个,不展示哪些属性

    如果在导出时需要合并单元格,先调用`public static void setMergeAreaList(List list)`
    例如:

   List<ExcelUtil.RectangleArea> list = new ArrayList<>();
ExcelUtil.RectangleArea area = new ExcelUtil.RectangleArea(2,2,0,4);
list.add(area);
ExcelUtil.setMergeAreaList(list);
根据字符串列表导出excel

public static void exportExcel(String title, List<List<String>> list, boolean useXSSF, HttpServletResponse response)

根据字符串列表自定义导出实现

调用

public static Workbook getExcelWorkBook(List<List<String>> list, boolean useXSSF)获取workbook,

public static void addMergeArea(Workbook workbook, int sheetIndex, List<RectangleArea> areas)添加合并单元格数据,然后workboo.write(输出流)即可。

其余依赖工具类,请参见github地址:

https://github.com/studentytj/sth_useful

package com.test.sth_useful.common.util;

import com.test.sth_useful.common.util.ReflectionUtils;
import com.test.sth_useful.common.util.StreamUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFRichTextString;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.formula.functions.T;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile; import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.locks.ReentrantLock; import static org.apache.poi.ss.usermodel.Cell.*; /**
* 关于本类线程安全性的解释:
* 多数工具方法不涉及共享变量问题,至于添加合并单元格方法addMergeArea,使用ThreadLocal变量存储合并数据,ThreadLocal
* 内部借用Thread.ThreadLocalMap以当前ThreadLocal为key进行存储,设置一次变量,则其他线程也会有上次数据的残留,因此在
* addMergeArea方法中进行清空的操作。为了保证原子性, 采用ReentrantLock确保一次只有一个线程可以进行添加合并数据的操作。
* 线程安全性从以上两个方面保证。
* 水平有限,难免会有疏漏,敬请谅解。
*
*
* @date 2018/10/15
*/
public class ExcelUtil {
/**
* logger
*/
private static final Logger LOGGER = LoggerFactory.getLogger(ExcelUtil.class); private static ThreadLocal<List<RectangleArea>> mergeAreaList = new ThreadLocal<>(); public static List<RectangleArea> getMergeAreaList() {
return mergeAreaList.get();
} public static void setMergeAreaList(List<RectangleArea> list) {
mergeAreaList.set(list);
} /**
* 获取单元格的值
*
* @param cell
* @return
*/
public static String getCellValue(Cell cell) {
String cellValue = "";
int cellType = cell.getCellType();
if (cellType == CELL_TYPE_STRING) {
cellValue = cell.getStringCellValue();
} else if (cellType == CELL_TYPE_NUMERIC) {
if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) {
LOGGER.info("date: {}", cell.getDateCellValue());
cellValue = String.valueOf(cell.getDateCellValue().getTime());
} else {
if (cell instanceof XSSFCell) {
cellValue = ((XSSFCell) cell).getRawValue();
} else {
cellValue = String.valueOf(cell.getNumericCellValue());
}
// cellValue = String.valueOf(df.format(cell.getNumericCellValue()));
}
} else if (cellType == CELL_TYPE_FORMULA) {
switch (cell.getCachedFormulaResultType()) {
case Cell.CELL_TYPE_NUMERIC:
// 保留两位小数
cellValue = String.valueOf(new DecimalFormat("#.00").format(cell.getNumericCellValue()));
break;
case Cell.CELL_TYPE_STRING:
cellValue = String.valueOf(cell.getRichStringCellValue());
break;
}
} else if (cellType == CELL_TYPE_BLANK) {
cellValue = cell.getStringCellValue();
}
return cellValue;
} /**
* 读取excel
* 注意: 默认读取的数据是在第一个sheet中的,暂不支持多个sheet的读取
*
* @param ins 输入流
* @param headRowNum 表头所在行数
* @return 返回excel中的字符串数据
* @throws Exception
*/
public static List<List<String>> readFile(InputStream ins, int headRowNum) throws Exception {
Workbook workbook = WorkbookFactory.create(ins);
Sheet sheet = workbook.getSheetAt(0);
// 获取excel总行数
int rownum = sheet.getLastRowNum();
List<List<String>> result = new ArrayList<>();
LOGGER.info("excel 总行数:{}", rownum);
// 获取表头那一行的数据长度
short allColumnNum = sheet.getRow(headRowNum).getLastCellNum(); for (int i = 0; i <= rownum; i++) {
Row row = sheet.getRow(i);
if (row == null || row.getPhysicalNumberOfCells() == 0) {
LOGGER.info("第{}行数据为空.", i);
continue;
}
List<String> oneRecord = new ArrayList<>();
for (int j = 0; j < allColumnNum; j++) {
Cell cell = row.getCell(j);
// cell.setCellStyle(cellStyle);
if (cell == null) {
oneRecord.add("");
continue;
}
String cellValue = getCellValue(cell);
oneRecord.add(cellValue);
}
result.add(oneRecord);
}
LOGGER.info("读取文件完成!");
return result;
} /**
* 将读取到的excel数据存入List<cls>列表中
*
* @param list 字符串
* @param cls 解析的类对象
* @param parseIndex 开始解析的行数
* @param excludeAttr 哪些属性不解析
* @return 对应的对象列表
* @throws Exception
*/
public static <T> List<T> getEntityList(List<List<String>> list, Class<T> cls, int parseIndex, String... excludeAttr) throws Exception {
List<T> result = new ArrayList<>();
for (int i = parseIndex; i < list.size(); i++) {
List<String> oneRecord = list.get(i);
T instance = cls.newInstance();
for (int j = 0, tmp = 0; j < oneRecord.size(); j++) {
String cellValue = oneRecord.get(j);
if (StringUtils.isEmpty(cellValue)) continue;
Field[] fields = cls.getDeclaredFields();
Field field = fields[j];
for (String s : excludeAttr) {
if (field.getName().equals(s)) {
tmp++;
break;
}
}
field = fields[j + tmp];
Object cellVal = field.getType().equals(Date.class) ? new Date(Long.parseLong(cellValue)) : cellValue;
ReflectionUtils.setFieldValue(instance, field.getName(), cellVal);
}
result.add(instance);
}
return result;
} /**
* 获取excel表中对应的实体数据
*
* @param ins
* @param headRowNum
* @param cls
* @param parseIndex
* @param excludeAttr
* @param <T>
* @return
* @throws Exception
*/
public static <T> List<T> getListFromExcel(InputStream ins, int headRowNum, Class<T> cls, int parseIndex, String... excludeAttr) throws Exception {
List<List<String>> strLists = readFile(ins, headRowNum);
return getEntityList(strLists, cls, parseIndex, excludeAttr);
} /**
* 获取excel对应的实体列表
*
* @param file 导入的excel文件
* @param headRowNum 表头行数,记住:从0开始
* @param cls 实体类
* @param parseIndex 从哪一行开始解析
* @param excludeAttr 哪些属性不映射
* @param <T>
* @return
* @throws Exception
*/
public static <T> List<T> getListFromExcel(MultipartFile file, int headRowNum, Class<T> cls, int parseIndex, String... excludeAttr) throws Exception {
// try(流)是Java7中的try-with-resource语法。当try语句块运行结束时,InputStream 会被自动关闭,只要实现了AutoCloseable接口的类都可以写在try括号里
try (InputStream ins = file.getInputStream()) {
List<List<String>> strLists = readFile(ins, headRowNum);
return getEntityList(strLists, cls, parseIndex, excludeAttr);
}
} /**
* 展示某些属性的方法
*
* @param headers
* @param list
* @param useXSSF 是否使用2007
* @param sheetName
* @param includeAttr
* @param isInclude
* @return
*/
private static <T> Workbook getExcelWorkBook(String[] headers, List<T> list, boolean useXSSF, String sheetName, List<String> includeAttr, boolean isInclude) {
Workbook workbook = getWorkbook(useXSSF);
// 创建sheet
Sheet sheet = workbook.createSheet(sheetName);
CellStyle cellStyle = getCellStyle(workbook);
// 创建表头
createHeader(headers, sheet, useXSSF, cellStyle);
Field[] fields = null;
// 确定行数
for (int i = 0; i < list.size(); i++) {
// 创建一行
Row dataRow = sheet.createRow(i + 1);
T t = list.get(i);
if (isInclude) {
// 为每个单元格设值
for (int j = 0; j < includeAttr.size(); j++) {
String fieldName = includeAttr.get(j);
formatCell(sheet, cellStyle, dataRow, t, j, fieldName);
}
} else {
fields = fields == null ? t.getClass().getDeclaredFields() : fields;
// k用来解决某些字段并不在excel中展示带来的数据和列名不匹配问题
for (int j = 0, k = 0; j < fields.length - 1; j++) {
Field field = fields[j];
String fieldName = field.getName();
// flag标志代表是否不展示该项的值
boolean flag = false;
if (includeAttr.contains(fieldName)) {
// 解决生成数据不匹配问题
k++;
flag = true;
break;
}
if (flag) continue; formatCell(sheet, cellStyle, dataRow, t, j - k, fieldName);
}
}
}
return workbook;
} /**
* 格式化单元格
* @param sheet
* @param cellStyle
* @param dataRow
* @param t
* @param j
* @param fieldName
* @param <T>
*/
private static <T> void formatCell(Sheet sheet, CellStyle cellStyle, Row dataRow, T t, int j, String fieldName) {
Cell dataCell = dataRow.createCell(j);
dataCell.setCellStyle(cellStyle);
// 设置固定宽度, 每个单元格可存放16个字符
sheet.setColumnWidth(j, 16 * 256);
setCellValue(dataCell, ReflectionUtils.getFieldValue(t, fieldName));
} /**
* 获取通用style
*
* @param workbook
* @return
*/
private static CellStyle getCellStyle(Workbook workbook) {
CellStyle cellStyle = workbook.createCellStyle();
cellStyle.setBorderLeft(CellStyle.BORDER_THIN);
cellStyle.setLeftBorderColor(IndexedColors.BLACK.getIndex());
cellStyle.setBorderRight(CellStyle.BORDER_THIN);
cellStyle.setRightBorderColor(IndexedColors.BLACK.getIndex());
cellStyle.setBorderBottom(CellStyle.BORDER_THIN);
cellStyle.setBottomBorderColor(IndexedColors.BLACK.getIndex());
cellStyle.setBorderTop(CellStyle.BORDER_THIN);
cellStyle.setTopBorderColor(IndexedColors.BLACK.getIndex());
return cellStyle;
} private static Workbook getWorkbook(boolean useXSSF) {
return useXSSF ? new XSSFWorkbook() : new HSSFWorkbook();
} /**
* 给出字符串的情况下获取WorkBook
*
* @param list
* @return
*/
public static Workbook getExcelWorkBook(List<List<String>> list, boolean useXSSF) {
Workbook workbook = getWorkbook(useXSSF);
// 创建sheet, 名字默认为sheet0
Sheet sheet = workbook.createSheet("sheet0");
// 确定行数
for (int i = 0; i < list.size(); i++) {
// 创建一行
Row dataRow = sheet.createRow(i);
List<String> t = list.get(i);
// 为每个单元格设值
for (int j = 0; j < t.size(); j++) {
Cell dataCell = dataRow.createCell(j);
// 设置固定宽度, 每个单元格可存放16个字符
setCellValue(dataCell, t.get(j));
}
}
return workbook;
} /**
* 创建表头
*
* @param headers
* @param sheet
*/
private static void createHeader(String[] headers, Sheet sheet, boolean useXSSF, CellStyle commonStyle) {
Row headerRow = sheet.createRow(0);
for (int i = 0; i < headers.length; i++) {
Cell headerRowCell = headerRow.createCell(i);
headerRowCell.setCellStyle(commonStyle);
RichTextString text = useXSSF ? new XSSFRichTextString(headers[i]) : new HSSFRichTextString(headers[i]);
headerRowCell.setCellValue(text);
}
} /**
* 为单元格设置值
*
* @param dataCell
* @param value
*/
public static void setCellValue(Cell dataCell, Object value) {
if (value instanceof Date) {
DateFormat sdf = DateUtil.secondFormat.get();
String zero = "00:00:00";
if (zero.equals(sdf.format(value).split(" ")[1])) {
sdf = DateUtil.dayFormat.get();
}
dataCell.setCellValue(sdf.format(value));
} else if (value instanceof Number) {
dataCell.setCellValue(Double.parseDouble(String.valueOf(value)));
} else if (value == null) {
dataCell.setCellValue("");
} else {
dataCell.setCellValue(value.toString());
}
} /**
* 导出excel ——展示某些属性
*
* @param title
* @param headers
* @param list
* @param response
* @param useXSSF
* @param sheetName
* @param includeAttr
*/
public static void exportExcel(String title, String[] headers, List<?> list, HttpServletResponse response, boolean useXSSF, String sheetName, List<String> includeAttr, boolean isInclude) {
try {
Workbook workbook = getExcelWorkBook(headers, list, useXSSF, sheetName, includeAttr, isInclude);
addMergeArea(workbook, 0, getMergeAreaList());
writeToResp(title, workbook, response, useXSSF);
} catch (Exception e) {
e.printStackTrace();
}
} /**
* 给出导出字符串的情况下的导出方法
*
* @param title
* @param list
* @param response
*/
public static void exportExcel(String title, List<List<String>> list, boolean useXSSF, HttpServletResponse response) {
try {
Workbook workbook = getExcelWorkBook(list, useXSSF);
writeToResp(title, workbook, response, useXSSF);
} catch (Exception e) {
e.printStackTrace();
}
} /**
* 将excel流写入response
*
* @param title
* @param workbook
* @param response
* @param useXSSF
*/
private static void writeToResp(String title, Workbook workbook, HttpServletResponse response, boolean useXSSF) {
ServletOutputStream outputStream = null;
try {
LOGGER.info("" + workbook);
outputStream = response.getOutputStream();
// 解决浏览器及中文乱码问题https://tools.ietf.org/html/rfc2231
response.setHeader("Content-Disposition", "attachment;filename*=utf-8'zh_cn'" + URLEncoder.encode(title, "UTF-8") + (useXSSF ? ".xlsx" : ".xls"));
response.setContentType("application/msexcel");
workbook.write(outputStream);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭流资源
StreamUtil.close(outputStream, workbook);
}
} /**
* 合并单元格并水平垂直居中实现
*
* @param areas
*/
public static void addMergeArea(Workbook workbook, int sheetIndex, List<RectangleArea> areas) {
ReentrantLock lock = new ReentrantLock();
try {
lock.lock();
LOGGER.info("Thread: {}, threadLocal is: {}.", Thread.currentThread().getName(), String.valueOf(getMergeAreaList()));
if (areas != null && areas.size() > 0) {
for (RectangleArea area : areas) {
workbook.getSheetAt(sheetIndex)
.addMergedRegion(new CellRangeAddress(area.getFirstRow(), area.getLastRow(), area.getFirstCol(), area.getLastCol()));
// 合并单元格时默认居中(水平、垂直居中)
CellStyle cellStyle = getCellStyle(workbook);
cellStyle.setAlignment(CellStyle.ALIGN_CENTER);
cellStyle.setVerticalAlignment(CellStyle.VERTICAL_CENTER);
workbook.getSheetAt(sheetIndex).getRow(area.getFirstRow()).getCell(area.getFirstCol()).setCellStyle(cellStyle);
}
mergeAreaList.set(null);
}
} finally {
lock.unlock();
}
} public static class RectangleArea {
private int firstRow, lastRow, firstCol, lastCol; public RectangleArea(int firstRow, int lastRow, int firstCol, int lastCol) {
this.firstRow = firstRow;
this.lastRow = lastRow;
this.firstCol = firstCol;
this.lastCol = lastCol;
} public int getFirstRow() {
return firstRow;
} public int getLastRow() {
return lastRow;
} public int getFirstCol() {
return firstCol;
} public int getLastCol() {
return lastCol;
}
} }

【原创】POI操作Excel导入导出工具类ExcelUtil的更多相关文章

  1. 一个基于POI的通用excel导入导出工具类的简单实现及使用方法

    前言: 最近PM来了一个需求,简单来说就是在录入数据时一条一条插入到系统显得非常麻烦,让我实现一个直接通过excel导入的方法一次性录入所有数据.网上关于excel导入导出的例子很多,但大多相互借鉴. ...

  2. Java基础学习总结(49)——Excel导入导出工具类

    在项目的pom文件中引入 <dependency> <groupId>net.sourceforge.jexcelapi</groupId> <artifac ...

  3. apache POI 操作excel<导入导出>

    1.首先导入maven依赖 <!-- POI核心依赖 --> <dependency> <groupId>org.apache.poi</groupId> ...

  4. java中excel导入\导出工具类

    1.导入工具 package com.linrain.jcs.test; import jxl.Cell; import jxl.Sheet; import jxl.Workbook; import ...

  5. java Excel导入导出工具类

    本文章,导入导出依赖提前定义好的模板 package com.shareworx.yjwy.utils; import java.io.File; import java.io.FileInputSt ...

  6. java简易excel导入导出工具(封装POI)

    Octopus 如何导入excel 如何导出excel github项目地址 Octopus Octopus 是一个简单的java excel导入导出工具. 如何导入excel 下面是一个excel文 ...

  7. Excel导入导出工具(简单、好用且轻量级的海量Excel文件导入导出解决方案.)

    Excel导入导出工具(简单.好用且轻量级的海量Excel文件导入导出解决方案.) 置顶 2019-09-07 16:47:10 $9420 阅读数 261更多 分类专栏: java   版权声明:本 ...

  8. Octopus——excel导入导出工具

    Octopus Octopus是一个简易的Excel导入导出工具.目前主要就两个功能: 导入:将excel中一行数据转换为指定的java对象,并通过指定的正则表达式检查合法性. 导出:按照给定的xml ...

  9. SpringBoot集成文件 - 集成POI之Excel导入导出

    Apache POI 是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程序对Microsoft Office格式档案读和写的功能.本文主要介绍通过Spr ...

随机推荐

  1. mysql双机热备实现方案

    一.概念 1.热备份和备份的区别 热备份指的是:High Available(HA)即高可用,而备份指的是Backup,数据备份的一种.这是两种不同的概念,应对的产品也是两种功能上完全不同的产品.热备 ...

  2. iOS组件化开发一本地环境配置(一)

    首先我们要使用pod支持组件化开发 解决CocoaPods慢的方案(gem和pod repo换源) gem换源 $ gem sources --remove https://rubygems.org/ ...

  3. kuangbin专题 专题一 简单搜索 Prime Path POJ - 3126

    题目链接:https://vjudge.net/problem/POJ-3126 题意:给你两个四位的素数N,M,每次改变N四位数中的其中一位,如果能经过有限次数的替换变成四位数M,那么求出最少替换次 ...

  4. JAVA面试题 启动线程是start()还是run()?为什么?

    面试官:请问启动线程是start()还是run()方法,能谈谈吗? 应聘者:start()方法 当用start()开始一个线程后,线程就进入就绪状态,使线程所代表的虚拟处理机处于可运行状态,这意味着它 ...

  5. 『开发技巧』Keras自定义对象(层、评价函数与损失)

    1.自定义层 对于简单.无状态的自定义操作,你也许可以通过 layers.core.Lambda 层来实现.但是对于那些包含了可训练权重的自定义层,你应该自己实现这种层. 这是一个 Keras2.0  ...

  6. Nginx代理和负载均衡实验

    一.构建两个tomcat容器并启动 [root@localhost bin]# ps -ef|grep tomcat root : pts/ :: /usr/bin/java -Djava.util. ...

  7. C++学习书籍推荐《More Exceptional C++》下载

    百度云及其他网盘下载地址:点我 编辑推荐 <More Exceptional C++:40个新的工程难题.编程疑问及解决方法(中文版)>作为广为人知的<Exceptional C++ ...

  8. spark 源码分析之十六 -- Spark内存存储剖析

    上篇spark 源码分析之十五 -- Spark内存管理剖析 讲解了Spark的内存管理机制,主要是MemoryManager的内容.跟Spark的内存管理机制最密切相关的就是内存存储,本篇文章主要介 ...

  9. [ERROR]:INST-07008: Oracle 主目录(O) 位置的验证失败。用户没有创建主目录/实例位置的权限

    安装weblogic12.1.3.0时,输入的安装命令是: 老是报这个错误. 百度半天好像没人报过这错……看来只有我这么粗心了…… 后来发现wls.rsp里面的Oracle_HOME指向目录错误,修改 ...

  10. 深入理解 JavaScript 面向对象

    我们在学习编程时,避免不了会接触一个概念,叫:面向对象编程(Object-oriented programming,缩写:oop) (不是搞对象那个对象哈),其实我们的编程方式,不止有面向对象,还有 ...