Golang复习

golang的特点

Golang 针对并发进行了优化,并且在规模上运行良好

自动垃圾收集明显比 Java 或 Python 更有效,因为它与程序同时执行

golang数据类型

基本数据类型(值类型)

  • 布尔类型

  • 数字类型

    • 整型

      根据有符号分为:有符号、无符号类型;

      根据占据的空间分为:8,16,32,64;

    • 浮点型

      float32:32位浮点型数;float64:64位浮点型数;

      complex64:32位实数和虚数;complex128:64位实数和虚数;

    • 其他

      byte:类似于uint8,代表了 ASCII 码的一个字符

      rune:类似于int32,表示的是一个 Unicode字符

      uint:长度取决于 CPU,如果是32位CPU就是4个字节,如果是64位就是8个字节

      uintptr:无符号整型,用于存放一个指针

  • 字符串类型

  • 数组类型

  • 结构体类型

引用数据类型

  • 指针类型

  • Channel类型

  • 切片类型

  • Map类型

  • 函数类型

  • 接口类型

make和new

他们都是分配内存空间的内置函数

make

  • make 用于创建切片、映射和通道(slice、map、channel)等引用类型的数据结构。

  • 它返回一个已初始化并且可以使用的引用类型变量,通常用于创建动态大小的数据结构。

new

  • new 主要用于创建值类型的变量,如结构体、整数、浮点数等,而不是引用类型。

  • new 用于创建并返回一个指向新分配的零值的指针。

浅拷贝,深拷贝

深拷贝:

拷贝的是数据本身,创造一个新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值

实现深拷贝的方式:

  • 值类型:对于 Go 中的基本数据类型(如整数、浮点数、字符串、结构体,数组等),赋值或传递参数时会进行深拷贝。这意味着创建一个新的值,而不是共享数据。

    a := 10
    b := a // 深拷贝arr1 := [3]int{1, 2, 3}
    arr2 := arr1 // 深拷贝
    
  • 使用copy赋值的数据是深拷贝

    slice1 := []int{1, 2, 3}
    slice2 := []int{0, 0, 0}
    copy(slice2, slice1)
    slice2[0] = 10
    fmt.Printf("slice1:%v\n", slice1)	//slice1:[1 2 3]
    fmt.Printf("slice2:%v\n", slice2)	//slice2:[10 2 3]
    

浅拷贝:

拷贝的是数据地址,只复制指向的对 象的指针,此时新对象和老对象指向的内存地址是一样的,新对象值修改时老对象也会变化

实现浅拷贝的方式

  • 引用数据类型默认赋值操作就是浅拷贝:slice2 := slice1

    arr1 := []int{1, 2, 3}
    arr2 := arr1 // 浅拷贝
    arr2[0] = 100
    fmt.Printf("slice1:%v\n", arr1)	//slice1:[100 2 3]
    fmt.Printf("slice2:%v\n", arr2)	//slice2:[100 2 3]
    

接口

接口是什么

  1. interface 是方法声明的集合

  2. 任何类型的对象实现了在interface 中声明的全部方法,则表明该类型实现了该接口

  3. interface可以作为一种数据类型,实现了该接口的任何对象都可以给对应的接口类型变量赋值

举实例,

某种类型可以比较吗

可以比较的数据结构:

  1. 基本数据类型:整数(int、int8、int16、int32、int64)、浮点数(float32、float64)、复数(complex64、complex128)、布尔值(bool)、字符串(string)等基本类型都可以进行比较
  2. 数组:数组是值类型,如果数组的元素类型是可比较的,则整个数组可以进行比较。例如,[3]int 和 [3]int 可以进行比较
  3. 结构体:结构体是用户自定义的复合数据类型,如果结构体的字段都是可比较的,则结构体可以进行比较
  4. 指针:指针类型可以进行比较,但比较的是指针的地址值
  5. 接口:接口类型可以进行比较,但比较的是接口的动态类型和动态值。只有当这两个变量的动态类型和动态值都相等的时候,才是相等的
  6. 通道:通道类型是可比较类型。当一个通道值被赋给另一个通道值后,这两个通道值将共享相同的底层部分。 换句话说,这两个通道引用着同一个底层的内部通道对象。 比较这两个通道的结果为true

