C++之空间配置器

news2024/11/16 13:22:30

目录

一、C语言中的类型转换

二、C++的类型转换

三、C++强制类型转换

static_cast

reinterpret_cast

const_cast

volatile关键字

dynamic_cast

什么情况下需要将父转成子呢?

static_cast与dynamic_cast转换对比

四、空间配置器

什么是空间配置器

为什么需要空间配置器

SGI-STL空间配置器实现原理

那什么才算是小块内存?

什么是内存碎片?

 一级空间配置器

二级空间配置器

内存池

SGI-STL中二级空间配置器设计 

为什么要用哈希桶管理呢?

空间配置器分配空间逻辑

与容器结合

源码中一级空间配置器的封装

容器实际上的alloc

容器调用alloc的流程 

五、C++的优缺点

C++要兼容C的基础语法,同时还有补充:

  • 1.新增一些细节语法和库去补充C的一些不足。比如:引用,函数重载,模板...
  • 2.STL
  • 3.面向对象相关的东西。

C++是强类型语言,一个类型的对象给给另外一个类型的对象是要进行转换的,有些类型可以转过去,有些类型是转不过去的。

强制类型定义的语言,即一旦某一个变量被定义类型,如果不经强制转换,那么它永远就死该数据类型。强类型语言包括:Java、.net、Python、C++等语言。

一、C语言中的类型转换

在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换。

  • 1. 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败。(相近类型--意义相同的类型支持隐式类型转换,eg:int可以转char和short,原因就是它们都可以用来时表示数据大小,当然char因为编码的原因通常去表示字符)。
  • 2. 显式类型转化:需要用户自己处理。 

但是这样的隐式类型转换是有缺陷的:

转换的可视性比较差,所有的转换形式都是以一种相同形式书写,难以跟踪错误的转换

比如对于这样的一个string接口,如果pos传0,就出问题了,size_t是无符号的整形,end减减到-1 的时候,-1作为无符号就变成全1了,就变成整形的最大值,就导致越界了。所以我们想的就是把end改成int类型的,但是改成int类型也发现死循环了,因为这个当end减减到-1的时候,pos是无符号类型,C语言作为强语言类型,在一个运算符遇到两个操作数,如果两个操作数的类型不一样,就要看他俩是否能用这个运算符,如果能用的话就要发生提升和截断,范围大的给给范围小的就会发生截断,范围小的给给范围大的就会发生提升。本质上提升和截断就是隐式类型转换。我们这里end>= pos 就发生了提升,int类型的提升成无符号类型的,你以为的是-1,但是它已经悄悄提升成无符号的数了。当end--成-1的时候,本来应该结束了,结果进入循环无法结束了,就是因为end被提升了。

C++就想规范一下这种行为,你想进行哪种转换你就说清楚些,不要悄悄的进行转换。其实从设计的角度,就应该取消掉隐式类型转换,但是要向前兼容就不能取消。所以C++就规范类型转换的方式,针对不同的类型转换进行归类。 

二、C++的类型转换

C风格的转换格式很简单,但是有不少缺点的:

  • 1. 隐式类型转化有些情况下可能会出问题:比如数据精度丢失
  • 2. 显式类型转换将所有情况混合在一起,代码不够清晰

因此C++提出了自己的类型转化风格,注意因为C++要兼容C语言,所以C++中还可以使用C语言的转化风格。

三、C++强制类型转换

标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:
static_cast、reinterpret_cast、const_cast、dynamic_cast

static_cast

static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用static_cast,但它不能用于两个不相关的类型进行转换。

reinterpret_cast

reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型 

但是这个操作符还可以做一件bug的事情:把有参数有返回值的函数转换成了无参数无返回值的,这个参数就成了随机值

const_cast

const_cast最常用的用途就是删除变量的const属性,方便赋值

C++中const修饰的变量是存在栈上的 ,并没有存在常量区。const修饰的意义是在语法层面上不能修改,const修饰的变量严格讲叫做常变量,实际上是可以修改的。

