设计网站页面要怎么切图,20m带宽做网站够用吗,自己做电台直播的网站,2024年1月流感情况Apache POI 基本介绍 Apache POI 是 Apache 软件基金会提供的 100% 开源库。支持 Excel 库的所有基本功能。 图片来源#xff1a;易百教程 基本概念 在 POI 中#xff0c;Workbook代表着一个 Excel 文件#xff08;工作簿#xff09;#xff0c;Sheet代表着 Workbook 中的…Apache POI 基本介绍 Apache POI 是 Apache 软件基金会提供的 100% 开源库。支持 Excel 库的所有基本功能。 图片来源易百教程 基本概念 在 POI 中Workbook代表着一个 Excel 文件工作簿Sheet代表着 Workbook 中的一个表格Row 代表 Sheet 中的一行而 Cell 代表着一个单元格。 HSSFWorkbook对应的就是一个 .xls 文件兼容 Office97-2003 版本。 XSSFWorkbook对应的是一个 .xlsx 文件兼容 Office2007 及以上版本。 在 HSSFWorkbook 中Sheet接口 的实现类为 HSSFSheetRow接口 的实现类为HSSFRowCell 接口的实现类为 HSSFCell。 XSSFWorkbook 中实现类的命名方式类似在 Sheet、Row、Cell 前加 XSSF 前缀即可。 引入依赖 !-- 基本依赖仅操作 xls 格式只需引入此依赖 --
dependencygroupIdorg.apache.poi/groupIdartifactIdpoi/artifactIdversion3.14/version
/dependency
!-- 使用 xlsx 格式需要额外引入此依赖 --
dependencygroupIdorg.apache.poi/groupIdartifactIdpoi-ooxml/artifactIdversion3.14/version
/dependency
复制代码使用 POI 使用 POI 的目的就是为了在 Java 中解析/操作 Excel 表格实现 Excel 的导入/导出的功能接下来我们依次来看它们的实现代码及注意事项。 导出 导出操作即使用 Java 写出数据到 Excel 中常见场景是将页面上的数据可能是经过条件查询的导出这些数据可能是财务数据也可能是商品数据生成 Excel 后返回给用户下载文件。 该操作主要涉及 Excel 的创建及使用流输出的操作在 Excel 创建过程中可能还涉及到单元格样式的操作。 创建并导出基本数据 进行导出操作的第一步是创建 Excel 文件我们写一个方法参数是需要写入 Excel 表格的数据和生成 Excel 方式HSSFXSSF返回一个 Workbook 接口对象。 在方法内部我们采用反射来创建 Workbook 的实例对象。 代码 探索阶段我们先将数据类型限定为 List并把列数限定为某个数字生成一个表格。 代码如下 import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;import java.util.List;
/*** Excel 工厂类负责 Workbook 的生成和解析** author calmer* since 2018/12/5 11:19*/
public class ExcelFactory {/*** 构造 Workbook 对象具体实例化哪种对象由 type 参数指定* param data 要导出的数据* param type Excel 生成方式* return 对应 type 的工作簿实例对象* throws Exception 反射生成对象时出现的异常* liInstantiationException/li* liIllegalAccessException/li* liInstantiationException/li*/public static Workbook createExcel(List data,String type) throws Exception{//根据 type 参数生成工作簿实例对象Workbook workbook (Workbook) Class.forName(type).newInstance();//这里还可以指定 sheet 的名字//Sheet sheet workbook.createSheet(sheetName);Sheet sheet workbook.createSheet();// 限定列数int cols 10;int rows data.size() / cols;int index 0;for (int rowNum 0; rowNum rows; rowNum) {Row row sheet.createRow(rowNum);for (int colNum 0; colNum cols; colNum) {Cell cell row.createCell(colNum);cell.setCellValue(data.get(index).toString());}}return workbook;}
}
复制代码调用时我们生成好数据并构造好 Workbook 对象再调用 Workbook 的 write(OutputStream stream) 方法生成 Excel 文件。 ListString strings new ArrayList();
for (int i 0; i 1000; i) {strings.add(Integer.toString(i1));
}
FileOutputStream out new FileOutputStream(F:\\testXSSF.xlsx);
ExcelFactory.createExcel(strings,org.apache.poi.xssf.usermodel.XSSFWorkbook).write(out);
out.close();
复制代码生成结果 问题 以上代码已经完成简单的 Excel 文件生成操作但其中还有几点问题没有解决 实际场景下Excel 表格中可能并不会存 Integer、String 这种基本数据结构的数据更多的可能是对象数据JSON、List需要有表头并将对象对应的属性一行行的显示出来参考数据库查询语句执行的结果。并且表头的样式一定是要控制的。 我们并没有对方法中 type 属性进行限制即外部可以传来任何类似“a”、“b”这样的无效值届时程序会抛出异常可以使用静态常量或枚举类来限定这样可以增强代码可读性和健壮性。这里我并不想用静态常量或枚举类打算使用注解的方式来控制参数的有效性。完善 我们已经明确了两个问题 之前的程序并不能在实际场景使用我们需要将其完善到具有处理实际数据的能力。利用注解限定参数的有效性。我们先来解决第二个问题即参数的问题。 使用注解限定参数 首先创建一个注解类 import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;/**** author calmer* since 2018/12/5 12:27*/
Retention(RetentionPolicy.SOURCE)
public interface ExcelType {String HSSF org.apache.poi.hssf.usermodel.HSSFWorkbook;String XSSF org.apache.poi.xssf.usermodel.XSSFWorkbook;
}复制代码在方法参数上加上注解 public static Workbook createExcel(List data, ExcelType String type) throws Exception {//内容省略}
复制代码调用时 ExcelFactory.createExcel(list,ExcelType.HSSF).write(out);
复制代码关于使用注解来限定参数的取值范围这种方式我也是偶然看到过可是这种方式在我这里编译器并不会给任何提示我对注解了解不够以后有机会要再好好研究一下。 解决实际数据问题 在实际应用中很常见的情况是我们有很多实体类比如 Person,Product,Order 等借助反射我们可以获取任意实体类的属性列表、getter 方法所以目前我打算利用反射来处理多个对象的 Excel 导出。 首先我们创建一个方法用来获取某个对象的属性列表暂时不考虑要获取父类属性的情况。 /*** 获取对象的属性名数组* param clazz Class 对象用于获取该类的信息* return 该类的所有属性名数组*/
private static String[] getFieldsName(Class clazz){Field[] fields clazz.getDeclaredFields();String[] fieldNames new String[fields.length];for (int i 0; i fields.length; i) {fieldNames[i] fields[i].getName();}return fieldNames;
}
复制代码然后我们完善 createExcel() 方法 public static Workbook createExcel(List data, ExcelType String type) throws Exception {if(data null || data.size() 0){throw new Exception(数据不能为空);}//根据类型生成工作簿Workbook workbook (Workbook) Class.forName(type).newInstance();//新建表格Sheet sheet workbook.createSheet();//生成表头Row thead sheet.createRow(0);String[] fieldsName getFieldsName(data.get(0).getClass());for (int i 0; i fieldsName.length; i) {Cell cell thead.createCell(i);cell.setCellValue(fieldsName[i]);}//保存所有属性的getter方法名Method[] methods new Method[fieldsName.length];for (int i 0; i data.size(); i) {Row row sheet.createRow(i1);Object obj data.get(i);for (int j 0; j fieldsName.length; j) {//加载第一行数据时初始化所有属性的getter方法if(i 0){String fieldName fieldsName[j];//处理布尔值命名 isXxx - setXxxif (fieldName.contains(is)) {fieldName fieldName.split(is)[1];}methods[j] obj.getClass().getMethod(get fieldName.substring(0,1).toUpperCase() fieldName.substring(1));}Cell cell row.createCell(j);Object value methods[j].invoke(obj);//注意判断 value 值是否为空if(value null){value 无;}cell.setCellValue(value.toString());}}return workbook;
}
复制代码测试 以上代码基本满足一开始的需求即以类的属性名为表头并生成表格。接下来我们生成一定量的数据并测试导出效果。 实体类代码 /**** author calmer* since 2018/12/5 14:50*/
public class Person {private Integer id;private String name;private Integer age;private String hobby;private String job;private String address;public Integer getId() {return id;}public void setId(Integer id) {this.id id;}public String getName() {return name;}public void setName(String name) {this.name name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age age;}public String getHobby() {return hobby;}public void setHobby(String hobby) {this.hobby hobby;}public String getJob() {return job;}public void setJob(String job) {this.job job;}public String getAddress() {return address;}public void setAddress(String address) {this.address address;}
}
复制代码测试类代码 ListPerson list new ArrayList();
for (int i 0; i 60000; i) {int num i 1;Person person new Person();person.setId(num);person.setName(张三-(num));person.setAddress(花园路num号(int)Math.ceil(Math.random()*10)号楼);person.setAge(i18);person.setHobby(洗脸刷牙打DOTA);person.setJob(程序员);list.add(person);
}
FileOutputStream out new FileOutputStream(F:\\testXSSF.xlsx);
ExcelFactory.createExcel(list,ExcelType.XSSF).write(out);
out.close();
复制代码生成的结果如下 其他 这里测试的时候我使用6W的数据所以程序进行的比较慢用时如下 像这种大数据量的导出我们可以使用 SXSSF 的方式网上也有很多例子官网的对比。使用 SXSSF 方式导出用时如下 可以看到时间缩短了很多。接下来我们单独来了解一下如何控制表格的样式。 样式 通常我们需要控制的样式有两个部分一个是表头部分的样式另一个是普通单元格的样式。这次我们就仅创建两个方法演示样式的设置方式。 在 POI 中控制单元格样式的对象是 CellStyle 接口可以通过 Workbook 的createStyle 方法获得实例对象这里我们写一个方法设置表头的样式。 private static CellStyle getTheadStyle(Workbook workbook){CellStyle style workbook.createCellStyle();//设置填充色style.setFillForegroundColor(IndexedColors.LIGHT_BLUE.index);style.setFillPattern(CellStyle.SOLID_FOREGROUND);//设置对齐方式style.setAlignment(CellStyle.ALIGN_CENTER);//字体样式Font font workbook.createFont();//设置字体名称font.setFontName(华文隶书);//斜体font.setItalic(true);//字体颜色font.setColor(IndexedColors.YELLOW.index);//字体大小font.setFontHeightInPoints((short)12);//不要忘记这句style.setFont(font);return style;
}
复制代码调用 Row thead sheet.createRow(0);
//设置行高
thead.setHeight((short) 500);
//仅使用 setRowStyle 方法会对除有值的表头设置样式
thead.setRowStyle(style);
String[] fieldsName getFieldsName(data.get(0));
for (int i 0; i fieldsName.length; i) {Cell cell thead.createCell(i);cell.setCellValue(fieldsName[i]);//在这里循环为每个有值的表头设置样式。//结合上面的 setRowStyle 会将表头行全部设置样式cell.setCellStyle(style);
}
复制代码接下来我们写获取普通单元格样式的方法 private static CellStyle getCommonStyle(Workbook workbook){CellStyle style workbook.createCellStyle();//设置填充色style.setFillForegroundColor(IndexedColors.GREEN.index);style.setFillPattern(CellStyle.SOLID_FOREGROUND);//设置居中对齐style.setAlignment(CellStyle.ALIGN_CENTER);Font font workbook.createFont();font.setFontName(华文彩云);//不要忘记这句style.setFont(font);return style;
}
复制代码完整调用 public static Workbook createExcel(List data, ExcelType String type) throws Exception {if(data null || data.size() 0){throw new Exception(数据不能为空);}//根据类型生成工作簿Workbook workbook (Workbook) Class.forName(type).newInstance();//生成样式CellStyle style getTheadStyle(workbook);//新建表格Sheet sheet workbook.createSheet();//生成表头Row thead sheet.createRow(0);//设置行高thead.setHeight((short) 500);//仅使用 setRowStyle 方法会对除有值的表头设置样式thead.setRowStyle(style);String[] fieldsName getFieldsName(data.get(0));for (int i 0; i fieldsName.length; i) {Cell cell thead.createCell(i);cell.setCellValue(fieldsName[i]);//在这里循环为每个有值的表头设置样式。//结合上面的 setRowStyle 会将表头行全部设置样式cell.setCellStyle(style);}//保存所有属性的getter方法名Method[] methods new Method[fieldsName.length];//获取普通单元格样式style getCommonStyle(workbook);for (int i 0; i data.size(); i) {Row row sheet.createRow(i1);Object obj data.get(i);for (int j 0; j fieldsName.length; j) {//加载第一行数据时初始化所有属性的getter方法if(i 0){String fieldName fieldsName[j];methods[j] obj.getClass().getMethod(get fieldName.substring(0,1).toUpperCase() fieldName.substring(1));}Cell cell row.createCell(j);Object value methods[j].invoke(obj);//注意判断 value 值是否为空if(value null){value 无;}cell.setCellValue(value.toString());//设置单元格样式cell.setCellStyle(style);}}return workbook;
}
复制代码生成结果如下忽视颜色搭配与美观程度 注意 这里我运行的出了一个问题在此记录。 注意上面代码的第 28 行和第 48 行这里我们在 for 循环外面获取 Style 对象在 for 循环中循环设置单元格样式的时候始终使用的是__同一个__ Style。而最开始我测试的时候并不是这样写而是像下面这样 for (int i 0; i data.size(); i) {Row row sheet.createRow(i1);Object obj data.get(i);for (int j 0; j fieldsName.length; j) {//加载第一行数据时初始化所有属性的getter方法if(i 0){String fieldName fieldsName[j];methods[j] obj.getClass().getMethod(get fieldName.substring(0,1).toUpperCase() fieldName.substring(1));}Cell cell row.createCell(j);Object value methods[j].invoke(obj);//注意判断 value 值是否为空if(value null){value 无;}cell.setCellValue(value.toString());//设置单元格样式cell.setCellStyle(getCommonStyle(workbook));}
}
复制代码注意 20 行在 getCommonStyle 方法中我们每次调用都会使用 Workbook 对象创建一个 Style 对象而我们的数据一共有 6W 条没条数据又有 6 个属性我们一共要渲染 36W 个单元格也就是要生成 36W 个 Style 对象。于是在我运行代码时便出现了如下报错。 F:\java\jdk1.8.0_151\bin\java.exe
Exception in thread main java.lang.IllegalStateException: The maximum number of Cell Styles was exceeded. You can define up to 64000 style in a .xlsx Workbookat org.apache.poi.xssf.model.StylesTable.createCellStyle(StylesTable.java:789)at org.apache.poi.xssf.usermodel.XSSFWorkbook.createCellStyle(XSSFWorkbook.java:682)at org.apache.poi.xssf.streaming.SXSSFWorkbook.createCellStyle(SXSSFWorkbook.java:869)at com.xhc.study.util.poi.ExcelFactory.getCommonStyle(ExcelFactory.java:114)at com.xhc.study.util.poi.ExcelFactory.createExcel(ExcelFactory.java:73)at Test.main(Test.java:62)Process finished with exit code 1复制代码这里提示我们最多让一个 Workbook 对象生成 64000 个 Style 对象。 以后一些危险的操作还是少做? 导入 导入操作即使用 Java 读取 Excel 中的数据常见场景是在页面上点击导入按钮用户选择 Excel 文件其中可能是多条商品数据包含编号、名称、参数等信息通过文件上传功能将 Excel 读取到我们的程序中解析其中的数据并存入数据库中。 读取数据并打印 导入操作主要依靠 Workbook 的一个构造函数源码如下 /*** Constructs a XSSFWorkbook object, by buffering the whole stream into memory* and then opening an {link OPCPackage} object for it.* * pUsing an {link InputStream} requires more memory than using a File, so* if a {link File} is available then you should instead do something like* precode* OPCPackage pkg OPCPackage.open(path);* XSSFWorkbook wb new XSSFWorkbook(pkg);* // work with the wb object* ......* pkg.close(); // gracefully closes the underlying zip file* /code/pre*/
public XSSFWorkbook(InputStream is) throws IOException {super(PackageHelper.open(is));beforeDocumentRead();// Build a tree of POIXMLDocumentParts, this workbook being the rootload(XSSFFactory.getInstance());// some broken Workbooks miss this...if(!workbook.isSetBookViews()) {CTBookViews bvs workbook.addNewBookViews();CTBookView bv bvs.addNewWorkbookView();bv.setActiveTab(0);}
}
复制代码从这个构造函数来看我们只需提供一个输入流便能构造一个 Workbook 对象出来接下来我们首先写一个处理 Workbook 的方法参数为一个 Workbook 对象我们在方法内部遍历表格并输出数据这里我们默认该文件是一个规则的表格即符合我们之前生成的 Excel 那样的格式。代码如下 /*** 读取 Excel 数据并处理* param workbook 完整的 Workbook 对象*/
public static void readExcel(Workbook workbook) {Sheet sheet workbook.getSheetAt(0);//获取总行数int rows sheet.getPhysicalNumberOfRows();//去除表头从第 1 行开始打印for (int i 0; i rows; i) {Row row sheet.getRow(i);//获取总列数int cols row.getPhysicalNumberOfCells();for (int j 0; j cols; j) {System.out.print(row.getCell(j) \t);}System.out.println();}
}
复制代码为了输出方便我已将 Excel 中的数据降为 100 条。调用代码如下 FileInputStream in new FileInputStream(F:\\testXSSF.xlsx);
XSSFWorkbook workbook new XSSFWorkbook(in);
ExcelFactory.readExcel(workbook);
in.close();
复制代码输出结果如下 数据已经拿到接下来的问题是解析为对象毕竟我们平时向数据库保存数据使用的 ORM 框架一般都使用了传输对象。这里我们再次利用反射完善代码使 readExcel 方法有读取 Excel 中的数据并将其映射为对象的能力。 完善 这里需要明确几个问题 如何确定对象如何将未知对象的每个字段的数据类型与 Excel 表格中的字符串数据进行转换出现空值我们如何解决日期格式的数据我们如何转换有几种日期格式接下来我们开始完善 readExcel 方法代码如下 /*** 读取 Excel 数据并处理** param workbook 完整的 Workbook 对象* param clazz Excel 中存储的数据的类的 Class 对象* param T 泛型* return 解析之后的对象列表与泛型一致* throws Exception*/
public static T ListT readExcel(Workbook workbook, ClassT clazz) throws Exception {ListT list new ArrayList();Sheet sheet workbook.getSheetAt(0);//获取总行数int rows sheet.getPhysicalNumberOfRows();//获取所有字段名String[] fieldsName getFieldsName(clazz);Method[] methods new Method[fieldsName.length];//去除表头从第 1 行开始打印for (int i 1; i rows; i) {T obj clazz.newInstance();Row row sheet.getRow(i);//获取总列数int cols row.getPhysicalNumberOfCells();//获取所有属性Field[] fields clazz.getDeclaredFields();//处理对象的每一个属性for (int j 0; j cols; j) {//第一次循环时初始化所有 setter 方法名if (i 1) {String fieldName fieldsName[j];//处理布尔值命名 isXxx - setXxxif (fieldName.contains(is)) {fieldName fieldName.split(is)[1];}methods[j] obj.getClass().getMethod(set fieldName.substring(0, 1).toUpperCase() fieldName.substring(1), fields[j].getType());}//先将单元格中的值按 String 保存String param row.getCell(j).getStringCellValue();//属性的类型String typeName fields[j].getType().getSimpleName();//set 方法Method method methods[j];//排除空值if (param null || .equals(param)) {continue;}//根据对象的不同属性字段转换单元格中的数据类型并调用 set 方法赋值if (Integer.equals(typeName) || int.equals(typeName)) {method.invoke(obj, Integer.parseInt(param));} else if (Date.equals(typeName)) {String pattern;if (param.contains(CST)) {//java.util.Date 的默认格式pattern EEE MMM dd HH:mm:ss zzz yyyy;} else if (param.contains(:)) {//带有时分秒的格式pattern yyyy-MM-dd HH:mm:ss;} else {//简单格式pattern yyyy-MM-dd;}method.invoke(obj, new SimpleDateFormat(pattern, Locale.UK).parse(param));} else if (Long.equalsIgnoreCase(typeName)) {method.invoke(obj, Long.parseLong(param));} else if (Double.equalsIgnoreCase(typeName)) {method.invoke(obj, Double.parseDouble(param));} else if (Boolean.equalsIgnoreCase(typeName)) {method.invoke(obj, Boolean.parseBoolean(param));} else if (Short.equalsIgnoreCase(typeName)) {method.invoke(obj, Short.parseShort(param));} else if (Character.equals(typeName) || char.equals(typeName)) {method.invoke(obj, param.toCharArray()[0]);} else {//若数据格式为 String 则不必转换method.invoke(obj, param);}}//不要忘记这句list.add(obj);}return list;
}
复制代码接下来我们改造 Person 类添加几个不同类型的数据并加入 toString() 方法供我们测试使用。 import java.util.Date;/**** author calmer* since 2018/12/5 14:50*/
public class Person {private Integer id;private String name;private Integer age;private String hobby;private String job;private String address;private Date birthday;private Character sex;private Long phone;private Boolean isWorked;Overridepublic String toString() {return Person{ id id , name name \ , age age , hobby hobby \ , job job \ , address address \ , birthday birthday , sex sex , phone phone , isWorked isWorked };}public Long getPhone() {return phone;}public void setPhone(Long phone) {this.phone phone;}public Boolean getWorked() {return isWorked;}public void setWorked(Boolean worked) {isWorked worked;}public Character getSex() {return sex;}public void setSex(Character sex) {this.sex sex;}public Date getBirthday() {return birthday;}public void setBirthday(Date birthday) {this.birthday birthday;}public Integer getId() {return id;}public void setId(Integer id) {this.id id;}public String getName() {return name;}public void setName(String name) {this.name name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age age;}public String getHobby() {return hobby;}public void setHobby(String hobby) {this.hobby hobby;}public String getJob() {return job;}public void setJob(String job) {this.job job;}public String getAddress() {return address;}public void setAddress(String address) {this.address address;}
}复制代码接下来是测试调用的代码我们直接将导出与导入两段代码一起执行。 public static void main(String[] args) throws Exception {//生成数据ListPerson list new ArrayList();for (int i 0; i 100; i) {int num i 1;Person person new Person();person.setId(num);person.setName(张三-(num));person.setAddress(花园路num号(int)Math.ceil(Math.random()*10)号楼);person.setAge(i18);person.setHobby(洗脸刷牙打DOTA);person.setJob(程序员);person.setBirthday(new Date());person.setSex(男);person.setPhone(4536456498778789123L);person.setWorked(true);list.add(person);}//导出 ExcelFileOutputStream out new FileOutputStream(F:\\testXSSF.xlsx);ExcelFactory.createExcel(list,ExcelType.SXSSF).write(out);out.close();//导入 ExcelFileInputStream in new FileInputStream(F:\\testXSSF.xlsx);XSSFWorkbook workbook new XSSFWorkbook(in);ListPerson personList ExcelFactory.readExcel(workbook,Person.class);in.close();//遍历结果for (Person person : personList) {System.out.println(person);}
}
复制代码执行结果如下 功能已经基本实现我们这次再试一下在大数据导入的情景下程序的耗时如何。我们这次同样适用 6W 条数据。结果如下 这里我们可以看到导入操作占用的内存和耗时都比导出操作多很多。在导出的时候我们知道 POI 在导出大数据量的时候提供了 SXSSF 的方式解决耗时和内存溢出问题那么在导入时是不是也会有某种方式可以解决这个问题呢 使用 Sax 事件驱动解析 关于这部分的代码可以在网上找到许多本次暂不讨论。另外听说有一个 EasyExcel 挺好用的有时间试一下。 感悟 通过这次探索深知自己不足的地方还很多原来写代码的时候考虑的太少有关效率内存使用等方面的问题在自己测试的时候是看不出来的真正使用的时候这些问题才会暴露出来比如某项操作可能会导致用户几十秒甚至几分钟的等待或者程序直接崩掉。 所以以后还是要小心谨慎对工具类的使用不能会用就够要尽量的深入研究。 道可顿悟事需渐修。 须知 JDK 版本1.8.0_151POI 版本3.14开发工具IDEA参考链接 POI-HSSF and POI-XSSF/SXSSFPOI读写大数据量excel解决超过几万行而导致内存溢出的问题EasyExcel转载于:https://juejin.im/post/5c09e559e51d451da152df9c