Go数据结构的底层原理(图文详解)

空结构体的底层原理

基本类型的字节数

fmt.Println(unsafe.Sizeof(0))  // 8
fmt.Println(unsafe.Sizeof(uint(0)))  // 8
a := 0
b := &a  
fmt.Println(unsafe.Sizeof(b))  // 8
  • int大小跟随系统字长
  • 指针的大小也是系统字长

空结构体

a := struct {  
}{}  
b := struct {  
}{}  
fmt.Println(unsafe.Sizeof(a))  // 0
fmt.Printf("a=%p \n", &a)  //0x10e1438
fmt.Printf("b=%p \n", &b)  //0x10e1438

空结构体指向的地址是zerobase,位置:runtime/malloc.go

// base address for all 0-byte allocations  
var zerobase uintptr

空结构体的地址均相同(不被包含在其它结构体中时)

空结构体主要是为了节约内存,不占用空间

  • 结合map,可以实现hashset(只要key,不要value)
  • 结合channel,可以当纯信号

字符串,数组,切片的底层原理

字符串底层原理

fmt.Println(unsafe.Sizeof("hello RdrB1te"))  //16
fmt.Println(unsafe.Sizeof("RdrB1te"))  //16

上面两行字符串的长度都为16个字节,这是为什么?大胆猜测这一个字符串是不是包含了两个指针

![[string的底层结构路径.png]]

string的底层结构:

type stringStruct struct {  str unsafe.Pointer  len int  
}
  • 字符串的本质是个结构体
  • Data指针指向底层Byte数组

Len表示Byte数组的长度?字符个数?

由于上面的stringStruct不允许外面的包使用,我们通过反射包类似的StringHeader来查看Len变量的大小

![[StringHeader的路径.png]]

StringHeader的底层结构

type StringHeader struct {  Data uintptr  Len  int  
}

打印出字符串中Len的值:

s := "武汉"  
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))  
fmt.Println(sh.Len) // 6

字符编码问题

  • 所有的字符均使用Unicode字符集
  • 使用UTF-8编码

Unicode

  • 一种统一的字符集
  • 囊括了159种文字的144679个字符
  • 14万个字符至少需要3个字节表示
  • 英文字母均排在前128个

UTF-8

  • Unicode的一种变长格式
  • 128个US-ASCII字符只需要一个字节编码
  • 西方常用字符需要两个字节
  • 其他字符需要3个字节,极少需要4个字节

按照UTF-8编码,下面的字符串的Len值应为10

s := "武汉haha"  
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))  
fmt.Println(sh.Len) // 10

结论:Len表示Byte数组的长度(字节数)

![[字符串的底层结构图示.png]]

字符串的访问

  • 对字符串使用len方法得到的是字节数不是字符数
  • 对字符串直接使用下标访问,得到的是字节
  • 字符串被range遍历时,被解码成rune类型的字符
  • UTF-8编码解码算法位于runtime/utf8.go

字符串的切分

先转为rune数组,切片,再转为string

s := "武汉武汉"  
s = string([]rune(s)[:2])  
fmt.Println(s) // 武汉

切片底层原理

slice的底层结构是不是也是一个结构体?猜对了

slice的底层结构(位于runtime/slice.go)

type slice struct {  array unsafe.Pointer  len   int  cap   int  
}

切片的本质是对数组的引用
![[切片的底层结构图示.png]]

切片的创建

根据数组或切片创建

arr := [4]int{1, 2, 3, 4}  
sli1 := arr[0:4]  
sli2 := sli1[0:1]  
fmt.Println(sli1) // [1 2 3 4]  
fmt.Println(sli2) // [1]

字面量:编译时插入创建数组的代码

sli1 := []int{1, 2, 3}  
fmt.Println(sli1) // [1 2 3]

make:运行时创建

sli1 := make([]int, 2)  
fmt.Println(sli1) // [0 0]

那切片的创建是在编译时完成的,还是运行时完成的呢?这该如何查看

使用go build -gcflags -S main.go查看

找到字面量创建切片的行数进行查看,发现是在编译时先创建了一个数组,再基于数组创建了切片:
![[字面量方式创建切片的底层过程.png]]

