当前位置: 首页 > news >正文

C++好难(5):内存管理

这一节学完,我们 C嘎嘎 就算是正式入门了,但是之后的课还会更上一阶d(ŐдŐ๑)  继续坚持!


【本节目标】

1. C/C++内存分布

2. C语言中动态内存管理方式

3. C++中动态内存管理

4. operator new与operator delete函数

5. new和delete的实现原理

6.常见问题


目录

【本节目标】

1. C/C++的内存分布

2.C语言中的动态内存管理方式:malloc/calloc/realloc/free

1)malloc

2)calloc

3)realloc

4)free

3.C++内存管理方式

3.1new和delete的基本操作

3.2new和delete操作符自定义类型

3.3总结:

4.operator new与operator delete函数

5.new和delete的实现原理

5.1内置类型

5.2自定义类型

6.常见问题:

1)malloc/free和new/delete的区别

2)内存泄漏

3)内存泄漏分类

4)如何避免内存泄漏


我们都知道在 C语言 中,可以使用 mallocrealloccalloc 来开辟空间,使用 free 来销毁空间

那我们 C++ 应该怎样去申请空间呢?

1. C/C++的内存分布

对于内存,我们的操作系统有自己的一个划分,划分为六个空间,分别是:内核空间、栈、内存映射段、堆、数据段、代码段

我们先来看一段代码:

int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{static int staticVar = 1;int localVar = 1;int num1[10] = { 1, 2, 3, 4 };char char2[] = "abcd";char* pChar3 = "abcd";int* ptr1 = (int*)malloc(sizeof(int) * 4);int* ptr2 = (int*)calloc(4, sizeof(int));int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);free(ptr1);free(ptr3);
}

 

 答案和解释:

1.选择题

globalVar 存储于 数据段

 
staticGlobalVar 存储于 数据段

 
staticVar 存储于 数据段

 
localVar 存储于

 
localVar1 存储于

 
num1 存储于


char2 存储于

因为 char2 是一个数组,存在栈上面的,而 “abcd” 是存在常量区的常量,只是拷贝给了 char2
 
*char2 存储于

数组名就是首元素的地址,也就是 char 是地址,*char2 相当于对 cahr2 进行解引用,找到它的内容,那么它的内容是存储在栈上面的
 
pChar3 存储于

pChar3 是一个指针变量,这个指针变量是在栈上面开的
 
*pChar3 存储于 代码段

pChar3 是一个指针变量,它存的是一个地址,它指向常量区的字符串 “a b c d”,*pChar 就是对这个指针变量解引用,找到了它的内容,也就是 “abcd” ,所以它是存在代码段的
 
ptr1 存储于
 
*ptr1 存储于

2.填空题

注意:sizeof 是求字节大小,strlen 是求字符串长度的。
 
sizeof(num1) = 40(算对象占用空间的大小)
 
sizeof(char2) = 5(char2 是一个字符数组,求大小要计算 ‘\0’)
 
sizeof(pChar3) = 4/8 (指针在 32 位平台大小是 4,64 位平台大小是 8)
 
sizeof(ptr1) = 4/8
 
strlen(char2) = 4(char2 是一个字符数组,求长度不计算 \0)
 
strlen(pChar3) = 4

其内存分布图如下:

 

说明:

  • 1. 栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。
  • 2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。
  • 3. 堆用于程序运行时动态内存分配,堆是可以上增长的。
  • 4. 数据段--存储全局数据和静态数据。
  • 5. 代码段--可执行的代码/只读常量。

2.C语言中的动态内存管理方式:malloc/calloc/realloc/free

1)malloc

malloc 函数的功能是开辟指定字节大小的内存空间,如果开辟成功就返回该空间的首地址,如果开辟失败就返回一个 NULL(空指针)。

 

它使用的时候,传参只需传入需要开辟的字节个数。

2)calloc

calloc 函数的功能也是开辟指定大小的内存空间,如果开辟成功就返回该空间的首地址,如果开辟失败就返回一个 NULL(空指针)。

calloc 函数传参时需要传入开辟的内存用于存放的元素个数每个元素的大小

calloc 函数开辟好内存后会将空间内容中的每一个字节都初始化为 0。

3)realloc

realloc 函数可以调整已经开辟好的动态内存的大小
第一个参数是需要调整大小的动态内存的首地址,第二个参数是动态内存调整后的新大小。

如果第一次使用realloc时检测到首地址未开空间,则realloc和malloc的功能一致