不可以比较的数据结构:

  1. 切片:切片是引用类型,不能直接进行比较。你可以比较切片是否为nil,但不能比较两个切片的内容是否相同
  2. 映射:映射也是引用类型,不能直接进行比较。你可以比较映射是否为nil,但不能比较两个映射的内容是否相同
  3. 函数:函数类型不能进行比较

channel

channel主要用于进程内各goroutine间的通信

数据结构

type hchan struct {qcount   uint           // 当前队列中剩余元素个数dataqsiz uint           // 环形队列长度,即可以存放的元素个数buf      unsafe.Pointer // 环形队列指针elemsize uint16         // 每个元素的大小closed   uint32         // 标识关闭状态elemtype *_type         // 元素类型sendx    uint           // 队列下标,指示元素写入时存放到队列中的位置recvx    uint           // 队列下标,指示元素从队列的该位置读出recvq    waitq          // 等待读消息的goroutine队列sendq    waitq          // 等待写消息的goroutine队列lock 	 mutex          // 互斥锁,chan不允许并发读写
}
环形队列:

环形队列作为其缓冲区,队列长度是创建channel时候指定的

在这里插入图片描述

等待队列

从channel读数据,如果channel缓冲区为空或者没有缓冲区,当前goroutine会被阻塞。
向channel写数据,如果channel缓冲区已满或者没有缓冲区,当前goroutine会被阻塞。

被阻塞的goroutine将会挂在channel的等待队列中:

  • 因读阻塞的goroutine会被向channel写入数据的goroutine唤醒;
  • 因写阻塞的goroutine会被从channel读数据的goroutine唤醒;

channel读写过程

创建channel

创建channel的过程实际上是初始化hchan结构。其中类型信息和缓冲区长度由make语句传入,buf的大小则与元素大小和缓冲区长度共同决定。

向一个channel中写数据过程如下:
  1. 如果等待接收队列recvq不为空,说明缓冲区中没有数据或者没有缓冲区,此时直接从recvq取出G,并把数据写入,最后把该G唤醒,结束发送过程;
  2. 如果缓冲区中有空余位置,将数据写入缓冲区,结束发送过程;
  3. 如果缓冲区中没有空余位置,将待发送数据写入G,将当前G加入sendq,进入睡眠,等待被读goroutine唤醒;
从一个channel读数据过程如下:
  1. 如果等待发送队列sendq不为空,且没有缓冲区,直接从sendq中取出G,把G中数据读出,最后把G唤醒,结束读取过程;
  2. 如果等待发送队列sendq不为空,此时说明缓冲区已满,从缓冲区中首部读出数据,把G中数据写入缓冲区尾部,把G唤醒,结束读取过程;
  3. 如果等待发送队列sendq为空,但缓冲区中有数据,则从缓冲区取出数据,结束读取过程;
  4. 如果等待发送队列sendq为空,并且缓冲区中没有数据,将当前goroutine加入recvq,进入睡眠,等待被写goroutine唤醒;
关闭channel会发生什么
  • 把recvq中的G全部唤醒,本该写入G的数据位置为nil。
  • 把sendq中的G全部唤醒,但这些G会panic。

特点:

  • 关闭通道后,已经在通道中的数据仍然可以被读取,直到通道中的数据全部被读取完毕。之后再次去读取,读取操作将不会阻塞,而是立即返回其元素类型的零值
  • 关闭通道后,向管道中写数据会发生panic

channel缓冲区的特点

同步与非同步:

无缓冲的 channel 是同步的,有缓冲的 channel 是非同步的,缓冲满时发送阻塞

