最近需要实现一个将几个Excel表格合并成一个的功能。原本是想用脚本语言来实现的(因为那样方便一些),但是后来考虑到自己对Python等脚本语言并不是特别熟悉,还是决定使用最熟悉的Java来完成这个功能,顺便借这个机会学习一下POI的使用,复习一下Java语言基础并准备明年三月的蓝桥杯。
不要问我这个IDEA死忠粉为什么改用Eclipse了,要问就问蓝桥杯的组织者们为什么规定必须要用这么难用的东西吧
Maven依赖
POI的Maven依赖可以直接从Maven中央仓库中找到。我这里使用了最新的4.1.1
版本。
org.apache.poi
poi
4.1.1
用Eclipse建立Maven项目以及Eclipse自动补全功能的配置也是一个坑,但是网上的教程很多,这里就不详细记录了。
Excel操作工具类的实现
为了方便之后对Excel的操作,将一些常用的方法封装成工具类以便调用。
根据文件路径返回Workbook对象
实际上就是打开一个Workbook。
/**
* 根据文件路径返回Workbook对象
*
* @param Excel文件的路径
*
* @author 江文
*/
private static HSSFWorkbook openWorkbook(String path) {
// 定义IO流和workbook对象备用
InputStream inputStream = null;
HSSFWorkbook workbook = null;
// 如果文件名为空
if (path == null || path.equals("")) {
throw new IllegalArgumentException("文件路径不能为空");
}
try {
// 通过IO操作打开该Excel文档
inputStream = new FileInputStream(path);
workbook = new HSSFWorkbook(inputStream);
} catch (FileNotFoundException e) {
System.out.println("找不到该文件");
e.printStackTrace();
} catch (EncryptedDocumentException e) {
System.out.println("该Excel文档有密码,请移除密码后再次尝试");
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return workbook;
}
打开某个Excel文件的一个Sheet
用到了上面那个打开Workbook的方法。
/**
* 根据文件名和sheet编号返回一个sheet
* sheet编号如果为null则是打开全部的sheet,但是这个功能所涉及到的Excel文件都只有一个sheet,所以为null就直接抛异常
*
* @param path 文件路径
* @param sheetNo sheet的编号,从0开始
*
* @author 江文
*/
public static HSSFSheet getStringByPathAndSheet(String path, Integer sheetNo) throws Exception {
// 打开Workbook
HSSFWorkbook workbook = openWorkbook(path);
// 打开失败(文件不存在或者openWorkbook方法出现异常)
if (workbook == null) {
throw new Exception("Excel文件打开失败");
} else if (sheetNo == null) {
throw new IllegalArgumentException("sheet编号不能为空");
} else {
// 文件打开成功,sheet编号也不为空,就直接返回这个sheet
HSSFSheet sheet = workbook.getSheetAt(sheetNo);
return sheet;
}
}
根据行和列返回单元格的内容
这里涉及到了单元格类型的问题。
POI定义单元格共有七种类型,如下(可以在CellType类中获取到):
/**
* Unknown type, used to represent a state prior to initialization or the
* lack of a concrete type.
* For internal use only.
*/
@Internal(since="POI 3.15 beta 3")
_NONE(-1),
/**
* Numeric cell type (whole numbers, fractional numbers, dates)
*/
NUMERIC(0),
/** String (text) cell type */
STRING(1),
/**
* Formula cell type
* @see FormulaType
*/
FORMULA(2),
/**
* Blank cell type
*/
BLANK(3),
/**
* Boolean cell type
*/
BOOLEAN(4),
/**
* Error cell type
* @see FormulaError
*/
ERROR(5);
而最后我们需要的是String类型的返回值,所以要根据这五种情况分别作不同的处理:
/**
* 读取sheet中某一行某一列的单元格内容并返回
*
* @param sheet 要读取的sheet
* @param rownum 需要读取的单元格所在的行
* @param cellnum 需要读取的单元格所在的列
*
* @author 江文
*/
public String getContentStringByRowAndCellNum(HSSFSheet sheet, Integer rownum, Integer cellnum) {
// 根据数值获取row
HSSFRow row = sheet.getRow(rownum);
// 从row中获取cell
HSSFCell cell = row.getCell(cellnum);
// 根据单元格的不同,读取单元格的值,但是最后统一返回String类型
// value变量存储返回值,默认为空,以便处理
String value = "";
// 根据单元格类型不同作不同处理
switch (cell.getCellType()) {
case _NONE:
// 未知,返回字符串UNKNOWN
value = "UNKNOWN";
return value;
case NUMERIC:
// 数字型,转换成String再返回
value = String.valueOf(cell.getNumericCellValue());
return value;
case STRING:
// 字符串型,直接返回
value = cell.getStringCellValue();
return value;
case FORMULA:
// 公式型,直接返回
value = cell.getCellFormula();
return value;
case BLANK:
// 空,返回空字符串
return value;
case BOOLEAN:
// 布尔型,返回true或者是false
value = String.valueOf(cell.getBooleanCellValue());
return value;
case ERROR:
// 错误类型,返回字符串ERROR
value = "ERROR";
return value;
default:
// 其他情况,也返回空字符串
return value;
} // switch
} // getContentStringByRowAndCellNum
}
对getContentStringByRowAndCellNum()方法进行测试
因为这个功能比较复杂,为了保证最后不会一下出现很多问题,顺便也学学JUnit,于是对getContentStringByRowAndCellNum()方法写了一个单元测试。
实际上也顺便测试了另外两个方法。
自带的JUnit是3.8版本,太旧了,于是从Maven中央仓库中找到新版本的JUnit并修改pom.xml
文件中JUnit相关的依赖,这里我用了4.12
版本:
junit
junit
4.12
test
在项目文件夹下新建一个Excel
文件夹,放入一个test.xls
,它的E2单元格中有一个数据:“4399”。
Maven的测试类命名模式如下:
**/Test*.java
**/*Test.java
**/*TestCase.java
按上述模式命名的类, 使用mvn test
命令就能自动运行这些类。
建立ExcelUtilsTest.java
文件:
package site.wendev.excelMerge;
import static org.junit.Assert.assertEquals;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.junit.Test;
import site.wendev.excelMerge.utils.ExcelUtils;
public class ExcelUtilsTest {
// 预期结果
String exceptedString = "4399";
// 这个必须使用绝对路径否则会报错,使用~也会提示找不到文件
String filePath = "/home/jiangwen/eclipse-workspace/excelMerge/Excel/test.xls";
// 预期结果位于的行数
Integer rownum = 1;
// 预期结果位于的列数
Integer cellnum = 4;
/**
* 测试getContentStringByRowAndCellNum方法
* 实际上也是顺便测试了getSheetByPathAndSheet方法
* (因为那两个不成功的话,getContentByRowAndCellNum也不会成功)
*
* @author 江文
*/
@Test
public void testGetString() {
HSSFSheet sheet = null;
try {
sheet = ExcelUtils.getSheetByPathAndSheet(filePath, 0);
} catch (Exception e) {
e.printStackTrace();
}
assertEquals(exceptedString, ExcelUtils.getContentStringByRowAndCellNum(sheet, rownum, cellnum));
}
}
在项目文件夹下执行mvn test
就可以进行测试了。
(Maven提示我运行了两个测试,一个是我刚刚写好的这个,另一个是自带的,暂时不用管它)