但是我们发现a还是2,原因是因为编译器进行优化了,编译器看到a是const,认为a是不会被改变的,所以访问a的时候每次就不会去内存里取a,有些地方直接取到a的常量值就进行压栈。这个流插入是函数调用,这个时候编译器就可以把a保存到寄存器或者直接去2这个值入栈,所以就会导致a在内存里面被改了,但是编译器有没有从内存里面去取。

volatile关键字

通过volatile这个关键字就可以解决。这个关键字就告诉编译器你不要进行优化,每次都到内存里面去取。

C语言的强转也有这个问题

C++把const_cast这个单独分出来,意思是提醒用的人注意,const属性去掉以后,会被修改,小心与编译器的优化进行冲突误判。 

dynamic_cast

前三个对应C语言的类型转换,只不过C++归了下类期望程序员去规范使用,但是这一个是C语言没有的,是C++特有的。

dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)

  • 向上转型:子类的对象指针/引用给给父类的指针/引用(不需要转换,赋值兼容规则) 。就是子给给父。C++天然支持,也叫切割或者切片。针对公有继承
  • 向下转型:父类的对象指针/引用给给子类的指针/引用(用dynamic_cast转型是安全的)。父类的对象转成子类的对象是无论如何都不允许转换的。但是父类的指针和引用是可以转成子类的。

eg: 

什么情况下需要将父转成子呢?

对于这样的一个函数,我调用的时候传过去的参数有可能是父,也有可能是子,这都是天然支持的。假设我要在这个函数里面做一件事情,如果pa是指向父类的,我不做任何处理,如果pa是指向子类的,请把他转回子类,并访问子类对象中的_b成员(为了方便设置成public)。 

这个时候用static_cast就可以把父类的指针转换成子类的,但这个static_cast对于父类和子类都是可以转成功的,所以是实现不了我们的需求的。

这个时候就可以用dynamic_cast--如果pa指向的是父类对象,那么则转换不成功,返回nullptr。
                                                     如果pa指向的是子类对象,那么转换成功,返回指针对象。

也就是说dynamic_cast可以想办法去识别父类的指针到底是指向子类对象还是父类对象。

static_cast与dynamic_cast转换对比

  • static_cast不管你父类的指针指向子类还是父类都可以转换成功。
  • dynamic_cast,如果父类的指针指向父类转换失败,如果父类的指针指向子类转换成功。

注意: 

  • 1. dynamic_cast只能用于父类含有虚函数的类(dynamic_cast底层识别的时候就是在虚表里面存了一些东西,表示是父类还是子类,所以就得需要虚函数).
  • 2. dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0.

这四种类型转换的意义就是期望C++程序员不要再去用C的隐式类型转换表和强制类型转换,而是规范使用,方便维护项目。

四、空间配置器

什么是空间配置器

空间配置器,顾名思义就是为各个容器高效的管理空间(空间的申请与回收)的,在默默地工作。虽然在常规使用STL时,可能用不到它,但站在学习研究的角度,学习它的实现原理对我们有很大的帮助。

为什么需要空间配置器

前面在模拟实现vector、list、map、unordered_map等容器时,所有需要空间的地方都是通过new申请的,虽然代码可以正常运行,但是有以下不足之处:

  • 空间申请与释放需要用户自己管理,容易造成内存泄漏
  • 频繁向系统申请小块内存块,容易造成内存碎片
  • 频繁向系统申请小块内存,影响程序运行效率
  • 直接使用malloc与new进行申请,每块空间前有额外空间浪费
  • 申请空间失败怎么应对
  • 代码结构比较混乱,代码复用率不高
  • 未考虑线程安全问题

因此需要设计一块高效的内存管理机制。

SGI-STL空间配置器实现原理

以上提到的几点不足之处,最主要还是:频繁向系统申请小块内存造成的。

那什么才算是小块内存?

