前言:

在Java技术生态圈中,可以进行Excel文件处理的主流技术包括:Apache POI、JXL、Alibaba EasyExcel等。
Apache POI基于DOM方式进行解析,将文件直接加载内存,所以速度较快,适合Excel文件数据量不大的应用场景。JXL只支持Excel 2003以下版本,所以不太常见。
Alibaba EasyExcel采用逐行读取的解析模式,将每一行的解析结果以观察者的模式通知处理(AnalysisEventListener),所以比较适合数据体量较大的Excel文件解析。

一、Apache POI

Apache POI是用Java编写的免费开源的跨平台的Java API,Apache POI提供给Java程序对Microsoft Office格式档案进行读写功能的API开源类库。
它分别提供对不同格式文件的解析:
●HSSF - 提供读写Microsoft Excel格式档案的功能。
●XSSF - 提供读写Microsoft Excel OOXML格式档案的功能。
●HWPF - 提供读写Microsoft Word格式档案的功能。
●HSLF - 提供读写Microsoft PowerPoint格式档案的功能。
●HDGF - 提供读写Microsoft Visio格式档案的功能。

二、XSSF解析Excel文件

HSSF用于解析旧版本(*.xls)Excel文件,由于旧版本的Excel文件只能存在65535行数据,所以目前已经不常用。所以目前主要采用XSSF进行新版本(*.xlsx)Exce文件的解析。

需要的jar包:在我的资源里

WorkBook(Excel文件)

WorkBook是一个接口,所以创建WorkBook引用时需指向子类对象创建好以后所代表的是一个Excel文件

// 解析一个excel文件

		try (// 通过输入流,读取excel文件
				FileInputStream in = new FileInputStream("D:\\1694318113\\FileRecv\\1627356552686.xlsx");
				// 将输入流传入Workbook
				Workbook workbook = new XSSFWorkbook(in)) {
			// Sheet:工作簿
			// 获取工作簿
			int sheetNumber = workbook.getNumberOfSheets();
			System.out.println("工作薄数量:" + sheetNumber);

			// 按照名称获取工作簿
			Sheet sheet1 = workbook.getSheet("Sheet0");
            // 通过下标方式获取工作簿
			Sheet sheet2 = workbook.getSheetAt(1);
			System.out.println("工作薄1中的数据行" + sheet1.getLastRowNum());
			System.out.println("工作薄2中的数据行" + sheet2.getLastRowNum());
		}

 我们需要使用IO流,所以推荐将输入流以及创建XSSFWorkbook对象代码块放入try(){}catch{}块中

需要解析什么类型文件创建什么类型的对象 

●HSSF - 提供读写Microsoft Excel格式档案的功能。
●XSSF - 提供读写Microsoft Excel OOXML格式档案的功能。
●HWPF - 提供读写Microsoft Word格式档案的功能。
●HSLF - 提供读写Microsoft PowerPoint格式档案的功能。
●HDGF - 提供读写Microsoft Visio格式档案的功能。

Sheet (工作簿)

通过Workbook来进行Excel工作簿Sheet对象的获取或创建

工作簿的获取采用下标的方式(工作簿下标从零开始)也可以直接getSheet(工作簿的名字)来获取工作簿

Sheet sheet1 = workbook.getSheet("Sheet0");
Sheet sheet2 = workbook.getSheetAt(1);

获取工作簿的数量

int num = workbook.getNumberofSheets();

Row(数据行) 

通过Sheet来进行数据行Row对象的获取或创建

Row row = sheet.createRow(0);

获取首行下标

int first = sheet.getFirstRowNum();

获取尾行下标

int last = sheet.getLastRowNum();

还可以根据下标获取指定行

Row row = sheet.getRow(0);

遍历所有行

for(Row row : sheet) {
    System.out.println(row);
}

遍历指定行,需要使用下标,所以采用for( ; ; ){ }循环

for (int i = 1; i <= sheet.getLastRowNum(); i++) {
    Row row = sheet.getRow(i);
    System.out.println(row);
}

Cell(单元格)

通过Row来进行单元格Cell对象的获取或创建

Cell cell0 = row.createCell(0);

设置单元格值

cell0.setCellValue(UUID.randomUUID().toString());

根据下标获取单元格

Cell cell = row.getCell(1);

遍历所有单元格

for(Cell cell : row) {
				
}

获取单元格类型

CellType type = cell.getCellType();

设置单元格样式:使用dataFormat.getFormat()方法获取一个shor类型的此类格式的编码

然后将其存入CellStyle类型的对象中(使用Workbook对象调用createCellStyle()方法来创建CellStyle类型对象),(用CellStyle对象调用setDataFormat()方法将其存入),最后将你需要设置格式的Cell调用setCellStyle()方法将设置好的CellStyle对象导入进去

// 创建单元格样式
DataFormat dataFormat = workbook.createDataFormat();
Short formatCode = dataFormat.getFormat("yyyy-MM-dd HH:mm:ss");
CellStyle cellStyle = workbook.createCellStyle();
cellStyle.setDataFormat(formatCode);

// ...

// 为当前行创建单元格
Cell cell1 = row.createCell(1);
cell1.setCellStyle(cellStyle); // 设置单元格样式
cell1.setCellValue(new Date()); // 保存当前日期时间至本单元格

设置单元格对齐

// 创建单元格样式
CellStyle cellStyle = workbook.createCellStyle();