注意:

  • 原地扩。需扩展的空间后方有足够的空间可供扩展,此时,realloc 函数直接在原空间后方进行扩展,并返回该内存空间首地址(即原来的首地址)。
  • 异地扩。需扩展的空间后方没有足够的空间可供扩展,此时,realloc 函数会在堆区中重新找一块满足要求的内存空间,把原空间内的数据拷贝到新空间中,并主动将原空间内存释放(即还给操作系统),返回新内存空间的首地址。
  • 扩容失败。需扩展的空间后方没有足够的空间可供扩展,并且堆区中也没有符合需要开辟的内存大小的空间。结果就是开辟内存失败,返回一个 NULL(空指针)。

4)free

free 函数的作用就是将 malloc、calloc 以及 realloc 函数申请的动态内存空间释放,其释放空间的大小取决于之前申请的内存空间的大小。

3.C++内存管理方式

C 语言内存管理方式在 C++ 中可以继续使用但C++有更简单的用法:

通过 new delete 操作符进行动态内存管理。

3.1new和delete的基本操作

(1)new  一个 int 类型的对象

int main()
{// 用malloc动态申请一个int类型的空间int* p1 = (int*)malloc(sizeof(int));// 销毁p1free(p1);// 动态申请一个int类型的空间int* p1 = new int;// 销毁p1delete p1;return 0;
}

(2)new 10 个 int 类型的对象

int main()
{// 用malloc动态申请一个int类型的空间int* p2 = (int*)malloc(10 * sizeof(int));// 销毁free(p2);// 动态申请一个int类型的空间int* p2 = new int[10];// 销毁delete[] p2;return 0;
}

delete[ ] 对应的是new[ ] ,是申请多个空间时的样子。 

(3)new 一个 int 类型对象,然后初始化为 10

{int* p3 = (int*)malloc(sizeof(int));*p3 = 10; //赋值//销毁free(p3);// 动态申请一个int类型的空间并初始化为10int* p3 = new int(10);// 销毁delete p3;return 0;
}

(4)new 10 个 int 类型对象,并进行初始化

int main()
{//动态申请10个int类型的空间并初始化为1到10int* p8 = (int*)malloc(sizeof(int) * 10); //申请for (int i = 0; i < 10; i++) //赋值{p8[i] = i;}free(p8); //销毁//动态申请10个int类型的空间并初始化为1到10int* p4 = new int[10] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//销毁p4delete[] p4;return 0;
}

当然如用new在赋值的时候,如果只给前面的控件赋值,后面的空间会赋值为0

 总结如下图:

  • 申请和释放 单个 元素的空间,使用 new 和 delete 
  • 申请和释放 连续 的空间,使用 new[ ] 和 delete[ ]

3.2new和delete操作符自定义类型

对于内置类型来说,malloc new 用法几乎一样,
但是对于自定义类型来说,newdelete相比于malloc,会调用构造函数和析构函数

这里以链表为列,看看malloc和new的区别

malloc:

//链表
struct ListNode
{ListNode* next;int val;
};//申请节点
struct ListNode* BuyListNode(int x) 
{struct ListNode* node = (struct ListNode*)malloc(sizeof(struct ListNode));assert(node);node->next = NULL;node->val = x;return node;
}int main()
{// 定义n1节点struct ListNode* n1 = BuyListNode(1);free(n1);return 0;
}

new:

//链表
class ListNode
{
public://构造函数ListNode(int val = 0):_next(nullptr), _val(val) // 初始化列表{cout << "ListNode" << endl;}~ListNode(){cout << "~ListNode" << endl;}
private:ListNode* _next;int _val;
};int main()
{// 定义n1节点ListNode* n2 = new ListNode(2); // new会去调用ListNode的构造函数delete n2;return 0;
}

 

3.3总结:

  • 1)malloc/free是函数,而new/delete是关键字
  • 2)C++ 中如果是申请内置类型的对象或是数组,用 new/delete 和 malloc/free 没有什么区别。
  • 3)如果是自定义类型的话,new 和 delete 分别是 开空间+构造函数、析构函数+释放空间,而 malloc 和 free 仅仅是 开空间和释放空间,可以看到区别还是很大的。
  • 4)建议在 C++ 中无论是内置类型还是自定义类型的申请和释放,尽量都使用 new 和 delete。

4.operator new与operator delete函数

new和delete是用户进行动态内存申请和释放的操作符operator new 和operator delete是系统提供的全局函数

new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。

 operator new 实际也是通过 malloc 来申请空间,如果 malloc 申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。
operator delete 最终是通过 free 来释放空间的。

5.new和delete的实现原理

5.1内置类型

如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。

5.2自定义类型

