-----
这是导出时,容易出现的问题,后面说到
设置字符编码:
Font font =wb.createFont();
font.setCharSet(font.ANSI_CHARSET);//注意这地方不能手写数字,这是常量
开始正文
一、黑历史
为什么要做excel打印:就是为了客户需求,有的客户习惯excel。
java操作excel主要分两类:
1、(全方位操作用)poi大概就是:属于apache的产品,操作microsoft excel word,ppt,visio等微软旗下所有的工具,支持office所有版本。
但是在poi早期,当时微软产品都时OLE2结构(底层就是2进制)文件,这是office2003以前;然而poi操作大数据时,就会有bug,然而jxl也是这种数据结构,但可以解决这问题,所以jxl当时比office厉害。
从office2007开始,微软就重新开发了office,底层使用OOXML结构,这种数据结构,可以操作大数据,所以excel底层就是xml格式文件。
2、(一般导入导出数据时用)jxl时仅用来操作excel,并且仅支持2003以下版本,不支持2007,也仅仅时OLE2文档结构。
二、jar准备
<dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>3.9</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>3.9</version> </dependency>
三、怎么使用(本质就是将内存中的数据通过流的方式写到硬盘上)
1、训练
第一节认清:excel怎么创建表然后怎么写内容然后再保存文件
- 创建一个工作簿
- 创建一个工作表,默认是3个工作表
- 定位哪一个行
- 定位哪一列
- 单元格写内容(这前面5步都是在内存中进行的,不要被表面迷惑)
- 点击保存(这将内存中数据序列化到硬盘上)
- 关闭
第二节:设置单元格内容
Workbook wb =new HSSFWorkbook(); Sheet sh = wb.createSheet(); Row row = sh.createRow(1); Cell cell = row.createCell(1); cell.setCellValue("我是中国人"); FileOutputStream op =new FileOutputStream("D:\B.xls"); wb.write(op);执行完这一步,内容就写到硬盘上,即B.xls创建了 op.close(); 如果还没执行这一步,那么excel进程提示被占用,不能编辑。关闭流后,才可以操作excel
第三节:设置样式(这和设置内容是不同的东西)--------重点
CellStyle cs=wb.createCellStyle();//注意这方法是工作簿的,单元格样式,excel无论是合并还是什么的都是单元格,还有边框,背景色等等都是样式,所以样式就是全局的 Font font=wb.createFont();//创建字体,也只能工作簿拥有,是全局的,字体无论在哪一张工作表都是拥有工作簿提供的所有字体,所以这个方法只能是工作簿的 font.setFontName("微软雅黑");//设置字体 font.setFontHeightInPoints((short)24);//设置字体高度值点数,就是设置大小,点数相当于单位-------注意的地方,也可以用setFontHeight,但单位不同,需要换算,所以统一用setFontHeightInPoints, cs.setFont(font);设置字体样式 cell.setCellStyle(cs);//设置单元格样式
创建样式对象-->创建字体对象-->设置字体对象各种样式-->设置字体-->设置单元格样式
第四节:优化代码
为了避免重复new对象,造成内存损失,可以引用利用(引用被利用,没有被引用指向的对象会被垃圾回收期回收)java是一个引用同时间只能指向一个对象(不然虚拟机不知道调用那个对象),多个对象可以指向一个引用
A n = new Hero();
n = new Hero();//同一个引用garen指向新创建的对象,上面那个对象就会被垃圾回收机制回收(多个对象可以指向一个引用)
1设置单元格内容再利用:
row1 = sh.createRow(2); 再利用 row引用 cell2 = row1.createCell(2);在利用cell引用 cell2.setCellValue("woshizhongguoren ");
2设置样式再利用(因为是全局的,所以会出现后者覆盖前者,所以不能直接利用font,cs,)
所以必须font 和cs初始化
font= wb.createFont();再利用font引用
cs = wb.createCellStyle();再利用cs引用
font.setFontHeightInPoints((short)18);
font.setFontName("隶书");
font.setBoldweight(Font.BOLDWEIGHT_BOLD);
cs.setFont(font);
cell.setCellStyle(cs);
以上代码可以抽取出来:
excel 标题样式一般相同,内容样式一般相同,简化代码,每次调用方法前,初始化对象
Workbook wb =new HSSFWorkbook(); Sheet sh = wb.createSheet(); Row row = sh.createRow(1); Cell cell = row.createCell(1); cell.setCellValue("我是中国人"); CellStyle cs=wb.createCellStyle(); Font font=wb.createFont(); this.getCellStylefont(font, cs, cell); 初始化: row = sh.createRow(2); cell = row.createCell(2); cell.setCellValue("woshizhongguoren "); font= wb.createFont(); cs = wb.createCellStyle(); this.getCellStyletext(font, cs, cell);
这是excel标题
public CellStyle getCellStyleTitle(Font font,CellStyle cs,Cell cell){ font.setFontHeightInPoints((short)18); font.setFontName("隶书"); font.setBoldweight(Font.BOLDWEIGHT_BOLD); cs.setFont(font); cell.setCellStyle(cs); return cs; }
这是excel 内容
public CellStyle getCellStyletext(Font font,CellStyle cs,Cell cell){ font.setFontName("微软雅黑"); font.setFontHeightInPoints((short)85); cs.setFont(font); cell.setCellStyle(cs); return cs; }
2、项目运用
打印一张出货表:
日期处理可以数据库处理,也可以在poi处理
加个链接,添加打印按钮,根据日期,后台通过sql查询,返回list集合,通过循环遍历导出一张excel表
添加单元格数据:
List<OutProduct> outProductList = outProductService.find(paraMap); HSSFWorkbook wb =new HSSFWorkbook(); HSSFSheet sh = wb.createSheet(); int i=1; //定义列起始索引 int j=0;//定义行起始索引 HSSFRow cr ;//声明行局部变量 HSSFCell cc;//声明单元格局部变量 String[] arr=new String[]{"客户","订单号","货号","数量","工厂","工厂交期","船期","贸易条款"}; //标题栏 cr=sh.createRow(j++); for (String val : arr) { cc=cr.createCell(i++); cc.setCellValue(val); } for(OutProduct op: outProductList){ //数据栏 i=1; //初始化列起始索引 cr=sh.createRow(j++); j++就是j先赋值,然后再自增 cc=cr.createCell(i++); String customName = op.getCustomName(); cc.setCellValue(customName); cc=cr.createCell(i++); String contractNo = op.getContractNo(); cc.setCellValue(contractNo); cc=cr.createCell(i++); String productNo = op.getProductNo(); cc.setCellValue(productNo); cc=cr.createCell(i++); String boxNum = op.getBoxNum(); cc.setCellValue(boxNum); cc=cr.createCell(i++); String factoryName = op.getFactoryName(); cc.setCellValue(factoryName); cc=cr.createCell(i++); String deliveryPeriod = op.getDeliveryPeriod(); cc.setCellValue(deliveryPeriod); cc=cr.createCell(i++); String shipTime = op.getShipTime(); cc.setCellValue(shipTime); cc=cr.createCell(i++); String tradeTerms = op.getTradeTerms(); cc.setCellValue(tradeTerms); } OutputStream out =new FileOutputStream("d://a.xls"); wb.write(out); out.close();
这里可以看出j++好处,中间插入一个大标题,下面的内容自动往下移动
这部分仍是添加单元格内容:大标题是合并单元格,
所以表格的合并单元格方法,指定4个参数,起始行,结束行,起始列,结束列
然后怎么做到“2018年8月份出货表”,时间是动态的,前台传过来的,
replaceFirst成功则返回替换的字符串,失败则返回原始字符串,所以要先进行-0替换,大范围替换,没有0则返回原始字符串,然后小范围-替换成年
sh.addMergedRegion(new CellRangeAddress(0, 0, 1, 8)); //合并单元格默认是设置在区域的第一行第一列 cr=sh.createRow(j++); cc=cr.createCell(1); cc.setCellValue(pdate.replaceFirst("-0", "年").replaceFirst("-", "年")+"月份出货表");
添加单元格样式:
设置字体和样式:
这两个对象都是workbook对象创建的,字体包含在样式中
HSSFCellStyle nstyle = wb.createCellStyle(); HSSFFont nfont = wb.createFont(); bigTitle(wb, nstyle, nfont); cc.setCellStyle(nstyle);
行高是行对象创建,
cr.setHeightInPoints(36);
字体加粗是字体对象
单元格内容居中这是样式对象,样式CellStyle中有横向和竖向常量
public void bigTitle(Workbook wb,CellStyle nstyle,Font nfont){ nfont.setFontHeightInPoints((short)16); nfont.setFontName("宋体"); nstyle.setFont(nfont); nfont.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD);//加粗,这是常量,是HSSFFont类中的常量
nstyle.setAlignment(CellStyle.ALIGN_CENTER);横向居中
nstyle.setVerticalAlignment(CellStyle.VERTICAL_CENTER);纵向居中
}
设置边框
nstyle.setBorderBottom(CellStyle.BORDER_THIN);
nstyle.setBorderLeft(CellStyle.BORDER_THIN);
nstyle.setBorderRight(CellStyle.BORDER_THIN);
nstyle.setBorderTop(CellStyle.BORDER_THIN);
分大标题 标题 正文设置样式,每次设置样式钱需要初始化,
初始化 nstyle = wb.createCellStyle(); nfont = wb.createFont();
public void bigTitle(Workbook wb,CellStyle nstyle,Font nfont){ nfont.setFontHeightInPoints((short)16); nfont.setFontName("宋体"); nfont.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD); nstyle.setAlignment(CellStyle.ALIGN_CENTER); nstyle.setVerticalAlignment(CellStyle.VERTICAL_CENTER); nstyle.setFont(nfont); } public CellStyle title(Workbook wb,CellStyle nstyle,Font nfont){ nfont.setFontHeightInPoints((short)12); nfont.setFontName("黑体"); nstyle.setAlignment(CellStyle.ALIGN_CENTER); nstyle.setVerticalAlignment(CellStyle.VERTICAL_CENTER); nstyle.setBorderBottom(CellStyle.BORDER_THIN); nstyle.setBorderLeft(CellStyle.BORDER_THIN); nstyle.setBorderRight(CellStyle.BORDER_THIN); nstyle.setBorderTop(CellStyle.BORDER_THIN); nstyle.setFont(nfont); return nstyle; } public CellStyle text(Workbook wb,CellStyle nstyle,Font nfont){ nfont.setFontHeightInPoints((short)10); nfont.setFontName("Times New Roman"); nstyle.setAlignment(CellStyle.ALIGN_LEFT); nstyle.setVerticalAlignment(CellStyle.VERTICAL_CENTER); nstyle.setFont(nfont); return nstyle; }
列宽特殊,这是表对象的,sheet:通过源码可以得出,需要乘以256,这是api bug
然而还是差那么一点点,这是poi一个bug,不能够精确。所以经过测试一般接近准确*300,
a列是为了装订线的位置
打印时,不能在一页显示,可以设置横向,拖到一页,可以设置页面方向
设置页眉页脚,设置重复标题行
上面所说的都是下载,用户体验不好,项目中都是模板开发,用户需求变更,只要改变模板上的样式,并且模板的样式都是通过excel手动设置,不需要在代码中设置,代码中只要获取一行模板样式,后面的内容全设置成模板样式就可以了,
可以解决上面所有问题。不然每次输出的文件在服务器端,用户没法看,
项目上传下载 用工具类
ByteArrayOutputStream bo=new ByteArrayOutputStream(); wb.write(bo); DownloadUtil du=new DownloadUtil(); du.download(bo, response, "出货表.xls");
工具类: * @param byteArrayOutputStream 将文件内容写入ByteArrayOutputStream * @param response HttpServletResponse 写入response * @param returnName 返回的文件名 */ public void download(ByteArrayOutputStream byteArrayOutputStream, HttpServletResponse response, String returnName) throws IOException{ response.setContentType("application/octet-stream;charset=utf-8"); returnName = response.encodeURL(new String(returnName.getBytes(),"iso8859-1")); //保存的文件名,必须和页面编码一致,否则乱码 response.addHeader("Content-Disposition", "attachment;filename=" + returnName); response.setContentLength(byteArrayOutputStream.size()); ServletOutputStream outputstream = response.getOutputStream(); //取得输出流 byteArrayOutputStream.writeTo(outputstream); //写到输出流 byteArrayOutputStream.close(); //关闭 outputstream.flush(); //刷数据 }
通过模板开发:下面是模板
主要步骤:读取服务器上的模板文件到内存中,然后在内存中对内存中的模板进行大标题:设置动态值,标题:不用管了,用内存中模板标题,内容上:设置内容、样式和模板一行内容样式一样
@RequestMapping("/cargo/outproduct/outProductPrint.action") public void print(String inputDate, HttpServletResponse response) throws FileNotFoundException, IOException, ParseException{ /* * 操作步骤: * 1、获取数据 * 2、POI写数据到文件 */ List<OutProduct> oList = outProductService.findOutProduct(inputDate+"%"); Workbook wb = new HSSFWorkbook(new FileInputStream(new File("c:\tFACTORY.xls"))); //打开模板文件 Sheet sheet = wb.getSheetAt(0); //打开第一个工作表 Row nRow = null; Cell nCell = null; int rowNo = 2; //行号 int colNo = 1; //列号 //处理标题 nRow = sheet.getRow(0); //获得行对象 nCell = nRow.getCell(1); //获得单元格对象 nCell.setCellValue(inputDate.replaceFirst("-0", "-").replaceFirst("-", "年")+"月份出货表"); //yyyy-MM 2010-08 //获取模板文件中的样式 nRow = sheet.getRow(2); nCell = nRow.getCell(1); CellStyle customNameStyle = nCell.getCellStyle(); //获取客户名称样式 nRow = sheet.getRow(2); nCell = nRow.getCell(2); CellStyle contractNoStyle = nCell.getCellStyle(); nRow = sheet.getRow(2); nCell = nRow.getCell(3); CellStyle productNoStyle = nCell.getCellStyle(); nRow = sheet.getRow(2); nCell = nRow.getCell(4); CellStyle cnumberStyle = nCell.getCellStyle(); nRow = sheet.getRow(2); nCell = nRow.getCell(5); CellStyle factoryStyle = nCell.getCellStyle(); nRow = sheet.getRow(2); nCell = nRow.getCell(6); CellStyle extStyle = nCell.getCellStyle(); nRow = sheet.getRow(2); nCell = nRow.getCell(7); CellStyle dateStyle = nCell.getCellStyle(); nRow = sheet.getRow(2); nCell = nRow.getCell(9); CellStyle tradeTermsStyle = nCell.getCellStyle(); for(int i=0;i<oList.size();i++){ colNo = 1; OutProduct op = oList.get(i); //获取每个出货表对象 nRow = sheet.createRow(rowNo++); //创建行 nRow.setHeightInPoints(24); //行高 nCell = nRow.createCell(colNo++); //创建单元格 nCell.setCellValue(op.getCustomName()); nCell.setCellStyle(customNameStyle); nCell = nRow.createCell(colNo++); nCell.setCellValue(op.getContractNo()); nCell.setCellStyle(contractNoStyle); nCell = nRow.createCell(colNo++); nCell.setCellValue(op.getProductNo()); nCell.setCellStyle(productNoStyle); nCell = nRow.createCell(colNo++); nCell.setCellValue(op.getCnumber()); nCell.setCellStyle(cnumberStyle); nCell = nRow.createCell(colNo++); nCell.setCellValue(op.getFactoryName()); nCell.setCellStyle(factoryStyle); nCell = nRow.createCell(colNo++); nCell.setCellValue("附件"); List<String> extNameList = outProductService.getExtName(op.getContractProductId()); String _extName = ""; if(extNameList!=null&&extNameList.size()>0){ for(String extName : extNameList){ _extName += extName + " "; //换行符 } _extName = _extName.substring(0,_extName.length()-1); //去掉最后一个字符 }else{ _extName = "无"; } nCell.setCellValue(_extName); nCell.setCellStyle(extStyle); nCell = nRow.createCell(colNo++); //nCell.setCellValue(UtilFuns.dateTimeFormat(op.getDeliveryPeriod())); //利用工具类转类型,同时进行格式化 nCell.setCellValue(op.getDeliveryPeriod()); nCell.setCellStyle(dateStyle); nCell = nRow.createCell(colNo++); //nCell.setCellValue(UtilFuns.dateTimeFormat(op.getShipTime())); nCell.setCellValue(op.getShipTime()); nCell.setCellStyle(dateStyle); nCell = nRow.createCell(colNo++); nCell.setCellValue(op.getTradeTerms()); nCell.setCellStyle(tradeTermsStyle); } DownloadUtil du = new DownloadUtil(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); //生成流对象 wb.write(byteArrayOutputStream); du.download(byteArrayOutputStream, response, "出货表.xls"); //弹出下载框,用户就可以直接下载 }
下面是导出下载后的结果:
String path=request.getSession().getServletContext().getRealPath("/")+"/make/xlsprint";//必须是/的虚拟路径,不然jdk1.8不会拼接/后面的字符串 ,这红色的斜杠最好加上,多个斜杠,底层会处理,不要紧 InputStream is =new FileInputStream(new File(path+"/tOUTPRODUCT.xls"));//这里红色/也一样,不加也可以 HSSFWorkbook wb =new HSSFWorkbook(is);