1 背景
最近做需求的时候需要做一个导入的功能,通过调研一些java导入的第三方jar包,最后选择了阿里的easyExcel,按照约定的规范能够简单快速的处理excel中的信息,通过@ExcelProperty注解和表头对应解析出excel的信息,使用的过程中发现了两个问题。
- 第一个是如果excel中某一行完全是空行的话,excel会直接跳过这一行信息(不知道为什么要这么设计,对于某些业务来说带来一定的不方便性。)
- easyExcel不支持导入图片的信息,完全处理不了图片的信息。在github上也得到了回答,不支持处理图片信息
2 Apache Poi实践
为了处理图片信息,后面在优化的过程中选择用poi处理excel的图片信息,这里做个demo的总结。
public void saveImg(InputStream inputStream) throws Exception {
Map<String, List<PictureData>> pictureMap = ExcelUtil.getWorkbook(inputStream);
Object[] key = pictureMap.keySet().toArray();
for (int i = 0; i < pictureMap.size(); i++) {
// 获取图片索引
String picIndex = key[i].toString();
// 获取图片流
List<PictureData> pictureDataList = pictureMap.get(picIndex);
if (CollectionUtils.isNotEmpty(pictureDataList)) {
pictureDataList.forEach(
pictureData -> {
byte[] data = pictureData.getData();
//图片保存路径
String imgPath = "D:\\image" + System.currentTimeMillis();
FileOutputStream out = null;
try {
out = new FileOutputStream(imgPath);
out.write(data);
out.close();
} catch (Exception e) {
e.printStackTrace();
}
});
}
}
}
public static Map<String, List<PictureData>> getWorkbook(InputStream inputStream) {
try {
// 这里要注意下excel表格的格式,不要有多余的空行的情况,否则这里可能会内存溢出,建议用高版本的easyExcel拿到excel的对象。
Workbook workbook = WorkbookFactory.create(inputStream);
Sheet sheet = workbook.getSheetAt(0);
if (sheet instanceof HSSFSheet) {
return getXlsPictures((HSSFSheet) sheet);
} else if (sheet instanceof XSSFSheet) {
return getXlsxPictures((XSSFSheet) sheet);
} else {
return Maps.newHashMap();
}
} catch (Exception e) {
return Maps.newHashMap();
}
}
/**
* 获取图片和位置 (xls)
*
* @param sheet
* @return
*/
public static Map<String, List<PictureData>> getXlsPictures(HSSFSheet sheet) {
Map<String, List<PictureData>> map = new HashMap<>();
List<HSSFShape> list = sheet.getDrawingPatriarch().getChildren();
for (HSSFShape shape : list) {
if (shape instanceof HSSFPicture) {
HSSFPicture picture = (HSSFPicture) shape;
HSSFClientAnchor cAnchor = (HSSFClientAnchor) picture.getAnchor();
String coordinate = cAnchor.getRow1() + "-" + cAnchor.getCol1();
// 处理一个单元格内有多个图片的情况
if (CollectionUtils.isNotEmpty(map.get(coordinate))) {
map.get(coordinate).add(picture.getPictureData());
} else {
map.put(coordinate, Lists.newArrayList(picture.getPictureData()));
}
}
}
return map;
}
/**
* 获取图片和位置 (xlsx)
*
* @param sheet
* @return
*/
public static Map<String, List<PictureData>> getXlsxPictures(XSSFSheet sheet) {
Map<String, List<PictureData>> map = new HashMap<>();
List<POIXMLDocumentPart> list = sheet.getRelations();
for (POIXMLDocumentPart part : list) {
if (part instanceof XSSFDrawing) {
XSSFDrawing drawing = (XSSFDrawing) part;
List<XSSFShape> shapes = drawing.getShapes();
for (XSSFShape shape : shapes) {
XSSFPicture picture = (XSSFPicture) shape;
XSSFClientAnchor anchor = (XSSFClientAnchor) shape.getAnchor();
// 获取excel的行列当作map的key
CTMarker marker = anchor.getFrom();
String coordinate = marker.getRow() + "-" + marker.getCol();
// 处理一个单元格内有多个图片的情况
if (CollectionUtils.isNotEmpty(map.get(coordinate))) {
map.get(coordinate).add(picture.getPictureData());
} else {
map.put(coordinate, Lists.newArrayList(picture.getPictureData()));
}
}
}
}
return map;
}
3 注意事项
1: 利用【XSSFClientAnchor anchor = picture.getPreferredSize()】这种方式获取图片所在坐标时会报空指针异常(在图片粘贴的时候覆盖了单元格之间分界线的情况会有问题),应该用【XSSFClientAnchor anchor = (XSSFClientAnchor) shape.getAnchor()】这种方式获取坐标。
2:【Workbook workbook = WorkbookFactory.create(inputStream)】这个方法获取Workbook会很吃机器内存,在excel有很多空行(看似空行,没有数据,其实excel认为不是空行)的时候可能会内存溢出,服务器卡死。
比如要导入的数据是下图所示,不要有多余的空行。
3:导入表格之前先要确定下excel表格的最后一行在哪(可以用ctrl+end确定)?如果发现你的表格的最后一行定位到了空数据的行,如:
你可以选中需要处理的行,然后清除下格式:
否则系统在用poi处理excel会导致内存溢出,cpu打满(其实是gc线程一直在尝试释放内存)。服务器会在短时间内拒绝服务!!!