channel无缓冲时,发送阻塞直到数据被接收,接收阻塞直到读到数据;
channel有缓冲时,当缓冲满时发送阻塞,当缓冲空时接收阻塞。

nil:

如果给一个nil的 channel 发送数据,会造成永远阻塞。
如果从一个nil的 channel 中接收数据,会造成永久阻塞。

panic:

关闭值为nil的channel
关闭已经被关闭的channel
向已经关闭的channel写数据

进程,线程,协程的区别

  • 进程:进程是具有一定独立功能的程序,进程是系统资源分配和调度的最小单位。每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。
  • 线程:线程是进程的一个实体,线程是内核态,而且是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。
  • 协程:协程是一种用户态的轻量级线程,协程的调度完全是由用户来控制的。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

切片

数据结构

Slice依托数组实现,底层数组对用户屏蔽,在底层数组容量不足时可以实现自动重分配并生成新的Slice。

type slice struct {array unsafe.Pointer	//array指向底层数组len   int				//len代表切片长度cap   int				//cap是底层数据的长度
}

Slice的扩容

  • 使用append向Slice追加元素时,如果Slice空间不足,将会触发Slice扩容,扩容实际上是重新分配一块更大的内存,将原Slice数据拷贝进新Slice,然后返回新Slice,扩容后再将数据追加进去。
  • 扩容操作只关心容量,会把原Slice数据拷贝到新Slice,追加数据由append在扩容结束后完成
append扩容过程
  1. 假如Slice容量够用,则将新元素追加进去,Slice.len++,返回原Slice
  2. 原Slice容量不够,则将Slice先扩容,扩容后得到新Slice
  3. 将新元素追加进新Slice,Slice.len++,返回新的Slice。
扩容容量规则

1.8版本之前

  • 如果原Slice容量小于1024,则新Slice容量将扩大为原来的2倍;
  • 如果原Slice容量大于等于1024,则新Slice容量将扩大为原来的1.25倍;

上面描述的是一次追加一个元素 append(a,1),如果是一次追加多个元素append(a,1,2,3),容量扩容到大于切片长度的最小的偶数

a := make([]int, 0)
a = append(a, 1, 2, 3, 4, 5)
fmt.Printf("len(a):%v,cap(a):%v\n", len(a), cap(a)) 
//len(a):5,cap(a):6
切片append之后还跟原来的一样吗

切片添加追加一个元素之后,如果切片的cap不会发生扩容,那么底层指向的还是原来的那个数组

// 情况一:切片扩容后仍然指向原数组
originalSlice := make([]int, 0, 5)
originalSlice = append(originalSlice, 1)
originalSlice = append(originalSlice, 2)
originalSlice = append(originalSlice, 3)modifiedSlice := originalSlice// 添加元素到切片
modifiedSlice = append(modifiedSlice, 4)fmt.Println("Original Slice:", originalSlice)
fmt.Println("Modified Slice:", modifiedSlice)
fmt.Printf("Original Slice Address: %p\n", &originalSlice[0])
fmt.Printf("Modified Slice Address: %p\n", &modifiedSlice[0])//运行结果
//Original Slice: [1 2 3]
//Modified Slice: [1 2 3 4]
//Original Slice Address: 0xc00000e390
//Modified Slice Address: 0xc00000e390

如果切片的cap发生扩容,那么底层指向的已经不是原来那个数组,而是对数组进行了拷贝

originalSlice := make([]int, 0, 2)
originalSlice = append(originalSlice, 1)
originalSlice = append(originalSlice, 2)modifiedSlice := originalSlice// 添加元素到切片
modifiedSlice = append(modifiedSlice, 3)fmt.Println("Original Slice:", originalSlice)
fmt.Println("Modified Slice:", modifiedSlice)
fmt.Printf("Original Slice Address: %p\n", &originalSlice[0])
fmt.Printf("Modified Slice Address: %p\n", &modifiedSlice[0])//运行结果
//Original Slice: [1 2]
//Modified Slice: [1 2 3]
//Original Slice Address: 0xc00001c0c0
//Modified Slice Address: 0xc0000141e0

