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

Go语言Slice切片底层

Go语言(Golang)中切片(slice)的相关知识、包括切片与数组的关系、底层结构、扩容机制、以及切片在函数传递、截取、增删元素、拷贝等操作中的特性。并给出了相关代码示例和一道面试题。关键要点包括:

  1. 数组特性:Go语言中数组是一个值、数组变量表示整个数组、不同于C语言中指向第一个元素的指针。传递数组到函数或拷贝数组时、会有不同的内存地址和数据独立性表现。

  2. 切片定义:切片是建立在Go数组之上的抽象类型、其底层结构包含指向底层数组的指针、长度和容量。

  3. 切片扩容

  • 新切片长度大于旧切片容量两倍时、新容量为新长度

  • 旧容量小于256时、新容量为旧容量两倍

  • 否则按1.25倍增速扩容、还会进行内存对齐。

  1. 函数传递:切片通过函数传递时、传的是切片结构、在函数内改变切片可能影响函数外的切片、取决于底层数组是否变化。

  2. 切片操作

  • 通过 “:” 作截取切片、新切片与原切片共享底层数组

  • 删除元素可通过拼接切片实现

  • 新增元素使用append操作

  • 深度拷贝可使用copy函数。

1.切片是什么

在Go语言中 切片(slice)是建立在数组之上的一种抽象类型。切片提供了一种更灵活的方式来处理数组、它允许动态地改变数组的大小、并且可以方便地进行切片操作。理解切片之前、我们需要先了解数组。

Go的数组 在Go语言中、数组的长度是类型的一部分、这意味着数组的长度是固定的、不能改变。

数组的传递和拷贝行为与C语言不同、Go语言中的数组是值类型、传递数组时会进行值拷贝。

1.1 示例一:

将数组传递到函数中 数组的地址不一样

package mainimport "fmt"func main() {array := [3]int{1, 2, 3}// 数组传递到函数中test(array)fmt.Printf("array 外: %p\n", &array)
}func test(array [3]int) {fmt.Printf("array 内: %p\n", &array)}
  • 由于数组是值类型、传递数组时会进行值拷贝、因此在test函数中打印的地址与main函数中打印的地址不同。

1.2 值拷贝

值拷贝意味着拷贝的是变量的内容、而不是内存地址。因此拷贝出来的变量有自己的独立副本、内容相同、但它们存储在不同的内存地址中。

Go 语言中的切片(slice)是动态扩容的。当你向切片中添加元素时、Go 会自动管理切片的大小、并在需要时进行扩容。

具体行为:

  • 初始容量:当你创建一个切片时、Go 会为切片分配一个初始容量。如果你添加的元素超过了切片当前的容量Go 会自动扩容。

  • 扩容规则:Go 会根据当前切片的容量自动扩展切片的大小、通常是原来容量的2倍。扩容后、切片的长度和容量都会增加。

  • 内部机制:当切片扩容时、Go 会为新切片分配新的底层数组、并将原数组的元素拷贝到新数组中。这是一个代价比较高的操作、尤其是在需要多次扩容的情况下

2.底层结构

type slice struct {// 底层数组指针(或者说是指向一块连续内存空间的起点)array unsafe.Pointer// 长度len  int// 容量cap  int
}

在这个结构中:

  • array:指向底层数组的指针、或者说是指向一块连续内存空间的起点。

  • len:切片的长度、即切片中实际包含的元素数量。

  • cap:切片的容量、即切片可以包含的元素的最大数量,不包括可能的扩展空间。

切片扩容

  1. 计算目标容量

    1. case1: 如果新切片的长度大于旧切片容量的两倍、则新切片容量就为新切片的长度。

    2. case2:

      • 如果旧切片的容量小于256、那么新切片的容量就是旧切片的容量的两倍。

      • 反之需要用旧切片容量按照1.25倍的增速、直到大于新切片长度。

    3. 为了更平滑的过渡、每次扩大1.25倍、还会加上3/4 * 256

    4. 进行内存对齐、需要按照Go内存管理的级别去对齐内存、最终容量以这个为准。

3.切片问题

3.1 切片通过函数传的是什么

package mainimport ("fmt""reflect""unsafe"
)func main() {s := make([]int, 5, 10)PrintSliceStruct(&s)test(s)
}func test(s []int) {PrintSliceStruct(&s)
}func PrintSliceStruct(s *[]int) {// 代码 将slice 转换成 reflect.SliceHeaderss := (*reflect.SliceHeader)(unsafe.Pointer(s))// 查看slice的结构fmt.Printf("slice struct: %+v, slice is %v\n", ss, s)
}

控制台输出:

slice struct: &{Data:1374389649568 Len:5 Cap:10}, slice is &[0 0 0 0 0]
slice struct: &{Data:1374389649568 Len:5 Cap:10}, slice is &[0 0 0 0 0]
  • 切片的定义:你创建了一个切片 s、通过 make([]int, 5, 10) 创建了一个长度为 5、容量为 10 的切片。

  • 也就是说它初始化了一个包含 5 个元素且最大容量为 10 的底层数组

