Go 切片常用操作与使用技巧
1.什么是切片
在 Go 语言中的切片(slice)是一种灵活的动态数组,它可以自动扩展和收缩,是 Go 语言中非常重要的数据结构之一。切片是基于数组实现的,它的底层是数组,可以理解为对底层数组的抽象。它会生成一个指向数组的指针,并通过切片长度关联到底层数组部分或者全部元素。
2.切片底层结构
在 Go 语言的源码包中,src/runtime/slice.go 定义了切片的数据结构:
type slice struct {array unsafe.Pointerlen intcap int
}
切片占用 24 个字节:
-
array:指向底层数组的指针,占用 8 个字节。当你创建一个切片时,Go 会分配一个底层数组,并将切片的指针指向该数组。 -
len:切片中元素的数量,占用 8 个字节。 -
cap:切片的容量,cap总是大于等于len,占用 8 个字节。
3.初始化方式
通常用以下三种初始化切片:
-
通过下标的方式获得数组的一部分;
slice := arr[1:3]
-
使用字面量初始化新的切片;
slice := []int{1, 2, 3}
-
使用关键字
make创建切片:
slice := make([]int, 10)
//或
slice := make([]int, 3, 5)
make函数有三个参数:
- 第一个参数为切片类型,可以是
[]int,[]string,[]float32等。 - 第二个参数为切片初始长度。
- 第三个为切片容量,该参数为可选参数。
注意点:
var slice1 []int 这是声明,此时切片为nil
例如:
package mainimport "fmt"func main() {var slice1 []intif slice1 == nil {fmt.Println("slice is nil!")}// slice1[0] = 9 报错panic: runtime error: index out of range [0] with length 0slice1 = append(slice1, 2) //可以fmt.Println(slice1)
}
结果:
slice is nil!
[2]
4.追加元素
append函数是Go语言内置的一个函数,用于向切片中追加元素。
s := []int{1, 2, 3}
s = append(s, 4)
fmt.Println(s) // 输出:[1 2 3 4]
或
//如果你要追加的元素是另一个切片,那么可以使用...运算符,这样可以把那个切片的所有元素都追加进来。
s := []int{1, 2, 3}
t := []int{4, 5, 6}
s = append(s, t...)
fmt.Println(s) // 输出:[1 2 3 4 5 6]
5.切片表达式
Golang 中通常的 slice 语法是 a[low:high],您可能很熟悉。还有另一种切片语法,形式为 a[low:high:max],它采用三个索引而不是两个索引。第三索引 max 是做什么的?
提示: 不是 Python 切片语法 a[low:high:step] 中的 step 步长索引。
答: 第三个索引用于设置切片的容量!在 Golang 规范中称为 “全切片表达式”。
简单切片表达式的格式[low:high]。
如下面例子,n为一个切片,当用这个表达式[1:4]表示的是左闭右开[low, high)区间截取一个新的切片(例子中结果是[2 3 4]),切片被截取之后,截取的长度是high-low。
n := []int{1, 2, 3, 4, 5, 6}
fmt.Println(n[1:4]) // [2 3 4]
切片表达式的开始low和结束索引high是可选的;它们分别默认为零和切片的长度:
n := []int{1, 2, 3, 4, 5, 6}
fmt.Println(n[:4]) // [1 2 3 4],:前面没有值,默认表示0
fmt.Println(n[1:]) // [2 3 4 5 6],:后面没有值,默认表示切片的长度
边界问题
-
1、当n为数组或字符串表达式n[low:high]中low和high的取值关系:
0 <= low <=high <= len(n)
-
2、当n为切片的时候,表达式n[low:high]中high最大值变成了cap(n),low和high的取值关系:
0 <= low <=high <= cap(n)
不满足以上条件会发送越界panic。
全切片表达式
n[low:high:max]
max表示新生成切片的容量,新切片容量等于max-low,表达式中low、high、max关系:
0 <= low <= high <= max <= cap(n)
继续刚才的例子,当计算n[1:4]的容量,用cap得到值等于5,用扩展表达式n[1:4:5],用cap重新计算得到新的容量值(5-1)等于4:
fmt.Println(cap(n[1:4])) // 5fmt.Println(cap(n[1:4:5])) // 4
关于容量
n[1:4]的长度是3好理解(4-1),容量为什么是5?
因为切片n[1:4]和切片n是共享底层空间,所以它的容量并不等于他的长度3,根据1等于索引1的位置(等于值2),从值2这个元素开始到末尾元素6,共5个,所以n[1:4]容量是5。
如果append超过切片的长度会重新生产一个全新的切片,不会覆盖原来的:
func main() {n := []int{1, 2, 3, 4, 5, 6}n2 := n[1:4:5] // 长度等于3,容量等于4fmt.Printf("%p\n", n2) // 0xc0000220c8n2 = append(n2, 5)fmt.Printf("%p\n", n2) // 0xc0000220c8n2 = append(n2, 6)fmt.Printf("%p\n", n2) // 地址发生改变,0xc00001e080
}
6.常见使用陷阱
切片作为参数传递陷阱
-
在函数里修改切片元素的值,原切片的值也会被改变;
-
在函数里通过
append方法,对切片执行追加元素的操作,可能会引起切片扩容,导致内存分配的问题,可能会对程序的性能 造成影响; -
在函数里通过
append函数,对切片执行追加元素的操作,原切片里不存在新元素。
看一下代码示例:
func main() {s := []int{0, 2, 3}fmt.Printf("main中1 切片的长度:%d, 切片的容量:%d, 切片的元素:%v\n", len(s), cap(s), s) // 3 3 [0, 2, 3]sliceOperation(s)fmt.Printf("main中2 切片的长度:%d, 切片的容量:%d, 切片的元素:%v\n", len(s), cap(s), s) // 3 3 [1, 2, 3]
}func sliceOperation(s []int) {s[0] = 1fmt.Printf("sliceOperation中 切片的长度:%d, 切片的容量:%d, 切片的元素:%v\n", len(s), cap(s), s) // 3 3 [1, 2, 3]
}
结果:
main中1 切片的长度:3, 切片的容量:3, 切片的元素:[0 2 3]
sliceOperation中 切片的长度:3, 切片的容量:3, 切片的元素:[1 2 3]
main中2 切片的长度:3, 切片的容量:3, 切片的元素:[1 2 3]
看到切片中的元素会被改变,就认为改变切片中的元素数据就直接将切片作为参数传过去就可以了,但是如果用了append追加元素,就会发现追加的元素并不会加到原切片中:
func main() {s := []int{0, 2, 3}fmt.Printf("main中1 切片的长度:%d, 切片的容量:%d, 切片的元素:%v\n", len(s), cap(s), s) // 3 3 [0, 2, 3]sliceOperation(s)fmt.Printf("main中2 切片的长度:%d, 切片的容量:%d, 切片的元素:%v\n", len(s), cap(s), s) // 3 3 [1, 2, 3]
}func sliceOperation(s []int) {s = append(s, 4)fmt.Printf("sliceOperation中 切片的长度:%d, 切片的容量:%d, 切片的元素:%v\n", len(s), cap(s), s) // 4 6 [0, 2, 3]
}
结果:
main中1 切片的长度:3, 切片的容量:3, 切片的元素:[0 2 3]
sliceOperation中 切片的长度:4, 切片的容量:6, 切片的元素:[0 2 3 4]
main中2 切片的长度:3, 切片的容量:3, 切片的元素:[0 2 3]
-
若想实现执行
append函数之后,原切片也能得到新元素;需将函数的参数类型由 切片类型 改成 切片指针类型。
func main() {s := []int{0, 2, 3}fmt.Printf("main中1 切片的长度:%d, 切片的容量:%d, 切片的元素:%v\n", len(s), cap(s), s) sliceOperation(&s)fmt.Printf("main中2 切片的长度:%d, 切片的容量:%d, 切片的元素:%v\n", len(s), cap(s), s)
}func sliceOperation(s *[]int) {*s = append(*s, 4)fmt.Printf("sliceOperation中 切片的长度:%d, 切片的容量:%d, 切片的元素:%v\n", len(*s), cap(*s), s)
}
结果:
main中1 切片的长度:3, 切片的容量:3, 切片的元素:[0 2 3]
sliceOperation中 切片的长度:4, 切片的容量:6, 切片的元素:[0 2 3 4]
main中2 切片的长度:4, 切片的容量:6, 切片的元素:[0 2 3 4]
slice 通过 make 函数初始化,后续操作不当所造成的陷阱
1.使用 make 函数初始化切片后,如果在后续操作中没有正确处理切片长度,容易造成以下陷阱:
func main() {s := make([]int, 0, 4)s[0] = 1 // panic: runtime error: index out of range [0] with length 0
}
通过 make([]int, 0, 4) 初始化切片,虽说容量为 4,但是长度为 0,如果通过索引去赋值,会发生panic;为避免 panic,可以通过 s := make([]int, 4) 或 s := make([]int, 4, 4) 对切片进行初始化。
2.切片初始化不当,通过 append 函数追加新元素的位置可能于预料之外
func main() {s := make([]int, 4)s = append(s, 1)fmt.Println(s[0]) // 0s2 := make([]int, 0, 4)s2 = append(s2, 1)fmt.Println(s2[0]) // 1
}
-
通过打印结果可知,对于切片
s,元素1没有被放置在第一个位置,而对于切片s2,元素1被放置在切片的第一个位置。这是因为通过make([]int, 4)和make([]int, 0, 4)初始化切片,底层所指向的数组的值是不一样的:-
第一种初始化的方式,切片的长度和容量都为
4,底层所指向的数组长度也是4,数组的值为[0, 0, 0, 0],每个位置的元素被赋值为零值,s = append(s, 1)执行后,s切片的值为[0, 0, 0, 0, 1]; -
第二种初始化的方式,切片的长度为
0,容量为4,底层所指向的数组长度为0,数组的值为[],s2 = append(s2, 1)执行后,s2切片的值为[1]; -
通过
append向切片追加元素,会执行尾插操作。如果我们需要初始化一个空切片,然后从第一个位置开始插入元素,需要避免make([]int, 4)这种初始化的方式,否则添加的结果会在预料之外。
-
-
性能陷阱
-
内存泄露
内存泄露是指程序分配内存后不再使用该内存,但未将其释放,导致内存资源被浪费。
切片引用切片场景:如果一个切片有大量的元素,而它只有少部分元素被引用,其他元素存在于内存中,但是没有被使用,则会造成内存泄露。代码示例如下:
var s []intfunc main() {sliceOperation()fmt.Println(s)}func sliceOperation() {a := make([]int, 0, 10)a = append(a, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)s = a[0:4]}上述代码中,切片
a的元素有10个,而切片s是基于a创建的,它底层所指向的数组与a所指向的数组是同一个,只不过范围为前四个元素,而后六个元素依然存在于内存中,却没有被使用,这样会造成内存泄露。为了避免内存泄露,我们可以对代码进行改造:s = a[0:4]→s = append(s, a[0:4]...),通过append进行元素追加,这样切片a底层的数组没有被引用,后面会被gc。
7.切片技巧
Go在Github的官方Wiki上介绍了切片的技巧SliceTricks[4],另外这个项目Go Slice Tricks Cheat Sheet[5]基于SliceTricks做了一系列的图,比较直观。