Slice线程不安全

Slice底层结构并没有使用加锁等方式,不支持并发读写,所以并不是线程安全的,使用多个goroutine 对类型为 slice 的变量进行操作,每次输出的值大概率都不会一样,与预期值不一致;slice在并发执行中不会报错,但是数据会丢失

map

数据结构

hmap

Golang的map使用哈希表作为底层实现,一个哈希表里可以有多个哈希表节点,也即bucket,而每个bucket就保存了map中的一个或一组键值对。

type hmap struct {count     int // 当前保存的元素个数...B         uint8...buckets    unsafe.Pointer // bucket数组指针,数组的大小为2^B...
}

一个拥有4个bucket的map:

bucket
type bmap struct {tophash [8]uint8 //存储哈希值的高8位data    byte[1]  //key value数据:key/key/key/.../value/value/value...overflow *bmap   //溢出bucket的地址
}
  • tophash:是长度为8的数组,哈希值低位相同的键存入当前bucket时,会将哈希值的高位存放到该数组中,以方便后续匹配

  • data区存放的是key-value数据,存放顺序是key/key/key/…value/value/value,如此存放是为了节省字节对齐带来的空间浪费。

  • overflow 指针指向的是下一个bucket,据此将所有冲突的键连接起来。

哈希冲突

当有两个或以上数量的键被哈希到了同一个bucket时,我们称这些键发生了冲突

Go使用链地址法来解决键冲突。由于每个bucket可以存放8个键值对,所以同一个bucket存放超过8个键值对时就会再创建一个键值对,用类似链表的方式将bucket连接起来。

bucket数据结构指示下一个bucket的指针称为overflow bucket,意为当前bucket盛不下而溢出的部分。事实上哈希冲突并不是好事情,它降低了存取效率

负载因子

负载因子用于衡量一个哈希表冲突情况,公式为:

负载因子 = 键数量/bucket数量

例如,对于一个bucket数量为4,包含4个键值对的哈希表来说,这个哈希表的负载因子为1

哈希表需要将负载因子控制在合适的大小,超过其阀值需要进行rehash,也即键值对重新组织:

  • 哈希因子过小,说明空间利用率低
  • 哈希因子过大,说明冲突严重,存取效率低

每个哈希表的实现对负载因子容忍程度不同,比如Redis实现中负载因子大于1时就会触发rehash,而Go则在在负载因子达到6.5时才会触发rehash,因为Redis的每个bucket只能存1个键值对,而Go的bucket可能存8个键值对,所以Go可以容忍更高的负载因子。

渐进式扩容

扩容的前提条件

为了保证访问效率,当新元素将要添加进map时,都会检查是否需要扩容,扩容实际上是以空间换时间的手段。
触发扩容的条件有二个:

  1. 负载因子 > 6.5时,也即平均每个bucket存储的键值对达到6.5个。
  2. overflow数量 > 2^15时,也即overflow数量超过32768时。
增量扩容
  • 当负载因子过大时,就先建一个bucket,新的bucket长度时原来的2倍,然后将旧的bucket数据搬迁到新的bucket
  • 考虑到map可能存储数以亿计的key-value,一次搬迁将会造成比较大的延时,go采用逐步搬迁策略,每次访问map时都会触发一次搬迁,每次搬迁2个键值对
  • hmap数据结构中oldbuckets成员指身原bucket,而buckets指向了新申请的bucket。新的键值对被插入新的bucket中。后续对map的访问操作会触发迁移,将oldbuckets中的键值对逐步的搬迁过来。当oldbuckets中的键值对全部搬迁完毕后,删除oldbuckets。
  • 数据搬迁过程中原bucket中的键值对将存在于新bucket的前面,新插入的键值对将存在于新bucket的后面。
等量扩容