SGI-STL以128作为小块内存与大块内存的分界线,将空间配置器其分为两级结构,一级空间配置器处理大块内存,二级空间配置器处理小块内存。

容器用的时候,通常要向堆去申请大量的小块内存,比如string,字符串通常都不大就是小块小块的内存,vector是一个偏大块的,再比如list,set.都是节点,都是几十个字节大小的,他们都是小块的。频繁的去申请小块的内存会导致:

1.效率问题(每次申请找堆,堆再去申请相对而言会影响效率,堆上面也会有内存池,是针对全局的内存池),堆上面有malloc(虽然看似是个函数调用,但是它的底层有一堆的机制),tcmalloc(在多线程下是比较高效的),它们也是内存池,这两个是针对整个进程的内存管理,STL太频繁的去堆上申请,效率太低,所以STL就专门搞了个自己专用的内存池,就叫做空间配置器。就好比:一个进程就像在一个村里面,村里面有一口水井,村民吃水就去水井里面打水,这时候程序里面有户人家,他是开了个餐馆,对他而言对水的需求就非常大,所以他就专门修了个给餐馆用的蓄水池(自己用的内存池),水还是水井里的,但是我提前就把蓄水池填满了,以后餐馆用水的时候就高效了很多。STL就是做餐馆的人,普通写一个程序就是普通村民,STL的空间配置器就是这个蓄水池,所以就更高效了。需要动态内存的只有容器。

2.内存碎片。一定程度缓解了内存碎片

一段大的内存如果随便申请是会有内存碎片的问题的。

什么是内存碎片?

一块大的内存,vector申请了100字节,list申请了48字节,string申请了8字节,剩下的内存也都分配出去了。过来一会图中蓝色区域的内存都还回来了(容器释放内存),现在有124字节的内存,但是我们要申请一个大于100字节的内存,会申请不出来。原因就是分配内存的时候是一块一块出去的,回来的时候也不是挨着回来的,有些用完的回来,中间没回来的就导致内存间隔开来,官方语言就是碎片化,比如其他地方有些需求需要大内存了,当前就是解决不了的。

内存碎片:虽然有足够的内存,但是内存中间没有还回来,部分间隔开了,导致申请不出大一点的内存。所以频繁的申请小块内存很容易导致内存碎片。所以好的内存池都要设计一个回收机制,STL的空间配置器是没有设计的,malloc和tcmalloc是设计了的。

 一级空间配置器

空间配置器在设计的时候分成了两层去设计,一个叫做一级空间配置器,一个叫做二级空间配置器

一级空间配置器的本质就是对malloc和free的封装

二级空间配置器

二级空间配置器放了一个联合体,这个联合体里面放了一个指向联合体自己的指针和一个char类型的变量,也就意味着这个联合体的大小一般是一个指针的大小。而且还有一个指针数组,这个指针数组就是内存的哈希桶。 

二级空间配置器申请空间的机制是,如果这个n大于128字节就去调用一级空间配置器,其实就是malloc和free。STL认定大块和小块内存就是以128字节为界限,n小于等于128字节调用二级空间配置器。对于这个free_list 就是一个哈希桶,每个桶下面挂的都是对应映射大小的内存块,他要解决申请128以内的内存。

内存池

内存池就是:先申请一块比较大的内存块已做备用,当需要内存时,直接到内存池中去去,当池中空间不够时,再向内存中去取,当用户不用时,直接还回内存池即可。避免了频繁向系统申请小块内存所造成的效率低、内存碎片以及额外浪费的问题。

1. 当用户需要空间时,能否直接从内存池中大块空间中直接截取?为什么?

答:不能。当用户申请空间的时候,首先应当从归还的空间中去取;如果归还的空间中没有合适的空间,再去内存池中大块空间中去截取。

