POI简介(Apache POI),Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office格式档案读和写的功能。
Apache POI官网
- HSSF - 提供读写Microsoft Excel格式档案的功能。(.xls)
- XSSF - 提供读写Microsoft Excel OOXML格式档案的功能。(.xlsx)
- HWPF - 提供读写Microsoft Word格式档案的功能。
- HSLF - 提供读写Microsoft PowerPoint格式档案的功能。
- HDGF - 提供读写Microsoft Visio格式档案的功能。
环境准备
- 创建普通Maven项目
- 导入相关依赖
<dependencies>
<!--xls(03)-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
<!--xlsx(07)-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
<!--日期格式化工具-->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.1</version>
</dependency>
<!--test-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
写Excel的功能
我们需要导出效果如下:
Excel的文件格式分为Excel2003【后缀名为xls】和Excel2007【后缀名为xlsx】两种,这两种的代码基本上一致的.
- Excel2003写的功能
@Test
public void testWrite03() throws IOException {
// 创建新的Excel 工作簿
Workbook workbook = new HSSFWorkbook();
// 在Excel工作簿中建一工作表,其名为默认名 Sheet0
//Sheet sheet = workbook.createSheet();
// 如要新建一名为"会员登录统计"的工作表,其语句为:
Sheet sheet = workbook.createSheet("疫苗接种统计");
// 创建行(row 1)
Row row1 = sheet.createRow(0);
// 创建单元格(col 1-1)第一行的第一列
Cell cell11 = row1.createCell(0);
//设置单元格中的内容
cell11.setCellValue("今日人数");
// 创建单元格(col 1-2)第一行的第二列
Cell cell12 = row1.createCell(1);
cell12.setCellValue(666);
// 创建行(row 2)
Row row2 = sheet.createRow(1);
// 创建单元格(col 2-1)第二行的第一列
Cell cell21 = row2.createCell(0);
cell21.setCellValue("统计时间");
//创建单元格(col 2-2)第二行的第二列
Cell cell22 = row2.createCell(1);
String dateTime = new DateTime().toString("yyyy-MM-dd HH:mm:ss");
cell22.setCellValue(dateTime);
// 新建一输出文件流(注意:要先创建文件夹)
FileOutputStream out = new FileOutputStream("d:/test-write03.xls");
// 把相应的Excel 工作簿存盘,如果是在修改该文件时,不能打开该文件,io正在占用导致无法写入
workbook.write(out);
// 操作结束,关闭流
out.close();
//关闭工作簿
workbook.close();
System.out.println("文件生成成功");
}
- Excel2007写功能
@Test
public void testWrite07() throws IOException {
// 创建新的Excel 工作簿
Workbook workbook = new XSSFWorkbook();
// 新建一输出文件流(注意:要先创建文件夹)
FileOutputStream out = new FileOutputStream("d:/test-write07.xlsx");
//和上面一样,只是用到的工作簿对象不一样和文件后缀名不一样
}
大文件写入
- 使用HSSF【Excel2003】
缺点:最多只能处理65536行,否则会抛出异常
java.lang.IllegalArgumentException: Invalid row number (65536) outside allowable range (0…65535)
优点:过程中写入缓存,不会操作磁盘,最后一次性写入磁盘,速度快
@Test
public void testWrite03BigData() throws IOException {
//记录开始时间
long begin = System.currentTimeMillis();
//创建一个HSSFWorkbook
Workbook workbook = new HSSFWorkbook();
//创建一个sheet
Sheet sheet = workbook.createSheet();
//xls文件最大支持65536行
for (int rowNum = 0; rowNum < 65537; rowNum++) {
//创建一个行
Row row = sheet.createRow(rowNum);
for (int cellNum = 0; cellNum < 10; cellNum++) {//创建单元格
Cell cell = row.createCell(cellNum);
cell.setCellValue(cellNum);
}
}
System.out.println("done");
FileOutputStream out = new FileOutputStream("d:/test-write03-bigdata.xls");
workbook.write(out);
// 操作结束,关闭文件
out.close();
//记录结束时间
long end = System.currentTimeMillis();
System.out.println((double)(end - begin)/1000);
}
- 使用XSSF【Excel2007】
缺点:写数据时速度非常慢,非常耗内存,也会发生内存溢出,如100万条
优点:可以写较大的数据量,如20万条
@Test
public void testWrite07BigData() throws IOException {
...
//创建一个XSSFWorkbook
Workbook workbook = new XSSFWorkbook();
...
FileOutputStream out = new FileOutputStream("d:/test-write07-bigdata.xlsx");
...
}
- 使用SXSSF
优点:可以写非常大的数据量,如100万条甚至更多条,写数据速度快,占用更少的内存
注意:
过程中会产生临时文件,需要清理临时文件
默认由100条记录被保存在内存中,如果超过这数量,则最前面的数据被写入临时文件
如果想自定义内存中数据的数量,可以使用new SXSSFWorkbook(数量)
@Test
public void testWrite07BigDataFast() throws IOException {
//记录开始时间
long begin = System.currentTimeMillis();//时间戳毫秒数
//创建一个SXSSFWorkbook
Workbook workbook = new SXSSFWorkbook();
...
FileOutputStream out = new FileOutputStream("d:/test-write07-bigdata-fast.xlsx");
//写.....
workbook.write(out);
// 操作结束,关闭文件
out.close();
//强转真实类型,清除临时文件
((SXSSFWorkbook)workbook).dispose();
//记录结束时间
long end = System.currentTimeMillis();
System.out.println((double)(end - begin)/1000);
}
SXSSFWorkbook-来至官方的解释:实现“BigGridDemo”策略的流式XSSFWorkbook版本。这允许写入非常大的文件而不会耗尽内存,因为任何时候只有可配置的行 部分被保存在内存中。
请注意,仍然可能会消耗大量内存,这些内存基于您正在使用的功能,例如单元格合并区域,注释…仍然只存储在内存中,因此如果广泛使用,可能需要大量内存。
读Excel的功能
@Test
public void testRead03() throws Exception{
//根据指定文件创建文件输入流
InputStream is = new FileInputStream("d:/test-write03.xls");
//创建工作簿对象
Workbook workbook = new HSSFWorkbook(is);
//获得第一张工作表
Sheet sheet = workbook.getSheetAt(0);
// 获得第一行
Row row = sheet.getRow(0);
// 读取第一行第一列
Cell cell = row.getCell(0);
// 输出单元内容,单元格内容不同,使用不同方法获取
System.out.println(cell.getStringCellValue());
// 操作结束,关闭文件
is.close();
}
- Excel2007读功能
@Test
public void testRead07() throws Exception{
InputStream is = new FileInputStream("d:/test-write07.xlsx");
Workbook workbook = new XSSFWorkbook(is);
Sheet sheet = workbook.getSheetAt(0);
//和上面操作一样
is.close();
}
- 读取不同类型的数据
内容类型不一样时该怎么读
@Test
public void testCellType() throws Exception {
InputStream is = new FileInputStream("d:/商品信息表.xlsx");
Workbook workbook = new XSSFWorkbook(is);
Sheet sheet = workbook.getSheetAt(0);
Row rowTitle = sheet.getRow(0);
// 读取这一行所有内容
if (rowTitle != null) {// 行不为空
// 读取这一行有多少列
int cellCount = rowTitle.getPhysicalNumberOfCells();
for (int cellNum = 0; cellNum < cellCount; cellNum++) {
//获取这一行 cellNum列位置的单元格
Cell cell = rowTitle.getCell(cellNum);
//判空
if (cell != null) {
//读取内容并打印
String cellValue = cell.getStringCellValue();
System.out.print(cellValue + "|");
}
}
System.out.println();
}
// 读取商品列表数据,获取最大行数
int rowCount = sheet.getPhysicalNumberOfRows();
for (int rowNum = 1; rowNum < rowCount; rowNum++) {
//获取行
Row rowData = sheet.getRow(rowNum);
if (rowData != null) {// 行不为空
// 获取这一行有多少列
int cellCount = rowTitle.getPhysicalNumberOfCells();
for (int cellNum = 0; cellNum < cellCount; cellNum++) {
System.out.print("【" + (rowNum + 1) + "-" + (cellNum + 1) + "】");
//获取这一行 cellNum列位置的单元格
Cell cell = rowData.getCell(cellNum);
if (cell != null) {
//获取单元格类型
CellType cellType = cell.getCellType();
//定义字符串用于接收内容
String cellValue = "";
//判断单元格数据类型
if(CellType.STRING.equals(cellType)){
System.out.print("【STRING】");
cellValue = cell.getStringCellValue();
}else if(CellType.BOOLEAN.equals(cellType)){
System.out.print("【BOOLEAN】");
cellValue = String.valueOf(cell.getBooleanCellValue());
}else if(CellType.NUMERIC.equals(cellType)){
System.out.print("【NUMERIC】");
//使用poi的工具类,判断是否是日期内容
if(DateUtil.isCellDateFormatted(cell)){
System.out.print("【日期】");
Date date = cell.getDateCellValue();
cellValue = new DateTime(date).toString("yyyy-MM-dd");
}else{
System.out.print("【转换成字符串】");
cellValue = String.valueOf(cell.getNumericCellValue());
}
}else if(CellType.BLANK.equals(cellType)){
//空白内容
System.out.print("【BLANK】");
}else{
System.out.println(cellType);
}
//打印内容
System.out.println(cellValue);
}
}
}
}
is.close();
}
读取Excel大文件内存溢出解决
Web环境集成Excel
- 添加POI依赖
导出表格
如导出权限表格
页面添加按钮和点击事件
<a href="#" class="btn btn-warning btn-input" style="margin: 10px" onclick="exportOP()">
<span class="glyphicon glyphicon-save"></span> 导出
</a>
function exportOP(){
//点击响应函数
//打开一个新窗口发起请求,如果响应的是一个二进制流文件则自动下载
window.open("/permission/export")
}
后端代码
@RequestMapping("/export")
@ResponseBody
public void export(HttpServletResponse response) throws IOException {
//设置响应内容为xlsx文件
response.setHeader("Content-Disposition","attachment;filename=permissionData.xlsx");
Workbook is = permissionService.export();
//工作簿使用响应对象写出到浏览器
is.write(response.getOutputStream());
}
@Override
public Workbook export() {
//模拟查询到的数据
List<Permission> permissions = listAll();
//创建工作簿
Workbook workbook = new XSSFWorkbook();
//创建工作表
Sheet sheet = workbook.createSheet("权限列表");
//创建行
Row row = sheet.createRow(0);
//创建列并设置内容
row.createCell(0).setCellValue("编号");
row.createCell(1).setCellValue("权限名称");
row.createCell(2).setCellValue("权限表达式");
Permission permission = null;
//取出权限内容
for(int i=0;i<permissions.size();i++){
//多少个权限对象,就有多少行内容
row = sheet.createRow(i+1);
//取出一个权限信息
permission = permissions.get(i);
//设置这一行的权限内容
row.createCell(0).setCellValue(permission.getId());
row.createCell(1).setCellValue(permission.getName());
row.createCell(2).setCellValue(permission.getExpression());
}
//返回这个工作薄
return workbook;
}
导入表格
添加按钮
<a href="#" class="btn btn-info btn-input" style="margin: 10px" onclick="importOP()">
<span class="glyphicon glyphicon-open"></span> 导入
</a>
添加模态框,
因为导入的表格不能让人乱填数据,所有必须提供模板文件下载,让用户按照模板文件填写
<#-- 模态框 -->
<div class="modal fade" id="importModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title" id="myModalLabel">导入</h4>
</div>
<form class="form-horizontal" enctype="multipart/form-data" method="post" id="importForm">
<div class="modal-body">
<div class="form-group" style="margin-top: 10px;">
<label for="name" class="col-sm-3 control-label"></label>
<div class="col-sm-6">
<!-- 文件上传框 -->
<input id="uploadFile" type="file" name="file"/>
</div>
</div>
<div class="form-group" style="margin-top: 10px;">
<div class="col-sm-3"></div>
<div class="col-sm-6">
<a href="#" onclick="downloadTemplateOP()" class="btn btn-success" >
<span class="glyphicon glyphicon-download"></span> 下载模板
</a>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">取消</button>
<button type="button" onclick="importSave()" class="btn btn-primary btn-submit">保存</button>
</div>
</form>
</div>
</div>
</div>
添加点击事件
function importOP(){
//点击弹出模态框
$("#importModal").modal("show");
}
function downloadTemplateOP(){
//打开窗口,访问静态资源下载模板文件,如配置了静态资源访问路径则加上
window.open("/Template.xlsx")
}
function importSave(){
//点击保存,使用异步提交,不用等待保存
var $file1 = $("#uploadFile").val();//用户文件内容(文件)
// 判断文件是否为空
if ($file1 == "") {
Swal.fire({
text: "请选择上传的目标文件! ",
icon: 'warning',
})
return false;
}
var formData = new FormData();//这里需要实例化一个FormData来进行文件上传,这是一个二进制流表单参数封装对象
formData.append("file",$("#uploadFile")[0].files[0]);//文件名 , //选中文件标签,[0]表示获取原生的html标签,files[0]表示获取这个标签中的第一个文件(用户在选择时可以选择多个文件)
$.ajax({
type : "post",
url : "/permission/importExcel",
data : formData,
processData : false,//异步表单提交时,如果使用formData携带流参数时需要配置的属性,表明不携带其他参数
contentType : false,
success : function(data){
if (data.success) {
Swal.fire({
text: data.msg,
icon: 'success',
})
//隐藏模态框
$("#importModal").modal("hide");
//刷新权限表格标签
$('#table').bootstrapTable('refresh');
}else{
Swal.fire({
text: "请选择上传的目标文件! ",
icon: 'warning',
})
}}
});
}
后端代码
按上面模板文件下载路径,提供一个模板文件放在static/目录下, 注意要对应文件名
导入接口
@RequestMapping("/importExcel")
@ResponseBody
public JsonResult importExcel(MultipartFile file){
try{
int count = permissionService.importExcel(file);
return new JsonResult(true,"成功导入:"+count+"条记录");
}catch(Exception ex){
return new JsonResult(false,"导入数据失败");
}
}
@Override
public int importExcel(MultipartFile file) throws IOException {
//文件获得输入流放入工作簿中
Workbook workbook = new XSSFWorkbook(file.getInputStream());
Sheet sheet = workbook.getSheetAt(0);
//获取最后一行的索引
int rowNum = sheet.getLastRowNum();
int insertCount = 0;
Row row = null;
Cell c1 = null;
Cell c2 = null;
//跳过第一行
for(int i=1;i<rowNum;i++){
row = sheet.getRow(i);
c1 = row.getCell(0);
c2 = row.getCell(1);
//判断单元格为空就不处理
if(c1!=null && c2!=null){
String name = c1.getStringCellValue();
String expression = c2.getStringCellValue();
//判断是否有内容
if(StringUtils.hasText(name) && StringUtils.hasText(expression)){
//判断表达式是否已经存在
int count = permissionMapper.getCountByExpression(expression);
if(count==0){
//不存在该权限
Permission p = new Permission();
p.setName(name);
p.setExpression(expression);
permissionMapper.insert(p);
insertCount++;
}
}
}
}
//返回保存多少条权限
return insertCount;
}