【Alibaba工具型技术系列】「EasyExcel技术专题」摒除OOM!让你的Excel操作变得更加优雅和安全

news2024/11/18 12:29:59

摒除OOM!让你的Excel操作变得更加优雅和安全

      • 前提概要
        • 存在隐患问题
        • 解决方案
        • 更优秀的选择
      • EasyExcel的介绍说明
        • 技术原理对比
          • POI
          • EasyExcel
            • 技术原理图
            • 节省内存的开销
        • Maven仓库依赖
        • 基础API介绍(参考官方文档)
        • 实战案例
          • 读取Excel实现Demo
            • 数据模型DemoModel
            • 读取回调监听器
            • 读取操作测试类
          • 写入Excel实现Demo
            • 写入数据模型
            • 写入操作代码
          • 复杂头写入
          • 复杂头的写入操作

前提概要

针对于后端开发者而言的,作为报表的导入和导出是一个很基础且有很棘手的问题!之前常用的工具和方案大概有这么几种:

  1. JXL(Java Excel API 工具服务),此种只支持xls的文件格式,而且对于内存的管理特别的差,现在基本不用了!
  2. 目前大多数会操作Excel工具服务或者解析都是利用Apache POI进行操作。
  3. 其他第三方的工具很多也是基于POI作为实现基础!
存在隐患问题

因为当数据量特别大的时候,比如:说Excel导出,如果数据量在百万级,很有可能会出现俩点内存溢出的问题以及页面极具卡顿。

解决方案
  • 首先,在百万级大数据量Excel文件的导出我们可以采用分批查询数据来避免内存溢出的剧增!

  • 此外,POI给出的方案是:使用SXSSFWorkbook方式缓存数据到文件上以解决下载大文件EXCEL卡死页面的问题。

    • SXSSFWorkbook数据模型:主要可以解决在下载传输到浏览器的时候大Excel文件转换的输出流内存溢出
    • 此外其还可以通过其构造函数执指定在内存中缓存的行数剩余的会自动缓存在硬盘的临时目录上,同时,并不会存在页面卡顿的情况
    1. SXSSF机制而言还是需要手动进行封装及定制化开发增加了一定的工作量!
    2. POI的操作方式仍然还是存在内存占用过大的问题,仍会存在内存溢出的隐患
    3. 存在空循环和整除的时候数据有缺陷的问题
更优秀的选择

对此阿里巴巴发明咯一个“万金油”!EasyExcel(简单Excel操作工具,让Excel变得更简单!),除此之外它可以将解析的Excel的内存占用控制在KB级别,并且绝对不会内存溢出,还有就是速度极快, 不用想了就它了!


EasyExcel的介绍说明

EasyExcel是一个基于Java实现的、以节省内存为主要目标的的读写Excel文件的开源项目。经过官方统计,在尽可能节约内存的情况下支持读写百M的Excel文件的读写操作能力!

  • 源码库(github地址):https://github.com/alibaba/easyexcel
  • 官方文档:https://alibaba-easyexcel.github.io/index.html
技术原理对比
POI

当利用POI去读取Excel时,首先会将数据全部加载到内存中,然后返回给调用者,当数据量比较大时,及其容易发生OOM。以下是执行流程图:

EasyExcel

与POI 不用的是,EasyExcel主要是采用sax模式一行一行解析,并将一行的解析结果以观察者的模式通知处理,即使数据量较大时也不会发生OOM,以下是其执行流程图

技术原理图

借用官方图

节省内存的开销

借用官方图片:https://alibaba-easyexcel.github.io/images/large.png

64M内存1分钟内读取75M(46W行25列)的ExcelExcel 内存开销图。当然还有急速模式能更快,但是内存占用会在100M多一点。其实就是拿空间换时间!

Maven仓库依赖

我用的版本是2.2.6

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>easyexcel</artifactId>
  <version>2.2.6</version>
</dependency>

