Java与报表知识概括
Excel表格(POI)
Excel报表简介:
- 在企业级应用开发中,Excel报表是一种最常见的报表需求。Excel报表开发一般分为两种形式:
①为了方便操作,基于Excel的报表批量上传数据,也就是把Excel中的数据导入到系统中。
②通过java代码生成Excel报表。也就是把系统中的数据导出到Excel中,方便查阅。 - Excel的两种版本:目前世面上的Excel分为两个大的版本Excel2003和Excel2007及以上两个版本,两者之间的区别如下:
①Excel2003 是一个特有的二进制格式,其核心结构是复合文档类型的结构,存储数据量较小;
②Excel2007 的核心结构是 XML 类型的结构,采用的是基于 XML 的压缩方式,使其占用的空间更小,操作效率更高 - 常见的Excel操作工具:
①JXL:JXL只能对Excel进行操作,属于比较老的框架,它只支持到Excel 95-2000的版本。现在已经停止更新和维护。
②POI:POI是apache的项目,可对微软的Word,Excel,PPT进行操作,包括office2003和2007,Excle2003和2007。poi现在一直有更新。所以现在主流使用POI。
<1>Apache POI是Apache软件基金会的开源项目,由Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java语言操作Microsoft Office的功能。
<2>API对象介绍:
1、工作簿 : WorkBook (HSSFWordBook : 2003版本,XSSFWorkBook : 2007级以上)
2、工作表 : Sheet (HSSFSheet : 2003版本,XSSFSheet : 2007级以上)
3、行 : Row (HSSFRow : 2003版本,XSSFRow : 2007级以上)
4、单元格 : Cell (HSSFCell : 2003版本,XSSFCell : 2007级以上)
4、单元格样式:Style(HSSFCellStyle:2003版本,XSSFCellStyle:2007级以上)
Excel报表数据导出与导入:
- 导入思路:
①一般来说,即将导入的文件,每个列代表什么意思基本上都是固定的,比如第1列就是用户姓名,最后一列就是用户的现住址,并且在做excel时对每个列的类型都是有要求的,这样就可以给我们开发带来很大的简便。
②最终的目标就是读取每一行数据,把数据转成用户的对象,保存到表中
③实现的步骤:
<1>根据上传的文件创建Workbook
<2>获取到第一个sheet工作表
<3>从第二行开始读取数据
<4>读取每一个单元格,把内容放入到用户对象的相关的属性中 - 导出基本思路:
①创建一个全新的工作薄
②在新的工作薄中创建一个新的工作表
③在工作表创建第一行作为标题行,标题固定
④从第二行循环遍历创建,有多少条用户数据就应该创建多少行
⑤把每一个user对象的属性放入到相应的单元格中
导出时样式的设置:
- 画框线
HSSFCellStyle contentStyle = book.createCellStyle();
contentStyle.setBorderBottom(HSSFCellStyle.BORDER_THIN);//底线
contentStyle.setBorderTop(HSSFCellStyle.BORDER_THIN);//顶部线
contentStyle.setBorderLeft(HSSFCellStyle.BORDER_THIN);//左侧线
contentStyle.setBorderRight(HSSFCellStyle.BORDER_THIN);//右侧线
- 合并单元格
//合并单元格 起始行, 结束行, 起始列, 结束列
sheet.addMergedRegion(new CellRangeAddress(0,0,0,4));
- 设置行高
sheet.getRow(1).setHeight((short)500);
- 设置表格的对齐方式和字体
//内容部分的样式
style_content.setAlignment(HSSFCellStyle.ALIGN_CENTER);//设置水平居中
style_content.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);//设置垂直居中
HSSFFont font = book.createFont();//创建字体
font.setFontName("宋体");//设置字体名称
font.setFontHeightInPoints((short)11);//设置字体大小
style_content.setFont(font);//对样式设置字体
//标题样式
HSSFCellStyle style_title = book.createCellStyle();//创建标题样式
style_title.setAlignment(HSSFCellStyle.ALIGN_CENTER);//设置水平居中
style_title.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);//设置垂直居中
HSSFFont titleFont = book.createFont();//设置标题字体
titleFont.setFontName("黑体");
titleFont.setBold(true);//加粗
titleFont.setFontHeightInPoints((short)18);//字体大小
style_title.setFont(titleFont);//将标题字体设置到标题样式
sheet.getRow(0).getCell(0).setCellStyle(style_title);//单元格设置标题样式
POI高级操作:
基于模板导出列表数据:
①首先准备一个excel模板,这个模板把复杂的样式和固定的内容先准备好并且放入到项目中,然后读取到模板后向里面放入数据。导出数据带图片:
①POI主要提供了两个类来处理照片,这两个类是Patriarch和ClientAnchor前者负责在表中创建图片,后者负责设置图片的大小位置。
②关于XSSFClientAnchor的8个参数说明:
<1>dx1 - the x coordinate within the first cell.//定义了图片在第一个cell内的偏移x坐标,既左上角所在cell的偏移x坐标,一般可设0
<2>dy1 - the y coordinate within the first cell.//定义了图片在第一个cell的偏移y坐标,既左上角所在cell的偏移y坐标,一般可设0
<3>dx2 - the x coordinate within the second cell.//定义了图片在第二个cell的偏移x坐标,既右下角所在cell的偏移x坐标,一般可设0
<4>dy2 - the y coordinate within the second cell.//定义了图片在第二个cell的偏移y坐标,既右下角所在cell的偏移y坐标,一般可设0
<5>col1 - the column (0 based) of the first cell.//第一个cell所在列,既图片左上角所在列
<6>row1 - the row (0 based) of the first cell.//图片左上角所在行
<7>col2 - the column (0 based) of the second cell.//图片右下角所在列
<8>row2 - the row (0 based) of the second cell.//图片右下角所在行
// 先创建一个字节输出流
ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
// BufferedImage是一个带缓冲区图像类,主要作用是将一幅图片加载到内存中
BufferedImage bufferImg = ImageIO.read(new File(rootPath + user.getPhoto()));
// 把读取到图像放入到输出流中
ImageIO.write(bufferImg, "jpg", byteArrayOut);
// 创建一个绘图控制类,负责画图
Drawing patriarch = sheet.createDrawingPatriarch();
// 指定把图片放到哪个位置
ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, 2, 1, 4, 5);
// 开始把图片写入到sheet指定的位置
patriarch.createPicture(anchor, workbook.addPicture(
byteArrayOut.toByteArray(), Workbook.PICTURE_TYPE_JPEG));
导出公式:
①在使用POI导出时使用setCellFormula方法来设置公式。
②POI支持公式:POI官网
③ps:其实在正常开发时应该在模板中直接设置好公式,这样打开直接导出的excel文档时公式会直接运行出我们想要的结果自定义导出详细数据的引擎:
①看我们刚才导出时写的代码,必须要提前知道要导出数据在哪一行哪一个单元格,但是如果模板一旦发生调整,那么我们的java代码必须要修改,我们可以自定义个导出的引擎,有了这个引擎即使模板修改了我们的java代码也不用修改
②在制作模板时,在需要插入数据的位置我们坐上标记,在导出时,对象的属性要和标记做对应,如果对应匹配一样,就把值赋值到相应的位置。
百万数据导出:
-
我们都知道Excel可以分为早期的Excel2003版本(使用POI的HSSF对象操作)和Excel2007版本(使用POI的XSSF操作),两者对百万数据的支持如下:
①Excel 2003:在POI中使用HSSF对象时,excel 2003最多只允许存储65536条数据,一般用来处理较少的数据量。这时对于百万级别数据,Excel肯定容纳不了。
②Excel 2007:当POI升级到XSSF对象时,它可以直接支持excel2007以上版本,因为它采用ooxml格式。这时excel可以支持1048576条数据,单个sheet表就支持近百万条数据。但实际运行时还可能存在问题,原因是执行POI报表所产生的行对象,单元格对象,字体对象,他们都不会销毁,这就导致OOM的风险。 -
解决方案分析:对于百万数据量的Excel导入导出,只讨论基于Excel2007的解决方法。在ApachePoi 官方提供了对操作大数据量的导入导出的工具和解决办法,操作Excel2007使用XSSF对象,可以分为三种模式:
①java代码解析xml
②dom4j:一次性加载xml文件再解析
③SAX:逐行加载,逐行解析 -
解决方案:
①用户模式:用户模式有许多封装好的方法操作简单,但创建太多的对象,非常耗内存。加载并读取Excel时,是通过一次性的将所有数据加载到内存中再去解析每个单元格内容。当Excel数据量较大时,由于不同的运行环境可能会造成内存不足甚至OOM异常。
<1>java代码解析xml
<2>dom4j:一次性加载xml文件再解析
②事件模式:基于SAX方式解析XML,SAX全称Simple API for XML,它是一个接口,也是一个软件包。它是一种XML解析的替代方法,不同于DOM解析XML文档时把所有内容一次性加载到内存中的方式,它逐行扫描文档,一边扫描,一边解析。
<1>SAX:逐行加载,逐行解析 -
SXSSF对象:是用来生成海量excel数据文件,主要原理是借助临时存储空间生成excel
-
原理分析 :
①在实例化SXSSFWorkBook这个对象时,可以指定在内存中所产生的POI导出相关对象的数量(默认100),一旦内存中的对象的个数达到这个指定值时,就将内存中的这些对象的内容写入到磁盘中(XML的文件格式),就可以将这些对象从内存中销毁,以后只要达到这个值,就会以类似的处理方式处理,直至Excel导出完成。 -
思路分析:
①导出时使用的是SXSSFWorkBook这个类,一个工作表sheet最多只能放1048576行数据, 当我们的业务数据已超过100万了,一个sheet就不够用了,必须拆分到多个工作表。
②导出百万数据时有两个弊端:
<1>不能使用模板
<2>不能使用太多的样式
③也就是说导出的数据太多时必须要放弃一些。 -
步骤分析:
①设置POI的事件模式:
<1>根据Excel获取文件流
<2>根据文件流创建OPCPackage 用来组合读取到的xml 组合出来的数据占用的空间更小
<3>创建XSSFReader对象
②Sax解析:
<1>自定义Sheet处理器
<2>创建Sax的XmlReader对象
<3>设置Sheet的事件处理器
<4>逐行读取
--------自定义处理器---------
public class SheetHandler implements XSSFSheetXMLHandler.SheetContentsHandler {
// 编号 用户名 手机号 入职日期 现住址
private User user=null;
@Override
public void startRow(int rowIndex) { //每一行的开始 rowIndex代表的是每一个sheet的行索引
if(rowIndex==0){
user = null;
}else{
user = new User();
}
}
@Override //处理每一行的所有单元格
public void cell(String cellName, String cellValue, XSSFComment comment) {
if(user!=null){
String letter = cellName.substring(0, 1); //每个单元名称的首字母 A B C
switch (letter){
case "A":{
user.setId(Long.parseLong(cellValue));
break;
}
case "B":{
user.setUserName(cellValue);
break;
}
}
}
}
@Override
public void endRow(int rowIndex) { //每一行的结束
if(rowIndex!=0){
System.out.println(user);
}
}
}
--------自定义解析---------
public class ExcelParser {
public void parse (String path) throws Exception {
//1.根据Excel获取OPCPackage对象
OPCPackage pkg = OPCPackage.open(path, PackageAccess.READ);
try {
//2.创建XSSFReader对象
XSSFReader reader = new XSSFReader(pkg);
//3.获取SharedStringsTable对象
SharedStringsTable sst = reader.getSharedStringsTable();
//4.获取StylesTable对象
StylesTable styles = reader.getStylesTable();
XMLReader parser = XMLReaderFactory.createXMLReader();
// 处理公共属性:Sheet名,Sheet合并单元格
parser.setContentHandler(new XSSFSheetXMLHandler(styles,sst, new SheetHandler(), false));
XSSFReader.SheetIterator sheets = (XSSFReader.SheetIterator) reader.getSheetsData();
while (sheets.hasNext()) {
InputStream sheetstream = sheets.next();
InputSource sheetSource = new InputSource(sheetstream);
try {
parser.parse(sheetSource);
} finally {
sheetstream.close();
}
}
} finally {
pkg.close();
}
}
}
CSV文件(opencsv)
CSV文件简介:
- 现在好多的网站中导出的文件会出现一种csv文件,我们接下来学习一下csv文件的导出方式。
- CSV文件:
Comma-Separated Values,中文叫逗号分隔值或者字符分割值,其文件以纯文本的形式存储表格数据。
该文件是一个字符序列,可以由任意数目的记录组成,记录间以某种换行符分割。每条记录由字段组成,字段间的分隔符是其他字符或者字符串。所有的记录都有完全相同的字段序列,相当于一个结构化表的纯文本形式。 用文本文件、excel或者类似与文本文件的编辑器都可以打开CSV文件。
- 为了简化开发,我们可以使用opencsv类库来导出csv文件。
- 需要的依赖:
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>4.5</version>
</dependency>
opencsv常用API:
- 写入到csv文件会用到CSVWriter对象,创建此对象常见API如下:
- 使用CSVWriter对象写入数据常用的方法如下:
- 读取csv文件会用到CSVReader对象,创建此对象常见API如下:
- 构造器涉及到的三个参数:
①reader:读取文件的流对象,常有的是BufferedReader,InputStreamReader。
②separator:用于定义前面提到的分割符,默认为逗号CSVWriter.DEFAULT_SEPARATOR
用于分割各列。
③quotechar:用于定义各个列的引号,有时候csv文件中会用引号或者其它符号将一个列引起来,例如一行可能是:“1”,“2”,“3”,如果想读出的字符不包含引号,就可以把参数设为:"CSVWriter.NO_QUOTE_CHARACTER " - read方法:
导出与读取CSV文件:
//导出CSV文件
public void downLoadCSV(HttpServletResponse response) {
try {
// 准备输出流
ServletOutputStream outputStream = response.getOutputStream();
// 文件名
String filename="百万数据.csv";
// 设置两个头 一个是文件的打开方式 一个是mime类型
response.setHeader( "Content-Disposition", "attachment;filename=" + new String(filename.getBytes(),"ISO8859-1"));
response.setContentType("text/csv");
// 创建一个用来写入到csv文件中的writer
CSVWriter writer = new CSVWriter(new OutputStreamWriter(outputStream,"utf-8"));
// 先写头信息
writer.writeNext(new String[]{"编号&