总结:

  • 切片传递:当切片通过参数传递到函数时、传递的是切片的值、但切片内部的底层数组地址(指针)并没有被复制。

  • PrintSliceStruct 打印的结构:无论是在 main 函数还是 test 函数中、切片的底层数组地址、长度和容量都是相同的、因为底层数组是共享的。

为什么输出相同:

输出显示的 Data 地址、Len 和 Cap 是一致的、因为 test(s) 传递的是切片的值(即切片的结构),但切片中的指针指向相同的底层数组。所以无论是传递给 PrintSliceStruct 函数的 s、还是 test 函数中的 s、它们指向的是同一个底层数组、并且它们的长度和容量保持一致

3.2 在函数里面改变切片 函数外的切片会被影响吗

package mainimport ("fmt""reflect""unsafe"
)func main() {s := make([]int, 5) // 创建一个长度为 5 的切片case1(s)             // 调用 case1 函数case2(s)             // 调用 case2 函数PrintSliceStruct(&s) // 打印切片结构
}// 底层数组不变
func case1(s []int) {s[1] = 1 // 修改切片中的元素PrintSliceStruct(&s) // 打印切片结构
}// 底层数组变化
func case2(s []int) {s = append(s, 0) // 扩容切片s[1] = 1         // 修改切片中的元素PrintSliceStruct(&s) // 打印切片结构
}func PrintSliceStruct(s *[]int) {// 将切片转换成 reflect.SliceHeaderss := (*reflect.SliceHeader)(unsafe.Pointer(s))// 打印切片的底层结构fmt.Printf("slice struct: %+v, slice is %v\n", ss, *s)
}

关键点:

  • case1 函数:

  • case1 中、你传入一个长度为 5 的切片 s、并修改切片中的元素。

  • 切片在函数内的操作是对原切片的修改、因此底层数组没有发生变化、切片的容量、长度仍然相同。

  • 打印的 slice struct 的 Data、LenCap 字段显示的是切片的原始底层数据结构。

  • case2 函数:

  • case2 中、你向切片添加一个元素(通过 append 操作)、这将可能导致切片的底层数组扩容。

  • 因为 append 操作在超出当前容量时会触发扩容、所以 s 的底层数组会发生变化、容量也可能增加。

  • case2 中、s 被赋值为 append(s, 0)、这将导致原有切片 s 的底层数组被扩展、并且一个新的数组被分配给 s(s 指向的是新的底层数组)

  • 打印时会看到 slice struct 中的 Data 指向一个新的地址、表示底层数组已经发生了变化。

  • append(s, 0) 函数会检查切片 s 是否有足够的容量来存储新的元素。如果切片的容量不足、append 函数会分配一个新的更大的数组、并复制旧数组的内容到新数组中、然后将新元素添加到新数组的末尾、并更新切片的指针以指向包含新元素的新底层数组。

3.3 截取切片

package mainimport ("fmt""reflect""unsafe"
)func main() {s := make([]int, 5)  // 创建一个长度为 5 的切片,默认初始化为 [0 0 0 0 0]case1(s)  // 调用 case1,修改切片内容case2(s)  // 调用 case2,修改切片并改变底层数组case3(s)  // 调用 case3,截取切片并改变其长度case4(s)  // 调用 case4,截取切片的部分元素PrintSliceStruct(&s)  // 最后打印切片的底层结构
}// case1:修改切片元素、底层数组不变
func case1(s []int) {s[1] = 1  // 修改切片中的第二个元素,s[1] = 1PrintSliceStruct(&s)  // 打印修改后的切片底层结构
}// case2:重新赋值为新的切片
func case2(s []int) {s = s[:]  // 这里实际上并没有改变切片的内容、它只是重新赋值为原切片的一个新引用。PrintSliceStruct(&s)  // 打印新的切片底层结构
}// case3:截取切片、底层数组不变
func case3(s []int) {s = s[:len(s)-1]  // 截取切片、去掉最后一个元素、新的切片长度为 4PrintSliceStruct(&s)  // 打印截取后的切片底层结构
}// case4:截取切片的部分元素、底层数组不变
func case4(s []int) {sl := s[1:2]  // 截取 s[1:2],即取出切片中索引为 1 的元素PrintSliceStruct(&sl)  // 打印截取后的新切片底层结构
}// PrintSliceStruct 打印切片的底层结构
func PrintSliceStruct(s *[]int) {// 将切片的指针转换为 reflect.SliceHeader 结构体,通过 unsafe.Pointer 获取底层数据ss := (*reflect.SliceHeader)(unsafe.Pointer(s))// 打印切片的底层数据结构、包括:指向底层数组的内存地址、切片的长度和容量fmt.Printf("slice struct: %+v, slice is %v\n", ss, *s)
}

总结:

  • 切片操作的影响

  • 修改切片元素不会改变底层数组的地址。

  • 重新赋值切片并没有改变底层数组、除非涉及扩容(例如 append)。

  • 截取切片时、底层数组不变、切片的长度和容量可能会变化。