1)new的原理

  • 1. 调用operator new函数申请空间
  • 2. 在申请的空间上执行构造函数,完成对象的构造

2)delete的原理

  • 1. 在空间上执行析构函数,完成对象中资源的清理工作
  • 2. 调用operator delete函数释放对象的空间

3)new T[N] 的原理

  • 1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请
  • 2. 在申请的空间上执行N次构造函数

4)delete[ ] 的原理

  • 1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
  • 2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间

6.常见问题:

1)malloc/free和new/delete的区别

malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。

不同的地方是:

  • 1. malloc和free是函数,new和delete是操作符
  • 2. malloc申请的空间不会初始化,new可以初始化
  • 3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可
  • 4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
  • 5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
  • 6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理

2)内存泄漏

什么是内存泄漏,内存泄漏的危害

什么是内存泄漏:

内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

 

内存泄漏的危害:

长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

3)内存泄漏分类

C/C++程序中一般我们关心两种方面的内存泄漏:

  • 堆内存泄漏(Heap leak)

堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。

假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生堆内存泄漏(Heap leak)

  • 系统资源泄漏

指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

4)如何避免内存泄漏

(1)工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。

        ps:这个是理想状态,但是如果碰上异常时,就算注意释放了,还是可能会出问题。可能
        要智能指针来管理才有保证。

(2)采用 RAII 思想或者智能指针来管理资源。

(3)有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。

(4)出问题了使用内存泄漏工具检测。(ps:不过很多工具都不够靠谱,或者收费昂贵)。

相关文章:

C++好难(5):内存管理

这一节学完&#xff0c;我们 C嘎嘎 就算是正式入门了&#xff0c;但是之后的课还会更上一阶d(ŐдŐ๑) 继续坚持&#xff01; 【本节目标】 1. C/C内存分布 2. C语言中动态内存管理方式 3. C中动态内存管理 4. operator new与operator delete函数 5. new和delete的实现原…...

vue-admin-template中vue动态路由不显示问题解决

使用的的是vue-admin-template&#xff0c;这是一个极简的 vue admin 管理后台&#xff0c;它只包含了 Element UI & axios & iconfont & permission control & lint&#xff0c;这些搭建后台必要的东西。需要根据自己的需求二次开发。 线上地址:vue-admin-tem…...

IP协议介绍

文章目录 一、IP协议的基本认识二、IP的协议头格式三、网段划分四、特殊的IP地址五、IP地址的数量限制六、私有IP地址和公网IP地址 一、IP协议的基本认识 IP在网络分层中属于网络层协议&#xff0c;传输层协议里的TCP协议解决的是可靠性问题&#xff0c;网络层协议里的IP协议能…...

将一个单体服务重构成微服务

将一个单体服务重构成微服务需要经过以下步骤&#xff1a; 1. 拆分服务&#xff1a;将单体服务拆分成多个小服务&#xff0c;每个服务只负责一个特定的功能。拆分的原则是将服务按照业务功能进行划分&#xff0c;每个服务都应该是相对独立的。 2. 设计API&#xff1a;为每个服务…...

SpringBoot项目如何打包成exe应用程序

准备 准备工作&#xff1a; 一个jar包&#xff0c;没有bug能正常启动的jar包 exe4j&#xff0c;一个将jar转换成exe的工具 链接: https://pan.baidu.com/s/1m1qA31Z8MEcWWkp9qe8AiA 提取码: f1wt inno setup&#xff0c;一个将依赖和exe一起打成一个安装程序的工具 链接:…...

一文读懂:客户管理系统平台是什么?有什么作用?

“客户管理系统平台是什么&#xff1f;” “客户管理系统平台有什么作用&#xff1f;在哪里可以应用&#xff1f;怎么用&#xff1f;” 经常可以听到企业内部关于客户管理系统平台的这些问题&#xff0c;本文将会为您一一解答&#xff1a; 一、客户管理系统平台是什么 顾名…...

Node.js 与 TypeScript

目录 1、什么是 TypeScript 2、运行TypeScript 3、TypeScript 在Node.js 生态中的情况 1、什么是 TypeScript TypeScript是一种流行的开源语言&#xff0c;由微软维护和开发。它受到了世界各地许多软件开发人员的喜爱和使用。 基本上&#xff0c;它是JavaScript的超集&…...

Python并发编程之进程理论

前言 本文将详细介绍进程相关概念。 进程和程序 计算机上的未运行的QQ、Wechat等都属于程序&#xff0c;但是一旦当这些程序运行起来的话&#xff0c;就可以被称为进程。因此可以如下定义程序和进程&#xff1a; 程序&#xff1a;就是存在硬盘上的一堆代码。 进程&#xf…...