注: 系统内部如果有POI的包一定让poi和poi-ooxml的版本要保持一致。

注: 如果是springboot2.0,则不需要poi依赖,如果是1.0,则需要poi依赖,并且poi和poi-ooxml的版本要保持一致。

<!-- poi -->
 <dependency>
          <groupId>org.apache.poi</groupId>
          <artifactId>poi</artifactId>
          <version>3.17</version>
 </dependency>
 <dependency>
          <groupId>org.apache.poi</groupId>
          <artifactId>poi-ooxml</artifactId>
          <version>3.17</version>
 </dependency>
基础API介绍(参考官方文档)
  • EasyExcel 入口类,用于构建开始各种操作,属于典型的门面+工厂模式
public void testReadEntity() {
  // 被读取的文件绝对路径
  String fileName = "path/testDemo.xlsx";
  // 接收解析出的目标对象(Entity)指的是你的实体类
   List<Entity> entityList = new ArrayList<>();
  // 这里需要指定读用哪个class去读,然后读取第一个sheet文件流会自动关闭
  // excel中表的列要与对象的字段相对应
   EasyExcel.read(fileName, Entity.class, new AnalysisEventListener<Student>() {
    // 每解析一条数据都会调用该方法
    @Override
    public void invoke(Entity entity, AnalysisContext analysisContext) {
      System.out.println("解析一条Row行对象:" + JSON.toJSONString(entity));
      entityList.add(student);
	}
    // 解析完毕的回调方法
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        System.out.println("excel文件读取完毕!");
    }
  }).sheet().doRead();
}

// 通过Map作为整体的数据结构模型
public void readWithoutObj() {
  // 被读取的文件绝对路径
  String fileName = "path/testDemo.xlsx";
  // 接收结果集,为一个List列表,每个元素为一个map对象,key-value对为excel中每个列对应的值
  List<Map<Integer,String>> resultList = new ArrayList<>();
  EasyExcel.read(fileName, new AnalysisEventListener<Map<Integer,String>>() {
    @Override
    public void invoke(Map<Integer, String> map, AnalysisContext analysisContext) {
      System.out.println("解析到一条数据:" + JSON.toJSONString(map));
      resultList.add(map);
    }
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
      System.out.println("excel文件解析完毕!" + JSON.toJSONString(resultList));
    }
  }).sheet().doRead();
}
  • invoke方法代表每解析一行就会调用一次,data数据表示解析出来一行的数据。

  • doAfterAllAnalysed 该方法表示将所有数据解析完毕以后才会去调用该方法。

  • 内部实现机制:(可以理解成一个excel对象,一个excel只要构建一个)

    • ExcelReaderBuilder 构建出一个 ReadWorkbook

      • excelType 当前excel的类型 默认会自动判断
      • inputStream 与file二选一。读取文件的流,如果接收到的是流就只用,不用流建议使用file参数。因为使用了inputStream easyexcel会帮忙创建临时文件,最终还是file
      • file 与inputStream二选一。读取文件的文件。
      • autoCloseStream 自动关闭流。
      • readCache 默认小于5M用 内存,超过5M会使用 EhCache,这里不建议使用这个参数。
      • useDefaultListener @since 2.1.4 默认会加入ModelBuildEventListener 来帮忙转换成传入class的对象,设置成false后将不会协助转换对象,自定义的监听器会接收到Map<Integer,CellData>对象,如果还想继续接听到class对象,请调用readListener方法,加入自定义的beforeListener、 ModelBuildEventListener、 自定义的afterListener即可。
      • 方法体现:
        • EasyExcel.read 该方法是用来创建ExcelReaderBuilder对象,该对象就是用来解析Excel文档
        • read方法需要传入三个参数:
          • 第一个参数:需要解析文件的路径,当然除了传入一个文件路径以外,还可以传入InputStream
          • 第二参数:数据类型的Class类型对象,可以不传
          • 第三个参数:事件监听器,在之前介绍这款框架时说过,该框架是基于SAX的一种解析,加载一行数据到内存就会去解析一行,主要是为了节约内存。
    • ExcelWriterBuilder 构建出一个 WriteWorkbook

      • excelType 当前excel的类型 默认xlsx
      • outputStream 与file二选一。写入文件的流
      • file 与outputStream二选一。写入的文件
      • templateInputStream 模板的文件流
      • templateFile 模板文件
      • autoCloseStream 自动关闭流。
      • password 写的时候是否需要使用密码
      • useDefaultStyle 写的时候是否是使用默认头
    • WriteTable(就把excel的一个Sheet,一块区域看一个table)参数

      • tableNo 需要写入的编码。默认0
  • 内部实现机制:(可以理解成excel里面的一页,每一页都要构建一个)

    • ExcelReaderSheetBuilder:构建出一个 ReadSheet对象。
      • sheetNo 需要读取Sheet的编码,建议使用这个来指定读取哪个Sheet。
      • sheetName 根据名字去匹配Sheet,excel 2003不支持根据名字去匹配。
    • ExcelWriterSheetBuilder 构建出一个 WriteSheet对象。
      • 需要写入的编码。默认0
      • sheetName 需要些的Sheet名称,默认同sheetNo
  • 内部实现机制:(可以理解成excel里面的一页,每一页都要构建一个)

    • ReadListener:每一行读取完毕后都会调用ReadListener来处理数据
    • WriteHandler :每一个操作包括创建单元格、创建表格等都会调用WriteHandler来处理数据

