SpringBoot 整合 Excel 轻松实现数据自由导入导出
01、背景介绍
在实际的业务系统开发过程中,操作 Excel 实现数据的导入导出基本上是个非常常见的需求。
之前,我们有介绍一款非常好用的工具:EasyPoi,有读者提出在数据量大的情况下,EasyPoi 会占用内存大,性能不够好,严重的时候,还会出现内存异常的现象。
今天我给大家推荐一款性能更好的 Excel 导入导出工具:EasyExcel,希望对大家有所帮助!
easyexcel 是阿里开源的一款 Excel导入导出工具,具有处理速度快、占用内存小、使用方便的特点,底层逻辑也是基于 apache poi 进行二次开发的,目前的应用也是非常广!
相比 EasyPoi,EasyExcel 的处理数据性能非常高,读取 75M (46W行25列) 的Excel,仅需使用 64M 内存,耗时 20s,极速模式还可以更快!
废话也不多说了,下面直奔主题!
02、方案实践
在 SpringBoot 项目中集成 EasyExcel 其实非常简单,仅需一个依赖即可。
<!--EasyExcel相关依赖-->
<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.0.5</version>
</dependency>
EasyExcel 的导出导入支持两种方式进行处理
-
第一种是通过实体类注解方式来生成文件和反解析文件数据映射成对象
-
第二种是通过动态参数化生成文件和反解析文件数据
下面我们以用户信息的导出导入为例,分别介绍两种处理方式。
2.1、简单导出
首先,我们只需要创建一个UserEntity
用户实体类,然后添加对应的注解字段即可,示例代码如下:
public class UserWriteEntity {@ExcelProperty(value = "姓名")private String name;@ExcelProperty(value = "年龄")private int age;@DateTimeFormat("yyyy-MM-dd HH:mm:ss")@ExcelProperty(value = "操作时间")private Date time;//set、get...
}
然后,使用 EasyExcel 提供的EasyExcel
工具类,即可实现文件的导出。
public static void main(String[] args) throws FileNotFoundException {List<UserWriteEntity> dataList = new ArrayList<>();for (int i = 0; i < 10; i++) {UserWriteEntity userEntity = new UserWriteEntity();userEntity.setName("张三" + i);userEntity.setAge(20 + i);userEntity.setTime(new Date(System.currentTimeMillis() + i));dataList.add(userEntity);}//定义文件输出位置FileOutputStream outputStream = new FileOutputStream(new File("/Users/panzhi/Documents/easyexcel-export-user1.xlsx"));EasyExcel.write(outputStream, UserWriteEntity.class).sheet("用户信息").doWrite(dataList);
}
运行程序,打开文件内容结果!
2.2、简单导入
这种简单固定表头的 Excel 文件,如果想要读取文件数据,操作也很简单。
以上面的导出文件为例,使用 EasyExcel 提供的EasyExcel
工具类,即可来实现文件内容数据的快速读取,示例代码如下:
首先创建读取实体类
/*** 读取实体类*/
public class UserReadEntity {@ExcelProperty(value = "姓名")private String name;/*** 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配*/@ExcelProperty(index = 1)private int age;@DateTimeFormat("yyyy-MM-dd HH:mm:ss")@ExcelProperty(value = "操作时间")private Date time;//set、get...
}
然后读取文件数据,并封装到对象里面
public static void main(String[] args) throws FileNotFoundException {//同步读取文件内容FileInputStream inputStream = new FileInputStream(new File("/Users/panzhi/Documents/easyexcel-user1.xls"));List<UserReadEntity> list = EasyExcel.read(inputStream).head(UserReadEntity.class).sheet().doReadSync();System.out.println(JSONArray.toJSONString(list));
}
运行程序,输出结果如下:
[{"age":20,"name":"张三0","time":1616920360000},{"age":21,"name":"张三1","time":1616920360000},{"age":22,"name":"张三2","time":1616920360000},{"age":23,"name":"张三3","time":1616920360000},{"age":24,"name":"张三4","time":1616920360000},{"age":25,"name":"张三5","time":1616920360000},{"age":26,"name":"张三6","time":1616920360000},{"age":27,"name":"张三7","time":1616920360000},{"age":28,"name":"张三8","time":1616920360000},{"age":29,"name":"张三9","time":1616920360000}]
2.3、动态自由导出导入
在实际使用开发中,我们不可能每来一个 excel 导入导出需求,就编写一个实体类,很多业务需求需要根据不同的字段来动态导入导出,没办法基于实体类注解的方式来读取文件或者写入文件。
因此,基于EasyExcel
提供的动态参数化生成文件和动态监听器读取文件方法,我们可以单独封装一套动态导出导出工具类,省的我们每次都需要重新编写大量重复工作,以下就是小编我在实际使用过程,封装出来的工具类,在此分享给大家!
-
首先,我们可以编写一个动态导出工具类
public class DynamicEasyExcelExportUtils {private static final Logger log = LoggerFactory.getLogger(DynamicEasyExcelExportUtils.class);private static final String DEFAULT_SHEET_NAME = "sheet1";/*** 动态生成导出模版(单表头)* @param headColumns 列名称* @return excel文件流*/public static byte[] exportTemplateExcelFile(List<String> headColumns){List<List<String>> excelHead = Lists.newArrayList();headColumns.forEach(columnName -> { excelHead.add(Lists.newArrayList(columnName)); });byte[] stream = createExcelFile(excelHead, new ArrayList<>());return stream;}/*** 动态生成模版(复杂表头)* @param excelHead 列名称* @return*/public static byte[] exportTemplateExcelFileCustomHead(List<List<String>> excelHead){byte[] stream = createExcelFile(excelHead, new ArrayList<>());return stream;}/*** 动态导出文件(通过map方式计算)* @param headColumnMap 有序列头部* @param dataList 数据体* @return*/public static byte[] exportExcelFile(LinkedHashMap<String, String> headColumnMap, List<Map<String, Object>> dataList){//获取列名称List<List<String>> excelHead = new ArrayList<>();if(MapUtils.isNotEmpty(headColumnMap)){//key为匹配符,value为列名,如果多级列名用逗号隔开headColumnMap.entrySet().forEach(entry -> {excelHead.add(Lists.newArrayList(entry.getValue().split(",")));});}List<List<Object>> excelRows = new ArrayList<>();if(MapUtils.isNotEmpty(headColumnMap) && CollectionUtils.isNotEmpty(dataList)){for (Map<String, Object> dataMap : dataList) {List<Object> rows = new ArrayList<>();headColumnMap.entrySet().forEach(headColumnEntry -> {if(dataMap.containsKey(headColumnEntry.getKey())){Object data = dataMap.get(headColumnEntry.getKey());rows.add(data);}});excelRows.add(rows);}}byte[] stream = createExcelFile(excelHead, excelRows);return stream;}/*** 生成文件(自定义头部排列)* @param rowHeads* @param excelRows* @return*/public static byte[] customerExportExcelFile(List<List<String>> rowHeads, List<List<Object>> excelRows){//将行头部转成easyexcel能识别的部分List<List<String>> excelHead = transferHead(rowHeads);return createExcelFile(excelHead, excelRows);}/*** 生成文件* @param excelHead* @param excelRows* @return*/private static byte[] createExcelFile(List<List<String>> excelHead, List<List<Object>> excelRows){try {if(CollectionUtils.isNotEmpty(excelHead)){ByteArrayOutputStream outputStream = new ByteArrayOutputStream();EasyExcel.write(outputStream).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).head(excelHead).sheet(DEFAULT_SHEET_NAME).doWrite(excelRows);return outputStream.toByteArray();}} catch (Exception e) {log.error("动态生成excel文件失败,headColumns:" + JSONArray.toJSONString(excelHead) + ",excelRows:" + JSONArray.toJSONString(excelRows), e);}return null;}/*** 将行头部转成easyexcel能识别的部分* @param rowHeads* @return*/public static List<List<String>> transferHead(List<List<String>> rowHeads){//将头部列进行反转List<List<String>> realHead = new ArrayList<>();if(CollectionUtils.isNotEmpty(rowHeads)){Map<Integer, List<String>> cellMap = new LinkedHashMap<>();//遍历行for (List<String> cells : rowHeads) {//遍历列for (int i = 0; i < cells.size(); i++) {if(cellMap.containsKey(i)){cellMap.get(i).add(cells.get(i));} else {cellMap.put(i, Lists.newArrayList(cells.get(i)));}}}//将列一行一行加入realHeadcellMap.entrySet().forEach(item -> realHead.add(item.getValue()));}return realHead;}/*** 导出文件测试* @param args* @throws IOException*/public static void main(String[] args) throws IOException {//导出包含数据内容的文件(方式一)LinkedHashMap<String, String> headColumnMap = Maps.newLinkedHashMap();headColumnMap.put("className","班级");headColumnMap.put("name","学生信息,姓名");headColumnMap.put("sex","学生信息,性别");List<Map<String, Object>> dataList = new ArrayList<>();for (int i = 0; i < 5; i++) {Map<String, Object> dataMap = Maps.newHashMap();dataMap.put("className", "一年级");dataMap.put("name", "张三" + i);dataMap.put("sex", "男");dataList.add(dataMap);}byte[] stream1 = exportExcelFile(headColumnMap, dataList);FileOutputStream outputStream1 = new FileOutputStream(new File("/Users/panzhi/Documents/easyexcel-export-user5.xlsx"));outputStream1.write(stream1);outputStream1.close();//导出包含数据内容的文件(方式二)//头部,第一层List<String> head1 = new ArrayList<>();head1.add("第一行头部列1");head1.add("第一行头部列1");head1.add("第一行头部列1");head1.add("第一行头部列1");//头部,第二层List<String> head2 = new ArrayList<>();head2.add("第二行头部列1");head2.add("第二行头部列1");head2.add("第二行头部列2");head2.add("第二行头部列2");//头部,第三层List<String> head3 = new ArrayList<>();head3.add("第三行头部列1");head3.add("第三行头部列2");head3.add("第三行头部列3");head3.add("第三行头部列4");//封装头部List<List<String>> allHead = new ArrayList<>();allHead.add(head1);allHead.add(head2);allHead.add(head3);//封装数据体//第一行数据List<Object> data1 = Lists.newArrayList(1,1,1,1);//第二行数据List<Object> data2 = Lists.newArrayList(2,2,2,2);List<List<Object>> allData = Lists.newArrayList(data1, data2);byte[] stream2 = customerExportExcelFile(allHead, allData);FileOutputStream outputStream2 = new FileOutputStream(new File("/Users/panzhi/Documents/easyexcel-export-user6.xlsx"));outputStream2.write(stream2);outputStream2.close();}
}
-
然后,编写一个动态导入工具类
/*** 创建一个文件读取监听器*/
public class DynamicEasyExcelListener extends AnalysisEventListener<Map<Integer, String>> {private static final Logger LOGGER = LoggerFactory.getLogger(UserDataListener.class);/*** 表头数据(存储所有的表头数据)*/private List<Map<Integer, String>> headList = new ArrayList<>();/*** 数据体*/private List<Map<Integer, String>> dataList = new ArrayList<>();/*** 这里会一行行的返回头** @param headMap* @param context*/@Overridepublic void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {LOGGER.info("解析到一条头数据:{}", JSON.toJSONString(headMap));//存储全部表头数据headList.add(headMap);}/*** 这个每一条数据解析都会来调用** @param data* one row value. Is is same as {@link AnalysisContext#readRowHolder()}* @param context*/@Overridepublic void invoke(Map<Integer, String> data, AnalysisContext context) {LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));dataList.add(data);}/*** 所有数据解析完成了 都会来调用** @param context*/@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {// 这里也要保存数据,确保最后遗留的数据也存储到数据库LOGGER.info("所有数据解析完成!");}public List<Map<Integer, String>> getHeadList() {return headList;}public List<Map<Integer, String>> getDataList() {return dataList;}
}
-
动态导入工具类
/*** 编写导入工具类*/
public class DynamicEasyExcelImportUtils {/*** 动态获取全部列和数据体,默认从第一行开始解析数据* @param stream* @return*/public static List<Map<String,String>> parseExcelToView(byte[] stream) {return parseExcelToView(stream, 1);}/*** 动态获取全部列和数据体* @param stream excel文件流* @param parseRowNumber 指定读取行* @return*/public static List<Map<String,String>> parseExcelToView(byte[] stream, Integer parseRowNumber) {DynamicEasyExcelListener readListener = new DynamicEasyExcelListener();EasyExcelFactory.read(new ByteArrayInputStream(stream)).registerReadListener(readListener).headRowNumber(parseRowNumber).sheet(0).doRead();List<Map<Integer, String>> headList = readListener.getHeadList();if(CollectionUtils.isEmpty(headList)){throw new RuntimeException("Excel未包含表头");}List<Map<Integer, String>> dataList = readListener.getDataList();if(CollectionUtils.isEmpty(dataList)){throw new RuntimeException("Excel未包含数据");}//获取头部,取最后一次解析的列头数据Map<Integer, String> excelHeadIdxNameMap = headList.get(headList.size() -1);//封装数据体List<Map<String,String>> excelDataList = Lists.newArrayList();for (Map<Integer, String> dataRow : dataList) {Map<String,String> rowData = new LinkedHashMap<>();excelHeadIdxNameMap.entrySet().forEach(columnHead -> {rowData.put(columnHead.getValue(), dataRow.get(columnHead.getKey()));});excelDataList.add(rowData);}return excelDataList;}/*** 文件导入测试* @param args* @throws IOException*/public static void main(String[] args) throws IOException {FileInputStream inputStream = new FileInputStream(new File("/Users/panzhi/Documents/easyexcel-export-user5.xlsx"));byte[] stream = IoUtils.toByteArray(inputStream);List<Map<String,String>> dataList = parseExcelToView(stream, 2);System.out.println(JSONArray.toJSONString(dataList));inputStream.close();}
}
为了方便后续的操作流程,在解析数据的时候,会将列名作为key
!
03、小结
在实际的业务开发过程中,根据参数动态实现 Excel 的导出导入还是非常广的。
当然,EasyExcel 的功能还不只上面介绍的那些内容,还有基于模版进行 excel的填充,web 端 restful 的导出导出,使用方法大致都差不多!
相关文章:

SpringBoot 整合 Excel 轻松实现数据自由导入导出
01、背景介绍 在实际的业务系统开发过程中,操作 Excel 实现数据的导入导出基本上是个非常常见的需求。 之前,我们有介绍一款非常好用的工具:EasyPoi,有读者提出在数据量大的情况下,EasyPoi 会占用内存大,…...
PyTorch 基础学习(13)- 混合精度训练
系列文章: 《PyTorch 基础学习》文章索引 基本概念 混合精度训练是深度学习中一种优化技术,旨在通过结合高精度(torch.float32)和低精度(如 torch.float16 或 torch.bfloat16)数据类型的优势,…...

Mycat分片-垂直拆分
目录 场景 配置 测试 全局表配置 续接上篇:MySQ分库分表与MyCat安装配置-CSDN博客 续接下篇:Mycat分片-水平拆分-CSDN博客 场景 在业务系统中, 涉及以下表结构 ,但是由于用户与订单每天都会产生大量的数据, 单台服务器的数据 存储及处理能力是有限…...

一元四次方程求解-【附MATLAB代码】
目录 前言 求解方法 编辑 MATLAB验证 附:一元四次方程的故事 前言 最近在研究机器人的干涉(碰撞)检测,遇到了一个问题,就是在求椭圆到原点的最短距离时,构建的方程是一个一元四次方程。无论是高中的…...

【极限性能,尽在掌控】ROG NUC:游戏与创作的微型巨擘
初见ROG NUC,你或许会为它的小巧体型惊讶。然而,这看似不起眼的机身内,蕴藏着游戏、创意的强大能量。 掌中风暴,性能无界 ROG NUC搭载英特尔高性能处理器,配合高速NVMe SSD固态硬盘以及可选的高端独立显卡(…...

Ecosmos开启公测,将深度赋能CIOE中国光博会元宇宙参会新体验
如今,生成式AI技术的发展,极大地降低了3D数字资产的制作成本,元宇宙作为一种可以无缝将物理和数字资产进行融合的技术,在推动电子产业数字化进程、助力产业高质量发展的方面展现出了巨大的潜力。 当前,发展新质生产力是…...

【Kubernetes】k8s集群之包管理器Helm
目录 一.Helm概述 1.Helm的简介 2.Helm的三个重要概念 3.Helm2与Helm3的的区别 二.Helm 部署 1.安装 helm 2.使用 helm 安装 Chart 3.Helm 自定义模板 4.Helm 仓库 每个成功的软件平台都有一个优秀的打包系统,比如Debian、Ubuntu 的 apt,RedH…...
嵌入式linux系统镜像制作day3(构建镜像)
点击上方"蓝字"关注我们 01、上节回顾 嵌入式linux系统镜像制作day1嵌入式linux系统镜像制作day2提前下载好准备工具,不然失败了大眼瞪小眼。 02、构建 Poky 的 Sato 镜像1 环境: ubuntu18.04poky版本:Dizzy 工具git 在开始之前,针对不同的发行版,需要先执行…...

【生日视频制作】教师节中秋节国庆节车模特美女举牌AE模板修改文字软件生成器教程特效素材【AE模板】
教师节中秋节国庆节车模特美女举牌生日视频制作教程AE模板改文字软件生成器素材 怎么如何做的【生日视频制作】教师节中秋节国庆节车模特美女举牌AE模板修改文字软件生成器教程特效素材【AE模板】 生日视频制作步骤: 安装AE软件下载AE模板把AE模板导入AE软件修改图…...

RongCallKit iOS 端本地私有 pod 方案
RongCallKit iOS 端本地私有 pod 方案 需求背景 适用于源码集成 CallKit 时,使用 pod 管理 RTC framework 以及源码。集成 CallKit 时,需要定制化修改 CallKit 的样式以及部分 UI 功能。适用于 CallKit 源码 Debug 调试便于定位相关问题。 解决方案 从…...

C++11:可变参数模板
目录 一、概述 二、场景 1.深拷贝的类 2.浅拷贝的类 C使用指南 一、概述 // Args是一个模板参数包,args是一个函数形参参数包 // 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。 template <class ...Args> void ShowList(…...

C++ 与 QML 之间进行数据交互的几种方法
https://www.cnblogs.com/jzcn/p/17774676.html 一、属性绑定 这是最简单的方式,可以在QML中直接绑定C 对象的属性。通过在C 对象中使用Q_PROPERTY宏定义属性,然后在QML中使用绑定语法将属性与QML元素关联起来。 1. person.h #include <QObject&g…...

Javaweb学习之Vue项目的创建(二)
学习资料 Vue.js - 渐进式 JavaScript 框架 | Vue.js (vuejs.org) 准备工作都做完了,接下来开始Vue的正式学习。 第一步,打开VS Code 在VS Code里,我们也需要使用到终端,如果不是以管理员身份打开,在新建Vue项目的时候…...

『深度长文』4种有效提高LLM输出质量的方法!
LLM,全称Large Language Model,意为大型语言模型,是一种基于深度学习的AI技术,能够生成、理解和处理自然语言文本,也因此成为当前大多数AI工具的核心引擎。LLM通过学习海量的文本数据,掌握了词汇、语法、语…...

【工业机器人】工业异常检测大模型AnomalyGPT
AnomalyGPT 工业异常检测视觉大模型AnomalyGPT AnomalyGPT: Detecting Industrial Anomalies using Large Vision-Language Models AnomalyGPT是一种基于大视觉语言模型(LVLM)的新型工业异常检测(IAD)方法。它利用LVLM的能力来理…...
【PGCCC】PostgreSQL案例:planning time超长问题分析#PG初级
在使用 PostgreSQL 时,查询的执行计划(planning time)有时会出现异常长的情况,这可能会影响数据库的整体性能。分析和解决这种问题可以从多个角度入手,以下是常见原因和相应的解决思路: 1. 统计信息不准确…...

【图文并茂】ant design pro 如何给后端发送 json web token - 请求拦截器的使用
上一节有讲过 【图文并茂】ant design pro 如何对接后端个人信息接口 还差一个东西,去获取个人信息的时候,是要发送 token 的,不然会报 403. 就是说在你登录之后才去获得个人信息。这样后端才能知道是谁的信息。 token 就代码了某个人。 …...

【微信小程序】自定义组件 - behaviors
1. 什么是 behaviors 2. behaviors 的工作方式 3. 创建 behavior 调用 Behavior(Object object) 方法即可创建一个共享的 behavior 实例对象,供所有的组件使用: 4. 导入并使用 behavior 5. behavior 中所有可用的节点 6. 同名字段的覆盖和组合规则* 关…...

Linux ubuntu 24.04 安装运行《帝国时代3》免安装绿色版游戏,解决 “Could not load DATAP.BAR”等问题
Linux ubuntu 24.04 安装运行《帝国时代3》游戏,解决 “Could not load DATAP.BAR" 等问题 《帝国时代 3》是一款比较经典的即时战斗游戏,伴随了我半个高中时代,周末有时间就去泡网吧,可惜玩的都是简单人机,高难…...
Springboot 图片
Springboot 图片 因为 server.servlet.context-path: /api 所以 url是这个的时候 http://127.0.0.1:9100/api/staticfiles/image/dd56a59d-da84-441a-8dac-1d97f9e42090.jpeg 配置代码的前面的 /api 是不要写的 package com.gk.study.config;import org.springframework.conte…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...

docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...

【Oracle APEX开发小技巧12】
有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...

React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...

Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...

基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...

04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

AI病理诊断七剑下天山,医疗未来触手可及
一、病理诊断困局:刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断",医生需通过显微镜观察组织切片,在细胞迷宫中捕捉癌变信号。某省病理质控报告显示,基层医院误诊率达12%-15%,专家会诊…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...

JVM虚拟机:内存结构、垃圾回收、性能优化
1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...