Java 利用POI对象 HSSFWorkbook 导出Excel


一、基础介绍请戳:CSV与Excel的对比,POI导出Excel的数据量限制,HSSF、XSSF、SXSSF的区别


二、HSSF

通过poi导出excel的过程大致是这样的:

  • 导入POI的jar包,使用对应的POI对象
  • 创建 sheet 表
  • 创建 row 行
  • 创建 cell 每行的单元格(可设置数据的格式,和单元格的格式)
  • 也可在所有数据写好之后,整体对某列某行来设置格式
  • 通过IO流输出
/**
 * 生成导出.xls文件,使用HSSFWorkbook
 */
public static File createExcelFile(List<Map<String, String>> exportData,
            Map<String, String> rowMapper, String outPutPath,
            String excelFileName) {
    File excelFile = null;
    FileOutputStream fOut = null;
    HSSFWorkbook wb = null;
    try {
        excelFile = File.createTempFile(excelFileName, ".xls", new File(outPutPath));
        //wb对象
        wb = new HSSFWorkbook();
        //创建sheet对象
        HSSFSheet sheet = wb.createSheet();
        //设置列默认的宽度
        sheet.setDefaultColumnWidth(15);
        //创建表头行
        HSSFRow rowHead = sheet.createRow(0);
        //设置表头行内容,可以在这里对表头设置一些样式,标红呀,加粗之类的
        //样式代码在下面有示范
        int i = 0;
        for (String str : rowMapper.values()) {
            HSSFCell cellHead = rowHead.createCell(i);
            cellHead.setCellValue(str);
            i++;
        }
            

然后

//设置表格主内容
            //单元格格式的创建需在循环体外,不然数据量过大时会报错
            HSSFCellStyle textStyle = wb.createCellStyle();
            //用于格式化单元格的数据
            HSSFDataFormat format = wb.createDataFormat();
            //设置为单元格文本格式
            textStyle.setDataFormat(format.getFormat("@"));
            //不换行
            textStyle.setWrapText(false);
            int j = 0;
            for (Map<String, String> datamap : exportData) {
                HSSFRow row = sheet.createRow(j + 1);
                int k = 0;
                for (String str : datamap.keySet()) {
                    HSSFCell cell = row.createCell(k);
                    //判断针对单号数据,需要设置单元格格式为文本格式,避免科学计数法
                    if (("运单编号").equals(rowMapper.get(str))) {
                        cell.setCellStyle(textStyle);
                        cell.setCellValue(datamap.get(str));
                        cell.setCellType(HSSFCell.CELL_TYPE_STRING);
                    }
                    else {
                        cell.setCellValue(datamap.get(str));
                    }
                    k++;
                }
                j++;
            }
            /*//设置第一列为文本格式
            HSSFDataFormat format = wb.createDataFormat();
            textStyle.setDataFormat(format.getFormat("@"));
            sheet.setDefaultColumnStyle(0, textStyle);*/
            fOut = new FileOutputStream(excelFile);
            //写内容,xls文件已经可以打开
            wb.write(fOut);
            //刷新缓冲区
            fOut.flush();
            fOut.close();
        } catch (IOException e) {
            logger.error("export IOException");
        } finally {
            try{
                fOut.close();
            }catch (Exception ex){
                logger.error("export io exception");
            }
        }
        return excelFile;

        

    }

三、单元的样式设置,标红加粗之类对

// 设置单元格各种样式
// 设置字体
HSSFFont font = wb.createFont();
//字体高度
font.setFontHeightInPoints((short) 11); 
//字体颜色
font.setColor(HSSFFont.COLOR_NORMAL); 
//字体
font.setFontName("宋体"); 

//设置单元格里的字体样式,使用上面设置对字体样式,以及设置单元格的格式
HSSFCellStyle cellStyle = wb.createCellStyle();
cellStyle.setFont(font);
//水平布局:居中
cellStyle.setAlignment(HSSFCellStyle.ALIGN_CENTER); 
//单元格垂直居中
cellStyle.setVerticalAlignment(HSSFCellStyle.VERTICAL_CENTER);
//换行
cellStyle.setWrapText(true);

四、对于身份证等长数字数据,设置CELL单元格为文本格式