所有配置都是继承的,Workbook的配置会被Sheet继承,所以在用EasyExcel设置参数的时候,在EasyExcel…sheet()方法之前作用域是整个sheet,之后针对单个sheet。

  • 相关使用注解
    • ExcelProperty index 指定写到第几列,默认根据成员变量排序。value指定写入的名称,默认成员变量的名字,多个value可以参照快速开始中的复杂头。
    • ExcelIgnore 默认所有字段都会写入excel,这个注解会忽略这个字段
    • DateTimeFormat 日期转换,将Date写到excel会调用这个注解。里面的value参照java.text.SimpleDateFormat
    • NumberFormat 数字转换,用Number写excel会调用这个注解。里面的value参照java.text.DecimalFormat
    • ExcelIgnoreUnannotated 默认不加ExcelProperty的注解的都会参与读写,加了不会参与。
    • 使用案例:
    	@Data
    	public class TestEntity {
    	  /**
    	   * 从0开始,2代表强制读取第三个,
    	   * 不建议 index 和 name 同时用
    	   */
    	  @ExcelProperty(index = 2)
    	  private Double doubleData;
    	  /**
    	   * 用名字去匹配,这里需要注意,
    	   * 名字重复,会导致只有一个字段读取到数据
    	   */
    	  @ExcelProperty("字符串标题")
    	  private String string;
    
    	  @ExcelProperty("日期标题")
    	  private Date date;
    	}
    
    	@Data
    	public class MultiHeaderEntity implements Serializable {
    
    		@ExcelProperty(value = {"一层信息","二层1"})
    		private Integer id;
    
    		@ExcelProperty(value = {"一层信息","二层2"})
    		private String name;
    
    		@ExcelProperty(value = {"一层信息","二层4"})
    		private String description;
    
    		@ExcelProperty(value = {"一层信息","二层3"})
    		private Date birthday;
    	}
    
    	@Data
    	public class ConverterData {
    		/**
    		 * 我自定义 转换器,不管数据库传过来什么 。我给他加上“自定义:”
    		 */
    		@ExcelProperty(converter = CustomStringStringConverter.class)
    		private String string;
    		/**
    		 * 这里用string 去接日期才能格式化。我想接收年月日格式
    		 */
    		@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
    		private String date;
    		/**
    		 * 我想接收百分比的数字
    		 */
    		@NumberFormat("#.##%")
    		private String doubleData;
    	}
    

