开发中经常会设计到excel的处理,如导出Excel,导入Excel到数据库中,几年前操作Excel常用的有两个框架,一个是apache 的poi, 另一个是 Java Excel,不过现在又出现了EasyPoi和阿里开源的EasyExcel,这两个后面再进行整理
Apache POI 是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程式对Microsoft Office(Excel、WORD、PowerPoint、Visio等)格式档案读和写的功能。POI为“Poor Obfuscation Implementation”的首字母缩写,意为“可怜的模糊实现”。
官方文档:http://poi.apache.org/apidocs/index.html
Java Excel是一开放源码项目,通过它Java开发人员可以读取Excel文件的内容、创建新的Excel文件、更新已经存在的Excel文件。jxl 由于其小巧 易用的特点, 逐渐已经取代了 POI-excel的地位, 成为了越来越多的java开发人员生成excel文件的首选。
XLS
格式档案的功能。【03版本】XLSX
格式档案的功能。【07版本】常用的类和方法
HSSFWorkbook :工作簿,代表一个excel的整个文档
HSSFWorkbook()
; // 创建一个新的工作簿HSSFSheet createSheet(String sheetname)
; 创建一个新的SheetHSSFSheet getSheetAt(int index)
; // 通过索引获取Sheet,索引从0开始setActiveSheet(int index)
; 设置默认选中的工作表write(File newFile);
//往指定文件写入数据HSSFSheet:工作表
HSSFRow createRow(int rownum);
创建新行,需要指定行号,行号从0开始HSSFRow getRow(int index);
根据索引获取指定的行int addMergedRegion(CellRangeAddress region);
合并单元格getLastRowNum();
获取最后的行的索引,没有行或者只有一行的时候返回0HSSFRow :行
HSSFCell createCell(int column);
创建新的单元格short getLastCellNum();
获取最后的单元格号,如果单元格有第一个开始算,lastCellNum就是列的个数HSSFCell:单元格
setCellValue(String value);
设置单元格的值String getStringCellValue();
获取单元格中的字符串值setCellFormula(String formula);
设置计算公式,计算的结果作为单元格的值,也提供了异常常用的函数,如求和"sum(A1,C1)"、日期函数、字符串相关函数、CountIf和SumIf函数、随机数函数等HSSFFont:字体,
HSSFName:名称
Excel中的工作簿、工作表、行、单元格中的关系:
一个Excel文件对应于一个workbook(HSSFWorkbook),
一个workbook可以有多个sheet(HSSFSheet)组成, 一个sheet是由多个row(HSSFRow)组成,
一个row是由多个cell(HSSFCell)组成
1.创建一个普通的maven项目
2.引入Apache poi的依赖
<!--xLs(03版本)--> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version >3.9</version> </dependency> <!--xLsx(07版本)--> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>3.9</version> </dependency>
@Test public void createExcel() throws IOException{ // 获取桌面路径 FileSystemView fsv = FileSystemView.getFileSystemView(); String desktop = fsv.getHomeDirectory().getPath(); System.out.println("桌面路径:"+desktop); String filePath = desktop + "/test.xls"; File file = new File(filePath); OutputStream outputStream = new FileOutputStream(file); //1.创建一个工作簿 HSSFWorkbook workbook = new HSSFWorkbook(); //2.创建一个名叫test1的工作表 HSSFSheet sheet = workbook.createSheet("test1"); //3.创建第一行并设置值 HSSFRow row = sheet.createRow(0); row.createCell(0).setCellValue("id"); row.createCell(1).setCellValue("订单号"); row.createCell(2).setCellValue("下单时间"); row.createCell(3).setCellValue("个数"); row.createCell(4).setCellValue("单价"); row.createCell(5).setCellValue("订单金额"); row.setHeightInPoints(30); // 设置行的高度 //4.创建第二行并设置值 HSSFRow row1 = sheet.createRow(1); row1.createCell(0).setCellValue("1"); row1.createCell(1).setCellValue("NO00001"); //4.1设置第三列值,并做日期格式化 HSSFCellStyle cellStyle2 = workbook.createCellStyle(); HSSFCreationHelper creationHelper = workbook.getCreationHelper(); cellStyle2.setDataFormat(creationHelper.createDataFormat().getFormat("yyyy-MM-dd HH:mm:ss")); sheet.setColumnWidth(2, 20 * 256); // 设置列的宽度 HSSFCell cell2 = row1.createCell(2); cell2.setCellStyle(cellStyle2); cell2.setCellValue(new Date()); //4.2设置第四列值 row1.createCell(3).setCellValue(2); //4.3设置第五列值 保留两位小数 HSSFCellStyle cellStyle3 = workbook.createCellStyle(); cellStyle3.setDataFormat(HSSFDataFormat.getBuiltinFormat("0.00")); HSSFCell cell4 = row1.createCell(4); cell4.setCellStyle(cellStyle3); cell4.setCellValue(29.5); //4.3设置第六列值 货币格式化 HSSFCellStyle cellStyle4 = workbook.createCellStyle(); HSSFFont font = workbook.createFont(); font.setFontName("华文行楷"); font.setFontHeightInPoints((short)15); font.setColor(HSSFColor.RED.index); cellStyle4.setFont(font); HSSFCell cell5 = row1.createCell(5); cell5.setCellFormula("D2*E2"); // 设置计算公式 //4.3.1获取计算公式的值 HSSFFormulaEvaluator e = new HSSFFormulaEvaluator(workbook); cell5 = e.evaluateInCell(cell5); System.out.println("计算得到的值为:"+cell5.getNumericCellValue()); //设置默认选中第一张工作表 workbook.setActiveSheet(0); //将数据写入文件 workbook.write(outputStream); //关闭流 outputStream.close(); }
运行结果:
桌面生成的文件为:
注意:07版本和03版本的区别是:
XSSFWorkbook
,03版本使用的是HSSFWorkbook
.xlsx
,03版本的文件后缀名为.xls
65536
行读写操作基本一样,不同的就是对象和后缀名不一样而已
java.lang.IllegalArgumentException:Invalid row number (65536) outside allowable range (0.. 65535)
@Test public void testwrite03BigData() throws IOException { //时间 long begin = System.currentTimeMillis(); //创建一个薄 Workbook workbook = new HSSFWorkbook(); //创建表 Sheet sheet = workbook.createSheet(); //写入数据 for (int rowNum = 0; rowNum < 65536; rowNum++) { Row row = sheet.createRow(rowNum); for (int cellNum = 0; cellNum < 10; cellNum++) { Cell cell = row.createCell(cellNum); cell.setCellValue(cellNum); } } FileOutputStream fos = new FileOutputStream(PATH + "03版本Excel大量数据测试.xls"); workbook.write(fos); fos.close(); System.out.println("over"); long end = System.currentTimeMillis(); System.out.println((double) (end - begin) / 1000); }
运行结果:
@Test public void testwrite07BigData() throws IOException { //时间 long begin = System.currentTimeMillis(); //创建一个薄 Workbook workbook = new XSSFWorkbook(); //创建表 Sheet sheet = workbook.createSheet(); //写入数据 for (int rowNum = 0; rowNum < 65536; rowNum++) { Row row = sheet.createRow(rowNum); for (int cellNum = 0; cellNum < 10; cellNum++) { Cell cell = row.createCell(cellNum); cell.setCellValue(cellNum); } } FileOutputStream fos = new FileOutputStream(PATH + "07版本Excel大量数据测试.xlsx"); workbook.write(fos); fos.close(); System.out.println("over"); long end = System.currentTimeMillis(); System.out.println((double) (end - begin) / 1000); }
运行结果:
可以发现07版本写入数据时非常慢,很耗内存,耗时比03版本还要长,接下来介绍另一种方式进行大数据写入,那就是SXSSF
优点:可以写非常大量的数据库,如100万条甚至更多条,写数据速度快,占用更少的内存
注意:
@Test public void testwrite07_S_BigData() throws IOException { //时间 long begin = System.currentTimeMillis(); //创建一个薄 Workbook workbook = new SXSSFWorkbook(100); //创建表 Sheet sheet = workbook.createSheet(); //写入数据 for (int rowNum = 0; rowNum < 65536; rowNum++) { Row row = sheet.createRow(rowNum); for (int cellNum = 0; cellNum < 10; cellNum++) { Cell cell = row.createCell(cellNum); cell.setCellValue(cellNum); } } FileOutputStream fos = new FileOutputStream(PATH + "07_S_版本Excel大量数据测试.xlsx"); workbook.write(fos); fos.close(); //清除临时缓存 ((SXSSFWorkbook)workbook).dispose(); System.out.println("over"); long end = System.currentTimeMillis(); System.out.println("耗时:"+(double) (end - begin) / 1000+"秒"); }
运行结果:速度大大提升了
SXSSWorkbook
来自官方解释:实现:BigGridDemo策略的流式XSSFWorkbook版本。这允许写入非常大的文件而不会耗尽内存,因为任何时候只有可配置的行部分被保存在内存中。
表数据如下所示:
表的特点是:里面的数据有字符串,数值,日期,布尔值,空值,所以读取的时候需要对表中的字段值进行类型判断,然后再进行解析,否则会报错:
@Test public void testCellType() throws Exception { //获取文件流 FileInputStream fis = new FileInputStream(PATH +"多类型测试表.xlsx"); //创建一个工作簿。使用 excel能操作的这边他都可以操作 Workbook workbook = new XSSFWorkbook(fis); Sheet sheet = workbook.getSheetAt(0); //获取标题内容 Row rowTitle = sheet.getRow(0); if (rowTitle != null) { //得到一行有多少列有数据 int cellCount = rowTitle.getPhysicalNumberOfCells(); for (int cellNum = 0; cellNum < cellCount; cellNum++) { Cell cell = rowTitle.getCell(cellNum); if (cell != null) { int cellType = cell.getCellType(); 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.println("[" + (rowNum + 1) + "-" + (cellNum + 1) + "]"); Cell cell = rowData.getCell(cellNum); //匹配列的数据类型 if (cell != null) { int cellType = cell.getCellType(); String cellValue = ""; switch (cellType) { case HSSFCell.CELL_TYPE_STRING://字符 System.out.print("【 String】"); cellValue = cell.getStringCellValue(); break; case HSSFCell.CELL_TYPE_BOOLEAN://布尔 System.out.print("【 BOOLEAN】"); cellValue = String.valueOf(cell.getBooleanCellValue()); break; case HSSFCell.CELL_TYPE_BLANK://空 System.out.print("【 BLANK】"); break; case HSSFCell.CELL_TYPE_NUMERIC://数字(日期、普通数字) System.out.print("【 NUMERIC】"); if (HSSFDateUtil.isCellDateFormatted(cell)) {// 日期 System.out.print("--【日期】"); Date date = cell.getDateCellValue(); cellValue = new DateTime(date).toString("yyyy-MM-dd HH:mm:ss"); } else { //不是日期格式,防止数字过长! System.out.print("--【转换为字符串输出】"); cell.setCellType(HSSFCell.CELL_TYPE_STRING); cellValue = cell.toString(); } break; case HSSFCell.CELL_TYPE_ERROR://错误 System.out.print("【 数据类型错误】"); break; } System.out.println(cellValue); } } } } //关闭流 fis.close(); }
运行结果:
我需要将下方列表的信息导出为Excel
controller层:
@RequestMapping(value = "/exportSearchEcomizeActivityList", method = RequestMethod.GET) private void exportSearchEcomizeActivityList(HttpServletResponse response){ adminService.exportSearchEcomizeActivityList(response); }
实现类:
@Override public void exportSearchEcomizeActivityList(HttpServletResponse response) { ServletOutputStream outputStream = null; SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); try{ // 处理日期 2019-12-03 00:00:00 ~ 2019-12-11 00:00:00 String startDate =""; String endDate=""; String selectDate = request.getParameter("selectDate"); String activityName = request.getParameter("activityName"); if(selectDate != null && !"".equals(selectDate) && selectDate.indexOf("~") !=-1){ String[] splitDate = selectDate.split("~"); startDate = splitDate[0].trim(); endDate = splitDate[1].trim(); } //获取活动信息 List<EcomizeDateInfoPojo> activityList = ecomizeActivityMapper.findEcomizeActivityByListForExport(activityName); if(CollectionUtils.isNotEmpty(activityList)){ for (EcomizeDateInfoPojo activity : activityList) { // 分类名称 List<EcomizeActivityTypeModel> typeList = ecomizeActivityMapper.findActivityTypeByActivity(activity.getId()); if(CollectionUtils.isNotEmpty(typeList) && typeList != null){ String typeName = ""; for (EcomizeActivityTypeModel type : typeList) { if(type.getName() != null){ typeName += type.getName() +"/"; } } activity.setType_name(typeName.substring(0,typeName.length() - 1)); } // 参与人数 int takeNumber = ecomizeActivityMapper.findEcomizeActivityTakeNumber(activity.getId(),startDate,endDate); activity.setTakeNumber(takeNumber); // 提交订单人数 int putNumber = ecomizeActivityMapper.findEcomizeActivityPutNumber(activity.getId(),startDate,endDate); activity.setPutNumber(putNumber); // 拉新人数 int helpNumber = ecomizeActivityMapper.findEcomizeActivityHelpNumber(activity.getId(),startDate,endDate); activity.setHelpNumber(helpNumber); // 助力人次 int newNumber = ecomizeActivityMapper.findEcomizeActivityNewNumber(activity.getId(),startDate,endDate); activity.setNewNumber(newNumber); } } //创建poi导出数据对象 SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook(); //创建sheet页 SXSSFSheet sheet = (SXSSFSheet) sxssfWorkbook.createSheet("导出测试"); //创建表头 SXSSFRow headRow = (SXSSFRow) sheet.createRow(0); //设置表头信息 headRow.createCell(0).setCellValue("活动id"); headRow.createCell(1).setCellValue("活动名称"); headRow.createCell(2).setCellValue("活动分类"); headRow.createCell(3).setCellValue("新增活动时间"); headRow.createCell(4).setCellValue("参与人数"); headRow.createCell(5).setCellValue("提交订单人数"); headRow.createCell(6).setCellValue("拉新人数"); headRow.createCell(7).setCellValue("助力人次"); activityList.forEach(obj -> { //填充数据 SXSSFRow dataRow = (SXSSFRow) sheet.createRow(sheet.getLastRowNum() + 1); //活动id if (obj.getId() != null) { dataRow.createCell(0).setCellValue(obj.getId().toString()); } else { dataRow.createCell(0).setCellValue(""); } //活动名称 nickname if (obj.getName() != null) { dataRow.createCell(1).setCellValue(obj.getName()); } else { dataRow.createCell(1).setCellValue(""); } // 活动分类 if (obj.getType_name() != null) { dataRow.createCell(2).setCellValue(obj.getType_name()); } else { dataRow.createCell(2).setCellValue(""); } // 新增活动时间 if (obj.getCreate_time() != null) { dataRow.createCell(3).setCellValue(simpleDateFormat.format(obj.getCreate_time())); } else { dataRow.createCell(3).setCellValue(""); } // 参与人数 if (obj.getTakeNumber() != null) { dataRow.createCell(4).setCellValue(obj.getTakeNumber().toString()); } else { dataRow.createCell(4).setCellValue(""); } // 提交订单人数 if (obj.getPutNumber() != null) { dataRow.createCell(5).setCellValue(obj.getPutNumber().toString()); } else { dataRow.createCell(5).setCellValue(""); } // 拉新人数 if (obj.getNewNumber() != null) { dataRow.createCell(6).setCellValue(obj.getNewNumber().toString()); } else { dataRow.createCell(6).setCellValue(""); } // 助力人次 if (obj.getHelpNumber() != null) { dataRow.createCell(7).setCellValue(obj.getHelpNumber().toString()); } else { dataRow.createCell(7).setCellValue(""); } }); //下载导出 String fileName = "导出测试"; // 设置头信息 this.request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); response.setContentType("application/vnd.ms-excel"); //一定要设置成xlsx格式 response.setHeader("Content-Disposition", "attachment;filename=" + new String(fileName .getBytes("gb2312"), "ISO-8859-1") + ".xlsx"); //创建一个输出流 outputStream = response.getOutputStream(); //写入数据 sxssfWorkbook.write(outputStream); } catch (Exception e) { e.printStackTrace(); } finally { try { if (outputStream != null) { outputStream.flush(); outputStream.close(); } } catch (IOException e) { e.printStackTrace(); } } }
前端:
1.绑定Onclick事件:
2.监听搜索
3.searchDownload()方法
导出测试:
其实导入就是导出的反向,导入的基本操作就是根据导入的文件,读取并解析里面的数据[上面有案例怎么解析Excel数据],然后拿到解析的数据后根据自己的业务需求对解析到的数据进行处理,这里不做详细展示
本质还是导出,基本操作和导出一样,只不过下载模板只有表头没有其他数据而已
controller层:
@GetMapping("/downloadBonusExcel") public void downloadBonusExcel(HttpServletRequest request, HttpServletResponse response) throws IOException { managerService.downloadBonusExcel(request,response); }
实现类:
@Override public void downloadBonusExcel(HttpServletRequest request, HttpServletResponse response) throws IOException { // 文件名 String fileName = "下载模板测试"; ServletOutputStream outputStream = null; try { //创建poi导出数据对象 SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook(); //创建sheet页 SXSSFSheet sheet =sxssfWorkbook.createSheet(); //创建表头 SXSSFRow headRow =sheet.createRow(0); //设置表头信息 headRow.createCell(0).setCellValue("奖金数额"); headRow.createCell(1).setCellValue("手机号"); headRow.createCell(2).setCellValue("备注"); // 设置头信息 request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); response.setContentType("application/vnd.ms-excel"); //一定要设置成xlsx格式 response.setHeader("Content-Disposition", "attachment;fileName=" + new String(fileName.getBytes("gb2312"), "ISO-8859-1") + ".xlsx"); //创建一个输出流 outputStream = response.getOutputStream(); //写入数据 sxssfWorkbook.write(outputStream); } catch (Exception e) { e.printStackTrace(); }finally { outputStream.close(); } }
说明:
ServletOutputStream
类:将二进制数据写入响应的流,这时需要得到输入流可以这样获取;ServletOutputStream out=response.getOutputStream();
当然也可以用其他的输出流,比如:File file = new File(filePath); //这样也能获取到outputStream OutputStream outputStream = new FileOutputStream(file);
导出文件/下载文件设置Content-Type
1.导出为Excel:需要设置Content-Type
为application/vnd.ms-excel
具体使用如下:
response.setContentType("application/vnd.ms-excel");
2.如果希望能够提供那个打开/保存的对话框:需要设置Content-Disposition
参数:
Content-Disposition
", “attachment
;fileName=” + new String(fileName.getBytes(“gb2312”), “ISO-8859-1”) + “.xlsx
”);.xlsx
和.xls
,如果是XSSFWorkbook则必须是.xlsx
这里列举一下其他常用的文件设置格式:
'bin' => 'application/octet-stream',
'exe' => 'application/octet-stream',
'so' => 'application/octet-stream',
'dll' => 'application/octet-stream',
'ai' => 'application/postscript',
'dir' => 'application/x-director',
'js' => 'application/x-javascript',
'swf' => 'application/x-shockwave-flash',
'xhtml' => 'application/xhtml+xml',
'xht' => 'application/xhtml+xml',
'mid' => 'audio/midi',
'midi' => 'audio/midi',
'rm' => 'audio/x-pn-realaudio',
'rpm' => 'audio/x-pn-realaudio-plugin',
'wav' => 'audio/x-wav',
'bmp' => 'image/bmp',
'css' => 'text/css',
'html' => 'text/html',
'htm' => 'text/html',
'xsl' => 'text/xml',
'xml' => 'text/xml',
'mpeg' => 'video/mpeg',
'mpg' => 'video/mpeg',