Java处理Excel导入的解决思路和方案
我们要处理导入的excel数据,这个是典型的信息技术处理。
所以按照惯有模式分为如下三步:
1.将数据录入到计算机
2.校验和格式化数据
3.处理这些数据
注意步骤一和步骤2,3可以同时进行的
我们将步骤一的输出 抽象出对应的数据结构,然后步骤2和3直接使用这些数据结构处理就可以了.
数据录入到计算机这个过程 方法比较多,也是处理的技术难点细节
我们可以扫描录入,使用流来读取文件等等一系列方法。
当前我们处理的是excel文件,因此要采用流来处理
读取excel的方法
和excel打交道肯定少不了这个POI组件了
我们要这个POI的jar包
Workbook ,Sheet , Row , Cell 这几个就是基本的处理对象
1.创建 Workbook
使用 其构造函数即可
public HSSFWorkbook(InputStream s) throws IOException {
this(s,true);
}
2.得到sheet
public static Sheet getSheet(Workbook work,int sheetIndex) throws Exception{
Sheet sheet = null;
if(work.getNumberOfSheets() >= 0){
sheet = work.getSheetAt(sheetIndex);
}
return sheet;
}
3.得到Row
public static Row getRow(Sheet sheet,int rowIndex) throws Exception{
return sheet.getRow(rowIndex);
}
4.得到Cell
使用 Row的 迭代器模式
Iterator<Cell> cellIterator();
因此可以如下访问:
for (Cell cell : row) {
}
或者使用
int getRowNum();
Cell getCell(int cellnum);
来进行也可以
通过上面的方法我们就可以定位到一个excel的具体的一个sheet和具体的一行的一个单元格了
将excel读取到的List数据放入到我们设计的数据结构中
接下来就是要将读取的excel数据放入到我们设计的数据结构中了
这里我们可以使用 注解+bean的方式 或者 将字段属性配置在xml中也可以
这里使用 注解的方式来进行.
考虑到放入的数据要 分类型 , 校验 , 格式化等
因此我们的注解可以从上面几个方面进行入手
1.创建注解
例如:
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelField {
/**
* 属性的标题名称
*/
String title();
/**
* 在excel的顺
*/
int colum() default 9999;
Class claz();
int maxLength() ;
}
2.解析注解方法
创建的注解将来标记我们对应的 数据vo中的字段
因此我们还需要将单元格的数据和vo中的字段类型进行绑定,因此需要一个解析注解的工具
我们输入的是一个bean的class对象
然后使用反射来解析这个class,注意我们加入了父类的处理
List<Field> fields = new ArrayList<>();
for (Class clazz = clz; clazz != Object.class; clazz = clazz.getSuperclass()) {
fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
}
for (Field field : fields) {
// 是否使用ExcelField注解
if (field.isAnnotationPresent(ExcelField.class)) {
ExcelField er = field.getAnnotation(ExcelField.class);
}
}
3.优化
通过上面的代码我们可以得到一个Field对应的注解
但是为了后续处理方便,我们在将数据存入到我们设计的数据结构中的时候我们其实还需要反射来获取这个 对象的某一个field,然后再获取它的注解
然后再使用注解中的colum的值和 excel列的colum的值比对,对应的值放入到对应的 field中
为了简化这个步骤,我们上面的处理可以多加一个 处理。
注意到excel每一行的值其实呢就是表头的对应的字段名的值
因此我们可以模拟一个表头ExcelHeader
这个类里面有
public class ExcelHeader implements Comparable<ExcelHeader> {
/**
* excel的标题名称
*/
private String title;
/**
* 每一个标题的顺
*/
private int order;
/**
* 字段变量名
*/
private String filed;
}
这样我们可以确定一个字段在第几个位置。
解析的时候我们解析 表头第一行的标题 然后可以得到一个 Map<Integer, ExcelHeader> ,也就是通过excel中单元格所在的列的号
来得到这个单元格的数据的变量名。
当我们处理其余数据行的时候,通过当前单元格的列号从上面的map中取得对应的变量名然后我们使用反射将单元格的值给塞到bean中属性为变量名的字段中.
这样做比我们现场再进行 bean的解析会避免掉多层的循环处理。
如果不这样做我们 还要在循环单元格的内部里面 再次循环 bean的fields然后做这个解析,效率就会很慢的。
可见 Map是一个 去掉循环层次的好方法。如果我们嵌套的循环只是为了去寻找某个值的时候,可以将嵌套循环中对应的集合的数据存入到Map中
然后我们就可以削减掉循环层次 。 典型的空间换时间方法.
处理得到的数据
经过上面的过程,数据已经放入到了我们的设计的数据结构中了
我们可以继续增加更加共通的处理方法。
因为注解中含有校验的信息和格式化的信息。
这种做法前面 介绍注解的使用的文章有过介绍。可以参考.
下面是上面涉及到的代码的文件:
ExcelField.java ExcelHeader.java ImportExcelHeaderUtil.java ImportExcelUtil.java
1. ExcelField.java
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelField {
/**
* 属性的标题名称
*/
String title();
/**
* 在excel的顺
*/
int colum() default 9999;
Class claz();
int maxLength() ;
}
2. ExcelHeader.java
public class ExcelHeader implements Comparable<ExcelHeader> {
/**
* excel的标题名称
*/
private String title;
/**
* 每一个标题的顺
*/
private int order;
/**
* 变量名
*/
private String filed;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getOrder() {
return order;
}
public void setOrder(int order) {
this.order = order;
}
public String getFiled() {
return filed;
}
public void setFiled(String filed) {
this.filed = filed;
}
public int compareTo(ExcelHeader o) {
return order - o.order;
}
public ExcelHeader(String title, int order, String filed) {
super();
this.title = title;
this.order = order;
this.filed = filed;
}
}
3. ImportExcelHeaderUtil.java
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import java.lang.reflect.Field;
import java.util.*;
public class ImportExcelHeaderUtil {
/**
* <p>根据JAVA对象注解获取Excel表头信息</p></br>
*/
public static List<ExcelHeader> getHeaderList(Class clz) {
List<ExcelHeader> headers = new ArrayList<>();
List<Field> fields = new ArrayList<>();
for (Class clazz = clz; clazz != Object.class; clazz = clazz.getSuperclass()) {
fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
}
for (Field field : fields) {
// 是否使用ExcelField注解
if (field.isAnnotationPresent(ExcelField.class)) {
ExcelField er = field.getAnnotation(ExcelField.class);
headers.add(new ExcelHeader(er.title(), er.colum(), field.getName()));
}
}
Collections.sort(headers);
return headers;
}
public static Map<Integer, ExcelHeader> getHeaderMap(Row titleRow, Class clz) {
List<ExcelHeader> headers = getHeaderList(clz);
Map<Integer, ExcelHeader> maps = new HashMap<>();
for (Cell c : titleRow) {
String title = c.getStringCellValue();
for (ExcelHeader eh : headers) {
if (eh.getTitle().equals(title.trim())) {
maps.put(c.getColumnIndex(), eh);
break;
}
}
}
return maps;
}
}
4.ImportExcelUtil.java
import org.apache.commons.beanutils.BeanUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class ImportExcelUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(ImportExcelUtil.class);
private final static String excel2003L =".xls"; //2003- 版本的excel
private final static String excel2007U =".xlsx"; //2007+ 版本的excel
/**
* 描述:获取IO流中的数据,组装成List<T>对象
* 获取第0个sheet中,从0行到最后一行的数据,默认第0行为title
* @param in 导入文件转换的流
* @param fileName 文件名称
* @param beanClass 转换成beanClass
* @return
* @throws IOException
*/
public static <T> List<T> getDefaultDataListByExcel(InputStream in,String fileName,Class<T> beanClass) throws Exception{
return getDataListByExcel(in,fileName,beanClass,0,0,Integer.MAX_VALUE);
}
/**
* 描述:获取IO流中的数据,组装成List<T>对象
* 获取第sheetIndex个sheet中,从0行到最后一行
* @param in 导入文件转换的流
* @param fileName 文件名称
* @param beanClass 转换成beanClass
* @param sheetIndex 要获取的sheet的index值
* @return
* @throws IOException
*/
public static <T> List<T> getFullDataListByExcel(InputStream in,String fileName,Class<T> beanClass,int sheetIndex) throws Exception{
return getDataListByExcel(in,fileName,beanClass,sheetIndex,0,Integer.MAX_VALUE);
}
/**
* 描述:获取IO流中的数据,组装成List<T>对象
* @param in 导入文件转换的流
* @param fileName 文件名称
* @param beanClass 转换成beanClass
* @param sheetIndex 要获取的sheet的index值
* @return
* @throws IOException
*/
public static <T> List<T> getDataListByExcel(InputStream in,String fileName,Class<T> beanClass,int sheetIndex,int beginRowNum,int endRownum) throws Exception{
//创建Excel工作薄
Workbook work = getWorkbook(in,fileName);
if(null == work){
throw new Exception("创建Excel工作薄为空!");
}
//获取响应的sheet
Sheet sheet = getSheet(work,sheetIndex);
//获取sheet的表头
Row titleRow = getRow(sheet,beginRowNum);
//返回的list<T>结果
List<T> list = getPartDataListByExcel(titleRow,sheet,beanClass,beginRowNum,endRownum);
work.close();
return list;
}
public static <T> List<T> getPartDataListByExcel(Row titleRow,Sheet sheet,Class<T> beanClass,int beginRowNum,int endRownum) throws Exception{
//返回的list<T>结果
List<T> list = new ArrayList<T>();
//获取导入文件对应的bean的标题map
Map<Integer, ExcelHeader> maps = ImportExcelHeaderUtil.getHeaderMap(titleRow, beanClass);
//遍历当前sheet中的所有行
for (int j = beginRowNum+1; j <= endRownum; j++) {
Row row = sheet.getRow(j);
//新建一个T类
T bean = beanClass.newInstance();
//遍历所有的列
for (Cell cell : row) {
int ci = cell.getColumnIndex();
ExcelHeader header = maps.get(ci);
if (null == header)
continue;
String filed = header.getFiled();
Object value = getCellValue(cell);
BeanUtils.copyProperty(bean, filed, value);
}
list.add(bean);
}
//work.close();
return list;
}
/**
* 描述:根据sheetIndex,得到响应的sheet
* @param work,fileName
* @return Sheet
* @throws Exception
*/
public static Sheet getSheet(Workbook work,int sheetIndex) throws Exception{
Sheet sheet = null;
if(work.getNumberOfSheets() >= 0){
sheet = work.getSheetAt(sheetIndex);
}
return sheet;
}
/**
* 描述:根据rowIndex,得到响应的row
* @param sheet,rowIndex
* @return Sheet
* @throws Exception
*/
public static Row getRow(Sheet sheet,int rowIndex) throws Exception{
return sheet.getRow(rowIndex);
}
/**
* 描述:根据文件后缀,自适应上传文件的版本
* @param inStr,fileName
* @return
* @throws Exception
*/
public static Workbook getWorkbook(InputStream inStr,String fileName) throws Exception{
Workbook wb = null;
String fileType = fileName.substring(fileName.lastIndexOf("."));
if(excel2003L.equals(fileType)){
wb = new HSSFWorkbook(inStr); //2003-
}else if(excel2007U.equals(fileType)){
wb = new XSSFWorkbook(inStr); //2007+
}else{
throw new Exception("解析的文件格式有误!");
}
return wb;
}
/**
* 描述:对表格中数值进行格式化
* @param cell
* @return
*/
private static Object getCellValue(Cell cell){
Object value = null;
DecimalFormat df = new DecimalFormat("0"); //格式化number String字符
SimpleDateFormat sdf = new SimpleDateFormat("yyy-MM-dd"); //日期格式化
DecimalFormat df2 = new DecimalFormat("0.00"); //格式化数字
switch (cell.getCellType()) {
case Cell.CELL_TYPE_STRING://字符串型
value = cell.getRichStringCellValue().getString();
break;
case Cell.CELL_TYPE_NUMERIC://日期或数字
if("General".equals(cell.getCellStyle().getDataFormatString())){
value = df.format(cell.getNumericCellValue());
}else if("m/d/yy".equals(cell.getCellStyle().getDataFormatString())){
value = sdf.format(cell.getDateCellValue());
}else{
value = df2.format(cell.getNumericCellValue());
}
break;
case Cell.CELL_TYPE_BOOLEAN://布尔型
value = cell.getBooleanCellValue();
break;
case Cell.CELL_TYPE_BLANK://空字符串
value = "";
break;
case Cell.CELL_TYPE_FORMULA://公式
CellStyle style = cell.getCellStyle();
value = cell.getNumericCellValue();
break;
default:
break;
}
return value;
}
public static void main(String[] args) throws FileNotFoundException {
String url = "/usr/kaifeng/test.xlsx";
File file = new File(url);
FileInputStream fileInputStream = new FileInputStream(file);
try {
List<DataVo> list = getDataListByExcel(fileInputStream,"redeem.xlsx", DataVo.class,0,0,2);
} catch (Exception e) {
e.printStackTrace();
}
}
}