注意 :如果使用该类的对象去装载Excel中的数据,那么读取时就只能读取以下样式的Excel数据模型,否则数据部分丢失或者全部丢失

实战案例
读取Excel实现Demo
数据模型DemoModel
@Data
public class DemoModel {
    private String attribute1;
    private Date attribute2;
    private Double attribute3;
}
读取回调监听器

注意:DemoModelAnalysisListener 不要是单例模式或者全局共享,要每次读取excel都要new,否则会出现复用之前读取的过程数据!

@Slf4j
public class DemoModelAnalysisListener extends AnalysisEventListener<DemoData> {
    /**
     * 每隔500条存储数据库,然后清理list ,方便内存回收
     */
    static final int BATCH_COUNT = 500;
    List<DemoModel> list = new ArrayList<>();
    /**
     * 这个也可以是一个service。当然如果不用存储这个对象没用。
     */
    private DemoService demoService;
    public DemoModelAnalysisListener(DemoService demoService) {
        this.demoService = demoService;
    }
    /**
     * 这个每一条数据解析都会来调用
     * @param data
     *            one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(DemoData data, AnalysisContext context) {
        log.info("解析到一条数据:{}", JSON.toJSONString(data));
        list.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,
		// 防止数据几万条数据在内存,容易OOM
        if (list.size() >= BATCH_COUNT) {
            saveData();
        }
    }

	/**
     * 所有数据解析完成了 都会来调用
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData();
        log.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        log.info("{}条数据,开始存储数据库!", list.size());
        demoService.insert(list);
		            // 存储完成清理 list
		list.clear();//解析结束销毁不用的资源
        log.info("存储数据库成功!");
    }
}
读取操作测试类
    @Test
    public void readExcelTest() {
        // 写法1:
		DemoService demoService = getService(); //获取service业务实现类
        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
        EasyExcel.read(fileName, DemoData.class, new DemoModelAnalysisListener(demoService)).sheet().doRead();

        // 写法2:
        fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        ExcelReader excelReader = null;
        try {
            excelReader = EasyExcel.read(fileName, DemoData.class, new DemoModelAnalysisListener(demoService)).build();
            ReadSheet readSheet = EasyExcel.readSheet(0).build();
            excelReader.read(readSheet);
        } finally {
            if (excelReader != null) {
                // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
                excelReader.finish();
            }
        }
    }
写入Excel实现Demo
写入数据模型
@Data
public class DemoData {
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
    @ExcelProperty("数字标题")
    private Double doubleData;
    /**
     * 忽略这个字段
     */
    @ExcelIgnore
    private String ignore;
}
写入操作代码
    @Test
    public void demoWriteTest() {
        // 写法1
        String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        // 如果这里想使用03 则 传入excelType参数即可
        EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
		// 写法2
        fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去写
        ExcelWriter excelWriter = null;
        try {
            excelWriter = EasyExcel.write(fileName, DemoData.class).build();
            WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
            excelWriter.write(data(), writeSheet);
        } finally {
            // 千万别忘记finish 会帮忙关闭流
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }
    }
复杂头写入
@Data
public class MultiHeaderEntity implements Serializable {

			@ExcelProperty(value = {"一层信息","二层1"})
			private Integer id;

			@ExcelProperty(value = {"一层信息","二层2"})
			private String name;

			@ExcelProperty(value = {"一层信息","二层4"})
			private String description;

			@ExcelProperty(value = {"一层信息","二层3"})
			private Date birthday;
		}
复杂头的写入操作
    @Test
    public void complexHeadWrite() {
        String fileName = TestFileUtil.getPath() + "complexHeadWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        EasyExcel.write(fileName, MultiHeaderEntity.class).sheet("复杂").doWrite(datalist());
    }

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

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

相关文章

网络性能评估工具Iperf