超级详细的mysql数据库安装指南

MySql数据库 如果你的电脑是mac那么你看这位大佬的分享。 如果你的电脑是windows&#xff0c;参考下面的安装步骤。 一、下载mysql数据库&#xff1f; 进入MySQL官方网站&#xff08;MySQL Community Downloads&#xff09;&#xff0c;按下图顺序点击 1、进入下载页面 2、…...

Java并发编程实践学习笔记(三)——共享对象之发布和异常

目录 1 公共静态变量逸出 2 非私有方法逸出私有变量 3 this引用逸出 4 构造函数中的可覆盖方法调用逸出 发布&#xff08;publishing&#xff09;一个对象的意思是&#xff1a;使对象能够在当前作用域之外的代码中使用。例如&#xff0c;将一个指向该对象的引用保存到其他代…...

Python学习之Image模块图片滤镜效果操作示例

前言 滤镜效果是图像处理中常用的一种技术&#xff0c;可以用来增强图像的视觉效果&#xff0c;实现不同的效果&#xff0c;比如增强对比度、饱和度、色彩等。滤镜效果可以帮助用户快速地调整图像的特性&#xff0c;从而使图像更加适合用户的需求。 Image模块对于图像处理的…...

Grafana 系列-统一展示-5-AWS Cloudwatch 仪表板

系列文章 Grafana 系列文章 &#x1f44d;️强烈推荐 强烈推荐使用 GitHub 上的 monitoringartist/grafana-aws-cloudwatch-dashboards 仪表板。该 repo 有一系列 AWS 资源的仪表板&#xff0c;包括但不限于&#xff1a; EC2EBSAPI GWAutoscalingBillingEKSLambdaLogsRDSS3…...

MySQL---控制流函数、窗口函数(序号函数、开窗聚合函数、分布函数、前后函数、头尾函数、其他函数)

1. 控制流函数 格式 解释 案例 IF(expr,v1,v2) 如果表达式 expr 成立&#xff0c;返回结果 v1&#xff1b;否则&#xff0c;返回结果 v2。 SELECT IF(1 > 0,正确,错误) ->正确 IFNULL(v1,v2&#xff09; 如果 v1 的值不为 NULL&#xff0c;则返回 v1&#xff…...

一心报国的西工大网安人走出新手村

大二下学期5月5日晚上&#xff0c;西工大长安校区教学西楼&#xff0c;作为一名网安专业本科生&#xff0c;从大一便立志学好网安知识&#xff0c;报效祖国&#xff0c;却苦于没有优秀学习资源&#xff0c;就把这事儿拖到了大二&#xff0c;最近上了一门专业课&#xff0c;如同…...

如何安装oracle的sample schema

首先从如下的地址选择合适的版本进行下载 https://github.com/oracle-samples/db-sample-schemas/releases 如果是rac环境&#xff0c;最好是将这个数据库停掉&#xff0c;然后只启动一个instance&#xff0c;然后再开始安装 [Tue May 09 20:26:34][377951][oraclenshqae01adm…...

ChatGPT :国内免费可用 ChatGPT +Midjourney绘图

前言 ChatGPT&#xff08;全名&#xff1a;Chat Generative Pre-trained Transformer&#xff09;&#xff0c;美国OpenAI 研发的聊天机器人程序 &#xff0c;于2022年11月30日发布 。ChatGPT是人工智能技术驱动的自然语言处理工具&#xff0c;它能够通过理解和学习人类的语言来…...

女孩子转数据分析难吗?难在哪里?

对于数据分析&#xff0c;很多人乍一听会觉得没啥技术难度&#xff0c;是个适合女孩子的专业。我们面对很多零基础小白也是用通俗的语言来形容这个专业&#xff1a;一般是通过Excel或者power BI工具对数据进行分析&#xff0c;制作成可视化的报表给领导层&#xff0c;为公司业务…...

基于常用设计模式的业务框架

前言 做开发也有好几年时间了&#xff0c;最近总结和梳理自己在工作中遇到的一些问题&#xff0c;工作中最容易写出BUG的需求就是改造需求了。一个成熟的业务系统是需要经过无数次迭代而成的&#xff0c;也意味着经过很多开发人员之手&#xff0c;最后到你这里&#xff0c;大部…...

ubuntu重启ssh服务

一、开启ssh服务首先需要安装打开ssh服务的库&#xff1a; sudo apt-get install openssh-server 二、检查当前的ssh开启情况&#xff1a; ps -e |grep ssh 三、如果有sshd&#xff0c;则ssh-server已经启动&#xff1b;若仅有agent&#xff0c;则尚未启动&#xff1b; 开启ssh…...

