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.mzph.cn/news/182289.shtml

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

相关文章

OSCP系列靶场-Esay保姆级

总结 getwebshell : ftp可匿名登录 → 发现隐藏文件夹 → 发现ssh密钥 → 猜解ssh用户名 → ssh密钥登录 提 权 思 路 : 发现suid权限文件 → cpulimit提权 准备工作 启动VPN 获取攻击机IP → 192.168.45.191 启动靶机 获取目标机器IP → 192.168.179.130 信息收集-端口扫…

虚拟机指定开放数据库3306端口

1、查看当前防火墙状态&#xff1a; sudo firewall-cmd --state 2、开放指定端口 sudo firewall-cmd --zonepublic --add-port3306/tcp --permanent 3、重新加载防火墙配置 sudo firewall-cmd --reload 4、检查端口是否开放成功 sudo firewall-cmd --zonepublic --list-por…

行情分析——加密货币市场大盘走势(11.29)

大饼已经形成了底背离&#xff0c;即MACD往下走&#xff0c;而价格还在往上走&#xff0c;这种后续往往会大跌。继续把空单拿好&#xff0c;已经持仓的无需加仓。多次上涨却一直不能突破&#xff0c;说明多空和空军力量都很强&#xff0c;等待后续出方向。在笔者看来&#xff0…

HotSpot 虚拟机中的对象

1、对象的创建 Java 是一门面向对象的编程语言&#xff0c;程序运行过程中无时无刻都有对象被创建出来。在语言层面上&#xff0c;创建对象通常仅仅是一个 new 关键字&#xff0c;而虚拟机中&#xff0c;对象&#xff08;仅限于普通 Java 对象&#xff0c;不包括数组和 Class …

统计元音字母c语言

以下是一个简单的C语言程序&#xff0c;用于统计一段文本中的元音字母数量&#xff1a; #include <stdio.h>#include <string.h>int main() { char str[1000]; int vowels 0; printf("请输入一段文本&#xff1a;\n"); fgets(str, siz…

关于神经网络,你不得不知的三大要点

什么是神经网络&#xff1f; 神经网络是一个具有相连节点层的计算模型&#xff0c;其分层结构与大脑中的神经元网络结构相似。神经网络可通过数据进行学习&#xff0c;因此&#xff0c;可训练其识别模式、对数据分类和预测未来事件。 神经网络将您的输入细分为多个抽象层。比…

JavaScript编程进阶 – Return语句

JavaScript编程进阶 – Return语句 JavaScript Programming Advanced – Return Statement By JacksonML 就像人们习惯的函数一样&#xff0c;总觉得在函数体最后需要一个return语句&#xff0c;标志着函数的结束,就像下面这个函数 theFunc() 那样。 function theFunc() { re…

沈阳师范大学期末考试复习pta循环数组函数指针经典编程题汇总+代码分析

前言&#xff1a;临近期末&#xff0c;接下来给大家分享一些经典的编程题&#xff0c;方便大家复习。不一定难&#xff0c;但都是入门的好题&#xff0c;尽可能的吃透彻。因为据说期末考试的题很多来自pta上面的原题。 对于一些语言我是用c来写的&#xff0c;不妨碍理解&#…

Linux下文件操作函数

一.常见IO函数 fopen fclose fread fwrite fseek fflush fopen 运行过程 &#xff1a;打开文件 写入数据 数据写到缓冲区 关闭文件后 将数据刷新入磁盘 1.fopen 返回文件类型的结构体的指针 包括三部分 1).文件描述符&#xff08;整形值 索引到磁盘文件&#xff09;…

AI4S Cup学习赛-中枢神经系统药物研发:药物筛选与优化

赛题介绍 链接&#xff1a;Bohrium 案例广场 (dp.tech) 中枢神经系统类疾病长期以来存在着重要的临床未满足需求。据统计&#xff0c;在当前人口老龄化趋势下&#xff0c;阿兹海默&#xff08;AD&#xff09;、帕金森病&#xff08;PD&#xff09;等神经退行性疾病和脑癌、中…

