java使用poi读写excel(处理上下标和科学计数法)

Background

  • 要读写如下图所示的excel,符号和单位中包含上下标,在读写时需要特殊处理;
  • 取值列中是科学计数法,读写时需要特殊处理;
  • excel中包含多个sheet,读的时候把所有sheet的数据读出来,写的时候把所有sheet的数据写进去;

在这里插入图片描述

1、读取所有sheet数据

readFromSheet方法,使用对象接收每个sheet的数据,返回每个sheet对应的数据集合

2、写入所有sheet数据

write2Sheet方法,传入每个sheet对应的数据集合,把所有sheet的数据写入到excel中,并且是基于模板写入

3、写数据时处理上下标

richTextString方法,写数据时把有上下标信息字符串处理成富文本

4、读数据时处理上下标

getCellValue方法,读数据时给有上下标信息字符串添加tag信息

5、数值科学计数法处理

scientificNotationString方法,返回处理后的科学计数法字符串

源码

在这里插入图片描述

  • Const.java
package com.yunlu.groundwater.constants;import com.yunlu.groundwater.gwParameters.entities.*;import java.util.HashMap;
import java.util.Map;public class Const {// 模型参数文件导入路径public static final String IMPORT_MODEL_PARAM_FILEPATH = "excel-import/inputTable.xlsx";// 模型参数模板文件路径public static final String TPL_MODEL_PARAM_FILEPATH = "model/tpl/inputTable-tpl.xlsx";// 模型计算时输入模型参数文件路径public static final String INPUT_MODEL_PARAM_FILEPATH = "model/input/inputTable.xlsx";// excel模板解析跳过行数public static final Map<String, Class<?>> EXCEL_SHEET_OBJ = new HashMap<String, Class<?>>() {{put("3_地下水理化毒性报表", GWBPhysicalChemicalToxicity.class);put("4_受体暴露参数", GWBReceptorExpose.class);put("5_土壤性质参数", GWBSoilNature.class);put("6_地下水性质参数", GWBWaterNature.class);put("7_建筑物特征参数", GWBBuildingFeature.class);put("8_空气特征参数", GWBAirFeature.class);put("9_离场迁移参数", GWBFieldMoving.class);}};// 字符串public static final String S_UID = "serialVersionUID";// 上标public static final String TAG_SUB_START = "<sub>";public static final String TAG_SUB_END = "</sub>";// 下标public static final String TAG_SUP_START = "<sup>";public static final String TAG_SUP_END = "</sup>";// punctuation[ptn][标点]public static final String PTN_EMPTY = "";public static final String PTN_BAR_MID = "-";// tplpublic static final String TPL_TAG = "%s%s%s";public static final String TPL_E1 = "%s+%s";// fmtpublic static final String FMT_DOUBLE = "0.00E00";
}
  • ExcelCol.java
package com.yunlu.groundwater.resume.mapper;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelCol {int index() default 0;boolean scientificNotation() default false;
}
  • GWBSoilNature.java
package com.yunlu.groundwater.gwParameters.entities;import com.yunlu.groundwater.resume.mapper.ExcelCol;
import lombok.Data;@Data
public class GWBSoilNature {private static final long serialVersionUID = 1L;@ExcelCol(index = 0)private String paramName;@ExcelCol(index = 1)private String sign;@ExcelCol(index = 2)private String unit;@ExcelCol(index = 3)private Double value;
}
  • ExcelHandler.java