eg: 假设你现在还要申请8字节的空间,如果你直切从内存池的大块空间中截取,这个时候就截取不出来,因为大块空间剩余空间只有7字节,是不够8字节的;但是我们发现这个时候,在归还的空间中是恰好有两个8字节的空间的,所以此时直接从归还的空间中直接拿一个即可

 PS:对于归还的空间,每个小的内存块都是有指针连接起来的,每个内存块都是独立的,他们并不是连续的。所以看似有24字节的空间,但是你不能完整的申请出24字节的空间,

2. 对用户归还的空间能否直接拼接在大块内存前?

答:不能。因为用户申请的空间的顺序和用户规范空间的顺序大概率都是不一致的。

eg:比如你按照1,2,3的顺序去申请内存,但是归还的时候,首先是1号空间先回来了,这个时候就算你想直接拼接也是没有办法的,因为地址的不连续,所以不能拼接。


3. 对用户归还的空间如何进行管理?

答: 由于用户归还的空间不能直接拼接在大块内存前,所以要通过链式结构进行管理。按照用户释放空间的顺序,用指针连接起来,就是一个链表。


4. 不断切割会有什么后果? 

答:会导致之前所说的内存碎片的问题。因为每次拿取的时候,会从大内存上进行切割,不一定该内存就正好和用户申请的一样大,如:现在链表上有8字节的内存,而用户要7字节的空间,现在剩下1字节的碎片无法使用。

SGI-STL中二级空间配置器设计 

SGI-STL中的二级空间配置器使用了内存池技术,但没有采用链表的方式对用户已经归还的空间进行管理(因为用户申请空间时在查找合适的小块内存时效率比较低),而是采用了哈希桶的方式进行管理。

那是否需要128桶个空间来管理用户已经归还的内存块呢?

答案是不需要,因为用户申请的空间基本都是4的整数倍,其他大小的空间几乎很少用到。因此:SGI-STL将用户申请的内存块向上对齐到了8的整数倍。

那么问题来了,为什么是8的整数倍,而不是4的整数倍?

因为哈希表中存放的是地址,一个地址的大小在32位系统下是 4 个字节,但如果实在64位系统下尼,地址就是8个字节,所以内存是8的整数倍。
实际上,还有另外一个原因,因为我们绝大多数申请空间都是自定义类型的,也就是struct,一个结构体的大小基本是 8 的倍数。

为什么要用哈希桶管理呢?

假设我们这样设计:因为频繁申请小块内存有效率低和内存碎片的问题,我们摒弃这个哈希桶,直接去堆申请大块内存(最终内存一定是来源于OS的堆上的),我直接申请10kb(10*1024字节)内存。我要管理这块大内存直接用两个指针(char*类型)就可以管理,一个start_free,一个end_free。这个大块内存就相当于我直接把水弄到我的蓄水池里了,很方便,而且也没有切小块内存。我开始分配内存,首先分配12个字节,我直接start_free+12就取到了,指针是char*类型就是为了方便加(指针P+1 = 指针P + sizeof(指针的类型) *  1)。这个内存从物理上还是连续的,从逻辑上就被切到了链表里面。(set同理)

如果是这样设计简单粗暴,我直接切空间,你就直接可以用。这种方式申请很方便,直接切即可,但是容器删除数据,归还内存的时候很麻烦,这个大块内存是不能直接free掉的,因为malloc出来10kb的内存就要一次性归还,操作系统不支持分期归还,要还就整体还。

如果是现在的设计归,就只能按照容器的归还顺序把内存块用指针连接起来。

但是要再次申请内存,就只能去链表查找,效率太低。PS:我们假设的这个玩意实际上就是内存池。

所以我们就用内存块哈希表去管理这些归还的内存。

这个时候你要1~8字节,我都给你8字节;你要9~16我都给你16字节...这样虽然会存在一定程度的浪费,但是我方便管理,每次切的内存也很整齐。所以,你申请小于128字节内存的时候,就去算你对应桶的下标,比如你要20字节,我就算出你在2号桶,然后在2号桶里取一块内存块,取的时候就是头删,当你不用还回来的时候就是头插,非常方便。

