【Java】用 poi-3.15.jar 实现对excel表格的读和写 (尝试完成从excel内取出对象list和存入某对象至excel的工具)
因为舍友项目的某种需求,需要将数据存储在excel表格中,我就下载并尝试使用了 poi-3.15 ,下面是我自己在学习中的一点所得
poi-3.15 下载地址:https://mvnrepository.com/artifact/org.apache.poi/poi/3.15
这是一个简单的excel表格,第一行给出了 表头
实际上poi包的基本操作也是对 行 列 和 cell 的操作
目录
建立
来看一个建立表的例子
//创建一个hssfWorkbook与一个excel对应
HSSFWorkbook hssfWorkbook = new HSSFWorkbook();
// 创建对应的sheet
HSSFSheet hssfSheet = hssfWorkbook.createSheet("message");
// 添加表格第0行
HSSFRow row = hssfSheet.createRow(0);
// 设置单元格,设置表头
HSSFCell cell = row.createCell(0);
cell.setCellValue("id");
cell = row.createCell(1);
cell.setCellValue("password");
这样就建立好一个表了,hssfSheet就是这个excel表格的一个“面”,叫“message”
通过这个面hssfSheet可以新建一个给定行号的行row
通过行可以构建一个给定列号的单元格cell
即,cell是某面某行某列的单元格
所有的操作都是基于单元格来说的
切记!保存操作:
FileOutputStream fos;
try {
// 输出流,新建excel表(路径文件名)
fos = new FileOutputStream("message.xls");
// 将hssfWorkbook对应的excel表格写入
hssfWorkbook.write(fos);
// 关闭输出流
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
结果:
写入
// 写 的前提是有这个excel表,因此要【取】也要【存】
// 故文件输入流和输出流都必须有
FileInputStream fis = null;
FileOutputStream fos = null;
try {
// 找到文件
fis = new FileInputStream("message.xls");
HSSFWorkbook hssfWorkbook = new HSSFWorkbook(fis);
HSSFSheet hssfSheet = hssfWorkbook.getSheet("message");
if (hssfSheet == null) {
// 未找到的操作
}
// 定位最后一行位置并新建行
int saveIndex = hssfSheet.getLastRowNum() + 1;
HSSFRow row = hssfSheet.createRow(saveIndex);
// 创建cell并填入
row.createCell(0).setCellValue("003");
row.createCell(1).setCellValue("003300");
// 写入文件
fos = new FileOutputStream("message.xls");
hssfWorkbook.write(fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} finally {
fis.close();
fos.close();
}
结果:
读
FileInputStream fis = null;
try {
fis = new FileInputStream("message.xls");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
HSSFWorkbook hssfWorkbook;
try {
hssfWorkbook = new HSSFWorkbook(fis);
HSSFSheet hssfSheet = hssfWorkbook.getSheet("message");
HSSFRow row = hssfSheet.getRow(1);
System.out.println(row.getCell(0));
System.out.println(row.getCell(1));
} catch (IOException e) {
e.printStackTrace();
}
运行结果:
这就是poi最简单的用法了,思考这样一个问题:实际编程中我们需要将一个对象写入excel表,或将excel表中的每条数据(每一行)读出来形成一个list,该如何完成?反射机制!
自己尝试的做了做,虽然只是局限于“竖”形表且只有一个“面”,但也好歹完成了读取list和写入对象的操作
问题:
1.如何处理HssfSheet名字的问题?这里我用类名字作为其HssfSheet名
2.如何处理类成员和表头的对应关系?采用注解方式
自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Cell {
String value();
}
使用该注解时必须注明其对应excel表的哪一列
从表中获取对象list
public static <T> List<T> getList(String excelPath, Class<?> klass) throws Exception {
List<T> resultList = new ArrayList<>();
FileInputStream fis = null;
try {
// 由参数传来的文件路径获取输入流,得到“面”sheet,得到表头
fis = new FileInputStream(excelPath);
HSSFWorkbook hssfWorkbook = new HSSFWorkbook(fis);
HSSFSheet hssfSheet = hssfWorkbook.getSheet(klass.getSimpleName());
HSSFRow headRow = hssfSheet.getRow(0);
// 检查传来的类的成员数是否等于表的列数
int index = 0;
HSSFCell cell = null;
Field[] fields = klass.getDeclaredFields();
do {
cell = headRow.getCell(index++);
} while (cell != null);
if (fields.length != --index) {
throw new Exception(excelPath + "列数与类" + klass + "成员数不匹配");
}
// 从第一行开始遍历
// 每遍历一行,新建一个该类的对象
index = 1;
HSSFRow row = null;
do {
row = hssfSheet.getRow(index++);
if (row == null) {
break;
}
int colIndex = 0;
Object object = klass.newInstance();
// 遍历该行的每一个cell,找到与之对应的成员(带有cell注解,且value为列名字)
// 将其设置到对象成员中
do {
cell = row.getCell(colIndex);
if (cell == null) {
break;
}
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
Cell fieldCell = field.getAnnotation(Cell.class);
if (headRow.getCell(colIndex).getStringCellValue()
.equals(fieldCell.value())) {
// 设置成员方法
setField(cell, object, field);
}
}
resultList.add((T) object);
} while (row != null);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
fis.close();
}
return resultList;
}
setField方法:
将表内单元格数据赋给队象的成员并没有那么简单,这里涉及类型转化
个人思想:先将单元格内数据转化为String类型,再在赋值时根据成员类型将数据转化为相应类型,完成赋值!当然,这里只支持String + 八大基本类型
private static void setField(HSSFCell cell, Object object, Field field) throws Exception {
Class<?> fieldType = field.getType();
cell.setCellType(CellType.STRING);
field.setAccessible(true);
try {
if (fieldType.equals(String.class)) {
field.set(object,cell.getStringCellValue());
} else if (fieldType.equals(int.class)) {
field.set(object,Integer.parseInt(cell.getStringCellValue()));
} else if (fieldType.equals(double.class)) {
field.set(object,Double.parseDouble(cell.getStringCellValue()));
} else if (fieldType.equals(float.class)) {
field.set(object,Float.parseFloat(cell.getStringCellValue()));
} else if (fieldType.equals(boolean.class)) {
field.set(object,Boolean.parseBoolean(cell.getStringCellValue()));
} else if (fieldType.equals(byte.class)) {
field.set(object,Byte.parseByte(cell.getStringCellValue()));
} else if (fieldType.equals(long.class)) {
field.set(object,Long.parseLong(cell.getStringCellValue()));
} else if (fieldType.equals(short.class)) {
field.set(object,Short.parseShort(cell.getStringCellValue()));
} else if (fieldType.equals(char.class)) {
field.set(object,cell.getStringCellValue().charAt(0));
} else {
throw new Exception("不支持的类型");
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
将对象存入Excel表
给定Excel表路径,将对象存至该表,若未找到文件,则以类名字为excel表名字新建文件
private static volatile boolean newExcel = false;
public static synchronized void creatExcel(Object object) throws Exception {
save("", object);
}
public static synchronized void save(String excelPath, Object object) throws Exception {
FileInputStream fis = null;
FileOutputStream fos = null;
HSSFWorkbook hssfWorkbook = null;
HSSFSheet hssfSheet = null;
HSSFRow headRow = null;
Class<?> klass = object.getClass();
try {
try {
fis = new FileInputStream(excelPath);
// 这里借助了异常,如果找不到该文件,则新建excel表
} catch (FileNotFoundException e) {
hssfWorkbook = new HSSFWorkbook();
hssfSheet = hssfWorkbook.createSheet(klass.getSimpleName());
headRow = hssfSheet.createRow(0);
// 新建表时要设计表头,利用注解
setExcelHead(klass, headRow);
} finally {
// 保存部分,若表不是新建的则获取旧表数据
if (!newExcel) {
hssfWorkbook = new HSSFWorkbook(fis);
hssfSheet = hssfWorkbook.getSheet(klass.getSimpleName());
headRow = hssfSheet.getRow(0);
}
// 保存方法
doSave(hssfSheet, object);
fos = new FileOutputStream(excelPath);
hssfWorkbook.write(fos);
}
} catch (IOException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} finally {
newExcel = false;
if (fis != null) {
fis.close();
}
if (fos != null) {
fos.close();
}
}
}
setExcelHead 设置表头
private static void setExcelHead(Class<?> klass, HSSFRow headRow) throws Exception {
Field[] headFields = klass.getDeclaredFields();
for (int i = 0; i < headFields.length; i++) {
Field headField = headFields[i];
if (!headField.isAnnotationPresent(Cell.class)) {
throw new Exception(headField + "缺少注解!");
}
Cell cell = headField.getAnnotation(Cell.class);
HSSFCell hssfCell = headRow.createCell(i);
hssfCell.setCellValue(cell.value());
}
newExcel = true;
}
遍历对象的所有成员,取其注解的value,设置表头
doSave 将对象保存至表中
private static void doSave(HSSFSheet hssfSheet, Object object) {
// 定位最后一行,创建新行
int saveIndex = hssfSheet.getLastRowNum() + 1;
HSSFRow row = hssfSheet.createRow(saveIndex);
Field[] fields = object.getClass().getDeclaredFields();
// 获取对象的所有成员,准备填入表
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
Cell myCell = field.getAnnotation(Cell.class);
HSSFRow headerRow = hssfSheet.getRow(0);
int lastCellNum = headerRow.getLastCellNum();
for (int col = 0; col < lastCellNum; col++) {
if (myCell.value().equals(headerRow.getCell(col).getStringCellValue())) {
// 若单元格列名与注解value相同 设置单元格
setCell(object, field, row.createCell(col));
}
}
}
}
setCell 设置单元格
private static void setCell(Object object, Field field, HSSFCell cell) {
field.setAccessible(true);
Class<?> fieldType = field.getType();
try {
if (fieldType.equals(String.class)) {
cell.setCellValue(field.get(object).toString());
} else if (fieldType.equals(int.class)) {
cell.setCellValue(field.getInt(object));
} else if (fieldType.equals(double.class)) {
cell.setCellValue(field.getDouble(object));
} else if (fieldType.equals(float.class)) {
cell.setCellValue(field.getFloat(object));
} else if (fieldType.equals(short.class)) {
cell.setCellValue(field.getShort(object));
} else if (fieldType.equals(long.class)) {
cell.setCellValue(field.getLong(object));
} else if (fieldType.equals(char.class)) {
cell.setCellValue(field.getChar(object));
} else if (fieldType.equals(byte.class)) {
cell.setCellValue(field.getByte(object));
} else if (fieldType.equals(boolean.class)) {
cell.setCellValue(field.getBoolean(object));
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
这里要注意的在于field的类型转换,根据field的类型来获取其值
测试:
public class Message {
@Cell("id")
private int id;
@Cell("password")
private String password;
public Message() {
}
public Message(int id, String password) {
this.id = id;
this.password = password;
}
// 各成员的 get 和 set 方法 省略
@Override
public String toString() {
return "Message [id=" + id + ", password=" + password + "]";
}
}
public class Test {
public static void main(String[] args) {
try {
Message message = new Message(54188, "funyoo");
ExcelHandler.save("message.xls", message);
List<Message> list = ExcelHandler.getList("message.xls", Message.class);
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试结果:
总结:
这样就尝试着完成了一个很low的从excel表中获取对象list和存入对象
关于成员和表头匹配还是略显臃肿,应该有更妙的解决方案
为保证安全,加了锁
还有太多方面需要完善,这里只是“竖”形表,另外,自己还没完成指定查询和删除,删除肯定还需要大量数据在表内的移动
望,各位多提意见