3.4 删除元素

package mainimport ("fmt""reflect""unsafe"
)func main() {// 创建一个包含5个整数的切片s := []int{0, 1, 2, 3, 4}// 打印切片的底层结构PrintSliceStruct(&s)// 删除切片中的最后一个元素,正确的做法是通过切片截取_ = s[4]                      // 访问并丢弃切片中的最后一个元素s1 := append(s[:1], s[2:]...) // 删除元素 s[1],//s[:1](即切片 [0])和 s[2:](即切片 [2, 3, 4])拼接在一起。// 打印修改后的切片fmt.Println(s)  // [0 2 3 4 4]fmt.Println(s1) // [0, 2, 3, 4]// 打印切片底层结构PrintSliceStruct(&s)PrintSliceStruct(&s1)// 访问切片的元素s = s[:4] // 截取切片、删除最后一个元素_ = s[3]  // 访问切片中的最后一个元素(索引为3的元素)
}// 打印切片的底层结构
func PrintSliceStruct(s *[]int) {// 将切片转换为 reflect.SliceHeaderss := (*reflect.SliceHeader)(unsafe.Pointer(s))// 打印切片的底层结构fmt.Printf("slice struct: %+v, slice is %v\n", ss, *s)
}
  • 在 Go 中、切片操作需要特别注意切片的索引和截取。访问切片中的元素时要小心类型不匹配(例如不能将一个切片元素赋值给切片)。

控制台输出:

slice struct: &{Data:1374390755328 Len:5 Cap:5}, slice is [0 1 2 3 4]
[0 2 3 4 4]
[0 2 3 4]
slice struct: &{Data:1374390755328 Len:5 Cap:5}, slice is [0 2 3 4 4]
slice struct: &{Data:1374390755328 Len:4 Cap:5}, slice is [0 2 3 4]

打印原切片 s 时、它仍然指向原底层数组(长度为 5、容量为 5)、而且由于 s[4] 在内存中并没有被移除、原底层数组中的最后一个元素 4 被保留、因此 s 显示为 [0 2 3 4 4]。

简而言之s 显示为 [0 2 3 4, 4] 是因为原始切片的底层数组并没有被修改、而 append 操作生成了一个新的切片(s1)并分配了新的底层数组。所以s 中仍然包含原数组中的所有元素、最后一个 4 仍然存在。

为什么 s变成了 [0, 2, 3, 4, 4]

  • append 会根据切片的容量决定是否会使用原来的底层数组。如果原切片的容量足够大、append 就会直接修改原切片。

  • 在这段代码中、由于原始切片 s 的容量足够大(原始切片 s 的容量为 5)、append 仍然修改了原始切片 s 的内容。切片的 s 和 s1 都指向相同的底层数组。

重点:

  • 原切片 s 的容量没有改变:s 底层的数组仍然包含原来 s 的所有元素。

  • append 没有重新分配新的底层数组:由于原切片的容量足够、所以 append 在修改原底层数组时、并没有创建新的底层数组。因此原始切片 s 中的 4 仍然存在。

  • 修改后 s 中的元素为 [0, 2, 3, 4, 4]:虽然你删除了 s[1] 这个元素、但 append 使得 s 的底层数组没有发生变化,因此原始的 4 元素仍然保留在切片中。

结论:

append 操作有时会创建新的底层数组(如果容量不足)、但如果原切片的容量足够、append 直接修改原切片的底层数组。在这种情况下原切片 s 会保持原来的容量和数据、导致 s 显示为 [0, 2, 3, 4, 4],即最后一个 4 保留下来了。

3.5 新增元素

package mainimport ("fmt""reflect""unsafe"
)func main() {case1()case2()case3()
}// case1 函数展示了使用 append 在切片末尾添加元素的行为
func case1() {// 创建一个长度为 3,容量为 3 的切片s1 := make([]int, 3, 3)// 向切片添加一个元素 1,append 返回一个新的切片s1 = append(s1, 1)// 打印切片的底层结构PrintSliceStruct(&s1) //1
}// case2 函数展示了在原切片上使用 append 并打印切片结构的变化
func case2() {// 创建一个长度为 3,容量为 4 的切片s1 := make([]int, 3, 4)// 向切片添加一个元素 1,append 会扩展切片的长度s2 := append(s1, 1)// 打印原切片 s1 和新切片 s2 的底层结构PrintSliceStruct(&s1)//2PrintSliceStruct(&s2)//3
}// case3 函数与 case2 类似,展示了切片长度、容量变化的行为
func case3() {// 创建一个长度为 3,容量为 3 的切片s1 := make([]int, 3, 3)// 向切片添加一个元素 1,append 返回一个新的切片s2 := append(s1, 1)// 打印原切片 s1 和新切片 s2 的底层结构PrintSliceStruct(&s1)//4PrintSliceStruct(&s2)//5
}// PrintSliceStruct 打印切片的底层结构
func PrintSliceStruct(s *[]int) {// 使用 reflect 和 unsafe 包将切片转换成 reflect.SliceHeader 结构体ss := (*reflect.SliceHeader)(unsafe.Pointer(s))// 打印切片的底层结构fmt.Printf("slice struct: %+v, slice is %v\n", ss, *s)
}