所谓的等量扩容并不是扩大容量。buckets数量不变,重新做一遍类似于增量扩容的搬迁动作,把松散的键值对重新排列一次,使得bucket的使用效率更高,进而保证更快的存取

查找过程

  1. 根据key值算出哈希值
  2. 取哈希值低位与hmap.B取模确定bucket位置
  3. 取哈希值高位在tophash数组中查询
  4. 如果tophash[i]中存储值也哈希值相等,则去找到该bucket中的key值进行比较
  5. 当前bucket没有找到,则继续从下个overflow的bucket中查找。
  6. 如果当前处于搬迁过程,则优先从oldbuckets查找

插入过程

  1. 根据key值算出哈希值
  2. 取哈希值低位与hmap.B取模确定bucket位置
  3. 查找该key是否已经存在,如果存在则直接更新值
  4. 如果没找到将key,将key插入

无序遍历

map在遍历时,并不是从固定的0号bucket开始遍历的,每次遍历,都会从一个随机值序号的bucket,然后再从该桶中随机选择一个单元格(cell)开始遍历

线程不安全

在Go语言中,普通的map(即map数据类型)是非线程安全的。这意味着在多个goroutine之间并发访问和修改同一个map时,可能会导致竞态条件和未定义的行为。

为了在多线程或多goroutine环境中安全地使用map,你有以下几种选项:

  1. 使用sync.Mutex进行同步:你可以在每次访问map之前使用sync.Mutex进行加锁和解锁操作,以确保一次只有一个goroutine能够访问map。

    m = make(map[keyType]valueType)
    var mu sync.Mutex
    // 在读取或写入map之前加锁
    mu.Lock()
    m[key] = value
    mu.Unlock()
    
  2. 使用sync.Map:Go语言提供了sync.Map类型,它是一种并发安全的map实现,可以安全地在多个goroutine之间进行读取和写入操作。

    var m sync.Map
    // 写入数据
    m.Store(key, value)
    // 读取数据
    val, ok := m.Load(key)
    

某种数据类型线程安全吗

GMP调度

G、M、P分别是什么,分别有多少数量

  • G(Goroutine):即Go协程,每个go关键字都会创建一个协程。
  • M(Machine):工作线程,在Go中称为Machine,数量对应真实的CPU数(真正干活的对象)。
  • P(Processor):处理器(Go中定义的一个摡念,非CPU),包含运行Go代码的必要资源,用来调度 G 和 M 之间的关联关系,其数量可通过 GOMAXPROCS() 来设置,默认为核心数。

M必须拥有P才可以执行G中的代码,P含有一个包含多个G的队列,P可以调度G交由M执行。

数量,调度过程:P维护调度(注意专业词汇)(了解G的生命周期)