在这个哈希桶申请内存的时候,如果对应的桶下面有内存就直接返回内存,没有内存就返回调用refill,refill就找那个大块内存去去切20块,假如你申请了1块,剩下的19块就挂在桶下面。如果这个大块内存也没了,就去堆申请。所以空间配置器去找系统的堆它就只申请大块内存,它把大块内存切小了挂在桶下面,你用就拿,不用了就还回来,而且还能重复使用,用哈希桶来管理。

空间配置器分配空间逻辑

一级空间配置器就是封装的malloc和free,一般不直接调用,都是去调用二级空间配置器,二级空间配置器在申请大于128字节的内存时就会去调用一级空间配置器,所以空间配置器申请大的内存就直接是malloc,小于128字节就算你申请的内存在对应的哪个桶,如果桶里面有内存就直接取,如果没有就去找start_free和end_free管理的这个大块内存,去切20块,返回1块给你用,剩下的19块挂起来,就还可以申请19次。如果这个大块内存也没有了,就去找堆申请大块内存。对于释放回来的内存就继续挂在哈希桶对应桶的下面,重复利用。某种程度也缓解了内存碎片,因为它对于系统没有小块内存了,都是申请的大块内存。注意:整个空间配置器对于申请的空间是没有释放的,也不方便释放,因为这个大块内存被切到了不同的位置挂起来了,没有办法很好的释放。所以不用释放,进程结束的时候就直接把物理内存都给还了。

与容器结合

具体的容器使用空间配置器,以list为例:

所有的容器都会增加一个模板参数,默认情况下这个alloc就是空间配置器,如果你觉得STL的空间配置器不好,那么你就可以把你认为更好的空间配置器拿过来,只要符合规范就可。

源码中一级空间配置器的封装

这个alloc就是malloc_alloc,这个malloc_alloc就是__malloc_alloc_template,这个__malloc_alloc_template就是一级空间配置器。

容器实际上的alloc

容器实际上的alloc指的是二级空间配置器:

源码中如果定义了__USE_MALLOC这个宏,表示不启用内存池,就直接是启动一级空间配置器,就是malloc,如果没定义就调用二级空间配置器。一般情况下都是不定义__USE_MALLOC这个宏的,所以实际上容器的alloc指的都是二级空间配置器。

容器调用alloc的流程 

在list里面,首先alloc被传给了simple_alloc,simple_alloc这个类里面对申请和释放进行了封装,可以申请多个或者单个T对象;simple_alloc<list_node, Alloc> 又被typedef成了list_node_allocator;list_node_allocator就负责申请和释放list的节点。这里的get_node是只申请出了节点,并没有初始化,因为内存池只管给内存。

我们要对data进行初始化,假设data是个string,这个时候肯定要调用string的构造函数初始化,这个时候就代用construct 

 

construct 用的就是定位new。定位new就是内存池用的,你只开出了空间,但是没初始化。

五、C++的优缺点

C++的缺点:多继承,没有gc(但引入了智能指针),继承C的一些缺点(复杂的指针:函数指针,数组指针,指针数组,多级指针),隐式类型转换,复杂的默认构造函数机制和缺点,语法更新慢,没网络库。

优点:性能不错,直接编译成二进制指令,直接跑在OS上面。像Java是跑在虚拟机上面的,虚拟机作为一个进程跑在OS上面。python是在执行的要去翻译,翻译完后在编译在执行。

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

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

相关文章

raspberry pi播放音视频

文章目录目的QMediaPlayerGStreamerwhat is GStreamer体系框架优势omxplayerwhat is omxplayercommand Linekey bindings运行过程中错误ALSA目的 实现在树莓派下外接扬声器&#xff0c; 播放某段音频&#xff0c; 进行回音测试。 QMediaPlayer 首先我的安装是5.11版本。 优先…