控制台输出

slice struct: &{Data:1374390755328 Len:4 Cap:6}, slice is [0 0 0 1]
slice struct: &{Data:1374390779936 Len:3 Cap:4}, slice is [0 0 0]
slice struct: &{Data:1374390779936 Len:4 Cap:4}, slice is [0 0 0 1]
slice struct: &{Data:1374390673552 Len:3 Cap:3}, slice is [0 0 0]
slice struct: &{Data:1374390755376 Len:4 Cap:6}, slice is [0 0 0 1]

case1:

  • 使用 make([]int, 3, 3) 创建了一个长度为 3,容量为 3 的切片 s1,初始内容为 [0, 0, 0]。

  • 然后append(s1, 1) 会将元素 1 添加到切片的末尾、生成一个新的切片并返回。由于容量是 3、append 会自动扩容新的切片长度是 4

  • 最后调用 PrintSliceStruct 打印 s1 切片的底层结构。

case2:

  • make([]int, 3, 4) 创建了一个长度为 3、容量为 4 的切片 s1

  • 使用 append(s1, 1) 向切片添加元素 1、生成一个新切片 s2。由于 s1 的容量已足够、不会触发扩容。

  • 通过 PrintSliceStruct 打印切片 s1 和 s2 的底层结构。

s3 和 s4 参考上面

3.6 操作原来切片会影响新的切片吗

在 Go 中、切片是引用类型,这意味着当你创建一个新切片时,它实际上可能会指向同一个底层数组。因此,如果你修改了原切片(比如通过 append 或其他操作),它可能会影响到新切片,特别是在底层数组没有被重新分配的情况下。

切片和底层数组

  • 切片(slice)是一个非常轻量级的抽象,它包含了三个部分:指向底层数组的指针、切片的长度和切片的容量。

  • 当你对切片进行操作时(例如使用 append、copy 或直接修改),这些操作通常会影响到底层数组。

  • 如果多个切片引用同一个底层数组,改变其中一个切片的内容可能会影响到其他切片,尤其是在没有扩容时。

append操作

  • 当使用 append 函数时、如果切片的容量足够、append 会直接在原底层数组上操作、不会创建新的底层数组。在这种情况下、修改原切片的内容会影响到新切片、因为它们指向相同的底层数组。

  • 如果容量不足、append 会创建一个新的底层数组、并将原切片的数据复制到新数组中、这时原切片和新切片就指向不同的底层数组了、它们互不影响。

例子:

没有扩容:

s1 := []int{1, 2, 3}
s2 := s1      // s2 指向与 s1 相同的底层数组
s1[0] = 100   // 修改 s1 中的第一个元素
fmt.Println(s1) // 输出 [100, 2, 3]
fmt.Println(s2) // 输出 [100, 2, 3]

这里s1 和 s2 指向相同的底层数组,因此修改 s1 会影响到 s2。

扩容时:

s1 := []int{1, 2, 3}
s2 := append(s1, 4)  // s2 创建了新的底层数组
s1[0] = 100          // 修改 s1 中的第一个元素
fmt.Println(s1) // 输出 [100, 2, 3]
fmt.Println(s2) // 输出 [1、2、3、 4]

这里s2 创建了一个新的底层数组,因此修改 s1 不会影响 s2。

结论:

  • 修改原切片会影响新切片:如果新切片是通过引用原切片的底层数组创建的(没有触发扩容)、修改原切片的内容会影响到新切片。

  • 扩容时不影响:如果 append 或其他操作导致了扩容、原切片和新切片就会指向不同的底层数组、互不影响。

4.字节面试题

下面这道题的输出是什么

package mainimport "fmt"func main() {// 定义一个匿名函数 doAppend,用来执行 append 操作并打印切片的长度和容量doAppend := func(s []int) {s = append(s, 1)         // 向切片中添加元素 1printLengthAndCapacity(s) // 打印切片的长度和容量}// 创建一个长度为 8,容量为 8 的切片 ss := make([]int, 8, 8)// 传递 s 的前 4 个元素(即 s[:4])到 doAppenddoAppend(s[:4])  // 只传递前4个元素的切片// 打印原始切片 s 的长度和容量printLengthAndCapacity(s)// 传递整个切片 s 到 doAppenddoAppend(s)// 打印原始切片 s 的长度和容量printLengthAndCapacity(s)
}func printLengthAndCapacity(s []int) {fmt.Println() fmt.Printf("len=%d cap=%d \n", len(s), cap(s))  // 打印切片的长度和容量
}
  • len(s) 是切片的长度。

  • cap(s) 是切片的容量、表示切片底层数组的大小。

len=5 cap=8 len=8 cap=8 len=9 cap=16 len=8 cap=8