AppendVector
这个是添加一个切片的操作,上面我们在切片操作中已经介绍过。
a = append(a, b...)

Copy
这边给我们展示了三种copy的写法:
b := make([]T, len(a))
copy(b, a)// 效率一般比上面的写法慢,但是如果有更多,他们效率更好
b = append([]T(nil), a...)
b = append(a[:0:0], a...)// 这个实现等价于make+copy。
// 但在Go工具链v1.16上实际上会比较慢。
b = append(make([]T, 0, len(a)), a...)

Cut
截掉切片[i,j)之间的元素:
a = append(a[:i], a[j:]...)

Cut(GC)
上面的Cut如果元素是指针的话,会存在内存泄露,所以我们要对删除的元素设置nil,等待GC。
copy(a[i:], a[j:])
for k, n := len(a)-j+i, len(a); k < n; k++ {a[k] = nil // or the zero value of T
}
a = a[:len(a)-j+i]

Delete
删除索引位置i的元素:
a = append(a[:i], a[i+1:]...)
// or
a = a[:i+copy(a[i:], a[i+1:])]

Delete(GC)
删除索引位置i的元素:
copy(a[i:], a[i+1:])
a[len(a)-1] = nil // or the zero value of T
a = a[:len(a)-1]

Delete without preserving order
删除索引位置i的元素,把最后一位放到索引位置i上,然后把最后一位元素删除。这种方式底层并没有发生复制操作。
a[i] = a[len(a)-1]
a = a[:len(a)-1]