GMP调度流程

  1. 创建G:通过go关键字来创建一个goroutine
  2. 保存G:新创建的G会先保存在P的本地队列中,如果P的本地队列已经满了就会保存在全局的队列中
  3. 唤醒或者新建M:M执行任务进入循环调度
  4. M获取G:M会从P的本地队列获取G来执行,如果P的本地队列为空,则从全局队列获取G,如果全局队列也为空,则会从另一个本地队列偷取一半数量的G(这种从其它P偷取的方式称之为work stealing
  5. M调度和执行G:M调用G.func()函数执行G,如果M在执行G的过程中发生系统调用阻塞,会阻塞G和M,P会和当前的M解绑,并寻找新的M,如果没有空闲的M就会新建一个M,接管正在阻塞G所属的P,继续执行P中其余的G,这种阻塞后释放P的方式称之为hand off
  6. 清理现场:M执行完G之后,清理现场,重新进入调度循环,(将M上运行的goroutine切换为G0,G0负责调度时候协程的切换)

垃圾回收机制

垃圾回收算法

  • 引用计数:对每个对象维护一个引用计数,当引用该对象的对象被销毁时,引用计数减1,当引用计数器为0时回收该对象。
    • 优点:对象可以很快地被回收,不会出现内存耗尽或达到某个阀值时才回收。
    • 缺点:不能很好地处理循环引用,而且实时维护引用计数,也有一定的代价。
    • 代表语言:Python、PHP、Swift
  • 标记-清除:从根变量开始遍历所有引用的对象,引用的对象标记为”被引用”,没有被标记的进行回收。
    • 优点:解决了引用计数的缺点。
    • 缺点:需要STW,即要暂时停掉程序运行。
    • 代表语言:Golang(其采用三色标记法)
  • 分代收集:按照对象生命周期长短划分不同的代空间,生命周期长的放入老年代,而短的放入新生代,不同代有不同的回收算法和回收频率。
    • 优点:回收性能好
    • 缺点:算法复杂
    • 代表语言: JAVA

Golang垃圾回收原理

内存标记
  • span区域中,位图allocBits表示每个内存块的分配情况(已被分配的内存块标记为1,未被分配的内存块标记0),位图gcmarkBits用于标记内存块被引用情况(已被对象引用的内存块标记为1,未被对象引用的内存块标记0)
  • allocBits和gcmarkBits数据结构是完全一样的,标记结束就是内存回收,回收时将allocBits指向gcmarkBits,则代表标记过的才是存活的,gcmarkBits则会在下次标记时重新分配内存,非常的巧妙
三色标记法的三种状态
  • 灰色:对象还在标记队列中等待
  • 黑色:对象已被标记,gcmarkBits对应的位为1,该对象不会再本次GC中被清除
  • 白色:对象未被标记,gcmarkBits对应的位为0,该对象将会再本次GC中被清除
三色标记的过程
  1. 每次新创建的对象,默认的颜色都是标记为白色
  2. 每次GC回收开始,会从根节点开始遍历所有对象,把遍历到的对象从白色集合放入灰色集合中
  3. 遍历灰色集合,将灰色对象引用的对象从白色集合中放入灰色集合,并将此灰色对象放入黑色集合
  4. 重复上一个步骤,直到灰色对象中没有任何对象
  5. 回收所有的白色对象

缺点是需要STW,程序出现卡顿

屏障机制

插入写屏障(对象被引用时触发的机制):

不存在黑色对象引用到白色对象,当黑色节点新增了白色节点的引用时,将对应的白色节点改为灰色(满足强三色不变式)

删除写屏障(对象被引用时触发的机制):

黑色节点允许引用白色节点,但是该白色节点有其他灰色节点间接的引用(满足弱三色不变式)

混合写屏障
  1. GC开始将栈上的对象全部扫描并标记为黑色
  2. GC期间,任何在栈上创建的新对象,均为黑色。
  3. 被删除的对象标记为灰色。
  4. 被添加的对象标记为灰色。

满足弱三色不变式

总结

GoV1.3- 普通标记清除法,整体过程需要启动STW,效率极低。

GoV1.5- 三色标记法, 堆空间启动写屏障,栈空间不启动,全部扫描之后,需要重新扫描一次栈(需要STW),效率普通

GoV1.8-三色标记法,混合写屏障机制, 栈空间不启动,堆空间启动。整个过程几乎不需要STW(在标记准备每个栈单独暂停),效率较高

defer

defer语句用于延迟函数的调用,每次defer都会把一个函数压入栈中,函数返回前再把延迟的函数取出并执行。

特点

  • 延迟函数的参数在defer语句出现时就已经确定下来了
  • 延迟函数执行按后进先出顺序执行,即先出现的defer最后执行
  • 延迟函数可能操作主函数的具名返回值

使用场景

  • 释放资源:关闭数据库连接,关闭文件
  • 流程控制
  • 异常处理
  • 与recover配合,消除panic

defer recover painc执行顺序

error

错误处理:

可预测,不可预测,panic(执行过程,里面涉及到recover)

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

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

相关文章

[NLP]LLM---FineTune自己的Llama2模型

一 数据集准备 Let’s talk a bit about the parameters we can tune here. First, we want to load a llama-2-7b-hf model and train it on the mlabonne/guanaco-llama2-1k (1,000 samples), which will produce our fine-tuned model llama-2-7b-miniguanaco. If you’re …

华为云API对话机器人CBS的魅力—实现简单的对话操作

云服务、API、SDK,调试,查看,我都行 阅读短文您可以学习到:人工智能AI智能的问答管理、全面的对话管理、高效训练部署 1.IntelliJ IDEA 之API插件介绍 API插件支持 VS Code IDE、IntelliJ IDEA等平台、以及华为云自研 CodeArts …

每日刷题-3

目录 一、选择题 二、编程题 1、计算糖果 2、进制转换 一、选择题 1、 解析:在C语言中,以0开头的整数常量是八进制的,而不是十进制的。所以,0123的八进制表示相当于83的十进制表示,而123的十进制表示不变。printf函数…

ASP.NET Core IOC容器

//IOC容器支持依赖注入{ServiceCollection serviceDescriptors new ServiceCollection();serviceDescriptors.AddTransient<IMicrophone, Microphone>();serviceDescriptors.AddTransient<IPower, Power>();serviceDescriptors.AddTransient<IHeadphone, Headp…

【SQL应知应会】索引 • Oracle版:B-树索引;位图索引;函数索引;单列与复合索引;分区索引

欢迎来到爱书不爱输的程序猿的博客, 本博客致力于知识分享,与更多的人进行学习交流 本文免费学习,自发文起3天后,会收录于SQL应知应会专栏,本专栏主要用于记录对于数据库的一些学习,有基础也有进阶,有MySQL也有Oracle 索引 • MySQL版 前言一、Oracle索引1.索引概述及分类…

upload-labs1-17思路

1 直接写一个php文件测试一下&#xff0c;发现弹窗不让上传 原理很简单&#xff0c;就是把后缀名拿出来过滤一遍&#xff0c;而白名单就是弹窗的这三个 解决方法&#xff1a; 因为这是在前端防御的一个手段&#xff0c;所以直接在浏览器设置上禁用js就行了&#xff1a; 也可…

微服务-OpenFeign基本使用

一、前言 二、OpenFeign基本使用 1、OpenFeign简介 OpenFeign是一种声明式、模板化的HTTP客户端&#xff0c;它使得调用RESTful网络服务变得简单。在Spring Cloud中使用OpenFeign&#xff0c;可以做到像调用本地方法一样使用HTTP请求访问远程服务&#xff0c;开发者无需关注…

stm32 学习笔记:GPIO输出

一、GPIO简介 引脚电平 0-3.3V,部分可容忍5V&#xff0c;对输出而言最大只能输出3.3V, 只要可以用高低电平来控制的地方&#xff0c;都可以用GPIO来完成&#xff0c;如果控制的功率比较大的设备&#xff0c;只需加入驱动电路即可 GPIO 通用输入输出口&#xff0c;可配置为 8种 …

2023国赛数学建模E题思路代码 - 黄河水沙监测数据分析

# 1 赛题 E 题 黄河水沙监测数据分析 黄河是中华民族的母亲河。研究黄河水沙通量的变化规律对沿黄流域的环境治理、气候变 化和人民生活的影响&#xff0c; 以及对优化黄河流域水资源分配、协调人地关系、调水调沙、防洪减灾 等方面都具有重要的理论指导意义。 附件 1 给出了位…

【实训】“宅急送”订餐管理系统(程序设计综合能力实训)

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 前言 大一小学期&#xff0c;我迎来了人生中的第一次实训…

2023/9/8 -- C++/QT

作业 1> 自行封装一个栈的类&#xff0c;包含私有成员属性&#xff1a;栈的数组、记录栈顶的变量 成员函数完成&#xff1a;构造函数、析构函数、拷贝构造函数、入栈、出栈、清空栈、判空、判满、获取栈顶元素、求栈的大小 02stack.h: #ifndef __02STACK_H__ #define __…

无涯教程-JavaScript - IMSIN函数

描述 IMSIN函数以x yi或x yj文本格式返回复数的正弦。复数的正弦为- $$\sin(x yi) \sin(x)\cosh(y) \cos(x)\sin(y)i $$ 语法 IMSIN (inumber)争论 Argument描述Required/OptionalInumberA Complex Number for which you want the sine.Required Notes Excel中的复数仅…

活动预告 | 龙智、紫龙游戏与JFrog专家将出席龙智DevSecOps研讨会,探讨企业大规模开发创新

2023年9月8日&#xff08;周五&#xff09;下午13:30-19:45&#xff0c;龙智即将携手Atlassian与JFrog在上海共同举办主题为“大规模开发创新&#xff1a;如何提升企业级开发效率与质量”的线下研讨会。 在此次研讨会上&#xff0c;龙智高级咨询顾问、Atlassian认证专家叶燕秀…

【List篇】使用Arrays.asList生成的List集合,操作add方法报错

早上到公司&#xff0c;刚到工位&#xff0c;测试同事就跑来说"功能不行了&#xff0c;报服务器异常了&#xff0c;咋回事";我一脸蒙&#xff0c;早饭都顾不上吃&#xff0c;要来了测试账号复现了一下&#xff0c;然后仔细观察测试服务器日志&#xff0c;发现报了一个…

K8S 基础概念学习

1.K8S 通过Deployment 实现滚动发布&#xff0c;比如左边的ReplicatSet 的 pod 中 是V1版本的镜像&#xff0c;Deployment通过 再启动一个 ReplicatSet 中启动 pod中 镜像就是V2 2.每个pod 中都有一个pause 容器&#xff0c;他会连接本pod中的其他容器&#xff0c;实现互通。p…

【计算机网络】HTTP(上)

文章目录 1.HTTP概念2. URLurlencode 和 urldecode转义规则 3. HTTP的宏观理解HTTP的请求HTTP的响应 4. 见一见HTTP请求和响应请求报头 1. 模拟一个简单的响应response响应报头 2. 从路径中获取内容ReadFile函数的实现 3.不同资源进行区分反序列化的实现ReadOneLine函数的实现P…

2.11 PE结构:添加新的节区

在可执行PE文件中&#xff0c;节&#xff08;section&#xff09;是文件的组成部分之一&#xff0c;用于存储特定类型的数据。每个节都具有特定的作用和属性&#xff0c;通常来说一个正常的程序在被编译器创建后会生成一些固定的节&#xff0c;通过将数据组织在不同的节中&…

数学建模B多波束测线问题B

数学建模多波束测线问题 1.问题重述&#xff1a; 单波束测深是一种利用声波在水中传播的技术来测量水深的方法。它通过测量从船上发送声波到声波返回所用的时间来计算水深。然而&#xff0c;由于它是在单一点上连续测量的&#xff0c;因此数据在航迹上非常密集&#xff0c;但…

多通道振弦数据记录仪应用桥梁安全监测的关键要点

多通道振弦数据记录仪应用桥梁安全监测的关键要点 随着近年来桥梁建设和维护的不断推进&#xff0c;桥梁安全监测越来越成为公共关注的焦点。多通道振弦数据记录仪因其高效、准确的数据采集和处理能力&#xff0c;已经成为桥梁安全监测中不可或缺的设备。本文将从以下几个方面…

zabbix企业微信告警

目前&#xff0c;企业微信使用要设置可信域名 华为云搜索云函数 创建函数 选择http函数&#xff0c;随便输入函数名字 回到函数列表&#xff0c;选择刚创建的函数&#xff0c;创建触发器&#xff0c;安全模式选择none 点击右上角管理 选刚创建的api&#xff0c;右边操作点…