调用 doAppend(s[:4]):

  • s[:4] 是 s 切片的前 4 个元素、创建一个新的切片 [0, 0, 0, 0],长度为 4、容量为 8(因为它引用的是原切片的底层数组)。

  • 在 doAppend 中、执行 append(s, 1)、这会向切片添加一个元素 1、导致切片的长度变为 5、容量保持为 8(因为它没有触发扩容)。

  • 打印结果为:len=5 cap=8。

调用 doAppend(s)

  • 这次传递整个 s 切片、长度为 8、容量为 8。

  • 执行 append(s 1)、这会向切片 s 添加一个元素 1。因为 s 的容量是 8、不能再容纳更多元素、因此会触发扩容、新的底层数组的容量将是原来的 2 倍、即 16、长度变为 9。

  • 打印结果为:len=9 cap=16。

  • 注意:

  • append 创建了一个新的底层数组、并返回了一个新的切片。如果你不把返回的新切片赋值回 s、原始切片 s 不会改变、仍然指向旧的底层数组。

  • 由于 append(s 1) 返回的是一个新的切片、但并没有将它赋值回 s、所以原始切片 s 的长度和容量没有变化、仍然是 len=8 和 cap=8

如果改成将新切片赋值回s

package mainimport "fmt"func main() {// 定义一个匿名函数 doAppend 用于向切片添加元素doAppend := func(s []int) {s = append(s, 1)          // 向切片中添加元素 1printLengthAndCapacity(s) // 打印切片的长度和容量}// 定义一个匿名函数 doAppend 用于向切片添加元素doAppends := func(s []int) []int {s = append(s, 1) // 使用 append 向切片添加一个元素 1printLengthAndCapacity(s)return s}// 创建一个长度为 8,容量为 8 的切片 ss := make([]int, 8, 8)// 传递前 4 个元素的切片doAppend(s[:4]) // 只传递前4个元素的切片printLengthAndCapacity(s)// 传递整个切片 ss = doAppends(s) // 将返回的新切片赋值回 sprintLengthAndCapacity(s)
}func printLengthAndCapacity(s []int) {fmt.Println()fmt.Printf("len=%d cap=%d \n", len(s), cap(s))
}
len=5 cap=8 len=8 cap=8 len=9 cap=16 len=9 cap=16 

相关文章:

Go语言Slice切片底层

Go语言(Golang)中切片(slice)的相关知识、包括切片与数组的关系、底层结构、扩容机制、以及切片在函数传递、截取、增删元素、拷贝等操作中的特性。并给出了相关代码示例和一道面试题。关键要点包括: 数组特性&#xf…...

导入 Excel 批量替换文件夹名称

文件夹重命名的需求是多种多样的,前面我们介绍过按照规则修改文件夹名称的方法。但是在某些场景下,这个方法可能是不适用的,比如我们修改文件夹的规则是多种多样的,是无规律的。那我们应该怎么做呢?今天我们就给大家介…...

数据库或表数据迁移(使用Navicat迁移MySQL数据库表数据)

🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 数据库或表数据迁移(使用Navicat…...

Matlab Add Legend To Graph-图例添加到图

Add Legeng To Graph: Matlab的legend()函数-图例添加到图 将图例添加到图 ,图例是标记绘制在图上的数据序列的有用方法。 下列示例说明如何创建图例并进行一些常见修改,例如更改位置、设置字体大小以及添加标题。您还可以创建具有多列的图…...

【Linux】what is pam?PAM模块学习笔记

文章目录 1. pam模块简介2. pam验证的工作流程3. pam模块配置文件3.1 配置文件的格式3.1.1 验证类别type3.1.2 验证的控制标识 control flag3.1.3 pam模块 4. login的PAM验证机制流程5. 补充:其他pam相关文件6. 参考内容 1. pam模块简介 PAM: Pluggable Authentica…...

5.1 GitHub订阅监控系统实战:FastAPI+SQLAlchemy高效架构设计与核心源码揭秘

GitHub Sentinel Agent 分析报告功能设计与实现 关键词:订阅管理 API 设计、GitHub API 集成、SQLAlchemy ORM、JWT 认证、单元测试框架 1. 订阅管理功能架构设计 订阅管理模块采用分层架构设计,通过 FastAPI 构建 RESTful 接口,结合 SQLAlchemy ORM 实现数据持久化: #me…...

【BEPU V1物理】BEPUphysics v1 入门指南 汉化笔记#1

BEPUphysics v1 入门指南 前言下载获取库工程1.创建物理模拟环境2.添加物理实体3.与物理系统交互4.发射物体5.构建环境6.事件处理7. 进阶学习 前言 本文档记录完成 BEPUphysics 物理引擎的基础设置。 文档链接:https://github.com/bepu/bepuphysics1/blob/master/Documentatio…...

方法加事务在多线程中注意事项

方法加事务在多线程中注意事项 redission分布式锁释放异常问题 https://www.jianshu.com/p/055ae798547a https://blog.csdn.net/cheng__yu/article/details/122625649 虽然文章 https://blog.csdn.net/cheng__yu/article/details/122625649 和 redission锁是没关系的&#…...

开源 2D 横版跳跃游戏 SuperTux

官网 https://www.supertux.org/ 正文 在游戏的世界里,开源游戏以其独特的魅力吸引着众多玩家和开发者。今天要介绍的 SuperTux,便是一款备受瞩目的开源 2D 横版跳跃游戏,风格类似经典的超级马里奥系列。 2024 年,SuperTux 开发团…...

基于HASM模型的高精度建模matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 本课题主要使用HASM进行高精度建模,主要对HASM模型进行介绍以及在实际中如何进行简化实现的。 2.测试软件版本以及运行结果展示 MATLAB2022A版本运行…...

C++多线程编程时的伪共享问题及其定位和解决

一、引言 在多线程编程和共享内存系统中,为了提高程序性能,常常需要对共享数据进行处理。然而,在并发环境下,一种名为“伪共享(False Sharing)”的问题可能会悄然出现,它虽然不像传统的多线程同…...

高并发短信系统设计:基于SharingJDBC的分库分表、大数据同步与实时计算方案

高并发短信系统设计:基于SharingJDBC的分库分表、大数据同步与实时计算方案 一、概述 在当今互联网应用中,短信服务是极为重要的一环。面对每天发送2000万条短信的需求,我们需要一个能够处理海量数据(一年下来达到数千万亿级别&…...

【HTML】html文件

HTML文件全解析:搭建网页的基石 在互联网的广袤世界里,每一个绚丽多彩、功能各异的网页背后,都离不开HTML文件的默默支撑。HTML,即超文本标记语言(HyperText Markup Language),作为网页创建的基…...

5.11 GitHub API调试五大高频坑:从JSON异常到异步阻塞的实战避坑指南

GitHub API调试五大高频坑:从JSON异常到异步阻塞的实战避坑指南 关键词:GitHub API 调试、JSON 解析异常、提示工程优化、异步任务阻塞、数据清洗策略 5.5 测试与调试:调试常见问题 问题1:GitHub API 调用异常 现象: requests.exceptions.HTTPError: 403 Client Error…...

协程的原生挂起与恢复机制

目录 🔍 一、从开发者视角看协程挂起与恢复 🧠 二、协程挂起和恢复的机制原理:核心关键词 ✅ suspend 函数 ≠ 普通函数 ✅ Continuation(协程的控制器) 🔧 三、编译器做了什么?&#xff0…...

机器学习中的数学(PartⅡ)——线性代数:2.2矩阵

概述 本节内容也相对简单,首先介绍了矩阵的定义,矩阵的表示方法;然后介绍了矩阵的加法和乘法,与标量的乘法,以及一些矩阵相关算数运算的性质,包括满足结合律、交换律;矩阵的逆和转置&#xff1…...

泉涌未来:科技与生态的共生诗篇-济南

故事背景 故事发生在中国山东济南的未来城市环境,这里不再是单纯的自然景观与现代建筑的堆砌,而是科技与生态深度融合的奇妙世界。泉水,这一济南的灵魂元素,在未来科技的赋能下,成为连接城市各个角落的纽带。量子态泉水…...

用AI生成系统架构图

DeepSeek+Drawio+SVG绘制架构图-找到一种真正可行实用的方法和思路 1、使用DeepSeek生成SVG文件,导入drawio工具的方法 🔥 问题根源分析 错误现象: • 导入时报错包含 data:image/SVG;base64 和 %20 等 URL 编码字符 • 代码被错误转换为 Base64 格式(适用于网页嵌入,但…...

网络基础1

目录 初识协议 协议分层 软件分层的好处 OSI七层模型 TCP/IP 五层(或四层)模型 再谈协议 为什么要有 TCP/IP 协议? TCP/IP 协议与操作系统的关系 所以究竟什么是协议? 网络传输基本流程 认识 MAC 地址 局域网(以太网为例)通信原理 报文的传…...

免费且好用的PDF水印添加工具

软件介绍 今天要给大家推荐一款超实用的PDF添加水印工具,它能够满足用户给PDF文件添加水印的需求,而且完全免费。 这款PDF添加水印的软件有着简洁的界面,操作简便,无需安装,解压后即可使用。 在使用前,先…...

C++Primer对象移动

欢迎阅读我的 【CPrimer】专栏 专栏简介:本专栏主要面向C初学者,解释C的一些基本概念和基础语言特性,涉及C标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级…...

互联网三高-数据库高并发之分库分表ShardingJDBC

1 ShardingJDBC介绍 1.1 常见概念术语 ① 数据节点Node:数据分片的最小单元,由数据源名称和数据表组成 如:ds0.product_order_0 ② 真实表:再分片的数据库中真实存在的物理表 如:product_order_0 ③ 逻辑表&#xff1a…...

七、自动化概念篇

自动化测试概念 自动化测试是把以人为驱动的测试行为转化为机器执行的一种过程。通常,在设计了测试用例并通过评审之后,由测试人员根据测试用例中描述的过程一步步执行测试,得到实际结果与期望结果的比较。在此过程中,为了节省人…...

python操作mongodb

1、安装包 pyMongo是MongoDB官方推荐的Python驱动程序,它提供了访问MongoDB数据库所需的接口。安装PyMongo非常简单,可以通过pip包管理工具来安装最新版本: pip install pymongo 安装完成后,我们可以使用以下Python代码来检查是否…...

IS-IS中特殊字段——OL过载

文章目录 OL 过载位 🏡作者主页:点击! 🤖Datacom专栏:点击! ⏰️创作时间:2025年04月13日20点12分 OL 过载位 路由过载 使用 IS-IS 的过载标记来标识过载状态 对设备设置过载标记后&#xff…...

行星际激波在日球层中的传播:Propagation of Interplanetary Shocks in the Heliosphere (第二部分)

行星际激波在日球层中的传播:Propagation of Interplanetary Shocks in the Heliosphere (第一部分)-CSDN博客 Propagation of Interplanetary Shocks in the Heliosphere [ Chapter 3 ] [PDF: arXiv] 本文保留原文及参考文献,参…...

紫光同创FPGA实现HSSTLP光口视频点对点传输,基于Aurora 8b/10b编解码架构,提供6套PDS工程源码和技术支持

目录 1、前言工程概述免责声明 2、相关方案推荐我已有的所有工程源码总目录----方便你快速找到自己喜欢的项目紫光同创FPGA相关方案推荐我这里已有的 GT 高速接口解决方案Xilinx系列FPGA实现GTP光口视频传输方案推荐Xilinx系列FPGA实现GTX光口视频传输方案推荐Xilinx系列FPGA实…...

正则表达式使用知识(日常翻阅)

正则表达式使用 一、字符匹配 1. 普通字符 描述:直接匹配字符本身。示例: abc 匹配字符串中的 “abc”。Hello 匹配字符串中的 “Hello”。 2. 特殊字符 .(点号): 描述:匹配任意单个字符(…...

CSS padding(填充)学习笔记

CSS 中的 padding(填充)是一个非常重要的属性,它用于定义元素边框与元素内容之间的空间,即上下左右的内边距。合理使用 padding 可以让页面布局更加美观、清晰。以下是对 CSS padding 的详细学习笔记。 一、padding 的作用 padd…...

Python中的字符串、列表、字典和集合详解

Python是一门强大的编程语言,其内置的数据结构丰富多样。其中,字符串、列表、字典和集合是最常用的数据类型。本文将对它们的区别、用法和使用场景进行详细介绍,帮助大家更好地理解和应用这些数据结构。 1. 字符串(String&#xf…...

【CUDA编程】CUDA Warp 与 Warp-Python 对比文档

相关文档: Nvidia-Warp GitHub:nvidia/warp CUDA Warp 和 Warp-Python 库 的对比与统一文档,涵盖两者的核心概念、区别、使用场景及示例: 1. CUDA Warp(硬件/编程模型概念) 1.1 定义与核心概念 定义: C…...

中厂算法岗面试总结

时间:2025.4.10 地点:上市的电子有限公司 面试流程: 1.由负责人讲解公司文化 2,由技术人员讲解公司的技术岗位,还有成果 3.带领参观各个工作位置,还有场所 4.中午吃饭 5.面试题,闭卷考试…...

Losson 4 NFS(network file system(网络文件系统))

网络文件系统:在互联网中共享服务器中文件资源。 使用nfs服务需要安装:nfs-utils 以及 rpcbind nfs-utils : 提供nfs服务的程序 rpcbind :管理nfs所有进程端口号的程序 nfs的部署 1.客户端和服务端都安装nfs-utils和rpcbind #安装nfs的软件rpcbind和…...

自动化运行后BeautifulReport内容为空

一、问题描述 自动化程序运行后发现运行目录下生成的html报告文件内容为空,没有运行结果。 二、测试环境 操作系统:Windows 11 家庭版BeautifulReport:0.1.3Python:3.11.9Appium-Python-Client:5.0.0Appium Server:2.…...

CTF-WEB排行榜制作

CTF-WEB排行榜制作 项目需求: 现在14道题对应有14个flag,我需要使用dockerfile搭建一个简单的,能够实现验证这些题目对应的flag来计分的简单网站(要求页面比较精美) 前十题设置为10分 11-14题设置为20分 1. flag{5a3…...

架构生命周期(高软57)

系列文章目录 架构生命周期 文章目录 系列文章目录前言一、软件架构是什么?二、软件架构的内容三、软件设计阶段四、构件总结 前言 本节讲明架构设计的架构生命周期概念。 一、软件架构是什么? 二、软件架构的内容 三、软件设计阶段 四、构件 总结 就…...

STM32单片机定时器的输入捕获和输出比较

目录 一、定时器的输入捕获 1、工作原理 2、示例代码 二、定时器的输出比较 1、工作原理 2、示例代码 三、总结 在STM32单片机中,定时器是一个非常重要的外设,广泛应用于时间管理、事件计时、波形生成等多种场景。其中输入捕获和输出比较是两个基…...

计算机组成原理-系统总线

1. 系统总线的定义 系统总线是计算机系统中各功能部件(CPU、存储器、I/O设备等)之间传递信息的公共通路,遵循统一的电气规范和时序协议,是计算机硬件互联的基础。 核心作用:实现数据、地址和控制信号的传输&#xff…...

【android bluetooth 框架分析 02】【Module详解 3】【HciHal 模块介绍】

1. 背景 我们在 gd_shim_module 介绍章节中&#xff0c;看到 我们将 HciHal 模块加入到了 modules 中。 modules.add<hal::HciHal>();在 ModuleRegistry::Start 函数中我们对 加入的所有 module 挨个初始化。 而在该函数中启动一个 module 都要执行那下面几步&#xff…...

Git 远程仓库

Git 入门笔记 远程仓库 Git 远程仓库 Git 远程仓库是一个托管在网络服务器上的代码仓库&#xff0c;它是团队协作开发的核心。 通过远程仓库&#xff0c;开发者可以共享代码、同步更新&#xff0c;实现分布式协作。 SSH 密钥 SSH 密钥可以让你在使用 Git 时安全地连接远程…...

209.长度最小的子数组- 力扣(LeetCode)

题目&#xff1a; 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的 子数组 [numsl, numsl1, ..., numsr-1, numsr] &#xff0c;并返回其长度。如果不存在符合条件的子数组&#xff0c;返回 0 。 示例 1&#xff1a;…...

符号右移“ >>= “ 与 无符号右移“ >>>= “ 的区别

符号右移" >> " 与 无符号右移" >>> " 的区别 一、符号右移" >> " 与 无符号右移" >>> " 的区别1. 符号右移&#xff08;>>&#xff09;与无符号右移&#xff08;>>>&#xff09;的区别…...

山东大学软件学院项目实训-基于大模型的模拟面试系统-专栏管理部分

本周我的主要任务是关于专栏管理部分的完善。 专栏图片的显示问题 问题分析 根据代码可知&#xff1a;图片URL来自于portfolio.headImgUrl&#xff0c;而且如果URL不存在的话&#xff0c;应该显示的是无图片&#xff0c;而网页中显示加载失败说明portfolio.headImgUrl应该是存…...

从 SYN Flood 到 XSS:常见网络攻击类型、区别及防御要点

常见的网络攻击类型 SYN Flood、DoS&#xff08;Denial of Service&#xff09; 和 DDoS&#xff08;Distributed Denial of Service&#xff09; 是常见的网络攻击类型&#xff0c;它们的目标都是使目标系统无法正常提供服务。以下是它们的详细说明&#xff1a; 1. SYN Flood…...

ros2-rviz2控制unity仿真的6关节机械臂,探索从仿真到实际应用的过程

文章目录 前言&#xff08;Introduction&#xff09;搭建开发环境&#xff08;Setup Development Environment&#xff09;在window中安装Unity&#xff08;Install Unity in window&#xff09;创建Docker容器&#xff0c;并安装相关软件&#xff08;Create Docker containers…...

01_通过调过api文字生成音频示例

第1 第2 第3&#xff0c;测试音色 第4 第5 第6 第7生成api_key 第8代码 import requestsurl "https://api.siliconflow.cn/v1/audio/speech"payload {"input": "在中国传统文化中&#xff0c;谦让被视为一种美德&#xff0c;但过度的让步…...

使用PyTorch实现目标检测边界框转换与可视化

一、引言 在目标检测任务中&#xff0c;边界框&#xff08;Bounding Box&#xff09;的坐标表示与转换是核心基础操作。本文将演示如何&#xff1a; 实现边界框的两种表示形式&#xff08;角点坐标 vs 中心坐标&#xff09;之间的转换 使用Matplotlib在图像上可视化边界框 验…...

QEMU学习之路(8)— ARM32通过u-boot 启动Linux

QEMU学习之路&#xff08;8&#xff09;— ARM32通过u-boot 启动Linux 一、前言 参考文章&#xff1a; Linux内核学习——内核的编译和启动 Linux 内核的编译和模拟执行 Linux内核运行——根文件系统 Linux 内核学习——使用 uboot 加载内核 二、构建Linux内核 1、获取Linu…...

flutter 桌面应用之右键菜单

​在 Flutter 桌面应用开发中&#xff0c;context_menu 和 contextual_menu 是两款常用的右键菜单插件&#xff0c;各有特色。以下是对它们的对比分析&#xff1a;​ &#x1f9e9; context_menu 集成方式&#xff1a;​通过 ContextMenuArea 组件包裹目标组件&#xff0c;定义…...

系统设计面试总结:高性能相关:CDN(内容分发网络)、什么是静态资源、负载均衡(Nginx)、canal、主从复制

以下为本人自学回顾使用&#xff0c;请支持javaGuide原版。 1.CDN概述 CDN 就是将静态资源分发到多个不同的地方以实现就近访问&#xff0c;进而加快静态资源的访问速度&#xff0c;减轻服务器以及带宽的负担。 你可以将 CDN 看作是服务上一层的特殊缓存服务&#xff0c;分布…...