Delete without preserving order(GC)
上面的删除操作,元素是一个指针的类型或结构体指针字段,会存在最后一个元素不能被GC掉,造成泄露,把末尾的元素设置nil,等待GC。
a[i] = a[len(a)-1]
a[len(a)-1] = nil
a = a[:len(a)-1]

Expand
这个本质上是多个append的组合操作。
a = append(a[:i], append(make([]T, j), a[i:]...)...)

Extend
用新列表扩展原来的列表
a = append(a, make([]T, j)...)

Filter (in place)
下面代码演示原地删除Go切片元素:
n := 0
for _, x := range a {if keep(x) {a[n] = xn++}
}
a = a[:n]

Insert
a = append(a[:i], append([]T{x}, a[i:]...)...)
第二个append会产生新的切片,产生一次copy,可以用以下代码方式,可免去第二次的copy:
s = append(s, 0 /* 先添加一个0值*/)
copy(s[i+1:], s[i:])
s[i] = x

InsertVector
下面代码演示插入向量(封装了动态大小数组的顺序容器)的实现:
a = append(a[:i], append(b, a[i:]...)...)
func Insert(s []int, k int, vs ...int) []int {if n := len(s) + len(vs); n <= cap(s) {s2 := s[:n]copy(s2[k+len(vs):], s[k:])copy(s2[k:], vs)return s2}s2 := make([]int, len(s) + len(vs))copy(s2, s[:k])copy(s2[k:], vs)copy(s2[k+len(vs):], s[k:])return s2
}a = Insert(a, i, b...)

