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

news2025/1/31 8:26:58

空结构体的底层原理

基本类型的字节数

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.coloradmin.cn/o/1575590.html

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

相关文章

jdk目录结构

jdk目录详解 JDK(Java Development Kit&#xff0c;Java开发包&#xff0c;Java开发工具)是一个写Java的applet和应用程序的程序开发环境。它由一个处于操作系统层之上的运行环境还有开发者 编译&#xff0c;调试和运行用Java语言写的applet和应用程序所需的工具组成。 JDK(J…

京东云轻量云主机8核16G配置租用价格1198元1年、4688元三年

京东云轻量云主机8核16G服务器租用优惠价格1198元1年、4688元三年&#xff0c;配置为8C16G-270G SSD系统盘-5M带宽-500G月流量&#xff0c;华北-北京地域。京东云8核16G服务器活动页面 yunfuwuqiba.com/go/jd 活动链接打开如下图&#xff1a; 京东云8核16G服务器优惠价格 京东云…

C语言基础语法-教案19(预处理-宏定义)

最近给大家争取到一个 深夜福利 保证你在深夜手机刷到 嘎嘎香~ 那就是 官方授权 大流量卡 缺点&#xff1a;月租太便宜 185GB~ 100分钟通话时长~ 长期套餐~ 畅想自由的气息 流量自由的同时还拥有超长通话&#xff0c;而且免费领取。 名额有限&#xff0c;咱们废话不…

流程表单平台优势明显,助力企业流程化办公!

要想提升办公效率&#xff0c;实现流程化办公&#xff0c;可以了解低代码技术平台、流程表单平台的应用价值和优势特点。在科技越来越发达和先进的今天&#xff0c;采用专业的平台和软件可以为企业带来超前的发展态势&#xff0c;创造更多市场价值。流辰信息为广大用户提供的流…

闲鱼订阅监控/上新提醒

以前闲鱼推出过一款服务&#xff0c;叫做闲鱼助手&#xff0c;帮助用户快速显示最新发布的信息。当时我也开发过一款闲鱼助手的工具。 写一个闲鱼助手的助手工具_闲鱼助手源码-CSDN博客 但是时间并不是很长&#xff0c;该功能被取消了。 最近不知道闲鱼从哪个版本开始&#x…

数字三角形(线性dp)-java

线性DP是动态规划问题中的一类问题&#xff0c;指状态之间有线性关系的动态规划问题。 文章目录 前言 一、数字三角形问题 二、算法思路 三、使用步骤 1.代码如下&#xff08;示例&#xff09;&#xff1a; 2.读入数据 3.代码运行结果 总结 前言 线性DP是动态规划问题中的一类…

工业和信息化部教育与考试中心颁发的证书有哪些?含金量如何?怎么考取?​

近期有很多网友朋友们对工业和信息化部教育与考试中心颁发的证书是否是真的证书&#xff0c;是否国家认可&#xff0c;是否全国通用&#xff0c;含金量如何&#xff1f;如何查询真假&#xff0c;以及如何报考等等相关问题有疑问&#xff0c;所以今天给大家在这里一一解答。 添加…

2.AK/SK鉴权

目录 什么是AK/SK AK/SK使用机制 时序图 什么是AK/SK 在云服务中&#xff0c;AK&#xff08;Access Key ID&#xff09;和SK&#xff08;Secret Access Key&#xff09;是访问云服务API的关键凭证对&#xff0c;主要用于身份验证和授权。AK是用户访问云服务的身份标识&…

AJ65SBTB1-32D1 三菱cc-link远程高速输入模块。

AJ65SBTB1-32D1 三菱cc-link远程高速输入模块 AJ65SBTB1-32D1用户手册, AJ65SBTB1-32D1外部连接。 AJ65SBTB1-32D1参数说明&#xff1a;DC输入32点 DC24V 5mA&#xff1b;响应时间0.2ms&#xff1b;32点1公共端&#xff1b;1线式&#xff1b;正/负公共端共用&#xff1b;端子排…

[StartingPoint][Tier1]Responder

Important 由于靶机IP是动态的,所以这里需要手动解析 # echo "<靶机IP> unika.htb">>/etc/hosts //10.10.16.59/testshare到底是什么? SMB&#xff08;Server Message Block&#xff09;是一种用于在计算机之间共享文件、打印机和其他资源的网络协议&…

1995-2021年各省分品种能源产量和消费量数据

1995-2021年各省分品种能源产量和消费量数据 1、时间&#xff1a;1995-2021年 2、来源&#xff1a;能源统计年鉴、各省年鉴 3、指标&#xff1a;能源消费总量、煤炭消费量、焦炭消费量、原油消费量、汽油消费量、煤油消费量、柴油消费量、燃料油消费量、天然气消费量、电力消…

Java入门基础知识第六课(超基础,超详细)——循环结构

前面二白讲了选择结构相关知识&#xff0c;主要是if选择结构和swich选择结构&#xff0c;这次咱们讲一下循环结构&#xff0c;主要是while、do-while、for这三种循环结构 一、while循环结构 语法&#xff1a; 初始值代码; while(循环条件){ 循环操作代码块; 迭代代码; } 执行…

产品经理考个PMP有用吗?

产品经理考PMP认证考试是否有用&#xff0c;这个问题答案是肯定的。项目管理作为一项通用管理技能&#xff0c;如果产品经理能够掌握&#xff0c;对产品设计和管理工作是十分有益的。 产品经理是企业中专门负责产品管理的职位&#xff0c;其负责明确产品需求和产品设计&#x…

关于goto的一点说明

1、goto的label是会被顺序执行的 如下例所示&#xff0c;error也会被执行。 #include <iostream>void test(bool flag) {if (flag){printf("--------------- yes.\n");}else {goto error;}error:printf("error.\n"); }int main() {std::cout <&l…

pytest教程-23-指定用例执行顺序插件-pytest-ordering

领取资料&#xff0c;咨询答疑&#xff0c;请➕wei: June__Go 上一小节我们学习了pytest用例依赖插件-pytest-dependency,本小节我们讲解一下pytest指定用例执行顺序插件-pytest-ordering。 pytest在执行用例的时候&#xff0c;默认是按照文件中用例的先后顺序执行&#xff…

达梦数据库记录

1.计算日期差 SELECT DATEDIFF(day,sysdate(), 2024-06-01) 2.出现HJ_BUF_GLOBAL_SIZE设置不当造成应用报错的问题&#xff0c;详细信息如下&#xff1a; dm.jdbc.driver.DMException: 超出全局hash join空间,适当增加HJ_BUF_GLOBAL_SIZEat dm.jdbc.driver.DBError.throwExce…

opencv图像处理技术(阈值处理与图像平滑)

进行图像处理时&#xff0c;常常需要对图像进行预处理以提取所需的信息或改善图像质量。阈值处理和图像平滑是两种常见的预处理技术。 阈值处理 阈值处理是一种图像分割技术&#xff0c;其基本思想是将图像中的像素值与一个或多个预先设定的阈值进行比较&#xff0c;根据比较…

VIM支持C/C++/Verilog/SystemVerilog配置并支持Win/Linux环境的配置

作为一个芯片公司打杂人口&#xff0c;同时兼数字IC和软件&#xff0c;往往需要一个皮实耐打上天入地的编辑器… 一、先附上github路径&#xff0c;方便取走 git clone gitgithub.com:qqqw4549/vim_config_c_verilog.git 二、效果展示 支持ctrl]函数/模块跳转&#xff0c;支持…

LeetCode-46. 全排列【数组 回溯】

LeetCode-46. 全排列【数组 回溯】 题目描述&#xff1a;解题思路一&#xff1a;回溯。回溯三部曲解题思路二&#xff1a;0解题思路三&#xff1a;0 题目描述&#xff1a; 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案…

基于HIL+RCP的三相整流电路实验

今天给大家分享的是利用easygo netbox的模型文件&#xff0c;仿真三相整流的电路实验。 首先&#xff0c;打开Desksim软件&#xff0c;载入这个模型文件。然后切换到User Interface界面&#xff0c;自定义模型的监控界面。 我们拖入chart&#xff0c;就可以选择观测模型的三相电…