package com.yunlu.groundwater.resume.controller;import com.yunlu.groundwater.constants.Const;
import com.yunlu.groundwater.resume.mapper.ExcelCol;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
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.xssf.usermodel.*;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.lang.reflect.Field;
import java.text.DecimalFormat;
import java.util.*;@Slf4j
public class ExcelHandler {public static void main(String[] args) throws Exception {// 从excel读取数据String file = Const.IMPORT_MODEL_PARAM_FILEPATH;Map<String, List<Object>> sheetValues = readFromSheet(file);for (String sheetName : sheetValues.keySet()) {System.out.println(sheetName);for (Object o : sheetValues.get(sheetName)) {System.out.println(o);}System.out.println();}// 向excel写入数据file = Const.INPUT_MODEL_PARAM_FILEPATH;write2Sheet(file, sheetValues);}/*** 从excel文件读取所有sheet数据(有上下标信息字符串自动处理成富文本)** @param filename 文件名称* @return 返回所有sheet数据对象集合*/public static Map<String, List<Object>> readFromSheet(String filename) {Map<String, List<Object>> res = new HashMap<>();try (FileInputStream in = new FileInputStream(filename);XSSFWorkbook workbook = new XSSFWorkbook(in);) {Iterator<Sheet> sheetIterator = workbook.sheetIterator();while (sheetIterator.hasNext()) {List<Object> objects = new ArrayList<>();int startRowIndex = 3;Sheet sheet = sheetIterator.next();String sheetName = sheet.getSheetName();Class<?> tClass = Const.EXCEL_SHEET_OBJ.get(sheetName);Map<Integer, Field> fieldMap = getFieldMap(tClass);for (int rowIndex = startRowIndex; rowIndex <= sheet.getLastRowNum(); rowIndex++) {Row row = sheet.getRow(rowIndex);Object t = tClass.newInstance();for (Integer colIndex : fieldMap.keySet()) {Field field = fieldMap.get(colIndex);boolean scientificNotation = getColScientificNotation(field);Cell cell = row.getCell(colIndex);if (cell != null) {String cellValue = getCellValue(cell, scientificNotation, workbook);setFieldValue(field, cellValue, t);}}if (!isAllFieldNull(t)) {objects.add(t);}}res.put(sheetName, objects);}} catch (Exception e) {log.error("readFromSheet error", e);e.printStackTrace();}return res;}/*** 把数据写入到excel文件中,指定sheet、起始行索引和起始列索引(有上下标信息字符串自动处理成富文本)** @param filename    文件名称* @param sheetValues <sheet名称,数据>*/public static void write2Sheet(String filename, Map<String, List<Object>> sheetValues) {String tplFile = Const.TPL_MODEL_PARAM_FILEPATH;try (FileOutputStream fos = new FileOutputStream(filename);FileInputStream fis = new FileInputStream(tplFile);XSSFWorkbook workbook = new XSSFWorkbook(fis);) {for (String sheetName : sheetValues.keySet()) {int startRowIndex = 3;List<Object> values = sheetValues.get(sheetName);if (values.size() > 0) {Class<?> tClass = values.get(0).getClass();Map<Integer, Field> fieldMap = getFieldMap(tClass);XSSFSheet sheet = workbook.getSheet(sheetName);for (Object t : values) {XSSFRow row = sheet.getRow(startRowIndex);for (Integer colIndex : fieldMap.keySet()) {Field field = fieldMap.get(colIndex);boolean scientificNotation = getColScientificNotation(field);String content;try {content = fieldMap.get(colIndex).get(t).toString();} catch (Exception e) {content = Const.PTN_EMPTY;}List<List<int[]>> tagIndexArr = new ArrayList<>();if (containTag(content)) {content = getIndexes(content, tagIndexArr);}XSSFCell cell = row.getCell(colIndex);if (null == cell) {cell = row.createCell(colIndex);}if (tagIndexArr.size() > 0) {cell.setCellValue(richTextString(workbook, content, tagIndexArr));} else {if (scientificNotation && !StringUtils.isEmpty(content) && !Const.PTN_BAR_MID.equals(content)) {cell.setCellValue(Double.parseDouble(content));} else {cell.setCellValue(content);}}}startRowIndex++;}}}workbook.write(fos);} catch (Exception e) {log.error("write2Sheet error", e);e.printStackTrace();}}/*** @param val 数值* @return 返回科学计数法字符串*/public static String scientificNotationString(Double val) {String res = new DecimalFormat(Const.FMT_DOUBLE).format(val);if (val >= 1) {int length = res.length();String prefix = res.substring(0, length - 2);String suffix = res.substring(length - 2, length);res = String.format(Const.TPL_E1, prefix, suffix);}return res;}/*** 获取字段集合信息*/private static Map<Integer, Field> getFieldMap(Class<?> tClass) {Map<Integer, Field> fieldMap = new HashMap<>();for (Field field : tClass.getDeclaredFields()) {ExcelCol col = field.getAnnotation(ExcelCol.class);if (null != col) {field.setAccessible(true);fieldMap.put(col.index(), field);}}return fieldMap;}/*** 获取字段是否需要科学计数法表示*/private static boolean getColScientificNotation(Field field) {return field.getAnnotation(ExcelCol.class).scientificNotation();}/*** 判断对象的所有字段值是否为空*/private static <T> boolean isAllFieldNull(T t) {boolean res = true;Class<?> tClass = t.getClass();for (Field field : tClass.getDeclaredFields()) {field.setAccessible(true);try {Object fieldValue = field.get(t);if (!Const.S_UID.equals(field.getName()) && null != fieldValue) {res = false;}} catch (Exception ignored) {}}return res;}/*** 设置字段值*/private static <T> void setFieldValue(Field field, String value, T t) throws Exception {if (null != field) {String type = field.getType().toString();if (StringUtils.isBlank(value)) {field.set(t, null);} else if (type.endsWith("String")) {field.set(t, value);} else if (type.endsWith("long") || type.endsWith("Long")) {field.set(t, Long.parseLong(value));} else if (type.endsWith("double") || type.endsWith("Double")) {field.set(t, Double.parseDouble(value));} else {field.set(t, value);}}}/*** @param cell cell* @return 返回cell内容(有上下标信息字符串自动处理成富文本)*/private static String getCellValue(Cell cell, boolean scientificNotation, XSSFWorkbook workbook) {switch (cell.getCellType()) {case NUMERIC:double cellValue = cell.getNumericCellValue();return scientificNotation ? scientificNotationString(cellValue) : String.valueOf(cellValue);case STRING:XSSFFont font;XSSFRichTextString rts = (XSSFRichTextString) cell.getRichStringCellValue();StringBuilder value = new StringBuilder();if (rts.numFormattingRuns() > 1) {for (int i = 0; i < rts.numFormattingRuns(); i++) {int runLength = rts.getLengthOfFormattingRun(i);int runIndex = rts.getIndexOfFormattingRun(i);String temp = rts.toString().substring(runIndex, (runIndex + runLength));try {font = rts.getFontOfFormattingRun(i);font.getTypeOffset();} catch (NullPointerException e) {font = workbook.getFontAt(XSSFFont.DEFAULT_CHARSET);font.setTypeOffset(XSSFFont.SS_NONE);}temp = addTagInfo(temp, font.getTypeOffset());value.append(temp);}} else {value.append(cell.getStringCellValue());}return value.toString();default:return Const.PTN_EMPTY;}}/*** 处理有上下标的字符串*/private static String addTagInfo(String str, short typeOffset) {if (typeOffset == XSSFFont.SS_SUPER) {str = String.format(Const.TPL_TAG, Const.TAG_SUP_START, str, Const.TAG_SUP_END);}if (typeOffset == XSSFFont.SS_SUB) {str = String.format(Const.TPL_TAG, Const.TAG_SUB_START, str, Const.TAG_SUB_END);}return str;}/*** 有上下标信息字符串处理成富文本** @param str 字符串* @return 处理后的富文本*/private static XSSFRichTextString richTextString(XSSFWorkbook workbook, String str, List<List<int[]>> tagIndexArr) {XSSFRichTextString richTextString = new XSSFRichTextString(str);List<int[]> subs = tagIndexArr.get(0);List<int[]> sups = tagIndexArr.get(1);if (subs.size() > 0) {XSSFFont font = workbook.createFont();font.setTypeOffset(XSSFFont.SS_SUB);for (int[] pair : subs) {richTextString.applyFont(pair[0], pair[1], font);}}if (sups.size() > 0) {XSSFFont font = workbook.createFont();font.setTypeOffset(XSSFFont.SS_SUPER);for (int[] pair : sups) {richTextString.applyFont(pair[0], pair[1], font);}}return richTextString;}/*** 获取下一对标签的index,不存在这些标签就返回null** @param str 字符串* @param tag SUB_START或者SUP_START* @return int[]中有两个元素,第一个是开始标签的index,第二个元素是结束标签的index*/private static int[] getNextTagsIndex(String str, String tag) {int firstStart = str.indexOf(tag);if (firstStart > -1) {int firstEnd = 0;if (tag.equals(Const.TAG_SUB_START)) {firstEnd = str.indexOf(Const.TAG_SUB_END);} else if (tag.equals(Const.TAG_SUP_START)) {firstEnd = str.indexOf(Const.TAG_SUP_END);}if (firstEnd > firstStart) {return new int[]{firstStart, firstEnd};}}return new int[]{};}/*** 移除下一对sub或者sup或者u或者strong或者em标签** @param str 字符串* @param tag SUB_START或者SUP_START* @return 返回移除后的字符串*/private static String removeNextTags(String str, String tag) {str = str.replaceFirst(tag, Const.PTN_EMPTY);if (tag.equals(Const.TAG_SUB_START)) {str = str.replaceFirst(Const.TAG_SUB_END, Const.PTN_EMPTY);} else if (tag.equals(Const.TAG_SUP_START)) {str = str.replaceFirst(Const.TAG_SUP_END, Const.PTN_EMPTY);}return str;}/*** 判断是不是包含sub、sup标签** @param str 字符串* @return 返回是否包含*/private static boolean containTag(String str) {return (str.contains(Const.TAG_SUB_START) && str.contains(Const.TAG_SUB_END)) || (str.contains(Const.TAG_SUP_START) && str.contains(Const.TAG_SUP_END));}/*** 处理字符串,得到每个sub、sup标签的开始和对应的结束的标签的index,方便后面根据这个标签做字体操作** @param str          字符串* @param tagIndexList 传一个新建的空list进来,方法结束的时候会存储好标签位置信息。*                     <br>tagIndexList.get(0)存放的sub*                     <br>tagIndexList.get(1)存放的是sup* @return 返回sub、sup处理完之后的字符串*/private static String getIndexes(String str, List<List<int[]>> tagIndexList) {List<int[]> subs = new ArrayList<>(), sups = new ArrayList<>();while (true) {int[] sub_pair = getNextTagsIndex(str, Const.TAG_SUB_START), sup_pair = getNextTagsIndex(str, Const.TAG_SUP_START);boolean subFirst = false, supFirst = false;List<Integer> a = new ArrayList<>();if (sub_pair.length > 0) {a.add(sub_pair[0]);}if (sup_pair.length > 0) {a.add(sup_pair[0]);}Collections.sort(a);if (sub_pair.length > 0) {if (sub_pair[0] == Integer.parseInt(a.get(0).toString())) {subFirst = true;}}if (sup_pair.length > 0) {if (sup_pair[0] == Integer.parseInt(a.get(0).toString())) {supFirst = true;}}if (subFirst) {str = removeNextTags(str, Const.TAG_SUB_START);// <sub>标签被去掉之后,结束标签需要相应往前移动sub_pair[1] = sub_pair[1] - Const.TAG_SUB_START.length();subs.add(sub_pair);continue;}if (supFirst) {str = removeNextTags(str, Const.TAG_SUP_START);// <sup>标签被去掉之后,结束标签需要相应往前移动sup_pair[1] = sup_pair[1] - Const.TAG_SUP_START.length();sups.add(sup_pair);continue;}if (sub_pair.length == 0 && sup_pair.length == 0) {break;}}tagIndexList.add(subs);tagIndexList.add(sups);return str;}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/210750.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

利用ElementUI配置商品的规格参数

需求&#xff1a;商品可以设置多个规格&#xff0c;需要自动生成对应规格的所有组合&#xff0c;并设置该规格商品的图片、价格、库存等数据。 <template><div class"sku-list"><template v-if"!disabled"><div class"sku-list-…

Leetcode每日一题学习训练——Python3版(到达首都的最少油耗)

版本说明 当前版本号[20231205]。 版本修改说明20231205初版 目录 文章目录 版本说明目录到达首都的最少油耗理解题目代码思路参考代码 原题可以点击此 2477. 到达首都的最少油耗 前去练习。 到达首都的最少油耗 ​ 给你一棵 n 个节点的树&#xff08;一个无向、连通、无环…

使用C语言创建高性能爬虫ip网络

之前写的python和GO语言的爬虫ip池的文章引起很大反响&#xff0c;这次我将以C语言来创建爬虫IP池&#xff0c;但是因为其复杂性&#xff0c;可能代码并非完美。但是最终也达到的想要的效果。 因为在C语言中创建代理IP池可能会比较复杂&#xff0c;且C语言并没有像Python那样的…

HarmonyOS应用开发者基础认证考试(98分答案)

基于最近大家都在考这个应用开发者基础认证考试&#xff0c;因此出了一期&#xff0c;一样复制word里面搜索做&#xff0c;很快&#xff0c;当然good luck 判断题 Ability是系统调度应用的最小单元,是能够完成一个独立功能的组件。一个应用可以包含一个或多个Ability。 正确(Tr…

批量创建/更新外协工序采购信息记录

批量创建/更新没有物料号的外协工序采购信息记录。 执行事务代码ZME1X_OP,下载模板。(此程序可同时用于外协工序的创建和修改)创建外协工序的时候如果是新建则不需要输入采购信息记录号,如果是要更新外协工序价格,则必须输入采购信息记录号。价格单位默认为‘1’,货币代码…

SpringBoot面试题:(一)SpringBoot自动装配原理源码解析

源码研究 SpringBoot启动类&#xff1a;SpringBootApplication注解 import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;SpringBootApplication public class SpringBoot1Application {public static …

【开源】基于JAVA的医院门诊预约挂号系统

项目编号&#xff1a; S 033 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S033&#xff0c;文末获取源码。} 项目编号&#xff1a;S033&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 功能性需求2.1.1 数据中心模块2.1.2…

使用C语言创建高性能网络爬虫IP池

目录 一、引言 二、IP池的设计 1、需求分析 2、架构设计 3、关键技术 三、IP池的实现 1、存储实现 2、调度实现 3、通信实现 4、异常处理实现 四、代码示例 五、性能优化 六、测试与分析 七、结论 一、引言 随着互联网的快速发展&#xff0c;网络爬虫成为了获取…

【深度学习笔记】09 权重衰减

09 权重衰减 范数和权重衰减利用高维线性回归实现权重衰减初始化模型参数定义 L 2 L_2 L2​范数惩罚定义训练代码实现忽略正则化直接训练使用权重衰减 权重衰减的简洁实现 范数和权重衰减 在训练参数化机器学习模型时&#xff0c;权重衰减&#xff08;decay weight&#xff09…

【人体解剖学与组织胚胎学】练习一高度相联知识点整理及对应习题

文章目录 [toc]骨性鼻旁窦填空题问答题 关节填空题简答题 胸廓填空题简答题![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/827e7d1db3af42858d8734bb81911fea.jpeg)补充 骨性鼻旁窦 填空题 问答题 关节 填空题 简答题 胸廓 填空题 简答题 补充 第二肋对应胸骨…

混音编曲软件tudio One 6.5.1 保姆级安装教程

根据软件大数据显示De-Esser驯服人声嘶嘶声和其他高频声音&#xff0c;和其他 Studio One 中新的去实体插件一样高效且直观易用&#xff0c;使用“收听”按钮查找有问题的频率&#xff0c;然后使用相关的旋钮和 S-Mon 功能拨入 S-Reduce 量即可。实际上我们可以这样讲工作流和协…

Linux进程间通信之共享内存

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;Linux &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 本博客主要内容讲解共享内存原理和相关接口的介绍&#xff0c;以及一个…

SpringBoot+SSM项目实战 苍穹外卖(3)

继续上一节的内容&#xff0c;本节完成菜品管理功能&#xff0c;包括公共字段自动填充、新增菜品、菜品分页查询、删除菜品、修改菜品。 目录 公共字段自动填充新增菜品文件上传实现新增菜品实现 useGeneratedKeys 菜品分页查询删除菜品修改菜品根据id查询菜品实现修改菜品实现…

Redis中的缓存穿透、雪崩、击穿(详细)

目录 一、概念 1. 缓存穿透&#xff08;Cache Penetration&#xff09; 解决方案&#xff1a; 2. 缓存雪崩&#xff08;Cache Avalanche&#xff09; 解决方案&#xff1a; 3. 缓存击穿&#xff08;Cache Breakdown&#xff09; 解决方案&#xff1a; 二、三者出现的根本原…

为XiunoBBS4.0开启redis缓存且支持密码验证

修改模块文件1 xiunoPHP/cache_redis.class.php: <?phpclass cache_redis {public $conf array();public $link NULL;public $cachepre ;public $errno 0;public $errstr ;public function __construct($conf array()) {if(!extension_loaded(Redis)) {return $thi…

有趣的代码——有故事背景的程序设计3

这篇文章再和大家分享一些有“背景”的程序设计&#xff0c;希望能够让大家学到知识的同时&#xff0c;对编程学习更感兴趣&#xff0c;更能在这条路上坚定地走下去。 目录 1.幻方问题 2.用函数打印九九乘法表 3.鸡兔同笼问题 4.字数统计 5.简单选择排序 1.幻方问题 幻方又…

Mac苹果视频剪辑:Final Cut Pro Mac

Final Cut Pro是一款由Apple公司开发的专业视频非线性编辑软件&#xff0c;是业界著名的视频剪辑软件之一。它最初发布于1999年&#xff0c;是Mac电脑上的一款独占软件。Final Cut Pro具有先进的剪辑工具、丰富的特效和颜色分级、音频处理等功能&#xff0c;使得用户可以轻松地…

Linux之重谈文件和c语言文件接口

重谈文件 文件 内容 属性, 所有对文件的操作都是: a.对内容操作 b.对属性操作 关于文件 一&#xff1a; 即使文件的内容为空&#xff0c;该文件也会在磁盘上也会占空间&#xff0c;因为文件不仅仅只有内容还有文件对应的属性&#xff0c;文件的内容会占用空间, 文件的属性也…

【面试】Java最新面试题资深开发-JVM第一弹

问题一&#xff1a;Java中的垃圾回收机制 在Java中&#xff0c;垃圾回收是如何工作的&#xff0c;可以简要描述一下垃圾回收的算法有哪些吗&#xff1f; 在Java中&#xff0c;垃圾回收是一种自动管理内存的机制&#xff0c;它负责识别不再被程序引用的对象并释放其占用的内存…

HarmonyOS与AbilitySlice路由配置

上一章我有教到鸿蒙应用开发——Ability鸿蒙应用开发的基础知识&#xff0c;那么今天我们来讲一下AbilitySlice路由配置 AbilitySlice路由配置 虽然一个Page可以包含多个AbilitySlice&#xff0c;但是Page进入前台时界面默认只展示一个AbilitySlice。默认展示的AbilitySlice是…