/**
*设置CELL格式为文本格式  
*/
HSSFCellStyle cellStyle = wb.createCellStyle();  
HSSFDataFormat format = wb.createDataFormat();  

//"@"是指文本的数据格式,主要是这段代码
cellStyle.setDataFormat(format.getFormat("@"));  
cell.setCellStyle(cellStyle);  
cell.setCellValue(你的值);
  
//CELL_TYPE_STRING是单元格格式,这里写不写没区别…… 原因如下解释
cell.setCellType(HSSFCell.CELL_TYPE_STRING);

踩坑点:

  • 通常大家都是想到既然是设置CELL格式肯定是通过cell.setCellType(HSSFCell.CELL_TYPE_STRING)然后插入数据再导出,诚然这种想法是对的,实际上不能起到任何作用,因为这个方法就是EXCEL默认的格式,写不写都一样;
  • 如果数据量大的话,系统可能会报错“The maximum number of cell styles was exceeded. You can define up to 4000 styles in a .xls workbook”,原因是cellStyle创建的次数太多了,解决这个问题的方法很简单,在循环体外面创建单元格格式(即把它当成一个“全局”变量),不要在循环内部创建;
  • 自调节单元格尺寸方法 autoSizeColumn(), api描述说数据较大时耗时非常大, 建议在结果的时候调用一次。(ps:autoSizeColumn会遍历每一列的每一行数据获取最大长度)

Excel的数据格式

图中的数据有数值、货币、时间、日期、文本等格式。这些数据格式在POI中的HSSFDataFormat类里都有相应的定义。 在HSSFDataFormat里一共定义了49种内置的数据格式,如下表所示:
Java 利用POI对象 HSSFWorkbook 导出Excel
在上面表中,字符串类型所对应的是数据格式为"@"(最后一行),也就是HSSFDataFormat中定义的值为0x31(49)的那行。Date类型的值的范围是0xe-0x11,本例子中的Date格式为"“m/d/yy”",在HSSFDataFormat定义的值为0xe(14)。

POI中Excel文件Cell的类型(单元格格式)

在读取每一个Cell的值的时候,通过getCellType方法获得当前Cell的类型,在Excel中Cell有6种类型,如下表所示:

CellType 说明
CELL_TYPE_BLANK 空值
CELL_TYPE_BOOLEAN 布尔型
CELL_TYPE_ERROR 错误
CELL_TYPE_FORMULA 公式型
CELL_TYPE_STRING 字符串型
CELL_TYPE_NUMERIC 数值型

一般都采用CELL_TYPE_STRING和CELL_TYPE_NUMERIC类型,因为在Excel文件中只有字符串和数字。如果Cell的Type为CELL_TYPE_NUMERIC时,还需要进一步判断该Cell的数据格式,因为它有可能是Date类型,在Excel中的Date类型也是以Double类型的数字存储的。Excel中的Date表示当前时间与1900年1月1日相隔的天数,所以需要调用HSSFDateUtil的isCellDateFormatted方法,判断该Cell的数据格式是否是Excel Date类型。如果是,则调用getDateCellValue方法,返回一个Java类型的Date。

五、拼好Excel之后,文件下载方法:

方法1:
//创建xls文件,无内容 0字节
FileOutputStream fOut = new FileOutputStream(xlsFile);        
//写内容,xls文件已经可以打开
wb.write(fOut);                            
//刷新缓冲区
fOut.flush();
//关闭                            
fOut.close();                            
方法2:
 //生成流对象
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();    
//将excel写入流        
wb.write(byteArrayOutputStream);                                
//工具类,封装弹出下载框:        
String outFile = "excelFile.xls";
DownloadBaseAction down = new DownloadBaseAction();
down.download(byteArrayOutputStream, response, outFile);
方法3:(适用于struts2)
ServletActionContext.getResponse().setContentType("application/octet-stream");
String returnName = ServletActionContext.getResponse().encodeURL( new String("excelFile.xls".getBytes(), "ISO-8859-1"));
ServletActionContext.getResponse().addHeader("Content-Disposition", "attachment;filename=" + returnName);
wb.write(ServletActionContext.getResponse().getOutputStream());

References:

参考1参考2参考3参考4