一、网络性能评估工具Iperf Iperf是一款基于TCP/IP和UDP/IP的网络性能测试工具&#xff0c;它可以用来测量网络带宽和网络质量&#xff0c;还可以提供网络延迟抖动、数据包丢失率、***传输单元等统计信息。网络管理员可以根据这些信息了解并判断网络性能问题&#xff0c;从而定…

Statistics with Python: Week2 Nhanes Assignment

这门课不知出于什么原因比较小众&#xff0c;如果有人在做&#xff0c;在week2的assignment中出现问题&#xff0c;希望我的回答可以帮到你。 这个作业的目的就是抓取nhanes&#xff08;美国健康与营养检测&#xff09;2015-2016的数据&#xff0c;然后计算平均数/中位数/方差…

如何利用SD-WAN升级企业网络,混合组网稳定性更高?

随着企业信息化的升级&#xff0c;传统网络架构已经无法满足企业复杂的、多样化的组网互联需求。 企业多样化的组网需求包括&#xff1a; 一是需要将各办公点互联起来进行数据传输、资源共享&#xff1b; 二是视频会议、ERP、OA、邮箱系统、云服务应用程序等访问需求&#xff…

71.网游逆向分析与插件开发-角色数据的获取-修复角色名与等级显示问题

内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;自动化助手UI显示角色数据-CSDN博客 码云地址&#xff08;ui显示角色数据 分支&#xff09;&#xff1a;https://gitee.com/dye_your_fingers/sro_-ex.git 码云版本号&#xff1a;0049452c079867779…

ChatGLM3报错:No chat template is defined for this tokenizer

使用官方提供的脚本创建ChatGLM3的DEMO&#xff1a; cd basic_demo python web_demo_gradio.py 出现效果异常问题&#xff1a; conversation [{role: user, content: 你好}, {role: assistant, content: 你好&#xff0c;有什么我可以帮助你的吗&#xff1f;\n\n<|im_end|…

数字时代的大对决

数字时代如今正酝酿着一场大对决&#xff0c;浏览器、艺术品、音乐平台和社交通信的巅峰之战正在发生。Brave、Yuga Labs、Audius和Discord分别对标着Chrome、Disney、Spotify和WhatsApp&#xff0c;这场数字时代的较量不仅涉及浏览器、艺术品、音乐平台和社交通信的竞争&#…

基于YOLOv8深度学习的100种中草药智能识别系统【python源码+Pyqt5界面+数据集+训练代码】目标检测、深度学习实战

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

uniapp实现微信小程序富文本之mp-html插件详解

uniapp实现微信小程序富文本之mp-html插件 1 文章背景1.1 正则表达式1.2 mp-html插件1.3 uniapp 2 过程详解2.1 下载mp-html插件2.2 项目中引入mp-html2.3 引入正则规范图片自适应2.4 效果展示 3 全部代码 1 文章背景 1.1 正则表达式 正则表达式&#xff0c;又称规则表达式,&…

游泳耳机怎么选?国产十大游泳耳机排行榜TOP4品牌

随着人们对健康生活的追求和对多样化运动方式的探索&#xff0c;游泳作为一项完美的全身运动逐渐受到更多人的喜爱。在游泳过程中&#xff0c;佩戴一副优质的游泳耳机&#xff0c;不仅可以享受音乐的陪伴&#xff0c;还能让您更好地沉浸在水下世界中。本文将为大家推荐国产十大…

微信小程序 image bindload 事件不触发,图片加载不出来

问题&#xff1a; 当小程序图片页面反复跳转时&#xff0c;或者微信打开小程序页面之后&#xff0c;处于后台运行。 图片加载不出来&#xff0c;我图片加载是通过bindload事件不判断是否下载完成再显示。但是现在bindload不触发&#xff0c;一直显示加载层。 分析&#xff1…

查看神经网络中间层特征矩阵及卷积核参数

