[自制]Excel读写工具——sv-excel介绍

一、综述

在项目中,经常会遇到Excel文件的写入和读取等功能,但是使用POI原生的写法,有些时候较为繁琐,尤其是有合并单元格的情况。因此,我写了一个Excel读写工具,下面将我的设计向大家分享一下。

二、使用jar包

使用的是poi-3.15版本
这里写图片描述

三、工作簿写入功能设计

封装了三个类,SVWorkbook、SVSheet和SVCell,分别对应POI的Workbook、Sheet以及Cell。

1. SVWorkbook

封装了创建工作簿,创建表格页,关闭工作簿等方法,同时可以使用getWorkBook方法获取POI的工作簿对象,进行POI原生代码的操作。

package com.sv.excel.writer;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

/**
 * sv工作簿
 * @className SVWorkbook.java
 * @author 银发Victorique
 * @email 823245670@qq.com
 * @date 2017年1月23日
 */
public class SVWorkbook {

    private Workbook workbook;//POI工作簿对象

    private FileOutputStream fos;//文件输出流

    /**
     * 构造函数,根据路径和文件名创建工作簿和输出流
     * @param path 文件路径(不包含文件名称),例如:D:\sv\excel\demo\
     * @param fileName 文件名,例如:svexceldemo.xls/svexceldemo.xlsx
     */
    public SVWorkbook(String path, String fileName){
        //判断文件类型,创建工作簿对象
        if(fileName.endsWith(".xls")){
            workbook = new HSSFWorkbook();
        }else if(fileName.endsWith(".xlsx")){
            workbook = new XSSFWorkbook();
        }else{
            return;
        }
        //根据文件路径和文件名称,创建文件输出流
        try {
            fos = new FileOutputStream(path+"/"+fileName);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 创建sv表格页,为了更方便的进行行和单元格的添加,创建表格页时,需要填写最大列数,操作时只需添加单元格,换行等操作自动完成
     * @author 银发Victorique
     * @email 823245670@qq.com
     * @param columns 表格的最大列数
     * @return sv表格页对象
     */
    public SVSheet createSheet(int columns){
        return new SVSheet(this.workbook, this.workbook.createSheet(), columns);
    }

    /**
     * 关闭,将创建的工作簿内容写入文件,同时关闭文件输出流
     * 注:完成创建全部内容后,必须执行此方法,否则会造成数据没有写入,流没有关闭等严重问题
     * @author 银发Victorique
     * @email 823245670@qq.com
     */
    public void close(){
        if(workbook != null){
            //写入内容
            try {
                workbook.write(fos);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if(fos != null){
                    //关闭文件流
                    try {
                        fos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     * 获取POI工作簿对象,此方法用于直接获取POI工作簿对象,使用POI原生的方法进行操作
     * @author 银发Victorique
     * @email 823245670@qq.com
     * @return POI工作簿对象
     */
    public Workbook getWorkBook(){
        return this.workbook;
    }

}
2. SVSheet

封装了添加单元格,以及getSheet方法。我的设计理念是,不要每次都创建行,而是通过游标在方法内部进行行的创建。为了实现这一想法,在创建sheet的时候,需要指定数据的总列数(通常情况列数总是有办法算出最大值的),这样每次添加单元格的时候,进行验证当前列游标是否到达总列数,在确定是否自动创建行。当然我还提供了addRow的方法,更加的便利。

package com.sv.excel.writer;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CreationHelper;
import org.apache.poi.ss.usermodel.RichTextString;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;

/**
 * sv表格页
 * @className SVSheet.java
 * @author 银发Victorique
 * @email 823245670@qq.com
 * @date 2017年1月23日
 */
public class SVSheet {

    private Workbook wb;//POI工作簿对象
    private Sheet sheet;//POI表格页对象
    private Row row;//POI行对象

    private int columns;//表格页总列数,用于自动换行

    private int rownum = 0;//行游标,用于记录当前插入单元格的行数
    private int colnum = 0;//列游标,用于记录当前插入单元格的列数
    private List<String> mergeBuffer = new ArrayList<>();//用于保存进行过合并单元格的单元格坐标,数据格式:行-列

    /**
     * 构造函数,用于初始化工作簿、表格页、总列数
     * @param wb POI工作簿对象
     * @param sheet POI表格页对象
     * @param columns 表格页最大列数
     */
    protected SVSheet(Workbook wb, Sheet sheet, int columns){
        this.wb = wb;
        this.sheet = sheet;
        this.columns = columns;
    }

    /**
     * 添加行
     * @author 银发Victorique
     * @email 823245670@qq.com
     */
    public void addRow(){
        row = this.sheet.createRow(rownum);
        rownum ++;
        colnum = 0;
    }

    /**
     * 添加sv单元格
     * @author 银发Victorique
     * @email 823245670@qq.com
     * @param svCell
     */
    public void addCell(SVCell svCell){
        //判断单元格是否被合并,并调整游标位置
        String key = String.valueOf(this.rownum)+"-"+String.valueOf(this.colnum);
        while(mergeBuffer.contains(key)){
            colnum ++;
            key = String.valueOf(this.rownum)+"-"+String.valueOf(this.colnum);
        }
        //判断是否需要创建行
        if(row == null || colnum >= columns){
            row = this.sheet.createRow(rownum);
            rownum ++;
            colnum = 0;
        }
        if(svCell.getRowspan() > 1 || svCell.getColspan() >1){
            //合并单元格
            if(row != null){
                sheet.addMergedRegion(new CellRangeAddress(rownum-1, rownum-1+svCell.getRowspan()-1, colnum, colnum+svCell.getColspan()-1));
            }else{
                sheet.addMergedRegion(new CellRangeAddress(rownum, rownum+svCell.getRowspan()-1, colnum, colnum+svCell.getColspan()-1));
            }
            //保存合并的单元格坐标
            for(int i=0;i<svCell.getRowspan();i++){
                for(int j=0;j<svCell.getColspan();j++){
                    String value = String.valueOf(this.rownum+i)+"-"+String.valueOf(this.colnum+j);
                    mergeBuffer.add(value);
                }
            }
        }
        //创建单元格
        Cell cell = row.createCell(colnum);
        CellStyle cellStyle = null;
        //创建单元格样式
        if(svCell.getCellStyle() == null){
            cellStyle = wb.createCellStyle();;
        }else{
            cellStyle = svCell.getCellStyle();
        }
        //获取单元格的值
        Object value = svCell.getCellValue();
        //单元格赋值
        if(value instanceof Double){
            cell.setCellValue((Double)value);
        }else if(value instanceof Date){
            CreationHelper createHelper = wb.getCreationHelper();
            cellStyle.setDataFormat(createHelper.createDataFormat().getFormat("m/d/yy h:mm"));
            cell.setCellValue((Date)value);
        }else if(value instanceof Calendar){
            CreationHelper createHelper = wb.getCreationHelper();
            cellStyle.setDataFormat(createHelper.createDataFormat().getFormat("m/d/yy h:mm"));
            cell.setCellValue((Calendar)value);
        }else if(value instanceof RichTextString){
            cell.setCellValue((RichTextString)value);
        }else if(value instanceof String){
            cell.setCellValue((String)value);
        }
        //添加单元格样式
        cell.setCellStyle(cellStyle);
        colnum ++;
        //设置MyCell对象
        svCell.setCell(cell);
    }

    /**
     * 获取POI表格页对象,此方法用于直接获取POI表格页对象,使用POI原生的方法进行操作
     * @author 银发Victorique
     * @email 823245670@qq.com
     * @return
     */
    public Sheet getSheet(){
        return this.sheet;
    }   
}
3. SVCell

封装了添加跨行、跨列属性,设置单元格样式等方法,以及getCell方法

package com.sv.excel.writer;

import java.util.Calendar;
import java.util.Date;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.RichTextString;

/**
 * sv单元格
 * @className SVCell.java
 * @author 银发Victorique
 * @email 823245670@qq.com
 * @date 2017年1月23日
 */
public class SVCell {

    private Cell cell;//POI单元格对象

    private int colspan = 1;//跨列
    private int rowspan = 1;//跨行

    private Double doubleValue;//双精度浮点数类型数据
    private Date dateValue;//日期类型数据
    private Calendar calendarValue;//日历类型数据
    private RichTextString richTextStringValue;//富文本类型数据
    private String stringValue;//字符串类型数据

    private CellStyle cellStyle;//POI单元格样式

    /**
     * 设置双精度浮点数类型的值
     * @author 银发Victorique
     * @email 823245670@qq.com
     * @param doubleValue
     * @return sv单元格对象
     */
    public SVCell setCellValue(double doubleValue){
        this.resetValues();
        this.doubleValue = doubleValue;
        return this;
    }

    /**
     * 设置日期类型的值
     * @author 银发Victorique
     * @email 823245670@qq.com
     * @param dateValue
     * @return sv单元格对象
     */
    public SVCell setCellValue(Date dateValue){
        this.resetValues();
        this.dateValue = dateValue;
        return this;
    }

    /**
     * 设置日历格式的值
     * @author 银发Victorique
     * @email 823245670@qq.com
     * @param calendarValue
     * @return sv单元格对象
     */
    public SVCell setCellValue(Calendar calendarValue){
        this.resetValues();
        this.calendarValue = calendarValue;
        return this;
    }

    /**
     * 设置POI富文本类型的值
     * @author 银发Victorique
     * @email 823245670@qq.com
     * @param richTextStringValue
     * @return sv单元格对象
     */
    public SVCell setCellValue(RichTextString richTextStringValue){
        this.resetValues();
        this.richTextStringValue = richTextStringValue;
        return this;
    }

    /**
     * 设置字符串类型的值
     * @author 银发Victorique
     * @email 823245670@qq.com
     * @param stringValue
     * @return sv单元格对象
     */
    public SVCell setCellValue(String stringValue){
        this.resetValues();
        this.stringValue = stringValue;
        return this;
    }

    /**
     * 获取单元格的值
     * @author 银发Victorique
     * @email 823245670@qq.com
     * @return 返回不为null的值
     */
    public Object getCellValue(){
        if(doubleValue != null){
            return this.doubleValue;
        }else if(dateValue != null){
            return this.dateValue;
        }else if(calendarValue != null){
            return this.calendarValue;
        }else if(richTextStringValue != null){
            return this.richTextStringValue;
        }else if(stringValue != null){
            return this.stringValue;
        }else{
            return null;
        }
    }

    /**
     * 设置单元格样式
     * @author 银发Victorique
     * @email 823245670@qq.com
     * @param cellStyle POI单元格样式
     * @return sv单元格
     */
    public SVCell setCellStyle(CellStyle cellStyle){
        this.cellStyle = cellStyle;
        return this;
    }

    /**
     * 获取单元格样式
     * @author 银发Victorique
     * @email 823245670@qq.com
     * @return POI单元格样式
     */
    public CellStyle getCellStyle(){
        return this.cellStyle;
    }

    /**
     * 获取POI单元格对象,此方法用于直接获取POI单元格对象,使用POI原生的方法进行操作
     * @author 银发Victorique
     * @email 823245670@qq.com
     * @return POI单元格对象
     */
    public Cell getCell(){
        return this.cell;
    }

    /**
     * 设置跨列
     * @author 银发Victorique
     * @email 823245670@qq.com
     * @param colspan 跨列数量,默认为1
     * @return sv单元格
     */
    public SVCell setColspan(int colspan){
        this.colspan = colspan;
        return this;
    }

    /**
     * 设置跨行
     * @author 银发Victorique
     * @email 823245670@qq.com
     * @param rowspan 跨行数量,默认为1
     * @return sv单元格
     */
    public SVCell setRowspan(int rowspan){
        this.rowspan = rowspan;
        return this;
    }

    /**
     * 设置单元格
     * @author 银发Victorique
     * @email 823245670@qq.com
     * @param cell POI单元格
     */
    protected void setCell(Cell cell){
        this.cell = cell;
    }

    /**
     * 获取跨列
     * @author 银发Victorique
     * @email 823245670@qq.com
     * @return 跨列数
     */
    protected int getColspan(){
        return this.colspan;
    }

    /**
     * 获取跨行
     * @author 银发Victorique
     * @email 823245670@qq.com
     * @return 跨行数
     */
    protected int getRowspan(){
        return this.rowspan;
    }

    /**
     * 重置单元格的值
     * @author 银发Victorique
     * @email 823245670@qq.com
     */
    private void resetValues(){
        doubleValue = null;
        dateValue = null;
        calendarValue = null;
        richTextStringValue = null;
        stringValue = null;
    }
}
4. 测试代码
    SVWorkbook wb = new SVWorkbook("./", "workbook.xlsx");
    SVSheet sheet = wb.createSheet(4);

    SVCell cell = new SVCell();
    cell.setCellValue("test");
    sheet.addCell(cell);

    cell = new SVCell();
    cell.setCellValue(Calendar.getInstance());
    cell.setColspan(3);
    cell.setRowspan(2);
    sheet.addCell(cell);

    cell = new SVCell();
    cell.setCellValue(123.231);
    sheet.addCell(cell);

    wb.close();

四、工作簿读取功能设计

该功能一共实现了三种读出格式

1. 二维数组,值的数据类型为原始数据类型
/**
 * 获取单元格原始数据
 * @author 银发Victorique
 * @email 823245670@qq.com
 * @param cell 单元格
 * @return 原始数据
 */
@SuppressWarnings("deprecation")
private static Object getCellValue(Cell cell){
    if(cell == null){
        return null;
    }
    switch (cell.getCellTypeEnum()) {
    case STRING:
        return cell.getRichStringCellValue();
    case NUMERIC:
        if (DateUtil.isCellDateFormatted(cell)) {
            return cell.getDateCellValue();
        } else {
            return cell.getNumericCellValue();
        }
    case BOOLEAN:
        return cell.getBooleanCellValue();
    case FORMULA:
        return cell.getCellFormula();
    case BLANK:
        return null;
    default:
        return null;
    }
}
/**
 * 获取原始数据类型的数组
 * @author 银发Victorique
 * @email 823245670@qq.com
 * @param path 文件路径
 * @param filename 文件名称
 * @param sheetnum 表格页编号,从0开始
 * @return 表格数据数组
 */
public static Object[][] getOriginalArray(String path, String filename, int sheetnum){
    Object[][] returnArray = null;
    InputStream is = null;
    Workbook wb = null;
    try {
        is = new FileInputStream(path+"/"+filename);
        if(filename.endsWith(".xls")){
            wb = new HSSFWorkbook(is);
        }else if(filename.endsWith(".xlsx")){
            wb = new XSSFWorkbook(is);
        }else{
            return null;
        }
        int maxcol = 0;
        for(Row row : wb.getSheetAt(sheetnum)){
            if(row.getPhysicalNumberOfCells() > maxcol){
                maxcol = row.getPhysicalNumberOfCells();
            }
        }
        returnArray = new Object[wb.getSheetAt(sheetnum).getPhysicalNumberOfRows()][maxcol];
        for(int i=0; i<wb.getSheetAt(sheetnum).getPhysicalNumberOfRows(); i++){
            Row row = wb.getSheetAt(sheetnum).getRow(i);
            for(int j=0; j<maxcol; j++){
                Cell cell = row.getCell(j);
                if(cell != null){
                    returnArray[i][j] = getCellValue(cell);
                }else{
                    returnArray[i][j] = "";
                }
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if(wb != null){
                wb.close();
            }
        } catch (IOException e1) {
            e1.printStackTrace();
        } finally {
            if(is != null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    return returnArray;
}
2. List
/**
 * 获取List<Map<String,Object>>类型的表格数据
 * 要求导入的EXCEL文件有这样的格式:
 * 第一行为表头,必须全部为字符串型数据,否则会抛出异常
 * 之后的每一行的数据,都会以其对应的表头作为键存储在map中
 * 如果某一单元格对应的表头为空,则此单元格数据被舍弃
 * @author 银发Victorique
 * @email 823245670@qq.com
 * @param path 文件路径
 * @param filename 文件名称
 * @param sheetnum 表格页编号,从0开始
 * @return 表格页数据
 */
public static List<Map<String, Object>> getMapList(String path, String filename, int sheetnum){
    List<Map<String, Object>> returnList = new ArrayList<>();
    InputStream is = null;
    Workbook wb = null;
    try {
        is = new FileInputStream(path+"/"+filename);
        if(filename.endsWith(".xls")){
            wb = new HSSFWorkbook(is);
        }else if(filename.endsWith(".xlsx")){
            wb = new XSSFWorkbook(is);
        }else{
            return null;
        }
        Row firstRow = wb.getSheetAt(sheetnum).getRow(0);
        for(int i=1; i<wb.getSheetAt(sheetnum).getPhysicalNumberOfRows(); i++){
            Row row = wb.getSheetAt(sheetnum).getRow(i);
            Map<String, Object> map = new HashMap<>();
            for(int j=0; j<row.getPhysicalNumberOfCells(); j++){
                Cell cell = row.getCell(j);
                map.put(firstRow.getCell(j).getStringCellValue(), getCellValue(cell));
            }
            returnList.add(map);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if(wb != null){
                wb.close();
            }
        } catch (IOException e1) {
            e1.printStackTrace();
        } finally {
            if(is != null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    return returnList;
}
3. javabean list格式
package com.sv.excel.reader;

import java.util.Map;

/**
 * Excel抽象类,用于读取数据为对象集合
 * @className Excel.java
 * @author 银发Victorique
 * @email 823245670@qq.com
 * @date 2017年1月23日
 */
public abstract class Excel {

    /**
     * 获取存放表头-字段对应关系表
     * 数据格式为:{表头名称:字段名称}
     * 阅读器将根据该关系表给字段赋值
     * @author 银发Victorique
     * @email 823245670@qq.com
     * @return 表头-字段对应关系表
     */
    public abstract Map<String, String> getLabelField();

}
/**
 * 获取bean集合类型的表格页数据
 * 要求导入的EXCEL文件有这样的格式:
 * 第一行为表头,必须全部为字符串型数据,否则会抛出异常
 * 实例excel必须是抽象类Excel的子类的实例,并且getLabelField方法获取的Map需要与表头相对应,否则会被舍弃
 * 之后的每一行的数据,都会以其对应的表头和getLabelField获取的Map作为依据,存放到Bean的对应字段中
 * 如果某一单元格对应的表头为空,则此单元格数据被舍弃
 * @author 银发Victorique
 * @email 823245670@qq.com
 * @param path 文件路径
 * @param filename 文件名
 * @param sheetnum 表格页编号,从0开始
 * @param excel 抽象类Excel的子类的实例
 * @return 表格页数据
 */
public static <T extends Excel> List<T> getBeanList(String path, String filename, int sheetnum, T excel){
    List<T> returnList = new ArrayList<>();
    Map<String, String> lfMap = excel.getLabelField();
    InputStream is = null;
    Workbook wb = null;
    try {
        is = new FileInputStream(path+"/"+filename);
        if(filename.endsWith(".xls")){
            wb = new HSSFWorkbook(is);
        }else if(filename.endsWith(".xlsx")){
            wb = new XSSFWorkbook(is);
        }else{
            return null;
        }
        Row firstRow = wb.getSheetAt(sheetnum).getRow(0);
        for(int i=1; i<wb.getSheetAt(sheetnum).getPhysicalNumberOfRows(); i++){
            Row row = wb.getSheetAt(sheetnum).getRow(i);
            @SuppressWarnings("unchecked")
            T instance = (T) excel.getClass().getConstructor().newInstance();
            for(int j=0; j<row.getPhysicalNumberOfCells(); j++){
                Cell cell = row.getCell(j);
                String field = lfMap.get(firstRow.getCell(j).getStringCellValue());
                if(field != null){
                    Object value = getCellValue(cell);
                    Class<?> clazz = null;{
                        if(value instanceof RichTextString){
                            clazz = String.class;
                            value = ((RichTextString)value).getString();
                        }else{
                            clazz = value.getClass();
                        }
                    }
                    try {
                        Method method = instance.getClass().getMethod("set"+field.substring(0,1).toUpperCase()+field.substring(1), clazz);
                        method.invoke(instance, value);
                    } catch (Exception e) {
                        if(value instanceof Double){
                            try {
                                Method method = instance.getClass().getMethod("set"+field.substring(0,1).toUpperCase()+field.substring(1), Integer.class);
                                method.invoke(instance, ((Double)value).intValue());
                            } catch(Exception e1){
                                Method method = instance.getClass().getMethod("set"+field.substring(0,1).toUpperCase()+field.substring(1), BigDecimal.class);
                                BigDecimal bd = new BigDecimal((Double)value);
                                method.invoke(instance, bd);
                            }
                        }else{
                            e.printStackTrace();
                        }
                    }
                }
            }
            returnList.add(instance);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (SecurityException e) {
        e.printStackTrace();
    } finally {
        try {
            if(wb != null){
                wb.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(is != null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    return returnList;
}
4. 测试代码
//getOriginalArray方法测试
Object[][] rows = SVExcelReader.getOriginalArray("./", "workbook.xlsx", 0);
for (Object[] cols : rows) {
    for (Object obj : cols) {
        System.out.print(obj+"\t");
    }
    System.out.println();
}

//getMapList方法测试
List<Map<String, Object>> rows = SVExcelReader.getMapList("./", "workbook.xlsx", 0);
for (Map<String, Object> cols : rows) {
    System.out.println(cols);
}

//getBeanList方法测试
List<Bean> rows = SVExcelReader.getBeanList("./", "workbook.xlsx", 0, new Bean());
for (Bean bean : rows) {
    System.out.println(bean);
}

五、项目分享

github:https://github.com/SVictorique/sv-excel

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值