//设置单元格的水平对齐类型。 此时水平居中
cellStyle.setAlignment(HorizontalAlignment.CENTER);

// 设置单元格的垂直对齐类型。 此时垂直靠底边
cellStyle.setVerticalAlignment(VerticalAlignment.BOTTOM);

超大Excel文件读写

1.使用POI写入

使用SXSSFWorkbook进行写入,通过设置SXXFWorkbook的构造参数,可以设置每次在内存中保持的行数,当达到这个值的时候,那么会把这些数据flush到磁盘上,这样就不会出现内存不够的情况

try (Workbook workbook = new SXSSFWorkbook(1000);
				FileOutputStream out = new FileOutputStream("D:\\测试\\10w.xlsx")) {
			Sheet sheet = workbook.createSheet();
			// 创建单元格格式对象
			DataFormat dataFormat = workbook.createDataFormat();
			// 获取单元格格式编码值
			short dateFormatCode = dataFormat.getFormat("yyyy年MM月dd日 HH:mm:ss");
			short moneyFormatCode = dataFormat.getFormat("¥#,###");
			
			// 创建日期格式对象
			CellStyle dateCellStyle = workbook.createCellStyle();
			dateCellStyle.setDataFormat(dateFormatCode);
			
			// 创建货币格式对象
			CellStyle moneyCellStyle = workbook.createCellStyle();
			moneyCellStyle.setDataFormat(moneyFormatCode);
			Row row = sheet.createRow(0);
			Cell cell0 = row.createCell(0);
			cell0.setCellValue("序号");
			
			Cell cell1 = row.createCell(1);
			cell1.setCellValue("姓名");
			
			Cell cell2 = row.createCell(2);
			cell2.setCellValue("创建时间");
			
			Cell cell3 = row.createCell(3);
			cell3.setCellValue("红包金额");
			for(int i = 0;i<100000;i++) {
				Row rowVal = sheet.createRow(i+1);
				Cell cellVal0 = rowVal.createCell(0);
				cellVal0.setCellValue(String.valueOf(i+1));
				
				Cell cellVal1 = rowVal.createCell(1);
				cellVal1.setCellValue("A"+i);
				
				Cell cellVal2 = rowVal.createCell(2);
				cellVal2.setCellStyle(dateCellStyle);
				cellVal2.setCellValue(new Date());
				
				Cell cellVal3 = rowVal.createCell(3);
				cellVal3.setCellStyle(moneyCellStyle);
				cellVal3.setCellValue(Math.random()*10000);
			}
			workbook.write(out);
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

 2.使用EasyExcel

使用此方法需要导入阿里巴巴的 alibaba easyexcel相关的jar包(在我上传的资源里)

第一步:准备实体类

public class Order {
    @ExcelProperty("订单编号")
	private String orderId; // 订单编号
	
    @ExcelProperty("支付金额")
	@NumberFormat("¥#,###")
	private Double payment; // 支付金额
	
    @ExcelProperty(value = "创建日期",converter = LocalDateTimeConverter.class)
	private LocalDateTime creationTime; // 创建时间

	public Order() {
		this.orderId = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddhhmmss"))
				+ UUID.randomUUID().toString().substring(0, 5);
		this.payment = Math.random() * 10000;
		this.creationTime = LocalDateTime.now();
	}

	public String getOrderId() {
		return orderId;
	}

	public void setOrderId(String orderId) {
		this.orderId = orderId;
	}

	public Double getPayment() {
		return payment;
	}

	public void setPayment(Double payment) {
		this.payment = payment;
	}

	public LocalDateTime getCreationTime() {
		return creationTime;
	}

	public void setCreationTime(LocalDateTime creationTime) {
		this.creationTime = creationTime;
	}



	@Override
	public String toString() {
		return "Order [orderId=" + orderId + ", payment=" + payment + ", creationTime=" + creationTime + "]";
	}
}

第二步:准备Converter转换类(兼容LocalDateTime日期时间类)此类的可以设置时间格式,否则导入的jar包不兼容,设置不成时间格式

public class LocalDateTimeConverter implements Converter<LocalDateTime> {

	@Override
	public Class<LocalDateTime> supportJavaTypeKey() {
		return LocalDateTime.class;
	}

	@Override
	public CellDataTypeEnum supportExcelTypeKey() {
		return CellDataTypeEnum.STRING;
	}

	@Override
	public LocalDateTime convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
			GlobalConfiguration globalConfiguration) {
		return LocalDateTime.parse(cellData.getStringValue(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
	}

	@Override
	public CellData<String> convertToExcelData(LocalDateTime value, ExcelContentProperty contentProperty,
			GlobalConfiguration globalConfiguration) {
		return new CellData<>(value.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
	}

}

 第三步:写入100w条Order类型数据

public class Demo {
	public static void main(String[] args) {
        // 写入100w
        EasyExcel.write("c:\\test\\run\\easy.xlsx", Order.class)
                 .sheet("订单列表")
                 .doWrite(data());
    }
    
    // 创建100w条订单数据
    private static List<Order> data() {
        List<Order> list = new ArrayList<Order>();
        for (int i = 0; i < 1000000; i++) {
            list.add(new Order());
        }
        return list;
    }
}

总结:使用easyExcel相比于SXSSFWorkbook可以大大缩短写入时间以及可以写入更大数据