【19】SCI易中期刊推荐——计算机 | 人工智能领域(中科院2区)

💖💖>>>加勒比海带,QQ2479200884<<<💖💖 🍀🍀>>>【YOLO魔法搭配&论文投稿咨询】<<<🍀🍀 ✨✨>>>学习交流 | 温澜潮生 | 合作共赢 | 共同进步<<<✨✨ 📚📚>>>人工智能 | 计算机视觉…...

Vue.js条件、循环语句

文章目录 条件语句v-ifv-elsev-else-ifv-show 循环语句v-for 指令v-for 迭代对象valuevalue ,keyvalue ,key&#xff0c;index v-for 迭代整数 条件语句 v-if 在元素 和 template 中使用 v-if 指令 <div id"app"><p v-if"seen">现在你看到我…...

Go语言学习查缺补漏ing Day4

Go语言学习查缺补漏ing Day4 一、掌握iota的使用 请看下面这段代码&#xff1a; package mainimport "fmt"const (a iota_bc "ReganYue"dd1e iotaf iota )func main() {fmt.Println(a, b, c, d, d1, e, f) }思考一下输出结果会是什么&#xff1f; …...

说服审稿人,只需牢记这 8 大返修套路!

本文作者&#xff1a;雁门飞雪 如果说科研是一场修炼&#xff0c;那么学术界就是江湖&#xff0c;投稿就是作者与审稿人或编辑之间的高手博弈。 在这一轮轮的对决中&#xff0c;有时靠的是实力&#xff0c;有时靠的是技巧&#xff0c;然而只有实力和技巧双加持的作者才能长久立…...

Java 责任链模式详解

责任链模式&#xff08;Chain of Responsibility Pattern&#xff09;是一种行为型设计模式&#xff0c;它用于将请求的发送者和接收者解耦&#xff0c;使得多个对象都有机会处理这个请求。在责任链模式中&#xff0c;有一个请求处理链条&#xff0c;每个处理请求的对象都是一个…...

使用MASA全家桶从零开始搭建IoT平台(三)管理设备的连接状态

文章目录 前言分析方案1:遗嘱消息演示遗嘱消息的使用实施流程 方案2:使用WebHook开启WebHook演示Webhook编写代码 前言 获取一个设备的在线和离线状态&#xff0c;是一个很关键的功能。我们对设备下发的控制指令&#xff0c;设备处于在线状态才能及时给我们反馈。这里的在线和…...

我的新书上架了!

talk is cheap&#xff0c;show you my book&#xff01; 新书《从0开始学ARM》终于在各大平台上架了&#xff01;&#xff01; 一、关于本书 1. 本书主要内容 ARM体系架构是目前市面上的主流处理器体系架构&#xff0c;在手机芯片和嵌入式芯片领域&#xff0c;ARM体系架构…...

语言与专业的奇迹:如何利用ChatGPT优化跨国贸易

贸易公司&#xff0c;在进行跨国贸易时&#xff0c;往往需要面对不同国家的甲方或者乙方&#xff0c;在与之沟通的过程中&#xff0c;语言和专业是必须要过的一关&#xff0c;顺畅的交流&#xff0c;往往会带来更好的收益。 今天以“茶”为例&#xff0c;给大家介绍一“知否AI…...

云服务器安装宝塔Linux面板命令脚本大全

阿里云服务器安装宝塔Linux面板&#xff0c;操作系统不同安装命令脚本也不同&#xff0c;支持CentOS、Alibaba Cloud Linux、Ubuntu/Deepin等Linux系统&#xff0c;阿里云服务器网分享阿里云服务器安装宝塔Linux面板命令脚本大全&#xff1a; 云服务器安装宝塔Linux面板命令 …...

zed2i相机中imu内参的标定及外参标定

zed2i中imu内参的标定 参考&#xff1a; https://blog.csdn.net/weixin_42681311/article/details/126109617 https://blog.csdn.net/weixin_43135184/article/details/123444090 值得注意&#xff0c;imu内参的标定其实不是那么重要&#xff0c;大致上给一个值应该影响不大…...

Java中的JUnit是什么?如何使用JUnit进行单元测试

JUnit是Java中最流行的单元测试框架之一。它可以帮助开发人员在代码编写过程中检测出错误和异常&#xff0c;从而提高代码的质量和可靠性。 什么是JUnit&#xff1f; JUnit是一个由Kent Beck和Erich Gamma创建的开源Java单元测试框架&#xff0c;它已经成为Java开发中最常用的…...