POI报表的高级应用
POI报表的高级应用
掌握基于模板打印的POI报表导出理解自定义工具类的执行流程
熟练使用SXSSFWorkbook完成百万数据报表打印理解基于事件驱动的POI报表导入
模板打印
概述
自定义生成Excel报表文件还是有很多不尽如意的地方,特别是针对复杂报表头,单元格样式,字体等操作。手写这些代码不仅费时费力,有时候效果还不太理想。那怎么样才能更方便的对报表样式,报表头进行处理呢?答案是 使用已经准备好的Excel模板,只需要关注模板中的数据即可。
模板打印的操作步骤
- 制作模版文件(模版文件的路径)
- 导入(加载)模版文件,从而得到一个工作簿
- 读取工作表
- 读取行
- 读取单元格
- 读取单元格样式
- 设置单元格内容
- 其他单元格就可以使用读到的样式了
代码实现
/*** 采用模板打印的形式完成报表生成* 模板* 参数:* 年月-月(2018-02%)** sxssf对象不支持模板打印*/@RequestMapping(value = "/export/{month}", method = RequestMethod.GET)public void export(@PathVariable String month) throws Exception {//1.获取报表数据List<EmployeeReportResult> list = userCompanyPersonalService.findByReport(companyId,month);//2.加载模板Resource resource = new ClassPathResource("excel-template/hr-demo.xlsx");FileInputStream fis = new FileInputStream(resource.getFile());//3.通过工具类完成下载
// new ExcelExportUtil(EmployeeReportResult.class,2,2).
// export(response,fis,list,month+"人事报表.xlsx");//3.根据模板创建工作簿Workbook wb = new XSSFWorkbook(fis);//4.读取工作表Sheet sheet = wb.getSheetAt(0);//5.抽取公共样式Row row = sheet.getRow(2);CellStyle styles [] = new CellStyle[row.getLastCellNum()];for(int i=0;i<row.getLastCellNum();i++) {Cell cell = row.getCell(i);styles[i] = cell.getCellStyle();}//6.构造单元格int rowIndex = 2;Cell cell=null;for(int i=0;i<10000;i++) {for (EmployeeReportResult employeeReportResult : list) {row = sheet.createRow(rowIndex++);// 编号,cell = row.createCell(0);cell.setCellValue(employeeReportResult.getUserId());cell.setCellStyle(styles[0]);// 姓名,cell = row.createCell(1);cell.setCellValue(employeeReportResult.getUsername());cell.setCellStyle(styles[1]);// 手机,cell = row.createCell(2);cell.setCellValue(employeeReportResult.getMobile());cell.setCellStyle(styles[2]);// 最高学历,cell = row.createCell(3);cell.setCellValue(employeeReportResult.getTheHighestDegreeOfEducation());cell.setCellStyle(styles[3]);// 国家地区,cell = row.createCell(4);cell.setCellValue(employeeReportResult.getNationalArea());cell.setCellStyle(styles[4]);// 护照号,cell = row.createCell(5);cell.setCellValue(employeeReportResult.getPassportNo());cell.setCellStyle(styles[5]);// 籍贯,cell = row.createCell(6);cell.setCellValue(employeeReportResult.getNativePlace());cell.setCellStyle(styles[6]);// 生日,cell = row.createCell(7);cell.setCellValue(employeeReportResult.getBirthday());cell.setCellStyle(styles[7]);// 属相,cell = row.createCell(8);cell.setCellValue(employeeReportResult.getZodiac());cell.setCellStyle(styles[8]);// 入职时间,cell = row.createCell(9);cell.setCellValue(employeeReportResult.getTimeOfEntry());cell.setCellStyle(styles[9]);// 离职类型,cell = row.createCell(10);cell.setCellValue(employeeReportResult.getTypeOfTurnover());cell.setCellStyle(styles[10]);// 离职原因,cell = row.createCell(11);cell.setCellValue(employeeReportResult.getReasonsForLeaving());cell.setCellStyle(styles[11]);// 离职时间cell = row.createCell(12);cell.setCellValue(employeeReportResult.getResignationTime());cell.setCellStyle(styles[12]);}}//7.下载//3.完成下载ByteArrayOutputStream os = new ByteArrayOutputStream();wb.write(os);new DownloadUtils().download(os,response,month+"人事报表.xlsx");}
骚戴理解:学会了一个小技巧,怎么获取模板数据?
Resource resource = new ClassPathResource("excel-template/hr-demo.xlsx");
FileInputStream fis = new FileInputStream(resource.getFile());
Workbook wb = new XSSFWorkbook(fis);
同时这里的 for(int i=0;i<10000;i++) 其实是为了造数据,数据库本来查询到的数据有90多条,然后循环10000次自然就是90多万条,接近百万数据量
自定义工具类
自定义注解ExcelAttribute
package com.ihrm.domain.poi;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelAttribute {/** 对应的列名称 */String name() default "";/** 列序号 */int sort();/** 字段类型对应的格式 */String format() default "";}
导出工具类
package com.ihrm.common.poi.utils;import com.ihrm.domain.poi.ExcelAttribute;
import lombok.Getter;
import lombok.Setter;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.formula.functions.T;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;@Getter
@Setter
public class ExcelExportUtil<T> {private int rowIndex;private int styleIndex;private String templatePath;private Class clazz;private Field fields[];public ExcelExportUtil(Class clazz,int rowIndex,int styleIndex) {this.clazz = clazz;this.rowIndex = rowIndex;this.styleIndex = styleIndex;fields = clazz.getDeclaredFields();}/*** 基于注解导出*/public void export(HttpServletResponse response,InputStream is, List<T> objs,String fileName) throws Exception {XSSFWorkbook workbook = new XSSFWorkbook(is);Sheet sheet = workbook.getSheetAt(0);CellStyle[] styles = getTemplateStyles(sheet.getRow(styleIndex));AtomicInteger datasAi = new AtomicInteger(rowIndex);for (T t : objs) {Row row = sheet.createRow(datasAi.getAndIncrement());for(int i=0;i<styles.length;i++) {Cell cell = row.createCell(i);cell.setCellStyle(styles[i]);for (Field field : fields) {if(field.isAnnotationPresent(ExcelAttribute.class)){field.setAccessible(true);ExcelAttribute ea = field.getAnnotation(ExcelAttribute.class);if(i == ea.sort()) {cell.setCellValue(field.get(t).toString());}}}}}fileName = URLEncoder.encode(fileName, "UTF-8");response.setContentType("application/octet-stream");response.setHeader("content-disposition", "attachment;filename=" + new String(fileName.getBytes("ISO8859-1")));response.setHeader("filename", fileName);workbook.write(response.getOutputStream());}public CellStyle[] getTemplateStyles(Row row) {CellStyle [] styles = new CellStyle[row.getLastCellNum()];for(int i=0;i<row.getLastCellNum();i++) {styles[i] = row.getCell(i).getCellStyle();}return styles;}
}
导入工具类
package com.ihrm.common.poi.utils;import com.ihrm.domain.poi.ExcelAttribute;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.format.CellFormat;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;public class ExcelImportUtil<T> {private Class clazz;private Field fields[];public ExcelImportUtil(Class clazz) {this.clazz = clazz;fields = clazz.getDeclaredFields();}/*** 基于注解读取excel*/public List<T> readExcel(InputStream is, int rowIndex,int cellIndex) {List<T> list = new ArrayList<T>();T entity = null;try {XSSFWorkbook workbook = new XSSFWorkbook(is);Sheet sheet = workbook.getSheetAt(0);// 不准确int rowLength = sheet.getLastRowNum();System.out.println(sheet.getLastRowNum());for (int rowNum = rowIndex; rowNum <= sheet.getLastRowNum(); rowNum++) {Row row = sheet.getRow(rowNum);entity = (T) clazz.newInstance();System.out.println(row.getLastCellNum());for (int j = cellIndex; j < row.getLastCellNum(); j++) {Cell cell = row.getCell(j);for (Field field : fields) {if(field.isAnnotationPresent(ExcelAttribute.class)){field.setAccessible(true);ExcelAttribute ea = field.getAnnotation(ExcelAttribute.class);if(j == ea.sort()) {field.set(entity, covertAttrType(field, cell));}}}}list.add(entity);}} catch (Exception e) {e.printStackTrace();}return list;}/*** 类型转换 将cell 单元格格式转为 字段类型*/private Object covertAttrType(Field field, Cell cell) throws Exception {String fieldType = field.getType().getSimpleName();if ("String".equals(fieldType)) {return getValue(cell);}else if ("Date".equals(fieldType)) {return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(getValue(cell)) ;}else if ("int".equals(fieldType) || "Integer".equals(fieldType)) {return Integer.parseInt(getValue(cell));}else if ("double".equals(fieldType) || "Double".equals(fieldType)) {return Double.parseDouble(getValue(cell));}else {return null;}}/*** 格式转为String* @param cell* @return*/public String getValue(Cell cell) {if (cell == null) {return "";}switch (cell.getCellType()) {case STRING:return cell.getRichStringCellValue().getString().trim();case NUMERIC:if (DateUtil.isCellDateFormatted(cell)) {Date dt = DateUtil.getJavaDate(cell.getNumericCellValue());return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(dt);} else {// 防止数值变成科学计数法String strCell = "";Double num = cell.getNumericCellValue();BigDecimal bd = new BigDecimal(num.toString());if (bd != null) {strCell = bd.toPlainString();}// 去除 浮点型 自动加的 .0if (strCell.endsWith(".0")) {strCell = strCell.substring(0, strCell.indexOf("."));}return strCell;}case BOOLEAN:return String.valueOf(cell.getBooleanCellValue());default:return "";}}
}
工具类完成导入导出
导入数据
给导入的User实体类上面加上自定义注解 @ExcelAttribute
package com.ihrm.domain.system;import com.fasterxml.jackson.annotation.JsonIgnore;
import com.ihrm.domain.poi.ExcelAttribute;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;import javax.persistence.*;
import java.io.Serializable;
import java.text.DecimalFormat;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;/*** 用户实体类*/
@Entity
@Table(name = "bs_user")
@Getter
@Setter
@NoArgsConstructor
public class User implements Serializable {private static final long serialVersionUID = 4297464181093070302L;/*** ID*/@Idprivate String id;/*** 手机号码*/@ExcelAttribute(sort = 2)private String mobile;/*** 用户名称*/@ExcelAttribute(sort = 1)private String username;/*** 密码*/private String password;/*** 启用状态 0为禁用 1为启用*/private Integer enableState;/*** 创建时间*/private Date createTime;private String companyId;private String companyName;/*** 部门ID*/@ExcelAttribute(sort = 6)private String departmentId;/*** 入职时间*/@ExcelAttribute(sort = 5)private Date timeOfEntry;/*** 聘用形式*/@ExcelAttribute(sort = 4)private Integer formOfEmployment;/*** 工号*/@ExcelAttribute(sort = 3)private String workNumber;/*** 管理形式*/private String formOfManagement;/*** 工作城市*/private String workingCity;/*** 转正时间*/private Date correctionTime;/*** 在职状态 1.在职 2.离职*/private Integer inServiceStatus;private String departmentName;/*** level* String* saasAdmin:saas管理员具备所有权限* coAdmin:企业管理(创建租户企业的时候添加)* user:普通用户(需要分配角色)*/private String level;private String staffPhoto; //用户头像public User(Object [] values) {//用户名 手机号 工号 聘用 形式 入职 时间 部门编码this.username = values[1].toString();this.mobile = values[2].toString();this.workNumber = new DecimalFormat("#").format(values[3]);this.formOfEmployment =((Double) values[4]).intValue();this.timeOfEntry = (Date) values[5];this.departmentId = values[6].toString(); //部门编码 != 部门id}/*** JsonIgnore* : 忽略json转化*/@JsonIgnore@ManyToMany@JoinTable(name="pe_user_role",joinColumns={@JoinColumn(name="user_id",referencedColumnName="id")},inverseJoinColumns={@JoinColumn(name="role_id",referencedColumnName="id")})private Set<Role> roles = new HashSet<Role>();//用户与角色 多对多
}
- 在UserController中importUser方法用工具类来导入数据
/*** 导入Excel,添加用户*/@RequestMapping(value = "/user/import" , method = RequestMethod.POST)public Result importUser(@RequestParam(name = "file") MultipartFile file) throws Exception {List<User> list = new ExcelImportUtil(User.class).readExcel(file.getInputStream(), 1, 1);//3.批量保存用户userService.saveAll(list , companyId , companyName);return new Result(ResultCode.SUCCESS);}
骚戴理解:这里也只要知道怎么用这个工具类即可
- User.class是指的导出实体类的字节码
- file.getInputStream()是指的传入文件的IO输入流
- 第一个1是指从模板的第二行开始读数据
- 第二个1是指从模板的第二列开始读数据
导出数据
给导出的实体类上面加上自定义注解 @ExcelAttribute
package com.ihrm.domain.employee.response;import com.ihrm.domain.employee.EmployeeResignation;
import com.ihrm.domain.employee.UserCompanyPersonal;
import com.ihrm.domain.poi.ExcelAttribute;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.springframework.beans.BeanUtils;@Getter
@Setter
@NoArgsConstructor
@ToString
public class EmployeeReportResult {@ExcelAttribute(sort = 0)private String userId;@ExcelAttribute(sort = 1)private String username;private String departmentName;@ExcelAttribute(sort = 2)private String mobile;@ExcelAttribute(sort = 9)private String timeOfEntry;private String companyId;private String sex;/*** 出生日期*/private String dateOfBirth;/*** 最高学历*/@ExcelAttribute(sort = 3)private String theHighestDegreeOfEducation;/*** 国家地区*/@ExcelAttribute(sort = 4)private String nationalArea;/*** 护照号*/@ExcelAttribute(sort = 5)private String passportNo;/*** 身份证号*/private String idNumber;/*** 身份证照片-正面*/private String idCardPhotoPositive;/*** 身份证照片-背面*/private String idCardPhotoBack;/*** 籍贯*/@ExcelAttribute(sort = 6)private String nativePlace;/*** 民族*/private String nation;/*** 英文名*/private String englishName;/*** 婚姻状况*/private String maritalStatus;/*** 员工照片*/private String staffPhoto;/*** 生日*/@ExcelAttribute(sort = 7)private String birthday;/*** 属相*/@ExcelAttribute(sort = 8)private String zodiac;/*** 年龄*/private String age;/*** 星座*/private String constellation;/*** 血型*/private String bloodType;/*** 户籍所在地*/private String domicile;/*** 政治面貌*/private String politicalOutlook;/*** 入党时间*/private String timeToJoinTheParty;/*** 存档机构*/private String archivingOrganization;/*** 子女状态*/private String stateOfChildren;/*** 子女有无商业保险*/private String doChildrenHaveCommercialInsurance;/*** 有无违法违纪行为*/private String isThereAnyViolationOfLawOrDiscipline;/*** 有无重大病史*/private String areThereAnyMajorMedicalHistories;/*** QQ*/private String qq;/*** 微信*/private String wechat;/*** 居住证城市*/private String residenceCardCity;/*** 居住证办理日期*/private String dateOfResidencePermit;/*** 居住证截止日期*/private String residencePermitDeadline;/*** 现居住地*/private String placeOfResidence;/*** 通讯地址*/private String postalAddress;/*** 联系手机*/private String contactTheMobilePhone;/*** 个人邮箱*/private String personalMailbox;/*** 紧急联系人*/private String emergencyContact;/*** 紧急联系电话*/private String emergencyContactNumber;/*** 社保电脑号*/private String socialSecurityComputerNumber;/*** 公积金账号*/private String providentFundAccount;/*** 银行卡号*/private String bankCardNumber;/*** 开户行*/private String openingBank;/*** 学历类型*/private String educationalType;/*** 毕业学校*/private String graduateSchool;/*** 入学时间*/private String enrolmentTime;/*** 毕业时间*/private String graduationTime;/*** 专业*/private String major;/*** 毕业证书*/private String graduationCertificate;/*** 学位证书*/private String certificateOfAcademicDegree;/*** 上家公司*/private String homeCompany;/*** 职称*/private String title;/*** 简历*/private String resume;/*** 有无竞业限制*/private String isThereAnyCompetitionRestriction;/*** 前公司离职证明*/private String proofOfDepartureOfFormerCompany;/*** 备注*/private String remarks;/*** 离职时间*/@ExcelAttribute(sort = 12)private String resignationTime;/*** 离职类型*/@ExcelAttribute(sort = 10)private String typeOfTurnover;/*** 申请离职原因*/@ExcelAttribute(sort = 11)private String reasonsForLeaving;public EmployeeReportResult(UserCompanyPersonal personal, EmployeeResignation resignation) {BeanUtils.copyProperties(personal,this);if(resignation != null) {BeanUtils.copyProperties(resignation,this);}}
}
骚戴理解: @ExcelAttribute注解就是用来定义实体类属性在模板中列的位置,例如模板中姓名对应的列号是1,所以 @ExcelAttribute(sort = 1) private String username;里的sort = 1,注意列号是从0开始的
在export方法里使用导入工具类
@RequestMapping(value = "/export/{month}", method = RequestMethod.GET)public void export(@PathVariable String month) throws Exception {//1.获取报表数据List<EmployeeReportResult> list = userCompanyPersonalService.findByReport(companyId,month+"%");//2.加载模板Resource resource = new ClassPathResource("excel-template/hr-demo.xlsx");FileInputStream fis = new FileInputStream(resource.getFile());//3.通过工具类完成下载new ExcelExportUtil(EmployeeReportResult.class,2,2).export(response,fis,list,month+"人事报表.xlsx");}
骚戴理解:下面的工具类会用就行,知道传入的参数是什么就可以了
new ExcelExportUtil(EmployeeReportResult.class,2,2).export(response,fis,list,month+"人事报表.xlsx");
- EmployeeReportResult.class是导出的实体类的字节码,也就是从数据库中查询到的数据集合的实体类的字节码
- 第一个2是指的行号,也就是把数据集合中的数据从第几行开始写到Excel表格中,注意这个2是指的把数据从第3行开始写入,因为下标是从0开始的
- 第二个2是指的样式的行号,可以看到我希望所有的数据样式都是如下面的第三行的样式一样,所以这个2是指的第三行的样式
- response其实就是HttpServletResponse
- fis是模板样式的文件输入IO流
FileInputStream fis = new FileInputStream(resource.getFile());
- list是需要导出数据库中查询到的所有集合数据
List<EmployeeReportResult> list = userCompanyPersonalService.findByReport(companyId,month+"%");
- month+"人事报表.xlsx"是导出文件的名称
百万数据报表概述
概述
我们都知道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的风险。
JDK性能监控工具介绍
没有性能监控工具一切推论都只能停留在理论阶段,我们可以使用Java的性能监控工具来监视程序的运行情况,包 括CUP,垃圾回收,内存的分配和使用情况,这让程序的运行阶段变得更加可控,也可以用来证明我们的推测。这里我们使用JDK提供的性能工具Jvisualvm来监控程序运行。
Jvisualvm概述
VisualVM 是Netbeans的profile子项目,已在JDK6.0 update 7 中自带,能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已被GC的对象,反向查看分配的堆栈
Jvisualvm的位置
Jvisualvm位于JAVA_HOME/bin目录下,直接双击就可以打开该程序。如果只是监控本地的java进程,是不需要配 置参数的,直接打开就能够进行监控。首先我们需要在本地打开一个Java程序,例如我打开员工微服务进程,这时 在jvisualvm界面就可以看到与IDEA相关的Java进程了:
骚戴理解:我的Jvisualvm默认路径在C:\Program Files\Java\jdk1.8.0_351\bin\jvisualvm.exe
Jvisualvm的使用
Jvisualvm使用起来比较简单,双击点击当前运行的进程即可进入到程序的监控界面
概述:可以看到进程的启动参数。
监视:
- 左上:cpu利用率,gc状态的监控
- 右上:堆利用率,永久内存区的利用率
- 左下:类的监控
- 右下: 线程的监控
线程:能够显示线程的名称和运行的状态,在调试多线程时必不可少,而且可以点进一个线程查看这个线程 的详细运行情况
解决方案分析
对于百万数据量的Excel导入导出,只讨论基于Excel2007的解决方法。在ApachePoi 官方提供了对操作大数据量的导入导出的工具和解决办法,操作Excel2007使用XSSF对象,可以分为三种模式:
- 用户模式:用户模式有许多封装好的方法操作简单,但创建太多的对象,非常耗内存(之前使用的方法)
- 事件模式:基于SAX方式解析XML,SAX全称Simple API for XML,它是一个接口,也是一个软件包。它是一种XML解析的替代方法,不同于DOM解析XML文档时把所有内容一次性加载到内存中的方式,它逐行扫描文 档,一边扫描,一边解析。
- SXSSF对象:是用来生成海量excel数据文件,主要原理是借助临时存储空间生成excel
这是一张Apache POI官方提供的图片,描述了基于用户模式,事件模式,以及使用SXSSF三种方式操作Excel的特性以及CUP和内存占用情况。
骚戴理解:用户模式就是之前写的那种代码就是用户模式,如下所示就是用户模式
/*** 采用模板打印的形式完成报表生成* 模板* 参数:* 年月-月(2018-02%)** sxssf对象不支持模板打印*/@RequestMapping(value = "/export/{month}", method = RequestMethod.GET)public void export(@PathVariable String month) throws Exception {//1.获取报表数据List<EmployeeReportResult> list = userCompanyPersonalService.findByReport(companyId,month);//2.加载模板Resource resource = new ClassPathResource("excel-template/hr-demo.xlsx");FileInputStream fis = new FileInputStream(resource.getFile());//3.通过工具类完成下载
// new ExcelExportUtil(EmployeeReportResult.class,2,2).
// export(response,fis,list,month+"人事报表.xlsx");//3.根据模板创建工作簿Workbook wb = new XSSFWorkbook(fis);//4.读取工作表Sheet sheet = wb.getSheetAt(0);//5.抽取公共样式Row row = sheet.getRow(2);CellStyle styles [] = new CellStyle[row.getLastCellNum()];for(int i=0;i<row.getLastCellNum();i++) {Cell cell = row.getCell(i);styles[i] = cell.getCellStyle();}//6.构造单元格int rowIndex = 2;Cell cell=null;for(int i=0;i<10000;i++) {for (EmployeeReportResult employeeReportResult : list) {row = sheet.createRow(rowIndex++);// 编号,cell = row.createCell(0);cell.setCellValue(employeeReportResult.getUserId());cell.setCellStyle(styles[0]);// 姓名,cell = row.createCell(1);cell.setCellValue(employeeReportResult.getUsername());cell.setCellStyle(styles[1]);// 手机,cell = row.createCell(2);cell.setCellValue(employeeReportResult.getMobile());cell.setCellStyle(styles[2]);// 最高学历,cell = row.createCell(3);cell.setCellValue(employeeReportResult.getTheHighestDegreeOfEducation());cell.setCellStyle(styles[3]);// 国家地区,cell = row.createCell(4);cell.setCellValue(employeeReportResult.getNationalArea());cell.setCellStyle(styles[4]);// 护照号,cell = row.createCell(5);cell.setCellValue(employeeReportResult.getPassportNo());cell.setCellStyle(styles[5]);// 籍贯,cell = row.createCell(6);cell.setCellValue(employeeReportResult.getNativePlace());cell.setCellStyle(styles[6]);// 生日,cell = row.createCell(7);cell.setCellValue(employeeReportResult.getBirthday());cell.setCellStyle(styles[7]);// 属相,cell = row.createCell(8);cell.setCellValue(employeeReportResult.getZodiac());cell.setCellStyle(styles[8]);// 入职时间,cell = row.createCell(9);cell.setCellValue(employeeReportResult.getTimeOfEntry());cell.setCellStyle(styles[9]);// 离职类型,cell = row.createCell(10);cell.setCellValue(employeeReportResult.getTypeOfTurnover());cell.setCellStyle(styles[10]);// 离职原因,cell = row.createCell(11);cell.setCellValue(employeeReportResult.getReasonsForLeaving());cell.setCellStyle(styles[11]);// 离职时间cell = row.createCell(12);cell.setCellValue(employeeReportResult.getResignationTime());cell.setCellStyle(styles[12]);}}//7.下载//3.完成下载ByteArrayOutputStream os = new ByteArrayOutputStream();wb.write(os);new DownloadUtils().download(os,response,month+"人事报表.xlsx");}
事件模式适用于大数据量的导入操作,SXSSF对象适用于大数据量的导出操作
百万数据报表导出
需求分析
使用Apache POI完成百万数据量的Excel报表导出
解决方案
思路分析
基于XSSFWork导出Excel报表,是通过将所有单元格对象保存到内存中,当所有的Excel单元格全部创建完成之后 一次性写入到Excel并导出。当百万数据级别的Excel导出时,随着表格的不断创建,内存中对象越来越多,直至内 存溢出。Apache Poi提供了SXSSFWork对象,专门用于处理大数据量Excel报表导出。
原理分析
在实例化SXSSFWork这个对象时,可以指定在内存中所产生的POI导出相关对象的数量(默认100),一旦内存中的对象的个数达到这个指定值时,就将内存中的这些对象的内容写入到磁盘中(XML的临时文件格式),就可以将这些对象从内存中销毁,以后只要达到这个值,就会以类似的处理方式处理,直至Excel导出完成。
代码实现
在原有代码的基础上替换之前的XSSFWorkbook,使用SXSSFWorkbook完成创建过程即可
/*** 采用模板打印的形式完成报表生成* 模板* 参数:* 年月-月(2018-02%)** sxssf对象不支持模板打印*/@RequestMapping(value = "/export/{month}", method = RequestMethod.GET)public void export(@PathVariable String month) throws Exception {//1.获取报表数据List<EmployeeReportResult> list = userCompanyPersonalService.findByReport(companyId,month);//2.加载模板Resource resource = new ClassPathResource("excel-template/hr-demo.xlsx");FileInputStream fis = new FileInputStream(resource.getFile());//3.通过工具类完成下载
// new ExcelExportUtil(EmployeeReportResult.class,2,2).
// export(response,fis,list,month+"人事报表.xlsx");//3.根据模板创建工作簿SXSSFWorkbook wb = new SXSSFWorkbook(fis);//4.读取工作表Sheet sheet = wb.getSheetAt(0);//5.抽取公共样式Row row = sheet.getRow(2);CellStyle styles [] = new CellStyle[row.getLastCellNum()];for(int i=0;i<row.getLastCellNum();i++) {Cell cell = row.getCell(i);styles[i] = cell.getCellStyle();}//6.构造单元格int rowIndex = 2;Cell cell=null;for(int i=0;i<10000;i++) {for (EmployeeReportResult employeeReportResult : list) {row = sheet.createRow(rowIndex++);// 编号,cell = row.createCell(0);cell.setCellValue(employeeReportResult.getUserId());cell.setCellStyle(styles[0]);// 姓名,cell = row.createCell(1);cell.setCellValue(employeeReportResult.getUsername());cell.setCellStyle(styles[1]);// 手机,cell = row.createCell(2);cell.setCellValue(employeeReportResult.getMobile());cell.setCellStyle(styles[2]);// 最高学历,cell = row.createCell(3);cell.setCellValue(employeeReportResult.getTheHighestDegreeOfEducation());cell.setCellStyle(styles[3]);// 国家地区,cell = row.createCell(4);cell.setCellValue(employeeReportResult.getNationalArea());cell.setCellStyle(styles[4]);// 护照号,cell = row.createCell(5);cell.setCellValue(employeeReportResult.getPassportNo());cell.setCellStyle(styles[5]);// 籍贯,cell = row.createCell(6);cell.setCellValue(employeeReportResult.getNativePlace());cell.setCellStyle(styles[6]);// 生日,cell = row.createCell(7);cell.setCellValue(employeeReportResult.getBirthday());cell.setCellStyle(styles[7]);// 属相,cell = row.createCell(8);cell.setCellValue(employeeReportResult.getZodiac());cell.setCellStyle(styles[8]);// 入职时间,cell = row.createCell(9);cell.setCellValue(employeeReportResult.getTimeOfEntry());cell.setCellStyle(styles[9]);// 离职类型,cell = row.createCell(10);cell.setCellValue(employeeReportResult.getTypeOfTurnover());cell.setCellStyle(styles[10]);// 离职原因,cell = row.createCell(11);cell.setCellValue(employeeReportResult.getReasonsForLeaving());cell.setCellStyle(styles[11]);// 离职时间cell = row.createCell(12);cell.setCellValue(employeeReportResult.getResignationTime());cell.setCellStyle(styles[12]);}}//7.下载//3.完成下载ByteArrayOutputStream os = new ByteArrayOutputStream();wb.write(os);new DownloadUtils().download(os,response,month+"人事报表.xlsx");}
对比测试
- XSSFWorkbook生成百万数据报表
使用XSSFWorkbook生成Excel报表,时间较长,随着时间推移,内存占用原来越多,直至内存溢出
- SXSSFWorkbook生成百万数据报表
使用SXSSFWorkbook生成Excel报表,内存占用比较平缓
骚戴理解:可以看到SXSSFWorkbook的堆内存变化,即蓝色的曲线图,可以看出它是一个个的山峰一样的图形,上升后会下降,下降的原因是因为对象达到了SXSSFWorkbook设置的阈值,默认是100,也就是达到了100个对象后就会把对象写到临时的xml文件里面,然后销毁掉内存中的这部分对象,所以才会下降,这样的确可以实现百万数据量的导出,但是也是有一些问题的,由于内存速度速度和磁盘速度是不一样的,首先主线程会把所有的对象创建好放到内存里,然后达到阈值才会写入xml临时文件,这是两个过程,然后通常内存写入的速度远远快于磁盘写入xml临时文件的速度,所以如果数据量是很多,例如千万级别的数据量的时候还是会把内存给挤爆,然后报错OOM内存溢出!
百万数据报表读取
需求分析
使用POI基于事件模式解析案例提供的Excel文件
解决方案
思路分析
用户模式:加载并读取Excel时,是通过一次性的将所有数据加载到内存中再去解析每个单元格内容。当Excel 数据量较大时,由于不同的运行环境可能会造成内存不足甚至OOM异常。
事件模式:它逐行扫描文档,一边扫描一边解析。由于应用程序只是在读取数据时检查数据,因此不需要将数据完全存储在内存中,这对于大型文档的解析是个巨大优势。
步骤分析
- 设置POI的事件模式
- 根据Excel获取文件流
- 根据文件流创建OPCPackage
- 创建XSSFReader对象
- Sax解析
- 自定义Sheet处理器
- 创建Sax的XmlReader对象
- 设置Sheet的事件处理器
- 逐行读取
原理分析
我们都知道对于Excel2007的实质是一种特殊的XML存储数据,那就可以使用基于SAX的方式解析XML完成Excel的读取。SAX提供了一种从XML文档中读取数据的机制。它逐行扫描文档,一边扫描一边解析。由于应用程序只是在读取数据时检查数据,因此不需要将数据完全存储在内存中,这对于大型文档的解析是个巨大优势
骚戴理解:事件驱动就是一边解析一边用,所以数据不完全放内存里,即拿即用,用完就删,所以对内存的占用很少,但是只能解析一次,因为数据会被删掉,不可逆!
代码实现
自定义处理器
package cn.itcast.poi.entity.cn.itcast.poi.handler;import cn.itcast.poi.entity.PoiEntity;
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;
import org.apache.poi.xssf.usermodel.XSSFComment;/*** 自定义的事件处理器* 处理每一行数据读取* 实现接口*/
public class SheetHandler implements XSSFSheetXMLHandler.SheetContentsHandler {private PoiEntity entity;/*** 当开始解析某一行的时候触发* i:行索引*/@Overridepublic void startRow(int i) {//实例化对象if(i>0) {entity = new PoiEntity();}}/*** 当结束解析某一行的时候触发* i:行索引*/@Overridepublic void endRow(int i) {//使用对象进行业务操作System.out.println(entity);}/*** 对行中的每一个表格进行处理* cellReference: 单元格名称* value:数据* xssfComment:批注*/@Overridepublic void cell(String cellReference, String value, XSSFComment xssfComment) {//对对象属性赋值if(entity != null) {String pix = cellReference.substring(0,1);switch (pix) {case "A":entity.setId(value);break;case "B":entity.setBreast(value);break;case "C":entity.setAdipocytes(value);break;case "D":entity.setNegative(value);break;case "E":entity.setStaining(value);break;case "F":entity.setSupportive(value);break;default:break;}}}
}
骚戴理解:String pix = cellReference.substring(0,1);是获取单元格名称的第一个字符,也就是标识列名的字母。在Excel中,单元格名称通常由列名字母和行号组成(例如A1、B2等),这里使用substring方法截取单元格名称的第一个字符来标识列名从而确定对应属性进行赋值操作。
自定义解析
package cn.itcast.poi.test;import cn.itcast.poi.entity.cn.itcast.poi.handler.SheetHandler;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackageAccess;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.model.StylesTable;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;import java.io.InputStream;
import java.util.Iterator;/*** 使用事件模型解析百万数据excel报表*/
public class PoiTest06 {public static void main(String[] args) throws Exception {String path = "C:\\Users\\ThinkPad\\Desktop\\ihrm\\day8\\资源\\百万数据报表\\demo.xlsx";//1.根据excel报表获取OPCPackageOPCPackage opcPackage = OPCPackage.open(path, PackageAccess.READ);//2.创建XSSFReaderXSSFReader reader = new XSSFReader(opcPackage);//3.获取SharedStringTable对象SharedStringsTable table = reader.getSharedStringsTable();//4.获取styleTable对象StylesTable stylesTable = reader.getStylesTable();//5.创建Sax的xmlReader对象XMLReader xmlReader = XMLReaderFactory.createXMLReader();//6.注册事件处理器XSSFSheetXMLHandler xmlHandler = new XSSFSheetXMLHandler(stylesTable,table,new SheetHandler(),false);xmlReader.setContentHandler(xmlHandler);//7.逐行读取XSSFReader.SheetIterator sheetIterator = (XSSFReader.SheetIterator) reader.getSheetsData();while (sheetIterator.hasNext()) {InputStream stream = sheetIterator.next(); //每一个sheet的流数据InputSource is = new InputSource(stream);xmlReader.parse(is);}}
}
骚戴理解:
- OPCPackage opcPackage = OPCPackage.open(path, PackageAccess.READ);这行代码的作用是打开并读取一个指定路径的Office Open XML (OOXML)包文件,该文件通常是.xlsx或.xlsm格式的Excel文件。具体来说,OPCPackage类可以从本地文件系统、InputStream或URL中打开OOXML包,并提供了读取、修改和创建OOXML文档的方法。其中,path参数表示要打开的OOXML包文件的路径;PackageAccess.READ表示采用仅读取模式的方式打开该文件。通过OPCPackage对象的实例,Java程序可以访问包中的各种元素,如workbook、sharedStrings等,以及它们的内容,继而进行相应的处理操作。
- XSSFReader reader = new XSSFReader(opcPackage);这行代码的作用是基于已打开的OOXML包文件,创建一个XSSFReader对象。在Apache POI中,XSSFReader类是用于读取Excel2007及以上版本文件(即.xlsx格式)的类,提供了对工作簿、工作表和单元格等元素的读取操作。reader变量是一个XSSFReader对象的引用,可以通过它访问某个.xlsx文件的所有部件(Parts)并读取其中的数据内容,如工作簿、电子表格、样式集等。具体来说,该代码中的opcPackage参数表示已经打开的.xlsx文件所对应的OPCPackage对象。通过该对象创建XSSFReader对象之后,程序就可以通过调用XSSFReader对象的相应方法,完成Excel文件的解析和读取工作。
- SharedStringsTable table = reader.getSharedStringsTable();这行代码的作用是通过XSSFReader对象获取Excel文件中的共享字符串表(SharedStringsTable)对象。在Excel2007及以上版本中,相比于早期的.xls格式,.xlsx格式的文件更加节省空间,其中一个原因是采用了共享字符串表来存储重复的字符串值,而非直接将它们写入每个单元格。共享字符串表主要用于存储文本型数据类型,包括文字和数字等。因此,在解析.xlsx格式的文件时,需要先将其包含的共享字符串表加载到内存中,才能通过索引查找对应字符串并读取单元格的值。该代码中的reader参数表示已经创建的XSSFReader对象,程序通过调用其getSharedStringsTable()方法,可以获取Excel文件中的共享字符串表对象,从而实现读取其中存储的字符串值及其索引的功能。得到共享字符串表对象之后,程序可以根据需要使用它的API进行相关的字符串操作,如添加、删除、修改、查询等。
- StylesTable stylesTable = reader.getStylesTable();这行代码的作用是通过XSSFReader对象获取Excel2007及以上版本文件中的样式表(StylesTable)对象。在Excel文件中,单元格的样式是由一系列属性和特征组成的。通常情况下,如果多个单元格具有相同的样式属性,则可以将它们定义为一个公共的样式引用,并在样式表中记录它们所包含的所有属性信息。样式表主要用于描述单元格、字体、背景色、边框、对齐方式等样式的各种属性和特征。该代码中的reader参数表示已经创建的XSSFReader对象,程序通过调用其getStylesTable()方法,可以获取Excel文件中的样式表对象,从而实现读取其中存储的样式信息的功能。得到样式表对象之后,程序可以根据需要使用它的API对样式进行相关的操作,如添加、修改、删除、查询等。获取样式表对象也是解析Excel2007及以上版本文件的关键步骤之一,因为对单元格的格式化和样式设置直接依赖于样式表。
- XMLReader xmlReader = XMLReaderFactory.createXMLReader();这行代码的作用是创建一个基于SAX(Simple API for XML)实现的XML解析器对象,该解析器由Java自带的XMLReaderFactory工厂类生成。在Java中,处理XML文档通常有两种方式:DOM和SAX。其中,DOM是一种基于树形结构的解析方式,可以将整个XML文档加载到内存中,并以树形结构表示出来;而SAX则是一种基于事件驱动的解析方式,它会在XML文件解析时产生一系列的事件,我们可以注册回调方法来获取并处理这些事件。由于SAX不需要将XML文档完全加载到内存中,所以对于大型XML文件的处理更加高效和灵活。该代码中使用了Java标准库提供的XMLReaderFactory类生成一个默认配置的SAX解析器对象xmlReader,程序可以通过该对象的相关API实现读取和解析Excel2007及以上版本的xlsx文件。通常情况下,在通过XSSFReader对象获取Excel文件各个部分数据之前,需要先通过该解析器对象将对应部分的XML内容解析为可读格式,再调用相应API进行访问和操作。
XSSFSheetXMLHandler xmlHandler = new XSSFSheetXMLHandler(stylesTable,table,new SheetHandler(),false);xmlReader.setContentHandler(xmlHandler);
- 这两行代码的作用是将读取到的Excel表格数据与对应的样式信息进行关联,并设置XMLReader对象的内容处理器为XSSFSheetXMLHandler对象,进而实现解析.xlsx文件中单个sheet页数据及其样式的功能。其中,参数stylesTable表示Excel文件的样式表对象(StylesTable),参数table表示Excel文件的 共享字符串表对象(SharedStringsTable)。这两个对象是在之前获取Excel数据(包括字符串和样式)时得到的。参数new SheetHandler()表示自定义的SheetHandler对象,该对象继承自DefaultHandler类,实现了XML文档解析时产生的各种事件的回调方法,以便程序能够相应地读取和处理表格中的数据。参数false表示XSSFSheetXMLHandler对象的构造函数不需要启用日期格式化功能,默认为关闭状态。接着,通过xmlReader.setContentHandler(xmlHandler)方法将XSSFSheetXMLHandler对象设置为XMLReader对象的内容处理器。当程序开始解析Excel文件时,XPath表达式会逐一匹配XML文档中的各个节点,当遇到符合条件的节点时,会触发XMLReader对象相应的解析事件,调用XSSFSheetXMLHandler对象对应的回调方法来进行解析、读取和处理。从而实现读取并解析Excel文件中单个表格页数据及其样式的操作。
XSSFReader.SheetIterator sheetIterator = (XSSFReader.SheetIterator) reader.getSheetsData();while (sheetIterator.hasNext()) {InputStream stream = sheetIterator.next(); //每一个sheet的流数据InputSource is = new InputSource(stream);xmlReader.parse(is);}
- 这行代码的作用是使用XSSFReader对象获取Excel文件中所有sheet页数据流信息,并封装成SheetIterator迭代器对象,以便程序可以遍历和访问其中的每个sheet页。在Excel文件中,一个.xlsx格式的文件通常包含多个sheet页,每个sheet页由若干行和列构成,其中存储着实际的数据内容。为了读取和处理这些数据内容,需要将各个sheet页数据流信息分别提取出来,并进行相应的解析和操作。该代码中,通过调用reader.getSheetsData()方法得到的SheetIterator对象,可以对sheet页数据流进行逐一迭代,并返回每个sheet页数据的相关信息,如名称、ID、XML路径等。程序可以通过该对象的hasNext()方法检查是否还有下一个sheet页待读取,然后再调用next()方法获取当前sheet页的输入流(InputStream),并对其进行相应的数据读取和解析。在while循环中,每次从迭代器中获取当前sheet页的输入流,并创建一个InputSource对象,以帮助XMLReader对象解析该输入流。然后调用xmlReader.parse(is)方法启动XMLReader对象解析输入流并触发各种回调事件,程序根据解析得到的事件来读取相应的表格数据和样式信息。由于Excel2007及以上版本采用了基于XML标准的文件格式,故在解析时需要借助SAX解析器进行读取。因此,在解析每个sheet页的XML数据之前,需要通过XSSFSheetXMLHandler对象将各个单元格的值与样式进行匹配。
对比测试
用户模式下读取测试Excel文件直接内存溢出,测试Excel文件映射到内存中还是占用了不少内存;事件模式下可以 流畅的运行。
- 使用用户模型解析
- 使用事件模型解析
总结
通过简单的分析以及运行两种模式进行比较,可以看到用户模式下使用更简单的代码实现了Excel读取,但是在读 取大文件时CPU和内存都不理想;而事件模式虽然代码写起来比较繁琐,但是在读取大文件时CPU和内存更加占优。
相关文章:
POI报表的高级应用
POI报表的高级应用 掌握基于模板打印的POI报表导出理解自定义工具类的执行流程 熟练使用SXSSFWorkbook完成百万数据报表打印理解基于事件驱动的POI报表导入 模板打印 概述 自定义生成Excel报表文件还是有很多不尽如意的地方,特别是针对复杂报表头,单…...
【计算机毕设选题推荐】超市管理系统SpringBoot+SSM+Vue
前言:我是IT源码社,从事计算机开发行业数年,专注Java领域,专业提供程序设计开发、源码分享、技术指导讲解、定制和毕业设计服务 项目名 基于SpringBoot的超市管理系统 技术栈 SpringBootVueMySQLMaven 文章目录 一、超市管理系统…...
【算法1-4】递推与递归-P1002 [NOIP2002 普及组] 过河卒
## 题目描述 棋盘上 A 点有一个过河卒,需要走到目标 B 点。卒行走的规则:可以向下、或者向右。同时在棋盘上 C 点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。 棋盘用坐标表示&#…...
浅谈压力测试的作用是什么
随着现代应用程序变得越来越复杂,用户的期望也在不断提高,对性能和可靠性的要求变得更加苛刻。在应用程序开发和维护的过程中,压力测试是一项至关重要的活动,它可以帮助发现潜在的问题、评估系统的性能极限,以及确保在…...
互联网Java工程师面试题·Java 总结篇·第一弹
目录 1、面向对象的特征有哪些方面? 2、访问修饰符 public,private,protected,以及不写(默认)时的区别? 3、String 是最基本的数据类型吗? 4、float f3.4;是否正确? 5、short s1 1; s1 s1 1;有错吗…...
Anylogic 读取和写入Excel文件
1、选择面板-连接-Excel文件,拖入到视图中 然后在excel文件的属性中进行绑定外部excel文件。 绑定完之后,在你需要读取的地方进行写代码, //定义开始读取的行数 //这里设为2,是因为第一行是数据名称 int row12; //读取excel文件信…...
茶百道全链路可观测实战
作者:山猎 茶百道是四川成都的本土茶饮连锁品牌,创立于 2008 年 。经过 15 年的发展,茶百道已成为餐饮标杆品牌,全国门店超 7000 家,遍布全国 31 个省市,实现中国大陆所有省份及各线级城市的全覆盖。2021 …...
Java-JDBC
JDBC JDBC英文名为:Java Data Base Connectivity(Java数据库连接),官方解释它是Java编程语言和广泛的数据库之间独立于数据库的连接标准的Java API 根本上说JDBC是一种规范,它提供的接口,一套完整的,允许便捷式访问底…...
【ROS】Nav2源码之nav2_planner详解
【ROS】郭老二博文之:ROS目录 1、简述 nav2_planner是路径规划器,把起始位置、姿势的信息输入nav2_planner模块,将会生成可行路径。 nav2_planner路径规划器和nav2_controller控制器相似,也使用插件的形式加载不同的路径规划器。 常用的路径规划器插件有: 1)NavFn Plan…...
mysql报SQLSTATE[22007]的错误的一个原因
最近在修改一个程序,打算将$video这个参数保存到数据库。修改的过程中出现错误。导致该程序不能发布新文章。在程序的一个db.php程序文件里使用var_dump($input, $stmt) ; 语句看到了错误信息,并找到了错误原因。信息里包含的错误代码是: SQ…...
Python —— UI自动化之 三大等待与三大切换
1、三大等待 1、硬性等待 1、概述 硬性等待也可以称之为强制等待,写法如下: time.sleep() 优点:使用简单 缺点:等待时间把握不准,容易造成时间浪费或者等待时间不足 2、实战 from time import sleep from sele…...
初识容器Docker
目前使用 Docker 基本上有两个选择:Docker Desktop和Docker Engine。Docker Desktop 是专门针对个人使用而设计的,支持 Mac 和 Windows 快速安装,具有直观的图形界面,还集成了许多周边工具,方便易用。 不是太推荐使用D…...
pikachu靶场搭建及通关
一、靶场搭建 下载工具:phpstudy Pikachu靶机下载地址: https://github.com/zhuifengshaonianhanlu/pikachu 下载后解压缩并放入如下文件夹(网站根目录) 建议修改文件名称为 pikachu 修改配置文件(mysql 用户名&…...
选择排序(学习笔记)
选择排序 选择排序的基本思想是冒泡排序,记录当前位置i和最小值k的位置,使用一个变量j往后寻找。 每一轮找到最小值后与第一个元素进行交换,以此类推。 不使用辅助变量交换两个元素的值方法 package com.company.sort;import java.util.Ra…...
PCL 生成球形点云
目录 一、算法原理二、代码实现三、结果展示四、参考链接一、算法原理 生成球体点云的方法有很多种,Marsaglia于1972年提出了一个简单易行的实现方法,它从(-1,1)上的独立均匀分布中选取 x 1 x_1 x...
Flutter 剪裁(Clip)
🔥 ClipOval 🔥 子组件为正方形时剪裁成内贴圆形;为矩形时,剪裁成内贴椭圆 裁剪纯色背景 ClipOval(child: Container(width: 300.w,height: 300.w,decoration: const BoxDecoration(color: Colors.red),),), 裁剪背景图片 裁剪前…...
嵌入式C语言自我修养《GNU C编译器扩展语法》学习笔记
目录 一、C语言标准和编译器 二、指定初始化 三、宏构造“利器”:语句表达式 四、typeof与container_of宏 五、零长度数组 六、属性声明:section 七、属性声明:aligned 一、C语言标准和编译器 C语言标准的发展过程: ●…...
密码学二: md5 网站服务器与用户通信过程 ca原理 签名原理 Flame 病毒原理
md5被破解? MD5(Message Digest Algorithm 5)是一个较早的哈希函数,但由于其弱点和漏洞,它已经被认为不再适合用于安全性要求较高的应用。MD5的一些安全性问题包括: 碰撞攻击: MD5已经被证明容易受到碰撞攻…...
Zend Framework 3.1.3 gadget chain
前言 在推特上的PT SWARM账号发布了一条消息。 一个名为Zend Framework的php框架出现了新的gadget chain,可导致RCE。笔者尝试复现,但失败了。所幸,我基于此链,发现在这个框架的最新版本中的另一条链。 复现过程 这里使用vscod…...
互联网Java工程师面试题·Java 并发编程篇·第四弹
目录 39、volatile 有什么用?能否用一句话说明下 volatile 的应用场景? 40、为什么代码会重排序? 41、在 java 中 wait 和 sleep 方法的不同? 42、用 Java 实现阻塞队列 43、一个线程运行时发生异常会怎样? 44、…...
3、Linux下安装
以下操作仅限于rh系列:支持rpm/yum安装方式,不支持deb/apt安装方式。 以下操作仅限于rh系列:支持rpm/yum安装方式,不支持 deb/apt安装方式。 1、在线下载安装包: wget https://downloads.mysql.com/archives/get/p/23/file/ m…...
Zookeeper【Curator客户端Java版】从0到1——万字学习笔记
目录 初识Zookeeper Zookeeper作用 维护配置信息 分布式锁服务 集群管理 生产分布式唯一ID Zookeeper的设计目标 Zookeeper 工作机制 数据模型 ZooKeeper 命令操作 服务端常用命令 客户端常用命令 ZooKeeper JavaAPI操作 Curator 介绍 Curator API 常用操作 导入依赖 建立连接 …...
生物标志物发现中的无偏数据分析策略
目录 0. 导论基本概念 1. 生物标志物发现的注意事项2. 数据预处理2.1 高质量原始数据和缺失值处理2.2 数据过滤2.3 数据归一化 3. 数据质量评估3.1 混杂因素3.2 类别分离3.3 功效分析3.4 批次效应 4. 生物标志物发现4.1 策略4.2 数据分析工具4.3 模型优化策略 0. 导论 组学技术…...
华为校招机试题- 机器人活动区域-2023年
题目描述: 现有一个机器人,可放置于 M N的网格中任意位置,每个网格包含一个非负整数编号。当相邻网格的数字编号差值的绝对值小于等于 1 时,机器人可在网格间移动 问题:求机器人可活动的最大范围对应的网格点数目。 说明: 1)网格左上角坐标为 (0, 0),右下角坐标为 (m-…...
半屏小程序
准备工作 tip 管理后台配置 设置-》第三方设置-》半屏小程序管理-》我调用的 添加小程序 有些手机会唤起失败,直接唤起了全屏的小程序,所以我们为了兼容,需要在app.config.ts加上 {"embeddedAppIdList": ["wxxxxxxxx"]/…...
2023年最新Python大数据之Python基础【七】管理系统
文章目录 7、学生管理系统8、函数递归9、lambda函数后记 7、学生管理系统 # 需求拆分:1.展示学生管理系统的功能有哪些,引导用户键入序号选择功能 2.获取用户键入的功能 3.分析具体要执行哪一项功能 4.执行功能 def print_all_option():"""用户功能界面展示&qu…...
【网安】网络安全防止个人信息泄露
网络安全防止个人信息泄露 1、尝试检查自己的网络隐私数据是否泄漏过,可以使用下面的网站2、使用安全非盈利组织的浏览器3、安装浏览器插件,防止网络跟踪4、保持安全的访问方式 1、尝试检查自己的网络隐私数据是否泄漏过,可以使用下面的网站 …...
ChatGPT,AIGC 数据库应用 Mysql 常见优化30例
使用ChatGPT,AIGC总结出Mysql的常见优化30例。 1. 建立合适的索引:在Mysql中,索引是重要的优化手段,可以提高查询效率。确保表的索引充分利用,可以减少查询所需的时间。如:create index idx_name on table_name(column_name); 2. 避免使用select * :尽可能指定要返回的…...
并查集路径压缩
并查集里的 find 函数里可以进行路径压缩,是为了更快速的查找一个点的根节点。对于一个集合树来说,它的根节点下面可以依附着许多的节点,因此,我们可以尝试在 find 的过程中,从底向上,如果此时访问的节点不…...
spring和springMVC的说明
Spring和Spring MVC都是Java应用程序开发中常用的框架,它们提供了一种结构化的方法来构建企业级Java应用程序。下面我将对它们进行详细的说明: Spring: 概述: Spring是一个综合的Java应用程序开发框架,旨在简化企业级…...
在酒店做那个网站好/白山网络推广
Spring简介 Spring是目前最流行的Java开发框架 Spring下面有好多个子项目 关于数据库,安全都有不同的子项目支撑 所有项目的底层就是Spring框架 这篇文章讲SpringBoot 流程 SpringBoot 要求 流程步骤 1.创建模块 2.写一个请求处理类-和方法 从普通类到请求处…...
精品课程网站怎么做/怎么建一个自己的网站
1. 两种细线表格做法 源码如下:<table width"100%" border"1" bordercolor"#000000"> <tr bordercolor"#FFFFFF"> <td>表格边线为1,线色为黑,行线色为白。</td> </…...
新手怎样做网站推广/企业查询系统官网天眼查
题目链接 题意:输入一个n,给出一个由n行4列组成的数组,要求在每一列找出一个数,使得四个数相加为0。输出一共有多少种情况。 题记:首先将四列的数变成两列,即将第一第二列的每两个数加起来存到一个新数组…...
wordpress 中文/店铺推广引流的方法
一般情况下下拉选择框的默认值都是第一个,比如下面这个代码的默认值肯定是“红色”: <select> <option value"红色">红色</option> <option value"绿色">绿色</option> <option value"蓝色&q…...
做简单网站怎么做/武汉网络推广广告公司
要处理一批文本类型的日期数据,这些文本日期的格式均为 2008-01-31 00:00:00 这样的格式,目标是通过一个函数转化为 20080131 这样的文本样式,于是乎写了一个转化程序: def btk_datetime2cvh(table, title): datetimes list(tabl…...
小清新文章网站/seo技术培训班
题目背景 L国即将与I国发动战争!! 题目描述 俗话说的好:“知己知彼,百战不殆”。L国的指挥官想派出间谍前往I国,于是,选人工作就落到了你身上。 你现在有N个人选,每个人都有这样一些数据&#x…...