【并发编程二十一:终章】c++20协程( co_yield、co_return、co_await )

【并发编程二十一】c20协程(co_yield、co_return、co_await &#xff09;一、协程分类1、控制机制划分2、有栈&#xff08;stackfull)/无栈&#xff08;stackless)划分二、c20协程三、co_yield1、demo2、相关知识点介绍四、co_return五、co_await一、协程分类 上一篇我们讲解了…

如何让AI帮你干活-娱乐(2)

背景&#xff1a;好容易完成朋友的任务&#xff0c;帮忙给小朋友绘画比赛生成一些创意参考图片。他给我个挑战更高的问题&#xff0c;是否可以帮他用AI生成一些视频。这个乍一听以现在AI技术根本不太可能完成。奈何他各种坚持&#xff0c;无奈被迫营业。苦脸接受了这个不可能完…

Java线程知识点总结

文章目录Java 线程基础线程简介什么是进程什么是线程进程和线程的区别创建线程ThreadRunnableCallable、Future、FutureTaskCallableFutureFutureTaskCallable Future FutureTask 示例线程基本用法线程休眠线程礼让终止线程守护线程线程通信wait/notify/notifyAlljoin管道线程…

MATLAB——数据及其运算

MATLAB数值数据数值数据类型的分类1&#xff0e;整型整型数据是不带小数的数&#xff0c;有带符号整数和无符号整数之分。表中列出了各种整型数据的取值范围和对应的转换函数。2&#xff0e;浮点型浮点型数据有单精度(single&#xff09;和双精度&#xff08;(double)之分&…

精粤X99M-PLUS D3+ E5-2696 v3电脑 Hackintosh 黑苹果efi引导文件

原文来源于黑果魏叔官网&#xff0c;转载需注明出处。硬件型号驱动情况主板精粤X99M-PLUS D3处理器E5-2696 v3已驱动内存64GB ECC DDR3 1866MHz (16GB*4)已驱动硬盘TOPMORE CAPRICORNUS NVMe 1TB已驱动显卡AMD Radeon™ RX 570 series (4GB/MSI)已驱动声卡Realtek ALC897 英特…

Android framework系列2 - Init进程

1、源码 入口&#xff1a;system/core/init/main.cpp2 流程图 https://note.youdao.com/s/EtnCswft 3、代码详解 主入口共三步&#xff0c;如流程图所示&#xff0c;我们主要看下最后一步 入口在init.cpp下&#xff0c;这个阶段主要来解析init.rc并执行此文件下的命令 看到…

多人协作|RecyclerView列表模块新架构设计

多人协作|RecyclerView列表模块新架构设计多人协作设计图新架构设计与实现设计背景与新需求新架构设计多人协作设计图 根据产品设计&#xff0c;将首页列表即将展示内容区域&#xff0c;以模块划分成多个。令团队开发成员分别承接不同模块进行开发&#xff0c;且互不影响任务开…

【Maven】P2 创建 Maven java/web 工程

Maven项目Maven 项目构建命令使用 Maven插件 创建 java/web 工程创建工程格式创建 java 工程创建 web 工程IDEA 中创建 Maven Java 工程IDEA 中创建 Maven web 工程Maven 项目构建命令 mvn compile # 编译 mvn clean # 清理 mvn test # 测试 mvn package # 打包 mvn …

0626-0631韩顺平Java Buffered字节处理流 学习笔记

如何去构建字节流package com.hspedu.outputstream_;import java.io.*;/*** author abner* version 1.0*/ public class BufferedCopy02 {public static void main(String[] args) {String srcFilePath "D:\\Users\\Pictures\\Camera Roll\\Pierre-Auguste_Renoir,_Le_Mo…

java基本数据类型变量间的运算规则

基本数据类型变量间的运算规则。 运算规则包括&#xff1a; 这里提到可以做运算的基本数据类型有7种&#xff0c;不包含boolean类型 1.自动类型提升 2.强制类型转换 自动类型提升日规则&#xff1a;当容量小的变量与容量大的变量做运算时&#xff0c;结果自动转换为容量大的数…

mvn命令

在IDEA右侧Maven菜单中&#xff0c;有以下几种指令。 clean&#xff1a;清理&#xff0c;清除上一次构建生产的文件。执行该命令会删除项目地址下的target文件&#xff0c;但不会删除本地的maven已生成的文件。 validate&#xff1a;验证&#xff0c;验证项目是否正确且所有必…

「史上最全的 TCG 规范解读」TCG 规范架构概述(下)

可信计算组织&#xff08;Ttrusted Computing Group,TCG&#xff09;是一个非盈利的工业标准组织&#xff0c;它的宗旨是加强不同计算机平台上计算环境的安全性。TCG 于 2003 年春成立&#xff0c;并采纳了由可信计算平台联盟&#xff08;the Trusted Computing Platform Allia…

【Linux】P4 Linux 权限 chmod chown

Linux 权限认知权限信息chmod 修改权限chown 修改用户与用户组认知权限信息 序号1&#xff1a;文件、文件夹权限控制信息&#xff1b; 权限控制信息一共有十位 第 1 位&#xff1a; - 表示文件&#xff0c;d 表示文件夹&#xff0c;l 表示软链接 第 2~4 位&#xff1a; 所属用…

JDK19下载、安装与测试的完整图文教程

一、下载JDK 1、官网获取&#xff1a;https://www.oracle.com/ 1.1 点击“Products”&#xff1b; 1.2 选择“Java”&#xff1b; 1.3 选择“Download Java”&#xff1b; 1.4 选择“Java downloads”&#xff0c;这里以最新版&#xff08;JDK19&#xff09;为例&#xff…

Python基础—文件操作(二)

Python基础—文件操作(二) CSV格式文件 逗号分隔值&#xff0c;以纯文本形式存储表格数据 由任意数目的记录组成&#xff0c;记录间以换行符分隔 每条记录由字段组成&#xff0c;字段间用逗号或制表符分隔 每条记录都有同样的字段序列 如有列名&#xff0c;位于文件第一行 每条…

【编程实践】代码之中有创意:“我一直认为工程师世界上最具创造性的工作之一”

代码之中有创意 “我一直认为工程师世界上最具创造性的工作之一”。 文章目录 代码之中有创意一、代码可以赋予创造力1.1 代码的创造力1.2 如何发挥代码的创造力二、有创意的代码可以提高工作效率2.1 代码创意可以提高工作效率2.2 如何利用代码创意来提高工作效率三、代码创意可…

【壹】嵌入式系统硬件基础

随手拍拍&#x1f481;‍♂️&#x1f4f7; 日期: 2023.2.28 地点: 杭州 介绍: 日子像旋转毒马&#x1f40e;&#xff0c;在脑海里转不停&#x1f92f; &#x1f332;&#x1f332;&#x1f332;&#x1f332;&#x1f332; 往期回顾 &#x1f332;&#x1f332;&#x1f332…

【Java 类】001-访问修饰符、命名规范

【Java 类】001-访问修饰符、命名规范 文章目录【Java 类】001-访问修饰符、命名规范一、访问修饰符概述1、是什么2、作用作用问题3、访问修饰符有哪些4、作用对象二、访问修饰符使用演示1、类访问修饰符演示第一步&#xff1a;创建 Dog 类&#xff1a;public第二步&#xff1a…

画图说透 ZooKeeper如何保证数据一致性:选举和ZAB协议

1、zookeeper是什么&#xff1f; zookeeper能被各个牛逼的中间件项目中所依赖&#xff0c;已经说明了他的地位。一出手就是稳定的杀招。zookeeper是什么&#xff1f;官网中所说&#xff0c;zookeeper致力于开发和维护成为一个高度可靠的分布式协调器。 开局一张图&#xff0c;…