echarts图表显示不全

图表显示是显示了&#xff0c;但是没有展示全部&#xff0c;一看控制台div的高度只有1px了&#xff0c;手动修改高度也只是拉伸图表&#xff0c;并没有按规定的尺寸展示 随之开始思考为什么呢 ? ? ? 因为 Echarts 的依赖是惰性的&#xff0c;需要手动设置resize&#xff0…

《软件工程原理与实践》复习总结与习题——软件工程

软件生命周期 软件生命周期分为三个时期、八个阶段 软件定义时期&#xff1a; 1&#xff09;问题定义阶段&#xff1a;要解决什么问题 2&#xff09;可行性研究阶段&#xff1a;确定软件开发可行 3&#xff09;需求分析阶段&#xff1a;系统做什么 软件开发时期&#xff1a;…

单片机霍尔测速系统设计+源程序

一、系统方案 1、本设计采用52单片机作为主控器。 2、霍尔测速送到液晶1602。 3、蜂鸣器报警。 二、硬件设计 原理图如下&#xff1a; 三、单片机软件设计 1、首先是系统初始化 void lcd_init()//液晶初始化函数* { write_1602com(0x38);//设置液晶工作模式&#xff0c;意思…

嵌入式八股 | 校招秋招 | 笔试面试 | 精选题目

欢迎关注微信公众号【赛博二哈】获取八股PDF 并加入嵌入式求职交流群。提供简历模板、学习路线、岗位整理等 欢迎加入知识星球【嵌入式求职星球】获取完整嵌入式八股。 提供简历修改、项目推荐、求职规划答疑。另有各城市、公司岗位、笔面难题、offer选择、薪资爆料等 嵌入式…

利用数据库的表,生成word文档的表结构注释说明

文章目录 1.场景说明2.解决办法3.生成文档3.1.实现思路3.2.引入Apache POI依赖3.3.获取表及表字段说明Mapper3.4.POI创建文档表格&#xff0c;并填充数据3.5.完整的接口下载代码3.6.效果展示 1.场景说明 在项目中表已经建立好了&#xff0c;但是现在想对外提供一个表的字段的描…

物联网开发(一)新版Onenet 基础配置

onenet新创建的账号&#xff0c;没有了多协议接入&#xff0c;只有新的物联网开放平台 第一讲&#xff0c;先给大家讲一下&#xff1a;新版Onenet 基础配置 创建产品 产品开发-->创建产品 产品的品类选择个&#xff1a;大致符合你项目的即可&#xff0c;没有影响 选择智…

watch函数与watchEffect函数

watach函数&#xff1a; 与vue2.x的配置功能一致 监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调 默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次 通过配置deep为true, 来指定深度监视 watchEffect函数&#xff1a;…

企业人力资源公司抖音直播招聘断播怎么处理?

企业人力资源公司抖音直播招聘断播怎么处理&#xff1f; 最直接的处理方式就是进行抖音直播招聘报白&#xff0c;报白后在直播和视频中发布招聘和企业信息&#xff0c;不用担心被封禁和限制流量。 可以通过抖音直播进行招聘&#xff0c;也可以在视频中添加小程序&#xff0c;…

【送书活动二期】Java和MySQL数据库中关于小数的保存问题

之前总结过一篇文章mysql数据库&#xff1a;decimal类型与decimal长度用法详解&#xff0c;主要是个人学习期间遇到的mysql中关于decimal字段的详解&#xff0c;最近在群里遇到一个小伙伴提出的问题&#xff0c;也有部分涉及&#xff0c;今天就再大致总结一下Java和MySQL数据库…

ChatGPT成了背锅侠:利用AI做蹭热点视频

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 在抖音\视频号上已经有很多人利用ChatGPT做热点视频的案例了&#xff0c;视频都是点赞大几千、几万。看完本文&#xff0c;你会略知一二&#xff0c;如下图所示&#xff1a; 这个视频&#xff0c…