一:Java POI 基本简介
Apache POI(Poor Obfuscation Implementation) 是用Java编写的免费开源的跨平台的Java API,Apache POI提供API给Java程序对Microsoft Office格式档案读和写的功能,其中使用最多的就是使用POI操作Excel文件。
它是创建和维护操作各种符合Office Open XML(OOXML)标准和微软的OLE2复合文档格式的Java API。用它可以使用Java读取、创建和修改MS Excel文件。而且还可以使用Java读取和创建MS Word文件和MS PowerPoint文件。Apache POI提供Java操作Excel解决方案(适用于 97-2003 或 2007)
OOXML格式主要包括以下几种文件类型:
SpreadsheetML(
Excel表格
如 .xlsx、.xls):存储Microsoft Excel电子表格的XML格式。WordprocessingML(
Word文档
如 .docx、.doc):存储Microsoft Word文档的XML格式。PresentationML(
PPT演示稿
如 .pptx、.ppt):存储Microsoft PowerPoint演示文稿的XML格式。
Apache POI - 组件概述 、Apache POI 5.0 - API文档
注:本文主要讲解.xlsx的2007版操作,而.xls的97-2003老版本使用起来和2007版差不多,而且老版本的使用也越来越少。
POI发行版可以对许多文档文件格式的支持。这种支持是在几个JAR文件中提供的。并不是每种格式都需要所有的JAR。下面我介绍一下POI组件、Maven存储库标记和项目的Jar文件之间的关系。
java
'组件' '操作格式' '依赖' '说明'
XSSF(读写*.xlsx 文件) Excel XLSX poi-ooxml
XSLF(处理*.pptx 文件) PowerPoint PPTX poi-ooxml
XWPF(处理*.docx 文件) Word DOCX poi-ooxml
XDGF(处理*.vsdx 文件) Visio VSDX poi-ooxml
HSLF(处理*.ppt文件) PowerPoint PPT poi-scratchpad
HWPF(处理*.doc文件) Word DOC poi-scratchpad
HDGF(处理*.vsd文件) Visio VSD poi-scratchpad
HSSF(读写*.xls文件) Excel XLS poi 仅支持HSSF(只可以操作XLS)
POIFS OLE2 Filesystem poi 需要处理基于OLE2/POIFS的文件
HPSF OLE2 Property Sets poi
DDF Escher common drawings poi
HPBF Publisher PUB poi-scratchpad
HSMF Outlook MSG poi-scratchpad
HWMF WMF drawings poi-scratchpad
特殊:
Common SL: PPT和PPTX poi-scratchpad 和 poi-ooxml
SL代码在核心POIjar中,但是实现的是poi-scratchpad和poi-ooxml
Common SS: XLS和XLSX poi-ooxml
WorkbookFactory和其它实现都需要poi-ooxml,而不仅仅是核心po
OpenXML4J: OOXML poi-ooxml和poi-ooxml-lite或poi-ooxml-full
<math xmlns="http://www.w3.org/1998/Math/MathML"> m a v e n 关于 P O I 操作的整套依赖 \color{#f00}{maven关于POI操作的整套依赖} </math>maven关于POI操作的整套依赖
xml
<dependencies>
<!--注意:4.x或5.x的版本jdk编译要必须1.8及以上 -->
<!--Apache POI 基本依赖坐标(处理Excel表格、Word文档、PPT演示稿)-->
<!--低版本97-2003的文档处理如:.xls、.ppt、.doc需要导入此坐标即可-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.5</version>
</dependency>
<!--高版本2007的文档处理如:.xlsx、.pptx、.docx需要导入此坐标即可-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version>
</dependency>
<!--若有导入导出百万需求的数据则需要导入此坐标-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>5.2.5</version>
</dependency>
</dependencies>
二:POI操作Excel(.xlsx、.xls)
说到Excel的表格操作就必须聊到低版本(97-2003)和高版本(2007+),其主要区别是低版本就是.xls,它最大支持65536条记录和256列,而高版本就是.xlsx,它最大支持记录为1048576条记录和16384列。
java
低版本中的类名 高版本中的类名 对应Excel名称
HSSFWorkbook XSSFWorkbook 工作簿
HSSFSheet XSSFSheet 工作表
HSSFRow XSSFRow 行
HSSFCell XSSFCell 单元格(列)
HSSFCellStyle XSSFCellStyle 单元格样式
"注:低版本和高版本的实现类是不一样的,但具体的操作方式都是原因的"
(一):Excel基本创建和读取
操作Excel的.xlsx和.xls文件则需要使用工作簿Workbook接口,它内部有着许多的实现类,按照类型可分为如:HSSFWorkbook(处理.xls)、XSSFWorkbook(处理.xlsx)、SXSSFWorkbook(处理批量读写)等。 下面是一个创建和读取Excel的基本流程,并且都是.xlsx类型的,其实需要变为.xls类型的则只需要把那些类名称前缀为XSSF的替换为HSSF即可。
java
public static void main(String[] args) throws IOException {
// 创建Excel文件
createExcel();
// 读取刚才创建Excel文件
readExcel();
}
/***
* 创建Excel(类型为.xlsx)
*/
public static void createExcel() throws IOException {
// 1:创建.xlsx类型的Excel工作簿
XSSFWorkbook workbook = new XSSFWorkbook();
// 2:创建工作表 就是Excel文件最下边的sheet1、sheet2工作表
XSSFSheet sheet = workbook.createSheet("sheet(工作表①)");
// 3:通过下面2行代码定位并创建一个坐标为 0,0 的单元格,并写上内容 对应Excel 1,1 位置
// 创建行 当前创建的为0行 对应Excel第一行
XSSFRow row = sheet.createRow(0);
// 创建列 当前创建列为0列 对应Excel第一列
XSSFCell cell = row.createCell(0);
// 写入内容
cell.setCellValue("测试写入内容!!");
//或者链式调用写法:sheet.createRow(0).createCell(0).setCellValue("测试写入内容!!");
// 4:把构建的内存数据写入到磁盘 确保有这个磁盘
OutputStream out = new FileOutputStream("D://test.xlsx");
workbook.write(out);
// 5:关闭资源
out.close();
workbook.close();
}
/***
* 读取刚才创建的Excel(类型为.xlsx)
*/
public static void readExcel() throws IOException {
// 1:创建.xlsx类型的Excel工作簿
XSSFWorkbook workbook = new XSSFWorkbook("D://test.xlsx");
// 2:获取文件的第一个工作表
XSSFSheet sheetAt = workbook.getSheetAt(0);
// 注:getSheet(String name); 通过Sheet名称来获取,不怎么用
// 3:通过下面2行代码定位并获取一个坐标为 0,0 的单元格,并获取内容 对应Excel 1,1 位置
// 获取行 当前获取的为0行 对应Excel第一行
XSSFRow row = sheetAt.getRow(0);
// 获取行 当前获取列为0列 对应Excel第一列
XSSFCell cell = row.getCell(0);
// 4:读取单元格内容
String stringCellValue = cell.getStringCellValue();
System.out.println("获取单元格0,0的内容为:"+stringCellValue);
// 5:关闭工作簿
workbook.close();
}
(二):创建不同类型的单元格
其实Excel文件里的单元格是有多种类型的,比如:字符串类型、日期类型、数字类型、布尔类型 等等,但是我们不能只通过一个字符串类型来表达全部吧;所以我们就得使用 CellStyle接口方法来操作样式的设置,其实这个接口操作很广泛,如:边框颜色、对齐方式、字体、颜色 等都可以操作;假设要设置日期类型的话,那么这里就得使用 CellStyle 里的 setDataFormat(short fmt)
方法,使用此方法还必须得搭配着接口 CreationHelper,它里面的createDataFormat()
方法可以创建DataFormat实例并返回short参数。下面就来看看不同类型的单元格内容如何设置:
点开查看详情:创建不同类型的Excel单元格
java
/***
* 创建多种类型单元格的文件
*/
public static void multipleTypesCreate() throws IOException {
//创建工作簿
Workbook workbook = new XSSFWorkbook();
//创建工作表 注:不指定名则默认Sheet0、Sheet1、SheetN...
Sheet sheet = workbook.createSheet();
// ========== 1:在Excel单元格的第一行第一列写入 日期类型 数据 ==========
// ■ 错误方式:直接写入是不可以的,会变为小数类型,需要调整类型格式
// Cell cellA = sheet.createRow(0).createCell(0);
// cellA.setCellValue(new Date());
// ■ 正确方式:更改为日期类型
// 返回一个对象,该对象处理XSSF各种实例的具体类的实例化
CreationHelper creationHelper = workbook.getCreationHelper();
// 创建新的DataFormat实例。获取与给定格式字符串匹配的格式索引,在需要时创建一个新的格式条目。
short format = creationHelper.createDataFormat().getFormat("yyyy-MM-dd");
// 通过Workbook来获取一个样式操作类来对已有的表设置样式
CellStyle cellStyle = workbook.createCellStyle();
// 把日期格式设置当当前样式cellStyle中
cellStyle.setDataFormat(format);
// 在第一行第一列位置写入日期数据 并对 0,0 位置写入样式
Cell cellB = sheet.createRow(0).createCell(0);
cellB.setCellValue(new Date());
cellB.setCellStyle(cellStyle); // 设置单元格类型
// ========== 2:在Excel单元格的第二行第二列写入 字符串类型 数据 ==========
sheet.createRow(1).createCell(1).setCellValue("我是字符串");
// ========== 3:在Excel单元格的第三行第三列写入 数值类型(整数或浮点) 数据 ==========
CellStyle style = workbook.createCellStyle();
// 设置数据格式为两位小数
style.setDataFormat(workbook.createDataFormat().getFormat("0.00"));
Cell cellC = sheet.createRow(2).createCell(2);
cellC.setCellValue(22.4634D);
cellC.setCellStyle(style);
// ========== 4:在Excel单元格的第四行第四列写入 布尔类型 数据 ==========
Cell cellD = sheet.createRow(3).createCell(3);
cellD.setCellValue(true);
// ========== 5:在Excel单元格的第五行第五列写入 错误类型 数据 ==========
Cell cellE = sheet.createRow(4).createCell(4);
cellE.setCellErrorValue(FormulaError.REF.getCode());
// 一些错误类型的枚举:
// DIV0:#DIV/0!,表示除数为零的错误。
// VALUE:#VALUE!,表示公式中使用了无效的数值。
// REF:#REF!,表示引用不可用的错误。
// NAME:#NAME?,表示公式中使用了未知的名称。
// NUM:#NUM!,表示公式中使用了无效的数值或数字格式。
// NULL:#NULL!,表示公式中存在无效的空值引用。
// NA:#N/A,表示未找到匹配项。
//把构建的内存数据写入到磁盘
OutputStream out = new FileOutputStream("D://test1.xlsx");
workbook.write(out);
//关闭资源
out.close();
workbook.close();
}
我们有了上面的基础知识铺垫以后,可以自己动手写个简单的创建.xlsx或.xls格式的Excel文件了,至于样式需要在后几节讲解;废话不多说,直接上操作:
java
/*** 测试基本的Excel表格创建 */
public static void testCreationXlsx() throws IOException {
//创建数据源
Object[] title = {"ID", "姓名", "零花钱", "生日", "是否被删除", "空值引用"};
Object[] s1 = {"tx01", "张三", 66.663D, new Date(), true, FormulaError.NULL};
Object[] s2 = {"tx02", "李四", 76.882D, new Date(), false, FormulaError.NULL};
List<Object[]> list = new ArrayList<>();
list.add(title);
list.add(s1);
list.add(s2);
// 创建工作簿
Workbook workbook = new XSSFWorkbook();
// 创建工作表
Sheet sheet = workbook.createSheet("总结");
// 循环行创建
for (int r = 0; r < list.size(); r++) {
// 创建行
Row row = sheet.createRow(r);
// 循环创建列操作
for (int c = 0; c < list.get(r).length; c++) {
// 创建列
Cell cell = row.createCell(c);
// 获取每个单元格数据然后匹配类型
Object o = list.get(r)[c];
// 此时数据是要改变样式(根据不同的数据类型,写入不同类型的单元格)
if (o instanceof Date) {
// 写入日期类型
CellStyle cellStyle = workbook.createCellStyle();
cellStyle.setDataFormat(workbook.getCreationHelper()
.createDataFormat().getFormat("yyyy-MM-dd"));
cell.setCellValue((Date) list.get(r)[c]);
cell.setCellStyle(cellStyle);
} else if (o instanceof Double) {
CellStyle cellStyle = workbook.createCellStyle();
cellStyle.setDataFormat(workbook.createDataFormat().getFormat("0.00"));
cell.setCellValue((Double) list.get(r)[c]);
cell.setCellStyle(cellStyle);
} else if (o instanceof FormulaError) {
// 写入错误类型
cell.setCellErrorValue(((FormulaError) o).getCode());
} else {
// 写入字符串
cell.setCellValue(list.get(r)[c].toString());
}
}
}
// 写出及关闭
OutputStream out = new FileOutputStream("D:\\test02.xlsx");
workbook.write(out);
out.close();
workbook.close();
}
(三):从Excel文件中遍历数据
遍历数据可谓是重点了,我们得创建一个工作簿并传入.xlsx或.xls文件,其中获取起始行和结尾行则使用 getFirstRowNum 和 getLastRowNum 方法,但是获取的每个单元格表格数据类型不同则需要使用类型匹配,下面就用来遍历上面创建的数据表格:
java
public static void testReadExcel() throws IOException {
// 读取的数据都往里面存
List<Object[]> list = new ArrayList<>();
// 创建工作簿并传入一个真实存在的文件
Workbook workbook = new XSSFWorkbook("D:\\test02.xlsx");
// 获取文件的第一个工作表
Sheet sheetAt = workbook.getSheetAt(0);
// 获取工作表中的起始行和结束行
int rowStart = sheetAt.getFirstRowNum();
int rowEnd = sheetAt.getLastRowNum();
// 循环开始行到结束行
for (int rowNum = rowStart; rowNum <= rowEnd; rowNum++) {
// 数据存储
Object[] obj = new Object[6];
// 获取行实例
Row row = sheetAt.getRow(rowNum);
// 一旦判断有空行则跳出本次循环
if (row == null) {
continue;
}
// 当前行若不为空的话则获取列(获取当前行的结束列)
int lastColumn = row.getLastCellNum();
// 获取定义的单元格数(不是实际行中的单元格数!)。
// 也就是说,如果只有0、4、5列有值,那么就有3列。
// int lastColumn = row.getPhysicalNumberOfCells();
// 循环列,从0开始循环到lastColumn
for (int colNum = 0; colNum <= lastColumn; colNum++) {
// 获取列实例
Cell cell = row.getCell(colNum);
// 若不为空的话则打印数据(因为表格的每个类型不一样,需要特殊处理)
obj[colNum] = parseDifferentTypesCells(cell);
}
list.add(obj);
}
// 关闭资源
workbook.close();
// 循环数据
for (Object[] arr : list) {
for (Object o : arr) {
System.out.print(o + " | ");
}
System.out.println();
}
// 打印结果:
// ID | 姓名 | 零花钱 | 生日 | 是否被删除 | 空值引用 |
// tx01 | 张三 | 66.663 | 2024-02-25 | true | #NULL! |
// tx02 | 李四 | 76.882 | 2024-02-25 | false | #NULL! |
}
/***
* 解析不同类型单元格
* @return Object
*/
public static Object parseDifferentTypesCells(Cell cell) {
// 返回的数据
Object result = null;
// 若为空则直接返回空
if (cell == null) {
return null;
} else {
switch (cell.getCellType()) {
case NUMERIC: // 数字类型
// 通过查找StylesSource获取格式字符串的内容
String formatString = cell.getCellStyle().getDataFormatString();
//判断当前的字符串形式的内容是否以m/d/yy的日期格式
if (formatString.equals("m/d/yy")) {
result = cell.getDateCellValue();
} else if (formatString.equals("yyyy-MM-dd")) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
result = sdf.format(cell.getDateCellValue());
} else {
result = cell.getNumericCellValue();
}
break;
case STRING: // 字符串类型
result = cell.getStringCellValue();
break;
case BOOLEAN: // 真假值类型
result = cell.getBooleanCellValue();
break;
case BLANK: // 空数据类型
result = "空";
break;
case FORMULA: // 计算类型
result = cell.getCellFormula();
break;
case _NONE: // 未知类型
result = "未知数据";
break;
case ERROR: // 错误类型
result = FormulaError.forInt(cell.getErrorCellValue()).getString();
break;
default:
return null;
}
}
return result;
}
(四):设置不同样式单元格
1:单元格对齐方式
当说到对齐方式我们就得谈谈POI提供的两个枚举类了 HorizontalAlignment(水平对齐)和 VerticalAlignment(垂直对齐),但是我们设置对齐方式还得借助 CellStyle 样式接口内部的实现类完成,通过 setAlignment
和 setVerticalAlignment
方法来对单元格设置对齐方式:
点开查看详情:设置单元格的对齐方式
java
public static void alignmentMethod() throws IOException {
// 创建工作簿和工作表(未指定工作表名称则为"Sheet0~N")
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet();
// 设置行,并指定行高为60
Row row = sheet.createRow(0);
row.setHeightInPoints(60);
// 创建坐标 0,1 的单元格(就是第一行第二列)
Cell cell = row.createCell(1);
cell.setCellValue("啦啦");
// 创建单元格样式
CellStyle cellStyle = workbook.createCellStyle();
// 设置单元格的水平对齐类型。 此时水平居中
cellStyle.setAlignment(HorizontalAlignment.CENTER);
// 设置单元格的垂直对齐类型。 此时垂直靠底边
cellStyle.setVerticalAlignment(VerticalAlignment.BOTTOM);
// 把样式设置到单元格上
cell.setCellStyle(cellStyle);
//写出及关闭
OutputStream out = new FileOutputStream("D:\\test03.xlsx");
workbook.write(out);
out.close();
workbook.close();
}
2:单元格边框及颜色
感觉设置单元格边框的颜色用处并不大,但要设置漂亮样式可以通过 CellStyle 来设置边框和边框颜色,具体的就得参照 BorderStyle(边框样式)、IndexedColors(背景色) 这2个枚举类来获取具体的样式,下面将对其写一个基本实例:
点开查看详情:设置单元格边框颜色及样式
java
public static void borderColor() throws IOException {
// 创建工作簿和创建工作表
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet();
// 创建单元格行信息
Row row = sheet.createRow(1);
// 为了可以看到样式 特地设置单元格高度
row.setHeightInPoints(60);
// 创建坐标 1,1 单元格(第二行第二列)
Cell cell = row.createCell(1);
cell.setCellValue("吆西");
// 创建样式(从workbook获取当前样式对象)
CellStyle cellStyle = workbook.createCellStyle();
// 通过set设置边框样式及边框颜色 反之通过get可以获取设置的样式
cellStyle.setBorderBottom(BorderStyle.DOTTED); // 设置单元格底部样式
cellStyle.setBorderTop(BorderStyle.THIN); // 设置单元格顶部样式
cellStyle.setBorderLeft(BorderStyle.HAIR); // 设置单元格左边样式
cellStyle.setBorderRight(BorderStyle.DASH_DOT_DOT); // 设置单元格右边样式
cellStyle.setBottomBorderColor(IndexedColors.GREEN.getIndex()); // 绿色
cellStyle.setTopBorderColor(IndexedColors.CORAL.getIndex()); // 珊瑚色
cellStyle.setLeftBorderColor(IndexedColors.RED.getIndex()); // 红色
cellStyle.setRightBorderColor(IndexedColors.AQUA.getIndex()); // 水绿色
// 别忘了把刚才这些设置的样式设置到单元格上
cell.setCellStyle(cellStyle);
//写出及关闭
OutputStream out = new FileOutputStream("D:\\test03.xlsx");
workbook.write(out);
out.close();
workbook.close();
}
3:单元格颜色填充
有时候我们会设置单元格的前景色以及背景色,甚者会对单元格填充图案,这时候我们就会用到样式类里面的填充方法;关于枚举类:FillPatternType(图案背景)、IndexedColors(背景色) 具体看代码:
点开查看详情:设置单元格前景色和背景色
java
public static void backgroundColor() throws IOException {
// 创建工作簿和Sheet0工作表
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet();
// 设置第一列的宽度是20个字符宽度
sheet.setColumnWidth(1, 20*256);
// 创建表格第一行并设置行高60
Row row = sheet.createRow(1);
row.setHeightInPoints(60);
// 获取当前workbook的样式对象
CellStyle style = workbook.createCellStyle();
// 设置背景色(绿色)
style.setFillBackgroundColor(IndexedColors.GREEN.getIndex());
// 设置填充图案(厚水平带)填充图案默认为黑色()
style.setFillPattern(FillPatternType.THICK_HORZ_BANDS);
// 此时我设置填充图案(前景色为红色)
style.setFillForegroundColor(IndexedColors.RED.getIndex());
//创建行(1,1)并设置文本加样式
Cell cell = row.createCell(1);
cell.setCellValue("蚂蚁小哥");
cell.setCellStyle(style);
//创建流并写出文件关闭流
OutputStream out = new FileOutputStream("D:\\test3.xlsx");
workbook.write(out);
out.close();
workbook.close();
}
点开查看详情:图案填充 FillPatternType 枚举类
java
常量名 汉译
ALT_BARS: 宽点
BIG_SPOTS: 大斑点
BRICKS: 砖状布局
DIAMONDS: 钻石
FINE_DOTS: 小细点
LEAST_DOTS: 最小点
LESS_DOTS: 少点
NO_FILL: 无背景
SPARSE_DOTS: 稀疏点
SQUARES: 正方形
THICK_BACKWARD_DIAG: 厚厚的后向对角线
THICK_FORWARD_DIAG: 厚正面对角线
THICK_HORZ_BANDS: 厚水平带
THICK_VERT_BANDS: 厚垂直带
THIN_BACKWARD_DIAG: 薄后向对角线
THIN_FORWARD_DIAG: 细正对角线
SOLID_FOREGROUND: 实填
点开查看详情:背景颜色 IndexedColors 枚举类
java
常量名 汉译
BLACK: 黑色
BLACK1: 黑色1
WHITE: 白色
WHITE1: 白色1
RED: 红色
RED1: 红色1
BRIGHT_GREEN: 亮绿色
BRIGHT_GREEN1: 亮绿色1
BLUE: 蓝色
BLUE1: 蓝色1
YELLOW: 黄色
YELLOW1: 黄色1
PINK: 粉红
PINK1: 粉色1
TURQUOISE: 青绿色
TURQUOISE1: 青绿色1
LEMON_CHIFFON: 柠檬雪纺
LIGHT_TURQUOISE: 浅青绿色
LIGHT_TURQUOISE1: 浅青绿色1
GREEN: 绿色
VIOLET: 紫罗兰
TEAL: 蓝绿色
MAROON: 栗色
ROSE: 粉红色
AQUA: 水绿色
LIME: 石灰色
GOLD: 金
LAVENDER: 淡紫色
BROWN: 棕色
PLUM: 紫红色
INDIGO: 靛蓝色
TAN: 棕黄色
ORCHID: 兰花色
CORAL: 珊瑚色
ROYAL_BLUE: 皇家蓝
ORNFLOWER_BLUE: 矢车菊蓝
ORANGE: 桔黄色的
OLIVE_GREEN: 橄榄绿
DARK_RED: 深红色
DARK_BLUE: 深蓝色
DARK_YELLOW: 深黄色
DARK_GREEN: 深绿色
DARK_TEAL: 深青色
LIGHT_GREEN: 浅绿色
LIGHT_YELLOW: 浅黄色
LIGHT_ORANGE: 淡橙色
LIGHT_BLUE: 浅蓝色
LIGHT_CORNFLOWER_BLUE:浅矢车菊蓝
PALE_BLUE: 淡蓝色
SEA_GREEN: 海绿色
BLUE_GREY: 蓝灰
SKY_BLUE: 天空蓝色
GREY_25_PERCENT: 灰25%
GREY_40_PERCENT: 灰40%
GREY_50_PERCENT: 灰50%
GREY_80_PERCENT: 灰80%
AUTOMATIC: 自然色
4:合并单元格
设置单元格的合并得掌握,其实也简单,只要使用addMergedRegion
方法即可完成,下面是示例代码:
点开查看详情:合并单元格
java
public static void mergeCells() throws IOException {
// 创建工作簿和创建Sheet0工作表
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet();
// 设置单元格合并 0开始索引
// 前2个参数设置开始行到结束行,
// 后2个参数设置开始列到结束列;切记这和我们坐标轴不一样
// CellRangeAddress(int firstRow, int lastRow, int firstCol, int lastCol)
sheet.addMergedRegion(new CellRangeAddress(1,1,1,5));
// 创建行和列,此时坐标为 1,1;在Excel里就是第二行第二列
Row row = sheet.createRow(1);
Cell cell = row.createCell(1);
cell.setCellValue("蚂蚁小哥");
//创建流并写出文件关闭流
OutputStream out = new FileOutputStream("D:\\test3.xlsx");
workbook.write(out);
out.close();
workbook.close();
}
5:字体样式
设置字体则通过工作簿先获取字体样式类,然后再对字体设置;字体样式类就使用 Font接口即可:
点开查看详情:设置字体样式
java
public static void fontStyle() throws IOException {
// 创建工作簿和创建Sheet0工作表
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet();
// 创建行
Row row = sheet.createRow(1);
// ## 创建字体类
Font font = workbook.createFont();
// ## 设置字体颜色
font.setColor(IndexedColors.RED1.getIndex());
// ## 设置字体 true粗体(默认) false细
font.setBold(false);
// ## 设置字体大小
font.setFontHeightInPoints((short) 60);
// ## 设置字体名 如楷体,微软雅黑....中文和英文表示都行
font.setFontName("楷体");
// ## 倾斜设置
font.setItalic(true);
// ## 设置删除线
font.setStrikeout(true);
// 创建样式类
CellStyle cellStyle = workbook.createCellStyle();
// 样式设置font样式
cellStyle.setFont(font);
// 创建列坐标为 1,1 就是Excel的第二行第二列的单元格设置信息
Cell cell = row.createCell(1);
cell.setCellValue("蚂蚁小哥_aBc_123");
cell.setCellStyle(cellStyle);
//创建流并写出文件关闭流
OutputStream out = new FileOutputStream("D:\\test3.xlsx");
workbook.write(out);
out.close();
workbook.close();
}
6:快速构建单元格
样式的创建和单元格的创建其实每次设置样式都要创建对象,麻烦;但是ReginUtil(区域工具)、CellUtil(样式工具)可以很快构建我们的样式,可以简写很多代码。示例代码:
CellUtil(样式工具): 可用于获取或创建单元格,并在创建单元格时设置给定的样式。
RegionUtil(区域工具): 对合并的单元格快速设置范围的边框、背景色和其它样式。
点开查看详情:快速构建单元格及样式
java
public static void fontStyle() throws IOException {
// 创建工作簿和创建名称为"new sheet"的工作表
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("new sheet");
// ========================================
// 创建单元格坐标 1,1 ;在Excel为第二行第二列;并设置内容
Cell cell = sheet.createRow(1).createCell(1);
cell.setCellValue("蚂蚁小哥");
// 创建单元格的范围 B2(1,1):E5(4,4) ; 说明,这个单元格以b2开始到e5结束
CellRangeAddress region = CellRangeAddress.valueOf("B2:E5");
// 上面的合并其实和 new CellRangeAddress(1,4,1,4) 一样的
// 传递给合并单元格方法里
sheet.addMergedRegion(region);
//设置四边样式及颜色 但是得传递Sheet和CellRangeAddress
RegionUtil.setBorderBottom(BorderStyle.MEDIUM_DASHED, region, sheet);
RegionUtil.setBorderTop(BorderStyle.MEDIUM_DASHED, region, sheet);
RegionUtil.setBorderLeft(BorderStyle.MEDIUM_DASH_DOT, region, sheet);
RegionUtil.setBorderRight(BorderStyle.MEDIUM_DASHED, region, sheet);
RegionUtil.setBottomBorderColor(IndexedColors.BLUE.getIndex(), region, sheet);
RegionUtil.setTopBorderColor(IndexedColors.AQUA.getIndex(), region, sheet);
RegionUtil.setLeftBorderColor(IndexedColors.RED.getIndex(), region, sheet);
RegionUtil.setRightBorderColor(IndexedColors.AQUA.getIndex(), region, sheet);
// ========================================
// (创建单元格第2行;Excel里就是第二列)(创建单元格第3行;Excel里就是第三列)
Row row = sheet.createRow(2);
Row row2 = sheet.createRow(3);
// 创建字体类并设置样式
Font font = workbook.createFont();
font.setColor(IndexedColors.RED1.getIndex());
font.setStrikeout(true);
// 创建样式类
CellStyle style = workbook.createCellStyle();
style.setFont(font);
// 快捷创建单元格的两种方式
CellUtil.createCell(row, 8, "Hello World!", style);
Cell cell2 = CellUtil.createCell(row2, 8, "阿三大苏打");
// 设置居中
CellUtil.setAlignment(cell2, HorizontalAlignment.CENTER);
// 创建流并写出文件关闭流
OutputStream out = new FileOutputStream("D:\\test3.xlsx");
workbook.write(out);
out.close();
workbook.close();
}
7:图片样式的处理
在POI中要在单元格内设置一张图片的话则需要使用HSSFPatriarch(.xls格式)或XSSFDrawing(.xlsx格式)类。这两个类的上层接口都是Drawing,而Drawing接口及其子类是用来控制图片写入;图片位置的单元格位置或单元格内的偏移则使用ClientAnchor接口来指定;下面主要以看具体代码:
点开查看详情:单元格图片设置
java
public static void setCellImages() throws IOException {
// 先获取图片的字节输出流
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
// 读取图片信息,并放入到带有缓存区的图片类中(这里图片可以设置流对象或者文件路径)
BufferedImage read = ImageIO.read(new File("D:\\images\\tupian.png"));
// 将图片写到字节输出流中
ImageIO.write(read, "PNG", byteArrayOutputStream);
//===========================================================================
// 创建工作簿,并设置个默认的工作表
XSSFWorkbook workbook = new XSSFWorkbook();
XSSFSheet sheet = workbook.createSheet();
//===========================================================================
// 需求:组合一个3*13的单元格,并把图片设置平铺到这3*13的单元格中(单元格组合范围:B2:D14)
// 先合并单元格
sheet.addMergedRegion(CellRangeAddress.valueOf("B2:D14"));
// 写入图片到Excel中
// 构建图片写入的Drawing接口实现类对象
XSSFDrawing patriarch = sheet.createDrawingPatriarch();
// 指定图片的位置(重要)
XSSFClientAnchor xssfClientAnchor = new XSSFClientAnchor(
36000 * 5, 36000 * 5, -36000 * 5, -36000 * 5,
1, 1, 4, 14);
// 把图片写入到sheet指定位置
int picture = workbook.addPicture(byteArrayOutputStream.toByteArray(),
XSSFWorkbook.PICTURE_TYPE_PNG);
patriarch.createPicture(xssfClientAnchor, picture);
//创建流并写出文件关闭流
OutputStream out = new FileOutputStream("D:\\test4.xlsx");
workbook.write(out);
out.close();
workbook.close();
}
"上面具体详细介绍:"
Ⅰ:关于创建XSSFClientAnchor对象的 8 个int类型参数(4个为一组):
col1,row1,col2,row2
col1和row1(左上角多少行多少列)
上面案例是左上角第二行第二列(确定了坐标1,1单元格左上角位置)
col2和row2(右下角多少行多少列)
上面案例是左上角第十五行第五列(确定了坐标14,4单元格左上角位置)
dx1,dy1,dx2,dy2
图片偏移位置;使用"英式公制"单位,36000=1毫米
上面设置的 36000*5 代表5毫米。
Ⅱ:枚举XSSFWorkbook.PICTURE_TYPE_PNG类型
需要设置类型时使用,通过枚举来获取图片的格式
场景如:
PICTURE_TYPE_GIF
PICTURE_TYPE_TIFF
PICTURE_TYPE_PNG
PICTURE_TYPE_JPEG...
(五):Excel百万数据导出
我们知道Excel可以分为早期的97-2003版本(使用POI的HSSF对象操作)和2007版本(使用POI的XSSF操作),两者对百万数据的支持如下:
- Excel(97-2003):在POI中使用HSSF对象时,97-2003最多只允许存储65536条数据,一般用来处理较少的数据量。这时对于百万级别数据,Excel肯定容纳不了。
- Excel(2007):POI中使用XSSF对象时,它可以直接支持2007以上版本,因为它采用ooxml格式。这时excel可以支持1048576条数据,单个sheet表就支持近百万条数据。但实际运行时还可能存在问题,原因是执行POI报表所产生的行对象,单元格对象,字体对象,它们都不会销毁,这就导致OOM的风险。
<math xmlns="http://www.w3.org/1998/Math/MathML"> 如何解决百万数据导出内存溢出问题: \color{#f00}{如何解决百万数据导出内存溢出问题:} </math>如何解决百万数据导出内存溢出问题:
使用SXSSFWorkbook对象,它是Apache POI中针对大数据量Excel导出而设计的一种流式工作簿实现。它能够有效地解决大数据导出时可能出现的内存溢出问题,具体有以下几点原因:
- 基于临时文件存储数据:SXSSFWorkbook在内部使用一种临时文件来存储数据,而不是将所有数据都存储在内存中。这样就可以避免在处理大量数据时耗尽内存空间导致内存溢出。
- 限制内存使用:SXSSFWorkbook有一个内存敏感的阈值,当工作簿占用的内存达到设定的阈值时,会自动将数据写入到临时文件中,释放内存空间。这样可以保持内存的稳定使用,避免过度占用内存。
- 基于XSSF实现:SXSSFWorkbook是基于XSSF(XML Spreadsheet Format)实现的,因此能够支持XLSX格式的Excel文件,同时提供了与XSSFWorkbook类似的API接口,方便使用和迁移。
- 优化写入性能:SXSSFWorkbook在数据写入时进行了优化处理,采用了一种缓冲机制来提高写入性能,同时避免造成内存压力过大。
- 自动清理临时文件:在SXSSFWorkbook关闭时,会自动清理临时文件,确保不会留下无用的临时文件占用磁盘空间。
我们可以设置 "内存敏感的阈值" ,这个阈值用来控制内存使用的情况。在SXSSFWorkbook中,默认的内存敏感的阈值是100行。这意味着当工作簿占用的内存达到100行数据时,SXSSFWorkbook会自动将数据写入到临时文件中,以释放内存空间。
也可以使用默认的内存敏感阈值:new SXSSFWorkbook(SXSSFWorkbook.DEFAULT_WINDOW_SIZE)
<math xmlns="http://www.w3.org/1998/Math/MathML"> 导出百万 E x c e l 思路: \color{#f00}{导出百万Excel思路:} </math>导出百万Excel思路:
HSSFWorkbook导出数据有限,而XSSFWorkbook虽然可以导每个Sheet一百多万条数据,但是这会导致内存溢出;导出百万Excel数据只能使用SXSSFWorkBook了。
假设现在导出200万条数据,那么我每个Sheet工作表存放100万条数据(实则最多可存放1048576条);这样两个Sheet就可以装下200万条数据了;但是需要导出的十万或百万数据从哪来呢?在日常开发中数据来自数据库查询,可千万不能一次性把所有数据都查出来了,这样内存会爆的,我们可以通过分页每次查询部分数据,再放到SXSSFWorkbook创建的Excel中;还需要注意的是:导出大量数据记录则不能使用模板和使用太多样式,因为这些样式的创建也都是占用内存的,防止内存溢出。下面就一起来操作吧:
java
public static void main(String[] args) throws IOException {
// 开始时间
long startTime = System.currentTimeMillis();
// 创建工作薄存放百万数据;要替换成XSSFWorkbook可能会报内存溢出且执行速度很慢
// SXSSFWorkbook构造器可以设置内存阈值,就是处理完多少行后释放内存,并进行下一轮的操作
SXSSFWorkbook workbook = new SXSSFWorkbook();
// 创建工作表
Sheet sheet = null;
// 分页模拟查询数据库数据
int pageSize = 1; // 当前页码
int num = 0; // 记录了处理的总行数
while (true) {
// 模拟分页查询数据库数据;查询完本轮数据完页码每次累加;每次查询一万条(具体查询以数据大小定义)
List<Student> studentList = findPage(pageSize++, 10000);
// 查不到数据则退出循环
if (studentList.isEmpty()) {
break;
}
// 计算共有多少工作表(总记录)
if (num % 1000000 == 0) {
String sheetName = "第" + ((num / 1000000) + 1) + "个工作表";
System.out.println("创建:" + sheetName);
sheet = workbook.createSheet(sheetName);
// 添加每个工作表标题
String[] titles = {"ID", "姓名", "手机号", "地址"};
Row rowTitle = sheet.createRow(0);
for (int i = 0; i < titles.length; i++) {
rowTitle.createCell(i).setCellValue(titles[i]);
}
}
// 循环遍历数据
for (Student student : studentList) {
// 每次循环后都创建一条记录表信息;获取当前最后一行并且+1,代表获取新行
Row row = sheet.createRow(sheet.getLastRowNum() + 1);
// 创建记录列
row.createCell(0).setCellValue(student.getId());
row.createCell(1).setCellValue(student.getName());
row.createCell(2).setCellValue(student.getPhone());
row.createCell(3).setCellValue(student.getProvince());
// 记录处理行数++
num++;
}
}
//创建流并写出文件关闭流
OutputStream out = Files.newOutputStream(Paths.get("D:\\test4.xlsx"));
workbook.write(out);
out.close();
workbook.close();
// 结束时间
System.out.println("总用时:" + (System.currentTimeMillis() - startTime) / 1000 + " 秒");
}
/***
* 模拟分页方法,传入参数后模拟去查询数据库并返回分页后的数据(这个方法不用管,能用就行)
* @param currentPage 当前页
* @param numPages 每页数
*/
public static List<Student> findPage(int currentPage, int numPages) {
// 总记录数为200万条,不管怎么分页都是200万
int total = 2000000;
ArrayList<Student> result = new ArrayList<>();
currentPage = currentPage == 0 ? currentPage + 1 : currentPage;
// 计算起始ID
int startID = currentPage * numPages - numPages;
// 若超出则返回空对象,未超出则返回数据
if (currentPage * numPages > total && currentPage > 1) {
return result;
} else {
startID = startID + 1;
int s = 0;
if (currentPage <= 1 && currentPage * numPages > total) {
s = total;
} else {
s = currentPage * numPages;
}
Random random = new Random();
for (int i = startID; i <= s; i++) {
int anInt = random.nextInt(6);
Student student = new Student(i, "测试" + i, "176" + (anInt++) + "13"
+ (anInt++) + "3" + (anInt++) + "8" + (anInt++), "安徽");
result.add(student);
}
}
return result;
}
// 自定义类
public class Student {
private Integer id; // 主键ID
private String name; // 姓名
private String phone; // 电话
private String province; // 省份
// 省略构造器,get,set方法
}
(六):Excel百万数据导入
百万级的数据导入场景其实并不多,但若真的遇到了,使用普通的HSSF或者XSSF的工作薄一行行读取的话,那么大数据量会报OOM内存溢出;所以我使用如下的方式来读取我那百万级的数据;处理思路就是加载Excel并读取,读取其实是按行读取的,把读取的指定数据存放到集合中,当读取的数据超过最大容量,则会通过回调的方式处理完本次数据,处理完成以后继续读取Excel并放到集合中。
点开查看详情:ExcelParse解析器
java
/***
* Excel解析器(用来读取Excel数据)
* 下面这段代码是固定的,可以复制使用,但需要注意传入的是,不同的Excel可能有着不同的handler处理方式
* @author Anhui AntLaddie (掘金蚂蚁小哥)
* @version 1.0
**/
public class ExcelParse {
/***
* Excel解析工具方法
* @param inputFilePath 文件输入路径信息
* @param handler 处理器(不同的Excel有着不同的处理方式)
*/
public void parse(String inputFilePath,
XSSFBSheetHandler.SheetContentsHandler handler) {
// 打开一个OPCPackage对象;以只读方式打开指定路径下的Excel文件
try (OPCPackage opcPackage = OPCPackage.open(inputFilePath, PackageAccess.READ)) {
// 使用 XSSFReader 对象来读取 OPCPackage 中的内容
XSSFReader reader = new XSSFReader(opcPackage);
// 获取 Excel 文件中的共享字符串表
SharedStrings stringsTable = reader.getSharedStringsTable();
// 获取 Excel 文件中的样式表信息
StylesTable stylesTable = reader.getStylesTable();
// 创建SAX解析器,然后配置为支持命名空间,并最终获取一个能够解析XML文档的XMLReader对象。
// 注:org.xml.sax.XMLReader;下的类
SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
saxParserFactory.setNamespaceAware(true);
XMLReader xmlReader = saxParserFactory.newSAXParser().getXMLReader();
// 设置 XMLReader 的内容处理器,用于处理 Excel 表格中的数据。
// stylesTable:表示Excel文件中的样式表,(单元格的格式、字体、颜色等样式信息)
// stringsTable:代表Excel文件中的共享字符串表,其中存储了单元格中使用的字符串。
// handler:表示自定义的处理器对象,用于对Excel表格中的每行每列数据进行处理。
// false:则表示不忽略空白单元格(若设置true代表忽略空白单元格字符串)
xmlReader.setContentHandler(new XSSFSheetXMLHandler(stylesTable,
stringsTable, handler, false));
// 循环遍历,处理Excel中的每个工作表数据
// XSSFReader中获取Excel文件的工作表数据,
//并创建一个XSSFReader.SheetIterator的实例来迭代处理各个工作表
XSSFReader.SheetIterator sheetIterator
= (XSSFReader.SheetIterator) reader.getSheetsData();
while (sheetIterator.hasNext()) {
InputStream sheetStream = sheetIterator.next();
// 注:org.xml.sax.InputSource;下的类
InputSource sheetSource = new InputSource(sheetStream);
try {
xmlReader.parse(sheetSource);
} finally {
sheetStream.close();
}
}
} catch (Exception e) {
// 这里请改成log日志方式打印
System.out.println("Excel解析处理异常:" + e.getMessage());
}
}
}
java
"自定义SheetHandler的Excel处理器(代码基本无需改动)"
/***
* 自定义sheet基于Sax的解析处理器
* @author Anhui AntLaddie(掘金蚂蚁小哥)
* @version 1.0
**/
public class SheetHandler implements XSSFBSheetHandler.SheetContentsHandler {
// 集合最大存放容量
public static int MAXIMUM_ROWS = 100000;
// 创建一个集合数组,读取的数据全部往集合内存放
private List<Map<String, String>> dataLists = new ArrayList<>();
// 每行数据信息
private Map<String, String> cellData = null;
// 单元格对应关系(就是单元格的标题和实体属性名的对应关系)
private Map<String, String> cellCorrespondence = new HashMap<>();
// 读取的数据处理回调函数(当返回true代表成功处理回调;当返回false则停止后续操作)
private final Function<List<Map<String, String>>, Boolean> readDataProcessFun;
/***
* 构造函数
* @param cellCorrespondence 单元格与对象属性对应关系
* @param readDataProcessFun 读取的数据回调函数
*/
public SheetHandler(Map<String, String> cellCorrespondence,
Function<List<Map<String, String>>, Boolean> readDataProcessFun) {
this.cellCorrespondence = cellCorrespondence;
this.readDataProcessFun = readDataProcessFun;
}
/***
* 当开始解析某一行的时候触发
* @param rowNum 当前处理的行号
*/
@Override
public void startRow(int rowNum) {
// 若等于0则代表是标题行,后面代码不在处理,反之为数据行需要处理
if (rowNum == 0) {
cellData = null;
} else {
cellData = new HashMap<>();
}
}
/***
* 当结束解析某一行的时候出发
* @param rowNum 当前处理的行号
*/
@Override
public void endRow(int rowNum) {
// 当前行读取完成后把当前行数据添加到集合中
if (cellData != null && !cellData.isEmpty()) {
dataLists.add(cellData);
}
// 添加完成后将当前行数据指定为null,继续下一行的读取
cellData = null;
// 校验当前存储的Excel行记录是否超过指定的最大容量
if (dataLists.size() >= MAXIMUM_ROWS) {
Boolean result = readDataProcessFun.apply(dataLists);
// 若result为true则代表当前一批数据处理完成,进行下一批数据的读取
// 若result为false则代表当前一批数据处理完成,强行停止Excel后续的操作
if (result) {
dataLists = new ArrayList<>();
} else {
throw new RuntimeException("导入Excel文件数据失败,手动干预停止!");
}
}
}
/***
* 对当前处理的行里的每一个单元格进行处理
* @param cellReference 单元格名称,如(A12,C4,D166)这种单元格名称
* @param formattedValue 单元格内的数据信息
* @param comment 单元格的批注信息
*/
@Override
public void cell(String cellReference, String formattedValue, XSSFComment comment) {
// 非标题数据则进行处理
if (cellData != null) {
// 剔除单元格名称的后面数值
String cellName = cellReference.replaceAll("\\d", "");
// 数据添加
if (cellCorrespondence.containsKey(cellName)) {
cellData.put(cellCorrespondence.get(cellName), formattedValue);
}
}
}
/***
* 当本页工作表写完后则强行刷出一次操作,将剩余的集合数据行处理掉
*/
@Override
public void endSheet() {
if (!dataLists.isEmpty()) {
if (readDataProcessFun.apply(dataLists)) {
dataLists = new ArrayList<>();
} else {
throw new RuntimeException("导入Excel文件数据失败,手动干预停止!");
}
}
}
/***
* 超链接单元格(一般用不上)
*/
@Override
public void hyperlinkCell(String cellReference, String formattedValue,
String url, String toolTip, XSSFComment comment) {}
}
java
/***
* 调用测试(我这边电脑200w条数据18-20秒)
*/
public static void main(String[] args) {
// 开始时间
long startTime = System.currentTimeMillis();
HashMap<String, String> maps = new HashMap<>();
maps.put("A", "id");
maps.put("B", "name");
maps.put("C", "phone");
maps.put("D", "province");
// 创建回调方式处理
Function<List<Map<String, String>>, Boolean> getNameFunction = e -> {
// 这里将返回每次读取的数据,在这里可以做业务处理,以及存放数据库
// 返回true代表本批次数据处理完成,返回false则强行终止后面的读取操作
// 读取的数据如下方式,上面的map关系需要对应好单元格
// [{province=安徽, phone=17631343586, name=测试1999998, id=1999998},
// {province=安徽, phone=17631343586, name=测试1999999, id=1999999},
// {province=安徽, phone=17641353687, name=测试2000000, id=2000000}......]
return true;
};
SheetHandler objectSheetHandler = new SheetHandler(maps, getNameFunction);
new ExcelParse().parse("E:\\test4.zip", objectSheetHandler);
System.out.println("执行完成");
System.out.println("总用时:" + (System.currentTimeMillis() - startTime) / 1000 + " 秒");
}
三:拥抱便捷开发EasyExcel
对于阿里巴巴的EasyExcel的工具类还是比较推荐的,官方有着详细的代码示例以及介绍;EasyExcel它是针对Apache POI技术的封装和优化,主要解决了POI技术的耗内存问题,并且提供了较好的API使用。不需要大量的代码就可以实现excel的操作功能。