同理,使用make创建切片的底层逻辑也可以通过上面的命令进行查看:

![[make方式创建切片的底层过程.png]]

不同于字面量的创建过程,使用make创建时直接调用了makeslice方法,这个方法是个运行时的方法,直接传入参数,返回新建切片的指针:

func makeslice(et *_type, len, cap int) unsafe.Pointer {
......
}

再通过下面的示例回顾下切片创建的原理:
![[切片创建的原理图示回顾.png]]

arr := [10]int{0,1,2,3,4,5,6,7,8,9}
slice := arr[1:4]

切片的访问

  • 下标直接访问元素
  • range遍历元素
  • len(slice)查看切片长度
  • cap(slice)查看数组容量

切片的追加

  • 不扩容时,只调整len(编译器负责)

  • 扩容时,编译时转为调用runtime.growslice()

  • 如果期望容量大于当前容量的两倍就会使用期望容量

  • 如果当前切片的长度小于1024,将容量翻倍

  • 如果当前切片的长度大于1024,每次增加25%

  • 切片扩容时,并发不安全,注意切片并发要加锁

上面扩容的逻辑可通过runtime.growslice()方法进行查看:

newcap := old.cap  
doublecap := newcap + newcap  
if cap > doublecap {  newcap = cap  
} else {  const threshold = 256  if old.cap < threshold {  newcap = doublecap  } else {  // Check 0 < newcap to detect overflow  // and prevent an infinite loop.      for 0 < newcap && newcap < cap {  // Transition from growing 2x for small slices  // to growing 1.25x for large slices. This formula         // gives a smooth-ish transition between the two.         newcap += (newcap + 3*threshold) / 4  }  // Set newcap to the requested cap when  // the newcap calculation overflowed.      if newcap <= 0 {  newcap = cap  }  }  
}

map的底层原理

golang语言的map底层本质是用hashmap进行实现的

hashmap的基本方案

开放寻址法

![[开放寻址法原理图示.png]]

底层实际是一个数组,数组元素是一个个的键值对。现在假设要插入一个新的键值对,key是字母b,value是字母B,先把key先hash,Hash后与下面的数组长度6求模为1,按理应放到下标为1的位置,但是被占了,就往后找,直到有空为止。

如果要读取key是字母b的值也是一样的,先hash再取模,如果下标为1的位置不是字母b,说明字母b往后放了,就继续往后找,找到为止。

拉链法

![[拉链法原理图示.png]]

前面的步骤一样,Hash后与下面的数组长度6求模为1,但是这里槽为1(下标为1)存放的并不是实际的键值对,而是一个指针,后面挂了一个相当于链表的东西,哈希碰撞或哈希值一样的情况下,会挂在1号槽的链表后面,假如1号槽的链表中已经有了一个键值对,则b:B会追加到后面。这样就不像开放寻址法横向地往后找,而是向拉链一样,纵向地向下拉一个链表,挂在后面。

如果要读取key是字母b的值也是一样的,hash求模后遍历链表,就能找到想要的数据。

Go map底层结构

runtime/map.go文件中,有个hmap的结构体,这就是map的底层结构:

type hmap struct {  // Note: the format of the hmap is also encoded in cmd/compile/internal/reflectdata/reflect.go.   // Make sure this stays in sync with the compiler's definition.   count     int // 存的键值对的数量flags     uint8  B         uint8  // lg2桶的大小noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details   hash0     uint32 // hash的种子buckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.  oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing  nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)  extra *mapextra // optional fields  
}

里面有个参数是buckets,意思是桶,这与上面讲到的拉链法中桶的概念名称一致,基本可以断定Go的map底层是用类似拉链法实现的。

那桶的数据结构是什么,继续往下看,buckets的指针指向的是一个由很多个bmap组成的数组,bmap由tophash、keys、elems、overflow四个参数组成,后三个参数是编译的时候才会生成(好支持不同的数据类型),tophash存了8个key的前一个字节的hash值,overflow是溢出指针,指向下一个bmap。

// A bucket for a Go map.
type bmap struct {  // tophash generally contains the top byte of the hash value  // for each key in this bucket. If tophash[0] < minTopHash,   // tophash[0] is a bucket evacuation state instead.   tophash [bucketCnt]uint8  // 存了8个key的前一个字节的hash值// Followed by bucketCnt keys and then bucketCnt elems.  // NOTE: packing all the keys together and then all the elems together makes the   // code a bit more complicated than alternating key/elem/key/elem/... but it allows   // us to eliminate padding which would be needed for, e.g., map[int64]int8.   // Followed by an overflow pointer.}

map的底层结构图示如下:
![[go的map的底层结构图示.png]]

map的初始化

make的方式初始化
m := make(map[string]int, 9)  
fmt.Println(m)

make初始化的过程一样可以用go build -gcflags -S main.go查看:

![[make方式初始化map的编译过程.png]]

进入makemap方法:

func makemap(t *maptype, hint int, h *hmap) *hmap {  ......
}

make map初始化解析,首先根据map元素的数量计算出B,根据B的值创建桶,还有溢出桶;还会创建mapextra的结构体,这个结构体有个参数nextOverflow,它会指向下一个可用的溢出桶。
![[makemap方法解析图示.png]]

字面量方式的初始化
m := map[string]string{"a": "A", "b": "B"}  
fmt.Println(m)
  • 元素少于25个时,转化为简单赋值
  • 元素多余25个时,转化为循环赋值
map的访问

如何读取a这个key的value:

1.先计算桶号:
![[map的访问-计算桶号图示.png]]

2.确认了它在2号桶里面:
![[map的访问-确认桶的位置.png]]

3.计算tophash,看在2号桶的哪个位置:
![[map的访问-计算桶中所在的位置图示.png]]

4.发现2号桶第一个位置的tophash就是0x5c,进一步匹配key值,获取最终的value;如果key的值没有匹配上,就会进一步往后匹配,如果都没找到,说明map中不存在这个key。
![[map的访问-确认最终的值.png]]

写入的过程,与访问的过程类似,这里不再赘述。

总结

  • Go语言使用拉链实现了hashmap
  • 每一个桶中存储键哈希的前8位
  • 桶超出8个数据,就会存储到溢出桶中

Go map扩容原理

map为什么需要扩容

哈希碰撞的频率越高,会导致溢出桶越多,链表也越来越长,哈希操作的效率就会越来越低,性能严重下降:
![[map扩容的原因图示.png]]

Go map插入数据调用的mapassign方法有相关的扩容逻辑:

// Like mapaccess, but allocates a slot for the key if it is not present in the map.  
func mapassign(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {  ......if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {  hashGrow(t, h)  goto again // Growing the table invalidates everything, so try again  ......
}

runtime.mapassign()可能会触发扩容的情况:

  • 装载因子超过6.5(平均每个槽6.5个key)
  • 使用了太多溢出桶(溢出桶超过了普通桶)

map扩容的类型

  • 等量扩容:数据不多但是溢出桶太多了(之前数据很多,都被删了,需要整理)
  • 翻倍扩容:数据太多了

map扩容步骤

map扩容:步骤1

可见hashGrow这个方法:
![[map扩容步骤1.png]]

  • 创建一组新桶
  • oldbuckets指向原有的桶数组
  • buckets指向新的桶数组
  • map标记为扩容状态

map扩容:步骤2
写入时,先找到原来的key的位置:
![[map扩容步骤2-1.png]]

扩容后,假如变成了8个桶,这时B变成了3,就要看哈希的后三位,如果前面一位是0,说明要去2号桶,如果前面一位是1,说明要去6号桶,就将原来的旧桶里的数据往新桶进行了一分为2的迁移。如果新桶的数量没有变,那相当于就是对旧桶做了一个整理。
![[map扩容步骤2-2.png]]

具体的扩容逻辑代码,可见runtime.map文件中的evacuate这个方法。

  • 将所有的数据从旧桶驱逐到新桶
  • 采用渐进式驱逐
  • 每次操作一个旧桶时,将旧桶数据驱逐到新桶
  • 读取时不进行驱逐,只判断读取新桶还是旧桶

map扩容:步骤3

![[map扩容步骤3.png]]

  • 所有的旧桶驱逐完成后
  • oldbuckets回收

总结

  • 装载系数或者溢出桶的增加,会触发map扩容
  • 扩容可能并不是增加桶数,而是整理
  • map扩容采用渐进式,桶被操作时才会重新分配

Go map并发问题

当map同时进行读写操作时,会弹出fatal error: concurrent map read and map write的报错,可用下面一段代码实验:

func main() {  testM := make(map[int]int)  go func() {  for {  _ = testM[0]  }  }()  go func() {  for {  testM[1] = 2  }  }()  select {}  
}

map为什么不支持并发读写

A正在旧桶读取数据时,B这时写入,要对这个旧桶进行驱逐:
![[map的并发问题缘由.png]]

  • map的读写有并发问题
  • A协程在桶中读数据时,B协程驱逐了这个桶
  • A协程会读到错误的数据或者找不到数据

map并发问题解决方案

  • 给map加锁(mutex)
  • 使用sync.Map

sync.map的原理

sync.map的查询、修改、新增

sync.map的底层结构
可见sync.map结构体:

type Map struct {  mu Mutex   read atomic.Value dirty map[any]*entry     misses int  
}

![[sync.map的底层结构图示.png]]

相当于一套value有两套一模一样的key

sync.map正常读写的过程
读出a这个key的值"A":
![[sync.map正常读写过程.png]]

sync.map追加的过程
先去read map找,发现没有,然后上锁,去下面的dirty map中(同时只能有一个协程去操作dirty map):
![[sync.map追加的过程.png]]

假如我要追加一个d:D,追加后,这时read中的amended变为了true,意思是提醒使用者这时read map已经不完整了,有追加的新键值:
![[sync.map追加后变化.png]]

sync.map追加后读写的过程
假如我现在要读写刚才追加的d的值,首先先去上面找,没找到,由于amended变为了true,我开始往下面找,找到了,这时misses加1(上面没有命中,下面命中了的数量):
![[sync.map追加后读写.png]]

sync.map dirty提升
当misses的值等于下面dirty中key的数量时,几乎每次读都要走下面的,于是上面的就可以不要了:
![[sync.map dirty提升-1.png]]

上面的不要了,dirty往上移:
![[sync.map dirty提升-2.png]]

dirty取代了原来m的位置,上面的amended置为false,结构体中的misses置为0:
![[sync.map dirty提升-3.png]]

如果要追加,会重建dirty,指针指向一个新的dirty:
![[sync.map dirty提升-4.png]]

sync.map的删除
  • 相比于查询、修改、新增,删除更麻烦
  • 删除可以分为正常删除和追加后删除
  • 提升后,被删key还需特殊处理

正常删除
没有追加的情况下,假如要删除d这个key,走上面的read map,将Pointter指针置为空,go的GC就会自动进行删除。
![[sync.map正常删除key.png]]

追加后删除
d是刚追加的情况下,要删除d这个key,首先还是走下面的dirty map,将Pointter指针置为空
![[sync.map追加后删除-1.png]]

后面遇到要提升dirty,提升上来后,下面要重建dirty map,是否要将d包含在其中,这是一个问题
![[sync.map追加后删除-2.png]]

sync.map采用的办法是,之前的d会指向expunged(被删除的),下面的dirty map重建的时候将不会在包含d
![[sync.map追加后删除-3.png]]

总结

  • map在扩容时会有并发问题
  • sync.Map使用了两个map,分离了扩容问题
  • 不会引发扩容的操作(查、改)使用read map
  • 可能引发扩容的操作(新增)使用dirty map

接口的底层原理

go隐式接口特点

  • 只要实现了接口的全部方法,就是自动实现接口
  • 可以在不修改代码的情况下抽象出新的接口

底层是如何表示接口的值

接口的简单用法:

type Phone interface {  call()  
}  
type Xiaomi struct {  Model string // 型号  
}  func (x Xiaomi) call() {  }  
func main() {  var phone Phone = Xiaomi{}  fmt.Println(phone)  
}

上面的phone的类型是Xiaomi,但是为什么phone.Model无法打印出Model这个成员参数,所以phone不是一个简单的转成了Xiaomi的类型,而是一个Phone接口的值,那接口的值底层是一个什么样的表示呢,找到runtime/runtime2.go文件:

type iface struct {  tab  *itab  data unsafe.Pointer  // 指向装载的结构体
}

继续看下itab这个指针接口体:

type itab struct {  inter *interfacetype  _type *_type  // 装载的结构体具体是一个什么类型hash  uint32 // copy of _type.hash. Used for type switches.  _     [4]byte  fun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.  这个类型实现了哪些方法
}
  • 接口数据使用runtime.iface表示
  • iface记录了数据的地址
  • iface中也记录了接口类型信息和实现的方法(方便类型断言)

类型断言

  • 类型断言是一个使用在接口值上的操作
  • 可以将接口值转换为其它类型值(实现或者兼容接口)
  • 可以配合switch进行类型判断

还是以上面的phone为例:

type Phone interface {  call()  
}  type CommunicationTools interface {  call()  
}  type Xiaomi struct {  Model string // 型号  
}  func (x Xiaomi) call() {  fmt.Println(x.Model)  
}  
func main() {  var phone Phone = Xiaomi{}  fmt.Println(phone)  c := phone.(CommunicationTools)  fmt.Println(c)  switch phone.(type) {  case CommunicationTools:  fmt.Println("ok")  }  }

结构体和指针实现接口

![[结构体和指针实现接口差异.png]]

用下面的示例代码来解释上面这个表格的意思:

type Phone interface {  call()  
}  type Xiaomi struct {  Model string // 型号  
}  type Huawei struct {  Model string // 型号  
}  
// Xiaomi结构体实现了Phone接口,go在编译时会自动让Xiaomi结构体指针也实现Phone接口  
func (x Xiaomi) call() {   fmt.Println(x.Model)  
}  
// 只有Huawei结构体指针实现了Phone接口   
func (x *Huawei) call() {  fmt.Println(x.Model)  
}  func main() {  var phone1 Phone = Xiaomi{}  var phone2 Phone = &Xiaomi{}  var phoneA Phone = Huawei{} // 报错了,因为Huawei结构体没有实现Phone接口  var phoneB Phone = &Huawei{}  fmt.Println(phone1, phone2)  fmt.Println(phoneA, phoneB)  
}

go build -gcflags -S main.go查看go在编译时自动给Xiaomi结构体指针实现的Phone接口:
![[查看go在编译时自动给结构体指针实现的接口.png]]

空接口的值及用途

空接口的值

  • runtime.eface结构体
  • 空接口底层不是普通接口
  • 空接口值可以承载任何数据

空接口的用途

  • 空接口的最大用途是作为任意类型的函数入参
  • 函数调用时,会新生成一个空接口,再传参

总结

  • Go的隐式接口更加方便系统的扩展和重构
  • 结构体和指针都可以实现接口
  • 空接口值可以承载任何类型的数据

nil,空结构体,空接口区分

nil

nil的变量定义位于builtin/builtin.go

// nil is a predeclared identifier representing the zero value for a  
// pointer, channel, func, interface, map, or slice type.  
var nil Type // Type must be a pointer, channel, func, interface, map, or slice type

a的零值和b的零值都为nil,但是它们两者之间并不能比较:

var a *int  
var b map[int]bool  
fmt.Println(a == nil) // true  
fmt.Println(b == nil) // true  fmt.Println(a == b) // error mismatched types *int and map[int]bool
  • nil是空,并不一定是“空指针”
  • nil是6种类型(pointer, channel, func, interface, map, or slice type)的“零值”
  • 每种类型的nil是不同的,无法比较

空结构体

  • 空结构体是Go中非常特殊的类型
  • 空结构体的值不是nil
  • 空结构体的指针也不是nil,但是都相同(zerobase)

空接口

var a interface{}  
fmt.Println(a == nil) // true  
var b *int  
a = b  
fmt.Println(b == nil) // true  
fmt.Println(a == nil) // false a接口底层的eface里面有了类型信息
  • 空接口不一定是“nil接口”
  • 两个属性都nil才是nil接口

总结

  • nil是多个类型的零值,或者空值
  • 空结构体的指针和值都不是nil
  • 空接口零值是nil,一旦有了类型信息就不是nil

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

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

相关文章

国内ChatGPT大数据模型

在中国&#xff0c;随着人工智能技术的迅猛发展&#xff0c;多个科技公司和研究机构已经开发出了与OpenAI的ChatGPT类似的大型语言模型。这些模型通常基于深度学习技术&#xff0c;尤其是Transformer架构&#xff0c;它们在大量的文本数据上进行训练&#xff0c;以理解和生成自…

每天五分钟掌握深度学习框架pytorch:本专栏说明

专栏大纲 专栏计划更新章节在100章左右&#xff0c;之后还会不断更新&#xff0c;都会配备代码实现。以下是专栏大纲 部分代码实现 代码获取 为了方便用户浏览代码&#xff0c;本专栏将代码同步更新到github中&#xff0c;所有用户可以读完专栏内容和代码解析之后&#xff0c…

Struts2:Action类的写法,推荐使用继承ActionSupport类的方法

文章目录 方法一&#xff1a;Action类是一个POJO类&#xff08;简单的Java类&#xff09;ActionDemo2.javastruts_demo2.xmlstruts.xml运行结果其他strutsz_demo1.xml 方法二&#xff1a;实现一个Action的接口ActionDemo2_2.javastruts_demo2.xml运行结果 推荐&#xff01;&…

SiteSpace 使用方法笔记

目录 介绍下载及安装准备工作知网 CNKI 文献分析数据准备数据转换新建项目图形处理 介绍 CiteSpace 是一个用于可视化和分析科学文献的工具。它可以从科学文献库中提取关键词、作者、机构和引用关系等信息&#xff0c;并将其可视化为图形网络。 一些使用案例 下载及安装 下载…

Redis从入门到精通(九)Redis实战(六)基于Redis队列实现异步秒杀下单

文章目录 前言4.5 分布式锁-Redisson4.5.4 Redission锁重试4.5.5 WatchDog机制4.5.5 MutiLock原理 4.6 秒杀优化4.6.1 优化方案4.6.2 完成秒杀优化 4.7 Redis消息队列4.7.1 基于List实现消息队列4.7.2 基于PubSub的消息队列4.7.3 基于Stream的消息队列4.7.4 基于Stream的消息队…

Golang单元测试和压力测试

一.单元测试 1.1 go test工具 go语言中的测试依赖go test命令。编写测试代码和编写普通的Go代码过程类似&#xff0c;并不需要学习新的语法&#xff0c;规则和工具。 go test命令是一个按照一定约定和组织的测试代码的驱动程序。在包目录内&#xff0c;所有以_test.go为后缀名的…

零代码编程:用kimichat打造一个最简单的window程序

用kimichat可以非常方便的自动生成程序代码&#xff0c;有些小程序可能会频繁使用&#xff0c;如果每次都在vscode中执行就会很麻烦。常用的Python代码&#xff0c;可以直接做成一个window程序&#xff0c;点击就可以打开使用&#xff0c;方便很多。 首先&#xff0c;把kimich…

Tokenize Anything via Prompting

SAM的延续&#xff0c;把SAM输出的token序列用来进行分类&#xff0c;分割和一个自然语言的decoder处理&#xff0c;但其实现在多模态的图像的tokenizer也几乎都是用VIT来实现的。一开始认为这篇文章可能是关于tokenize的&#xff0c;tokenize还是很重要的&#xff0c;后来看完…

JVM虚拟机(一)介绍、JVM组成、堆、栈、方法区/元空间、直接内存

目录 一、JVM 介绍1.1 为什么要学 JVM&#xff1f;1.2 JVM 是什么&#xff1f; 二、JVM 组成2.1 程序计数器2.2 Java堆1&#xff09;JVM 内存结构2&#xff09;Java 1.7 和 1.8 中堆的区别 2.3 Java虚拟机栈1&#xff09;虚拟机栈 和 栈帧2&#xff09;常见面试题 2.4 方法区/元…

搜索二维矩阵2 合并两个有序链表

240. 搜索二维矩阵 II - 力扣&#xff08;LeetCode&#xff09; class Solution { public:bool searchMatrix(vector<vector<int>>& matrix, int target) {int i matrix.size() - 1, j 0;while(i > 0 && j < matrix[0].size()){if(matrix[i][j…

基于wsl的Ubuntu20.04上安装桌面环境

在子系统Ubuntu20.04上安装桌面环境 1. 更换软件源 由于Ubuntu默认的软件源在国外&#xff0c;有时候后可能会造成下载软件卡顿&#xff0c;这里我们更换为国内的阿里云源&#xff0c;其他国内源亦可。 双击打开Ubuntu20.04 LTS图标&#xff0c;在命令行中输入 # 备份原来的软…

Java(二)面向对象进阶

目录 面向对象 多态性 向下转型 Object equals() toString() clone() finalize() Static 单例模式 代码块 final 抽象类与抽象方法(或abstract关键字&#xff09; 接口 接口的多态性 接口的默认方法 内部类 成员内部类 局部内部类 枚举类 实现接口的枚举类 …

网络安全流量平台_优缺点分析

FlowShadow&#xff08;流影&#xff09;&#xff0c;Ntm&#xff08;派网&#xff09;&#xff0c;Elastiflow。 Arkimesuricata&#xff0c;QNSMsuricata&#xff0c;Malcolm套件。 Malcolm套件优点&#xff1a;支持文件还原反病毒引擎&#xff08;clamav/yara&#xff09;…

IntelliJ IDEA 2024.1 更新亮点汇总:全面提升开发体验

IntelliJ IDEA 2024.1 更新亮点汇总&#xff1a;全面提升开发体验 文章目录 IntelliJ IDEA 2024.1 更新亮点汇总&#xff1a;全面提升开发体验摘要引言 IntelliJ IDEA 2024.1 的新增功能主要亮点全行代码完成 最终的支持 Java 22 功能新航站楼 贝塔编辑器中的粘滞线 人工智能助…

【SpringBoot3】SpringBoot入门

需求&#xff1a;使用 SpringBoot 开发一个web应用&#xff0c;浏览器发起请求 /hello后&#xff0c;给浏览器返回字符串 “hello world "。 步骤 ①. 创建Maven工程 ②. 导入spring-boot-stater-web起步依赖 <dependency> <groupId>org.springframework…

React18从入门到实战

文章目录 一、React环境的搭建二、项目文件的介绍&#xff08;1&#xff09;package.json&#xff0c;他是项目存放依赖包的地方&#xff0c;里面包括了一些项目核心包及下载的其他插件包&#xff08;2&#xff09;src文件夹是项目源码目录&#xff0c;平时开发页面就在其中&am…

Leetcode 581. 最短无序连续子数组

心路历程&#xff1a; 本以为这道题要用动态规划求解&#xff0c;因为题目中这几个关键字与动态规划太匹配了&#xff0c;结果想了半天也没发现dp(i)和dp(i-1)的递推关系。 这道题本意考察双指针的做法&#xff0c;也可以用排序后做比较的方式来做。 注意的点&#xff1a; 1…

修电机所需要的基本工具

等距式 模具 同心式模具 电机划线刀 压脚 千分尺 -----测量线径 钳形电流表------- 测量 空载 满载下的电流值 摇表&#xff0c; 测量线圈是否碰到外壳 指针式万用表 胶锤 整理线圈 绝缘纸和青稞纸&#xf…

服务器主机安全受到危害的严重性

为了让小伙伴们了解到服务器主机安全受到危害的严重性&#xff0c;以下详细说明一下&#xff1a;1. 数据泄露&#xff1a;如果服务器主机遭受攻击&#xff0c;攻击者可能会窃取敏感数据&#xff0c;如用户数据、商业秘密、机密文件等&#xff0c;导致数据泄露和商业机密的泄漏。…

设计模式深度解析:AI大模型下的策略模式与模板方法模式对比解析

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》《MYSQL应用》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 策略模式与模板方法模式对比解析 文章目录 &#x1f31f;引言&#x1f31f;Part 1:…