Push
a = append(a, x)

Pop
x, a = a[len(a)-1], a[:len(a)-1]

Push Front/Unshift
a = append([]T{x}, a...)

Pop Front/Shift
x, a = a[0], a[1:]

7 切片额外技巧
Filtering without allocating
下面例子演示数据过滤的时候,b基于原来的a存储空间来操作,并没有重新生成新的存储空间。
b := a[:0]
for _, x := range a {if f(x) {b = append(b, x)}
}
为了让截取之后没有使用的存储被GC掉,需要设置成nil:
for i := len(b); i < len(a); i++ {a[i] = nil // or the zero value of T
}

Reversing
反转操作演示:
for i := len(a)/2-1; i >= 0; i-- {opp := len(a)-1-ia[i], a[opp] = a[opp], a[i]
}
还有一种方法:
for left, right := 0, len(a)-1; left < right; left, right = left+1, right-1 {a[left], a[right] = a[right], a[left]
}

Shuffling
洗牌算法。算法思想就是从原始数组中随机抽取一个新的数字到新数组中。
for i := len(a) - 1; i > 0; i-- {j := rand.Intn(i + 1)a[i], a[j] = a[j], a[i]
}
go1.10之后有内置函数Shuffle[6]

Batching with minimal allocation
做批处理大的切片的时候,这个技巧可以了解下:
actions := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
batchSize := 3
batches := make([][]int, 0, (len(actions) + batchSize - 1) / batchSize)for batchSize < len(actions) {actions, batches = actions[batchSize:], append(batches, actions[0:batchSize:batchSize])
}
batches = append(batches, actions)// 结果:
// [[0 1 2] [3 4 5] [6 7 8] [9]]

In-place deduplicate (comparable)
删除有序数组中的重复项:
import "sort"in := []int{3,2,1,4,3,2,1,4,1} // any item can be sorted
sort.Ints(in)
j := 0
for i := 1; i < len(in); i++ {if in[j] == in[i] {continue}j++// preserve the original data// in[i], in[j] = in[j], in[i]// only set what is requiredin[j] = in[i]
}
result := in[:j+1]
fmt.Println(result) // [1 2 3 4]

Move to front, or prepend if not present, in place if possible.
下面代码演示移动指定元素到头部:
// moveToFront moves needle to the front of haystack, in place if possible.
func moveToFront(needle string, haystack []string) []string {if len(haystack) != 0 && haystack[0] == needle {return haystack}prev := needlefor i, elem := range haystack {switch {case i == 0:haystack[0] = needleprev = elemcase elem == needle:haystack[i] = prevreturn haystackdefault:haystack[i] = prevprev = elem}}return append(haystack, prev)
}haystack := []string{"a", "b", "c", "d", "e"} // [a b c d e]
haystack = moveToFront("c", haystack) // [c a b d e]
haystack = moveToFront("f", haystack) // [f c a b d e]