可视化feature maps以及kernel weights&#xff0c;使用alexnet模型进行演示。 1. 查看中间层特征矩阵 alexnet模型&#xff0c;修改了向前传播 import torch from torch import nn from torch.nn import functional as F# 对花图像数据进行分类 class AlexNet(nn.Module):d…

Elasticsearch8 集群搭建(二)配置篇:(3)安全配置

此篇记录Elasticsearch 8.x传输层的安全配置。 传输层节点间&#xff1a; 如果集群有多个节点&#xff0c;必须在节点间配置TLS。生产模式下&#xff0c;如果不启用TLS&#xff0c;集群将无法启动。 图片来源&#xff1a;Set up basic security for the Elastic Stack | Elas…

2018年认证杯SPSSPRO杯数学建模D题(第二阶段)投篮的最佳出手点全过程文档及程序

2018年认证杯SPSSPRO杯数学建模 D题 投篮的最佳出手点 原题再现&#xff1a; 影响投篮命中率的因素不仅仅有出手角度、球感、出手速度&#xff0c;还有出手点的选择。规范的投篮动作包含两膝微屈、重心落在两脚掌上、下肢蹬地发力、身体随之向前上方伸展、同时抬肘向投篮方向…

vue2使用mapbox

1.安装mapbox 这里安装的是"mapbox-gl": "^3.0.1", npm install --save mapbox-gl 安装mapbox 2.安装worker-loader npm install worker-loader --save-dev 安装worker-loader 配置vue.config.js const { defineConfig } require(vue/cli-servic…

MFC 序列化机制

目录 文件操作相关类 序列化机制相关类 序列化机制使用 序列化机制执行过程 序列化类对象 文件操作相关类 CFile&#xff1a;文件操作类&#xff0c;封装了关于文件读写等操作&#xff0c;常见的方法&#xff1a; CFile::Open&#xff1a;打开或者创建文件CFile::Write/…

AI图片物体移除器:高效、便捷的AI照片物体擦除工具

在我们的日常生活中&#xff0c;照片是一种重要的记录和表达方式。然而&#xff0c;有时候我们会遇到需要将照片中的某些物体和元素去除的情况。这时候&#xff0c;传统的图像处理软件可能过于复杂&#xff0c;让人望而却步。为了解决这个问题&#xff0c;AI图片物体移除器的软…

目标检测--02(Two Stage目标检测算法1)

Two Stage目标检测算法 R-CNN R-CNN有哪些创新点&#xff1f; 使用CNN&#xff08;ConvNet&#xff09;对 region proposals 计算 feature vectors。从经验驱动特征&#xff08;SIFT、HOG&#xff09;到数据驱动特征&#xff08;CNN feature map&#xff09;&#xff0c;提高特…

游泳耳机有什么好处?四款适合水下听歌的优质游泳耳机分享

游泳是一项健康有益的运动&#xff0c;而搭配一副高质量的游泳耳机&#xff0c;更能在游泳过程中享受音乐的陪伴。本文将介绍游泳耳机的好处&#xff0c;并为大家推荐四款适合水下听歌的游泳耳机&#xff0c;让大家在游泳中拥有更加丰富的体验。 接下来跟我一起看看游泳耳机的好…

GAN在图像数据增强中的应用

在图像数据增强领域&#xff0c;生成对抗网络&#xff08;GAN&#xff09;的应用主要集中在通过生成新的图像数据来扩展现有数据集的规模和多样性。这种方法特别适用于训练数据有限的情况&#xff0c;可以通过增加数据的多样性来提高机器学习模型的性能和泛化能力。 以下是GAN在…

Java如何做到无感知刷新token含示例代码(值得珍藏)

1. 前言 在系统页面进行业务操作时&#xff0c;有时会突然遇到应用闪退&#xff0c;并被重定向至登录页面&#xff0c;要求重新登录。此问题的出现&#xff0c;通常与系统中用于存储用户ID和token信息的Redis缓存有关。具体来说&#xff0c;这可能是由于token过期所导致的身份…