简介
- poi就是提供api给java,以处理Microsoft office文件得工具包
常用得POI有
apache POI
和阿里得easyPOI
推荐使用阿里得easyPOI
,因为apache的io比较耗费内存,文件比较大时可能内存溢出(要看具体的工作簿类是哪个)excel结构
Microsoft office的excel分为2个版本,一个是03版本,一个是07版本!
- 03的excel为
**xls**
,只能放65536行数据,即普通的excel - 07的excel为
**xlsx**
,行数无限制,即excel ooxml
- 03的excel为
一个excel有工作簿(即整个文件)工作表(即一个excel里还有些子表
sheet
),然后就是行,列,单元格依赖
poi的不同版本的使用区别比较大,我们尽量使用曾经使用过的
**4.1.0**
或者是**3.9**
,优先使用**4.1.0**
<!-- 03-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.9/4.1.0</version>
</dependency>
<!-- 07-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.9/4.1.0</version>
</dependency>
Apache POI
分类
apache poi里一般如下分类,Workbook结构接口有不同种类的实现类,如工作簿接口有hssf工作簿实现类,xssf工作簿实现类
不同类型的excel切换下工作簿实现类和最后输出的文件后缀即可,不过sxssf需要额外设置个自动删除临时文件
- HSSF -提供读写MicrosoftExcel格式档案的功能。
- hssf为03版excel,行数有限制。写过程写入缓存,不操作磁盘,最后一次写才写入磁盘,速度较快
- XSSF -提供读写MicrosoftExcel OOXML格式档案的功能。
- 为07版excel,写速度比较慢,耗费内存,不过行数无限制,但是行数太多时可能内存溢出
**SXSSF**
XSSF的升级版,速度更快,耗费内存更少,但是会生成一个临时文件,需要手动删除- HWPF-提供读写MicrosoftWord格式档案的功能。
- HSLF -提供读写Microsof PowerPoint格式档案的功能。
- HDGF-提供读写MicrosoftVisio格式档案的功能
Excel结构类
- HSSF -提供读写MicrosoftExcel格式档案的功能。
工作簿:
Workbook
- 工作表:
Sheet
- 行:
Row
行的行位置索引0开始 -
Excel写
注意如果是循环生成一行的多个单元格,row对象一定要在循环外,在内部理论上是至少会有一个单元格生成,实际上一个都不会有
String PATH="D:\\";
Workbook workbook = new HSSFWorkbook();
Sheet sheet = workbook.createSheet("我的工作薄");
// 创建第一行
Row row1 = sheet.createRow(0);
// 创建第一行第一个单元格
Cell cell11 = row1.createCell(0);
cell11.setCellValue("日期");
// 创建第一行第二个单元格
Cell cell12 = row1.createCell(1);
cell12.setCellValue(new DateTime().toString("yyyy-MM-dd HH:mm:ss"));
// 创建第二行
Row row2 = sheet.createRow(1);
// 创建第二行第一个单元格
Cell cell21 = row2.createCell(0);
cell21.setCellValue("姓名");
Cell cell22 = row2.createCell(1);
cell22.setCellValue("Jago");
// 写入excel
FileOutputStream fos = new FileOutputStream(PATH + "poi.xls");
workbook.write(fos);
fos.close();
}
不同类型的excel切换下工作簿实现类和最后输出的文件后缀即可,不过sxssf需要删除临时文件
//在流关闭后加
((SXSSFWorkbook) workbook).dispose(); //自动删除临时文件
Excel读
Workbook workbook = new HSSFWorkbook(InputStream); //传个输入流
Sheet sheet= workbook.getSheetAt(?);
Row row=sheet.getRow(?);
int x=sheet.getLastRowNum(); //返回任意一行的总列数(包括最后一列前的空列,实际使用时对于空列计算不太准确,如果一行都是非空列,建议使用下面那个方法计算)
int x2=row.getPhysicalNumberOfRows(); 获取某一行非空列的列数
int y=row.getLastCellNum(); //返回总行数-1(对于非第一行开始的空行不知道会不会计入)
Cell cell=row.getCell(?) //获取某行的某个单元格
cell.getStringCellValue(); //获取单元格值,还有很多其他类型方法,读取不同类型的值
//.getNumbericCellValue()
```java Cell cell=…; int type=cell.getCellType(); HSSFCell.CELL_TYPE_STRING //这个枚举类里存了类型对应的int值,BLANk为空类型,ERROR为识别失败时的返回
.CELL_TYPE_BLANK
...
//对于时间,数字等值,枚举类型都为HSSFCell.CELL_TYPE_NUMERIC
- 读取日期或者数字,对于时间,数字等值,枚举类型都为`HSSFCell.CELL_TYPE_NUMERIC` 通过如下操作可以再进行类型识别 ,数字读取为字符串是为了防止数字过大
![image.png](https://cdn.nlark.com/yuque/0/2022/png/2319994/1648897757388-d5e70b88-3e7f-4c3d-9613-6ea7d621e463.png#clientId=ub310798f-bd44-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=145&id=u9a27976e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=270&originWidth=752&originalType=binary&ratio=1&rotation=0&showTitle=false&size=193437&status=done&style=none&taskId=ue28783f5-9d9e-4188-b126-19d4235662b&title=&width=402.6000061035156)
<a name="jQ4C8"></a>
## excel样式
```java
//单元格样式 4.1.0以上版本,不过如果这个4.1.0以上的用不了3.9的也尝试下
CellStyle comm = workbook.createCellStyle();
comm.setAlignment(HorizontalAlignment.CENTER); //水平居中
comm.setVerticalAlignment(VerticalAlignment.CENTER); //垂直居中
comm.setBorderTop(BorderStyle.THIN); //顶部边框设置,THIN就是黑色实线
cell.setCellStyle(comm); //设置一个单元格的样式
//3.9以下版本单元格样式
XSSFCellStyle setBorder = (XSSFCellStyle) workbook.createCellStyle();
setBorder.setBorderBottom(XSSFCellStyle); //下边框
//列宽设置
sheet.setColumnWidth(index,15*256); //index为第几列,0开始 单位为一个字符长度的256分之一。大概15个字符长度相当于7个中文字符的长度
EasyPOI
**easypoi**
本质还是基于**apache poi**
,更加快速高效,且解决了apache poi大数据量时容易**oom**
的问题Excel写
定义列,会同时生成标题行和内容行:
@ExcelProperty
忽略字段:
@ExcelIgnore
@Data public class DemoData { @ExcelProperty("字符串标题") //标题默认为灰色,顺序为实体类中定义的顺序 private String string; @ExcelProperty("数字标题") private Double doubleData; /** * 忽略这个字段 */ @ExcelIgnore private String ignore; } //write第一个参数可以传要生成的文件的路径与名字,也可以传个输出流 //第二个参数传实体类class //doWrite传实体类的List //默认是输出07版本的格式 String fileName = PATH + "EasyTest.xlsx"; EasyExcel.write(fileName, DemoData.class).sheet("sheet1").doWrite(List<DemoData>); .write().excelType(ExcelTypeEnum.XLS) //创建03版本
Excel读
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead(); //传入文件路径或者输入流接收 DemoDataListener是一个定义的读取工具
hutool工具生成Excel
hutool工具的导出好处在于简单,支持的数据格式多(除了实体类List,还可以
List<List>
List<Map>
) 注意使用hutool的ExcelWriter对象.flush(输出流);
生成excel时,如果该流是HttpServletResponse
,那么flush写完后会自动将流关闭。此时如果控制器返回数据就会报错,因为流已经关闭不能二次调用。所以控制器方法应该返回null模板引擎-Word
word结构
word本身就是一种特殊的xml文件,我们可以先定义好word的模板然后另存为word-xml格式,再将xml后缀改为
ftl
。(这里使用freemarek
模板引擎)
模板
比如一个Word表格
<#list infolist as listKey> <w:tr> <w:t>${listKey.name}</w:t> </w:tr> </#list>
工具类
传入一个map,map里可以存list或者是非集合数据,模板ftl存在resource下。
- map里的对象不要有null,map里的list的元素也是。否则很容易报错。另外模板里渲染的值在map里必须存在,否则也会报错
设置了include文件打包过滤的,记得把ftl文件也设置打包进去
<include>**/*.ftl</include>
public static void createWordrzd(Map<?, ?> dataMap, String templateName, String filePath, String fileName) { try { Configuration configuration = new Configuration(); configuration.setDefaultEncoding("UTF-8"); // ftl模板文件的resource的位置 configuration.setClassForTemplateLoading(WordUtils.class, "/templates/"); // 获取模板 Template template = configuration.getTemplate(templateName); File outFile = new File(filePath + File.separator + fileName); if (!outFile.getParentFile().exists()) { outFile.getParentFile().mkdirs(); } // 将模板和数据模型合并生成文件 Writer out = new BufferedWriter(new OutputStreamWriter( new FileOutputStream(outFile), "UTF-8")); // 生成文件 template.process(dataMap, out); out.flush(); out.close(); } catch (Exception e) { e.printStackTrace(); } }
调用
@GetMapping("/daying") public void daying(HttpServletResponse response){ Map<String, Object> dataMap = new HashMap<>(); dataMaP.put("title","xxx"); dataMap.put("infolist",new ArrayList<List>()); //生成docx到本地 WordUtils.createWordrzd(dataMap,"huizong.ftl","D:/backup/","sdf.docx"); //再获取本地生成的docx文件流,然后删除生成的docx File file=new File("D:/backup/sdf.docx"); FileInputStream fin = new FileInputStream(file); response.setCharacterEncoding("UTF-8"); response.setContentType("application/msword"); // 设置浏览器以下载的方式处理该文件名 response.setHeader("Content-Disposition", "attachment;filename=" .concat(String.valueOf(URLEncoder.encode("huizong", "UTF-8")))); OutputStream out = response.getOutputStream(); byte[] buffer = new byte[1024]; // 缓冲区 int bytesToRead = -1; // 通过循环将读入的Word文件的内容输出到浏览器中 while((bytesToRead = fin.read(buffer)) != -1) { out.write(buffer, 0, bytesToRead); } if(fin != null) fin.close(); if(out != null) out.close(); if(file != null) file.delete(); }
前端打印
function PrintForm() { $("#daying").hide(); var strHead = "<head>" + document.getElementById("head1").innerHTML + "</head>"; //这个代码亏内 var strFormHtml = strHead + "<body style=\"font-size:16px\">" + $("#div_form").html() + "</body>"; var isWin = (navigator.platform == "Win32") || (navigator.platform == "Windows") if (isWin) { var userAgent = navigator.userAgent; if (userAgent.indexOf("Chrome") > -1) { var strPrintStyle = "<style media='print'>@page {size: auto;margin: 0mm 15mm 0mm 15mm;}</style><div style='height: 25px;'></div>"; //去掉页眉页尾 window.document.body.innerHTML = strPrintStyle + strFormHtml; window.print(); window.location.href = window.location.href; } else { var LODOP = getLodop(document.getElementById('LODOP_OB'), document.getElementById('LODOP_EM')); LODOP.PRINT_INIT("打印_表单"); LODOP.SET_PRINT_PAGESIZE(1, 0, 0, "A4"); LODOP.ADD_PRINT_HTM("0%", "4%", "80%", "100%", strFormHtml); LODOP.PREVIEW(); } } else { var strPrintStyle = "<style media='print'>@page {size: auto;margin: 0mm 15mm 0mm 15mm;}</style><div style='height: 25px;'></div>"; //去掉页眉页尾 window.document.body.innerHTML = strPrintStyle + strFormHtml; window.print(); window.location.href = window.location.href; } $("#daying").show(); }