Sliding Window
下面实现根据size的滑动窗口输出:
func slidingWindow(size int, input []int) [][]int {// returns the input slice as the first elementif len(input) <= size {return [][]int{input}}// allocate slice at the precise size we needr := make([][]int, 0, len(input)-size+1)for i, j := 0, size; j <= len(input); i, j = i+1, j+1 {r = append(r, input[i:j])}return r
}func TestSlidingWindow(t *testing.T) {result := slidingWindow(2, []int{1, 2, 3, 4, 5})fmt.Println(result) // [[1 2] [2 3] [3 4] [4 5]]
}

最后的技巧章节参考微信公众号太白技术: Go切片与技巧(附图解)
相关文章:
Go 切片常用操作与使用技巧
1.什么是切片 在 Go 语言中的切片(slice)是一种灵活的动态数组,它可以自动扩展和收缩,是 Go 语言中非常重要的数据结构之一。切片是基于数组实现的,它的底层是数组,可以理解为对底层数组的抽象。它会生成一…...
2024 中青杯高校数学建模竞赛(A题)数学建模完整思路+完整代码全解全析
你是否在寻找数学建模比赛的突破点?数学建模进阶思路! 作为经验丰富的数学建模团队,我们将为你带来2024 长三角高校数学建模竞赛(A题)的全面解析。这个解决方案包不仅包括完整的代码实现,还有详尽的建模过…...
开源与闭源:AI模型发展的双重路径之争
前言 随着人工智能(AI)技术的飞速发展,AI模型的应用已经渗透到各行各业,从医疗、金融到制造、教育,无不受到AI技术的深刻影响。在讨论一个AI模型“好不好”“有没有发展”时,绕不过“开源”和“闭源”两条…...
微信小程序---小程序文档配置(2)
一、小程序文档配置 1、小程序的目录结构 1.1、目录结构 小程序包含一个描述整体程序的 app 和多个描述各自页面的 page 一个小程序主体部分由三个文件组成,必须放在项目的根目录 比如当前我们的《第一个小程序》项目根目录下就存在这三个文件: 1…...
15:00面试,15:08就出来了,问的问题有点变态。。。
从小厂出来,没想到在另一家公司又寄了。 到这家公司开始上班,加班是每天必不可少的,看在钱给的比较多的份上,就不太计较了。没想到8月一纸通知,所有人不准加班,加班费不仅没有了,薪资还要降40%…...
电磁兼容(EMC):去耦电容设计详解
目录 1. 概念 2. 去耦电容工作机理 3. 去耦电容大小选择 4. 去耦电容PCB布局 电容在电路中不同作用有不同的称呼去耦电容、旁路电容、储能电容,而这些作用又可以统称为滤波。本文将详细解读一下三者之间的差别,并着重说明一下去耦电容的设计方法。 …...
《数组逆序输出》
描述 编写程序,输入10个整数n存入,再按逆序重新存放后再输出。 输入描述 输入共10个数。 输出描述 输出共1行,每个数字用空格隔开。 样例输入 1 -5 -4 -3 -2 -1 0 1 2 3 4 样例输出 1 4 3 2 1 0 -1 -2 -3 -4 -5 提示 对于100%的数据…...
必应崩了?
目录 今天使用必应发现出现了不能搜索,弹出乱码的情况。 搜了一下,发现其他人也出现了同样的问题。 使用Edge浏览器的话,可以试着改一下DNS,有可能会恢复正常(等官方修复了记得改回来) 使用谷歌浏览器打开…...
Elasticsearch集群和Logstash、Kibana部署
1、 Elasticsearch集群部署 服务器 安装软件主机名IP地址系统版本配置ElasticsearchElk10.3.145.14centos7.5.18042核4GElasticsearchEs110.3.145.56centos7.5.18042核3GElasticsearchEs210.3.145.57centos7.5.18042核3G 软件版本:elasticsearch-7.13.2.tar.gz 示…...
网络的基础理解
文章目录 网络的基础认识 网络协议协议分层OSI七层模型TCP/IP 五层/四层 模型 网络的基础认识 先来看下面几个问题 什么是网络? 网络就是有许多台设备包括计算机单不仅限于计算机,这些设备通过相互通信所组成起来系统,我们称之为网络所以如…...
Android Studio 与 Gradle 及插件版本兼容性
Android Studio 开始新项目时,会自动创建其中部分文件,并为其填充合理的默认值。 项目文件结构布局: 一、Android Gradle 及插件作用: Android Studio 构建系统以 Gradle 为基础,并且 Android Gradle 插件 (AGP) 添加…...
【BUG】Edge|联想电脑 Bing 搜索报错“Ref A: 乱码、 Ref B:乱码、Ref C: 日期” 的解决办法
文章目录 省流版前言解决办法 详细解释版前言问题描述与排查过程解决办法与总结 省流版 我原以为我解决了,才发的博客,晚上用了一下其他设备发现还是会出现这个问题… 这篇博客并未解决该问题,如果评论里有人解决了这个问题不胜感激&#x…...
深度学习小车操作手册全
深度学习小车_操作手册_全 资源链接 分享文件:深度学习小车_操作手册_全.pdf 链接:https://pan.xunlei.com/s/VNy-KXPDZw64RqQGXiWVEDMRA1?pwdymu4# 复制这段内容后打开手机迅雷App,查看更方便智能车简介 2019 年的特斯拉自动驾驶开放日上…...
Python实现天气数据采集
Python实现天气数据采集 一、需求介绍二、完整代码一、需求介绍 本次天气数据采集的需求是获取每日的最高温、最低温、风力、风向、天气状况、AQI指数,如图所示,完整代码附后: 本次采集的目标网址是2345天气网: 上图的URL中,beijing是城市名称的缩写,54511即为城市代码…...
05 JavaSE-- 异常、IOStream、多线程、反射、Annotation、泛型、序列化
Exception 异常 异常也是对象,也有自己的体系,在这个体系中,所有异常对象的根类是 throwable 接口。异常和 error 错误是不同的概念。 错误是严重的 JVM 系统问题,一般不期待程序员去捕获、处理这些错误,同时…...
c++/c语法基础【2】
目录 1.memset 数组批量赋值 2.字符数组 编辑输入输出: 字符数组直接输入输出%s: gets! string.h 1.strlen:字符串去掉末尾\0的长度...
python 庆余年2收视率数据分析与可视化
为了对《庆余年2》的收视率进行数据分析与可视化,我们首先需要假设有一组收视率数据。由于实际数据可能无法直接获取,这里我们将使用模拟数据来演示整个过程。 以下是一个简单的步骤,展示如何使用Python(特别是pandas和matplotli…...
yolov8训练自己数据集时出现loss值为nan。
具体原因目前暂未寻找到。 解决办法 将参数amp改成False即可。 相关资料: https://zhuanlan.zhihu.com/p/165152789 https://github.com/ultralytics/ultralytics/issues/1148...
[Chapter 5]线程级并行,《计算机系统结构》,《计算机体系结构:量化研究方法》
文章目录 一、互连网络1.1 互连网络概述1.1 互连函数1.1.1 互连函数1.1.2 几种基本的互连函数1.1.2.1 恒等函数1.1.2.2 交换函数1.1.2.3 均匀洗牌函数1.1.2.4 碟式函数1.1.2.5 反位序函数1.1.2.6 移数函数1.1.2.7 PM2I函数 1.2 互连网络的结构参数与性能指标1.2.1 互连网络的结…...
首发!飞凌嵌入式FETMX6ULL-S核心板已适配OpenHarmony 4.1
近日,飞凌嵌入式在FETMX6ULL-S核心板上率先适配了OpenHarmony 4.1,这也是业内的首个应用案例,嵌入式核心板与OpenHarmony操作系统的结合与应用,将进一步推动千行百业的数智化进程。 飞凌嵌入式FETMX6ULL-S核心板基于NXP i.MX 6ULL…...
工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...
【2025年】解决Burpsuite抓不到https包的问题
环境:windows11 burpsuite:2025.5 在抓取https网站时,burpsuite抓取不到https数据包,只显示: 解决该问题只需如下三个步骤: 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...
Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...
短视频矩阵系统文案创作功能开发实践,定制化开发
在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...
嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...
无人机侦测与反制技术的进展与应用
国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机(无人驾驶飞行器,UAV)技术的快速发展,其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统,无人机的“黑飞”&…...
