书接上篇:工具类——Java 浏览器导入、导出Excel(Java import、export)demo
POI的导出方式:创建/加载Workbook,设置样式,填充数据,然后生成本地临时文件,最终以浏览器的形式打开,完成整个导出动作。
demo如下,
private void setCellStyleDemo(T wb, T sheet) {
def row1 = sheet.createRow(1)
// 创建style并设置样式
CellStyle style1 = wb.createCellStyle()
style1.setWrapText(true)// 文本自动换行
style1.setAlignment(HorizontalAlignment.CENTER)// 水平居中
style1.setVerticalAlignment(VerticalAlignment.CENTER)// 垂直居中
style1.setBorderLeft(BorderStyle.THIN)// 设置左边框
style1.setBorderTop(BorderStyle.THIN)// 设置上边框
style1.setBorderRight(BorderStyle.THIN)// 设置右边框
style1.setBorderBottom(BorderStyle.THIN)// 设置下边框
// 设置字体
Font font1 = wb.createFont()
font1.setFontHeight(11)// 大小
font1.setBold(true)// 加粗与否
style1.setFont(font1)
// 填充单元格value为“序号”,并未它设置以上样式
row1.createCell(0).setCellValue("序号")
row1.getCell(0).setCellStyle(style1)
// 设置列宽、行高
sheet.setColumnWidth(0, 5 * 256)// 设置第一列宽度为5个字符宽度
for (int i = 1; i <= len; i++) {
sheet.setColumnWidth(i, 20 * 256)// 设置第二至…宽度为20个字符宽度
}
row0.setHeightInPoints(50)// 设置第1行高度为50px
}
demo如下,
/**
* 加载头像,并填充
* @param wb (xlsx)
* @param sheet 页签
* @param imgUrl 图片路径
* @return 是否填充图片:否,填充(2寸照片)的字眼
*/
boolean setPngToSheet(XSSFWorkbook wb, XSSFSheet sheet, String imgUrl) {
if (imgUrl.indexOf(".") == -1) {
return false
}
String imgType = imgUrl.substring(imgUrl.indexOf(".") + 1)// jpg、png…
FileInputStream fs
ByteArrayOutputStream baos
try {
File imgFile = new File(imgUrl)
fs = new FileInputStream(imgFile)
baos = new ByteArrayOutputStream()
BufferedImage bi = ImageIO.read(fs)
// 图片入流
ImageIO.write(bi, imgType, baos)
// 写入excel
XSSFDrawing patriarch = sheet.createDrawingPatriarch()
// 图片导入指定单元格
XSSFClientAnchor anchor = new XSSFClientAnchor(350, 100, 150, 0, (short) 8, 2, (short) 9, 6)
int type
switch (imgType) {
case "png":
type = XSSFWorkbook.PICTURE_TYPE_PNG
break
case "jpg":
type = XSSFWorkbook.PICTURE_TYPE_JPEG
break
default:
type = 6// PNG
}
// 插入图片
patriarch.createPicture(anchor, wb.addPicture(baos.toByteArray(), type))
} catch (Exception ex) {
logger.error("个人图片插入失败", ex)
return false
} finally {
if (fs != null) {
fs.close()
}
if (baos != null) {
baos.close()
}
}
return true
}
XSSFClientAnchor anchor = new XSSFClientAnchor(int dx1, int dy1, int dx2, int dy2, int col1, int row1, int col2, int row2)
源码,
/**
* Creates a new client anchor and sets the top-left and bottom-right
* coordinates of the anchor by cell references and offsets.
* Sets the type to {@link AnchorType#MOVE_AND_RESIZE}.
*
* @param dx1 the x coordinate within the first cell.
* @param dy1 the y coordinate within the first cell.
* @param dx2 the x coordinate within the second cell.
* @param dy2 the y coordinate within the second cell.
* @param col1 the column (0 based) of the first cell.
* @param row1 the row (0 based) of the first cell.
* @param col2 the column (0 based) of the second cell.
* @param row2 the row (0 based) of the second cell.
*/
public XSSFClientAnchor(int dx1, int dy1, int dx2, int dy2, int col1, int row1, int col2, int row2) {
anchorType = AnchorType.MOVE_AND_RESIZE;
cell1 = CTMarker.Factory.newInstance();
cell1.setCol(col1);
cell1.setColOff(dx1);
cell1.setRow(row1);
cell1.setRowOff(dy1);
cell2 = CTMarker.Factory.newInstance();
cell2.setCol(col2);
cell2.setColOff(dx2);
cell2.setRow(row2);
cell2.setRowOff(dy2);
}
解释,
例如,
XSSFClientAnchor anchor = new XSSFClientAnchor(350, 100, 150, 0, (short) 8, 2, (short) 9, 6)
1)后四位,设置图片位置:
2)前四位,设置图片收缩距离:
demo如下,
/**
* 加载指定模板
* @param path 文件路径
* @return 返回Workbook,可指定XSSFWorkbook或者SXSSFWorkbook
*/
private Workbook readTemplateFile(String path) {
FileInputStream fs
Workbook wb = null
try {
File temFile = new File(path)
fs = new FileInputStream(temFile)
// 读取模板文件
wb = new Workbook(fs)
} catch (Exception ex) {
logger.error("加载EXCEL模板失败", ex)
} finally {
if (fs != null) {
fs.close()
}
}
return wb
}
Workbook是XSSFWorkbook与SXSSFWorkbook的父类,可以用父类指向子类来调用不确定的子类方法。
XSSFWorkbook和SXSSFWorkbook都是Java中Apache POI库提供的用于操作Excel文档的对象,其中XSSFWorkbook是基于内存模型(in-memory model)实现的,而SXSSFWorkbook则是基于流的方式(streaming way)实现的。
区别主要体现在以下两个方面:
1、内存占用:XSSFWorkbook会将整个Excel文档加载到内存中,因此在处理大量数据时,需要消耗大量的内存。而SXSSFWorkbook采用基于流的方式,将一个sheet中的数据分段写入磁盘以避免内存溢出,因此能够更好地处理海量数据,并且占用的内存较少。
2、性能表现:由于SXSSFWorkbook采用了基于流的方式进行Excel文件的操作,使得其性能表现优于XSSFWorkbook,尤其是对于导出海量数据的情况下,SXSSFWorkbook的速度可能会显著快于XSSFWorkbook。
因此,建议在处理大规模Excel数据时,选择采用SXSSFWorkbook对象,而在需求较小的情况下,XSSFWorkbook也可以满足相关操作需求。
注意点一:
SXSSFWorkbook wb = new SXSSFWorkbook()
初始化时,未指定窗口大小,默认使用100.
1、在写入数据时,只有在当前窗口内的数据才会被缓存到内存中,之外的数据将直接写入到硬盘上,并且该窗口会随着当前位置的移动而进行滚动更新
2、缓存窗口的大小一般需要根据具体应用场景和系统资源情况来确定,并没有一个固定的标准。缓存窗口可以理解为内存中的缓存区,它用于暂时存储待处理的数据或文件,并能够提高程序的读写速度和响应时间。
3、 在实际应用中,选择合适的缓存窗口大小需要考虑多个因素,包括数据大小、读写频率、系统内存大小、CPU处理能力等,以及操作系统、数据库、网络传输等其他相关因素。通常情况下,我们可以通过建立模拟测试环境来评估不同缓存窗口大小对系统性能的影响,并根据测试结果进行调整和优化。
注意点二:
其中数据量大时,使用SXSSFWorkbook需要:
if (sheet instanceof SXSSFWorkbook) {
// 一些情况下,可能会在循环遍历过程中频繁地创建行(Row)对象,此时执行完一定数量的行(Row)对象后,调用flushRows方法可以显式地告知系统将缓存数据写入磁盘,降低内存占用(避免OOM)。
((SXSSFSheet) sheet).flushRows()
}
1、一般来说,flushRows方法的调用频率取决于内存使用情况和可用内存的大小。如果数据量很大,内存持续占用,就需要更加频繁地调用flushRows方法,以便及时释放内存。
2、 具体来说,可以根据系统的可用内存、JVM的堆内存设置以及数据集合所需内存的大小等变化情况,适当调整flushRows方法的使用策略。常见的建议是每3000~5000行调用一次flushRows方法,这样可以有效地平衡内存占用和程序性能。
3、特别是在处理大数据集时,推荐使用SXSSF工作簿,并结合设置缓存窗口大小的方式来优化性能:通过SXSSFWorkbook类的setRowAccessWindowSize(int rowAccessWindowSize)方法,来设置缓存窗口大小,在写入数据时,只有在当前窗口内的数据才会被缓存到内存中,之外的数据将直接写入到硬盘上,并且该窗口会随着当前位置的移动而进行滚动更新,从而实现最大程度的内存优化。
已有一套XSSFWorkbook导出,但是当数据量较大时(假如3000条以上),我想使用SXSSFWorkbook流式导出
demo如下,
在我保留XSSFWorkbook的前提下,数据量大使用SXSSFWorkbook,数据量小依旧使用XSSFWorkbook,
def exportPersonPool(@Nullable Object... args) {
try {
// 查询数据量
Long count = 2000// 查询数据库,统计待导出数据总数
def wb
if (count > PERSON_MAX_SIZE) {
wb = exportPersonPoolLarge(args, count)
} else {
wb = exportPersonPoolSmall(args)
}
// 生成导出路径,略
return MyExcelUtil.getMyExportExcel(wb, String.format("测试导出表%s%s",System.currentTimeMillis(), ExcelUtil.XLSX))
} catch (Exception ex) {
logger.error("导出失败", ex)
}
}
/**
* 大数据量
*/
SXSSFWorkbook exportPersonPoolLarge(@Nullable Object... args, Long count) {
// 使用默认窗口大小100
SXSSFWorkbook wb = new SXSSFWorkbook()
// 获取sheet,并填充数据
SXSSFSheet sheet = wb.createSheet("mySheet")
// 设置导出头,略
buildPoolSheetHeader(wb, sheet, args)
// 设置当前页1\2,
int countPageIndex = (int) Math.ceil(count / PERSON_MAX_SIZE)
for (int i = 1; i <= countPageIndex; i++) {
List<Map<String, Object>> dataList = getPersonPool(args, i)// 分页查询数据库
// 填充sheet,略
fillPoolSheet(wb, sheet, fillList, dataList, i)
}
return wb
}
/**
* 小数据量
*/
XSSFWorkbook exportPersonPoolSmall(@Nullable Object... args) {
XSSFWorkbook wb = new XSSFWorkbook()
// 获取sheet,并填充数据
XSSFSheet sheet = wb.createSheet("mySheet")
// 设置导出头,略
buildPoolSheetHeader(wb, sheet, args)
List<Map<String, Object>> dataList = getPersonPool(args,1)// 查询数据库(第一页)
// 填充sheet,略
fillPoolSheet(wb, sheet, fillList, dataList, 1)
return wb
}
/**
* 填充sheet
*/
private void fillPoolSheet(@Nullable Object... args) {
// 略
if (sheet instanceof SXSSFWorkbook) {
((SXSSFSheet) sheet).flushRows()
}
}
上文路径:工具类——Java 浏览器导入、导出Excel(Java import、export)
如何生成临时文件,参考:导出/service后半段
如何浏览器下载,参考:导出/controller后半段
为脱敏手改代码,可能存在错误,还请与我交流,谢谢!