10分钟了解Golang泛型
泛型是Golang在1.18版本引入的强大工具,能够帮助我们在合适的场合实现简洁、可读、可维护的代码。原文: Go Generics: Everything You Need To Know
导言
可能有人会觉得Go泛型很难,因此想要借鉴其他语言(比如Java、NodeJS)的泛型实践。事实上Go泛型很容易学,本文希望能帮助读者更好的理解Go泛型。
👉注:本文不会将 Go 泛型与其他语言的泛型实现进行比较,但会帮助你理解 Go 泛型元素背后的上下文、结构及其原理。
前置条件
要编写本文中的示例代码,需要:
-
在计算机上安装 Go 1.18+ -
对Golang结构、类型、函数和方法有最低限度的了解
概述
在 2020 年之前,Go泛型既是风险也是机遇。
当 Go 泛型在 2009 年左右被首次提出时(当时该编程语言已经公开),该特性是 Go 语言的主要弱点之一(Go 团队调查发现)。
此后,Go 团队在 Go 草案设计中接受了许多泛型实现,并在 Go 1.18 版本中首次引入了泛型。
Go 2020 调查显示,自 Go 语言诞生以来,Go 社区一直要求引入泛型功能。
Go 开发人员(以及 Go 团队成员)看到这一缺陷阻碍了 Go 语言的发展,同时,如果得到修复,Go将具有更大的灵活性和性能。
什么是程序设计中的泛型?
根据维基百科[1]的解释,泛型编程是一种计算机编程风格,在这种编程风格中,算法的具体类型可以在以后指定。
简单解释一下:泛型是一种可以与多种类型结合使用的类型,泛型函数是一种可以与多种类型结合使用的函数。
☝️ 简单提一下:尽管"泛型"在过去和现在都可以通过
interface{}、反射包或代码生成器在 Go 中实现,但还是要提一下在使用这三种方法之前需要仔细考虑。
为了帮助我们以实用的方式理解和学习 Go 泛型,我们将在本文稍后部分提供示例代码。
但要知道,既然 Go 泛型已经可用,就可以消除模板代码,不必担心向后兼容问题,同时还能编写可重用、类型安全和可维护的代码。
那么......为什么需要 Go 泛型?
简而言之,最多可提高 20% 性能。
根据 Go 博客的描述,Go 泛型为 Go 语言增加了三个主要组件:
-
函数和类型的类型参数。 -
将接口类型定义为类型集,包括没有方法的类型。 -
类型推导,允许在调用函数时省略类型参数。
在 Go 1.18 之前没有这种功能吗?
从技术上讲,早在 Go 泛型发布之前,Go 就有一些处理"泛型"的方法:
-
使用"泛型"代码生成器生成 Go 软件包,如 https://github.com/cheekybits/genny [2] -
使用带有 switch语句和类型转换的接口 -
使用带有参数验证的反射软件包
然而,与正式的Go泛型相比,这些方法还远远不够,有如下缺点:
-
使用类型 switch和转换时性能较低 -
类型安全损耗:接口和反射不是类型安全的,这意味着代码可能会传递任何类型,而这些类型在编译过程中会被忽略,从而在运行时引起 panic。 -
Go 项目构建更复杂,编译时间更长 -
可能需要对调用代码和函数代码进行类型断言 -
缺乏对自定义派生类型的支持 -
代码可读性差(使用反射时更明显)
👉注:上述观点并不意味着在 Go 编程中使用接口或反射包不好;它们还有其他用途,应该在合适的场景下应用。
巧合的是,上述几点 ☝️ 使 Go 泛型适合处理目前在 Go 中的泛型实现,因为:
-
类型安全 ( 运行时不会丢失类型,也不需要类型验证、切换或转换) -
高性能 -
Go IDE 的支持 -
向后兼容 ( 使用 Go 1.18+ 重构后,旧版代码仍可运行) -
对自定义数据类型的高度支持
入门:使用 Go 泛型
在开始重构之前,我们借助一个迷你 Go 程序来了解 Go 泛型使用的一些术语和逻辑。
作为实操案例,我们将首先在不使用 Go 泛型的情况下解决 Leetcode 问题。然后,随着我们对这一主题的了解加深,我们将使用 Go 泛型对其进行重构。
Leetcode 问题
有几家公司在技术面试时都问过这个问题,我们对措辞稍作改动,但逻辑不变。Leetcode 链接为:https://leetcode.com/problems/contains-duplicate[3]。
📌问题:给定一个整型(int 或 in32 或 int64)数组
nums,如果任何值在数组中至少出现两次,则返回true;如果每个元素都不同,则返回false。
现在,我们在不使用 Go 泛型的情况下解决这个问题。
进入开发目录,创建一个新的 Go 项目目录,名称不限。我将其命名为 leetcode1。然后将目录更改为新创建的项目目录。
按照惯例,我们在终端的项目根目录下运行 go mod init github.com/username/leetcode1,为项目创建一个 Go 模块。
❗️ 记住:不要忘记将username替换为你自己的 Github 用户名
接下来,创建 leetcode.go 文件并将下面的代码复制进去:
package main
import "fmt"
type FilterInt map[int]bool
type FilterInt32 map[int32]bool
type FilterInt64 map[int64]bool
func main() {
data := []int{1, 3, 4, 4, 5, 8, 7, 3, 2} // sample array
data32 := []int32{1, 3, 4, 4, 5, 8, 7, 3, 2} // sample array
data64 := []int64{1, 3, 4, 4, 5, 8, 7, 3, 2} // sample array
fmt.Printf("Duplicate found %t\n", FindDuplicateInt(data))
fmt.Printf("Duplicate found %t\n", FindDuplicateInt32(data32))
fmt.Printf("Duplicate found %t\n", FindDuplicateInt64(data64))
}
func FindDuplicateInt(data []int) bool {
inArray := FilterInt{}
for _, datum := range data {
if inArray.has(datum) {
return true
}
inArray.add(datum)
}
return false
}
func FindDuplicateInt32(data []int32) bool {
inArray := FilterInt32{}
for _, datum := range data {
if inArray.has(datum) {
return true
}
inArray.add(datum)
}
return false
}
func FindDuplicateInt64(data []int64) bool {
inArray := FilterInt64{}
for _, datum := range data {
if inArray.has(datum) {
return true
}
inArray.add(datum)
}
return false
}
func (r FilterInt) add(datum int) {
r[datum] = true
}
func (r FilterInt32) add(datum int32) {
r[datum] = true
}
func (r FilterInt64) add(datum int64) {
r[datum] = true
}
func (r FilterInt) has(datum int) bool {
_, ok := r[datum]
return ok
}
func (r FilterInt32) has(datum int32) bool {
_, ok := r[datum]
return ok
}
func (r FilterInt64) has(datum int64) bool {
_, ok := r[datum]
return ok
}
再看一下 Leetcode 的问题,程序应该检查输入的数组(可以是 INT、INT32 或 INT64),并找出是否有重复数据,如果有则返回 true,否则返回 false,上面这段代码就是完成这个任务的。
在第 10、11 和 12 行,分别提供了 int、int32 和 int64 类型数据的示例数组。
在第 5、6 和 7 行,分别创建了关键字类型为 int、int32 和 int64 的 map 类型 FilterInt、FilterInt32 和 FilterInt64。
所有类型 map 的值都是布尔值,所有类型都有相同的 has 和 add 方法。从本质上讲,add 方法将接受 datum 参数,并在 map 中创建值为 true 的键。根据 map 是否包含作为 datum 传入的键,has 方法将返回 true 或 false。
现在,第 18 行的函数 FindDuplicateInt、第 29 行的函数 FindDuplicateInt32 和第 40 行的函数 FindDuplicateInt64 实现了相同的逻辑,即验证所提供的数据中是否存在重复数据,如果发现重复数据,则返回 true,否则返回 false。
看看这些重复代码。
有没有让你感到恶心🤕?
总之,如果我们在终端运行项目根目录下的 go run leetcode.go,就会编译成功并运行。输出结果应该与此类似:
Duplicate found true
Duplicate found true
Duplicate found true
如果我们要查找 float32、float64 或字符串的重复内容,该怎么办?
我们可以为每种类型编写一个实现,为不同类型明确编写多个函数,或者使用接口,或者通过包生成"泛型"代码。这就是"泛型"诞生的过程。
通过泛型,我们可以编写泛型函数来替代多个函数,或使用带有类型转换的接口。
接下来我们用泛型来重构代码,但首先需要熟悉一些术语和概念。
泛型基础知识
1.类型参数
上图描述的是泛型函数 FindDuplicate,T 是类型参数,any 是类型参数的约束条件(接下来将讨论约束条件)。
类型参数就像一个抽象的数据层,通常用紧跟函数或类型名称的方括号中的大写字母(多为字母 T)来表示。下面是一些例子:
...
// map type with type parameter T and constraint comparable
type Filter[T comparable] map[T]bool
...
...
// Function FindDuplicate with type parameter T and constraint any
func FindDuplicate[T any](data T) bool {
// find duplicate code
}
...
2.类型推导
泛型函数必须了解其支持的数据类型,才能正常运行。
🎯要点:泛型类型参数的约束条件是在编译时由调用代码确定的代表单一类型的一组类型。
进一步来说,类型参数的约束代表了一系列可允许的类型,但在编译时,类型参数只代表一种类型,因为 Go 是一种强类型的静态检查语言。
❗️提醒:由于 Go 是一种强类型的静态语言,因此会在应用程序编译期间而非运行时检查类型。Go 泛型解决了这个问题。
类型由调用代码类型推导提供,如果泛型类型参数的约束条件不允许使用该类型,代码将无法编译。
由于类型是通过约束知道的,因此在大多数情况下,编译器可以在编译时推断出参数类型。
通过类型推导,可以避免从调用代码中为泛型函数或泛型类型实例化进行人工类型推导。
👉注意:如果编译器无法推断类型(即类型推导失败),可以在实例化时或在调用代码中手动指定类型。
下面是 FindDuplicate 泛型函数的一个很好的示例:
我们可以忽略调用代码中的 [int],因为编译器会推断出[int],但我更倾向于加入[int]以提高代码的可读性。
3.约束
在引入泛型之前,Go 接口用于定义方法集。然而,随着泛型约束的引入,接口现在既可以定义类型集,也可以定义方法集。
约束是用于指定允许使用的泛型的接口,在上述 FindDuplicate 函数中使用了 any 约束。
❗️Pro 提示:除非必要,否则避免使用
any接口约束。
在底层实现上,any关键字只是一个空接口,这意味着可以用 interface{} 替换,编译时不会出现任何错误。
上述接口约束允许使用 int、int16、int32 和 int64 类型。这些类型是约束联合体,用管道符 | 分隔类型。
约束在以下几个方面有好处:
-
通过类型参数定义了一组允许的类型 -
明确发现泛型函数的误用 -
提高代码可读性 -
有助于编写更具可维护性、可重用性和可测试性的代码
☝️ 简单提一下:使用约束时有一个小问题
请看下面的代码:
package main
import "fmt"
type CustomType int16
func main() {
var value CustomType
value = 2
printValue(value)
}
func printValue[T int16](value T) {
fmt.Printf("Value %d", value)
}
在上面的代码中,第 5 行定义了一个名为 CustomType 的自定义类型,其基础类型为 int16。
在第 8 行,声明了一个以 CustomType 为类型的变量,并在第 9 行为其赋值。
然后,在第 10 行调用带有值的 printValue 泛型函数。
...🤔
...🤔
你认为代码可以编译运行吗?
如果我们在终端执行 go run custom-generics.go,就会出现这样的错误。
./custom-type-generics.go:10:12: CustomType does not implement int16 (possibly missing ~ for int16 in constraint int16)
尽管自定义类型 CustomType 是 int16 类型,但 printValue 泛型函数的类型参数约束无法识别。
鉴于函数约束不允许使用该类型,这也是合理的。不过,可以修改 printValue 函数,使其接受我们的自定义类型。
现在,更新 printValue 函数如下:
func printValue[T int16 | CustomType](value T) {
fmt.Println(value)
}
使用管道操作符,我们将自定义类型 CustomType 添加到 printValue 泛型函数类型参数的约束中,现在有了一个联合约束。
如果我们再次运行该程序,编译和运行都不会出现任何错误。
但是,等等!为什么需要 int16 类型和"int16"类型的约束联合?
我们将在下一节介绍波浪线 ~ 运算符。
4.波浪线(Tilde)运算符和基础类型
幸运的是,Go 1.18 通过波浪线运算符引入了底层类型,波浪线运算符允许约束支持底层类型。
在上一步代码示例中,CustomType 类型的底层类型是 int16。现在,我们使用 ~ 波浪线更新 printValue 泛型函数类型参数的约束,如下所示:
func printValue[T ~int16](value T) {
fmt.Println(value)
}
新代码应该是这样的:
package main
import "fmt"
type CustomType int16
func main() {
var value CustomType
value = 2
printValue(value)
}
func printValue[T ~int16](value T) {
fmt.Printf("Value %d", value)
}
再次运行程序,应该可以成功编译和运行。我们删除了约束联合,并在约束中的 int16 类型前用 ~ 波浪线运算符替换了 CustomType。
编译器现在可以理解,CustomType 类型之所以可以使用,仅仅是因为它的底层类型是 int16。
💡 简单来说,
~告诉约束接受任何int16类型以及任何以int16作为底层类型的类型。
下面是一个泛型约束接口示例,它也允许函数声明:
type Number interface {
int | float32 | float64
IsEven() bool
}
不过,下一步还有更多东西要学。
5.预定义约束
Go 团队非常慷慨的为我们提供了一个常用约束的预定义包,可在 golang.org/x/exp/constraints[4] 找到。
以下是预定义约束包中包含的约束示例:
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
type Unsigned interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
type Integer interface {
Signed | Unsigned
}
type Float interface {
~float32 | ~float64
}
type Ordered interface {
Integer | Float | ~string
}
因此,我们可以更新之前示例中的 printValue 泛型函数,使其接受所有整数,具体方法如下。
func printValue[T Integer](value T) {
fmt.Println(value)
}
❗️ 记住:不要忘记导入预定义约束包 golang.org/x/exp/constraints。
重构 Leetcode 示例
现在我们对泛型有了一些了解,接下来重构 FindDuplicate 程序,通过泛型在整数、浮点数和字符串类型的切片及其底层类型中查找是否有重复数据。
具体修改为:
-
创建允许使用整数、浮点和字符串及其底层类型的接口约束 -
使用 go get将约束包下载到项目中,在终端的 Leetcode 根目录中执行如下指令:
go get -u golang.org/x/exp/constraints
-
添加到项目中后,在主函数上方创建名为 AllowedData的约束,如下所示:
type AllowedData interface {
constraints.Ordered
}
constraints.Ordered 是一种约束,允许任何使用支持比较运算符(如 ≤=≥===)的有序类型。
👉注:可以在泛型函数中使用
constraint.Ordered,而无需创建新的接口约束。不过,为了便于学习,我们还是创建了自己的约束AllowData。
-
接下来,删除类型 map 中的所有 FilterIntX 类型,创建一个名为 Filter的新类型,如下所示,该类型以T为类型参数,以AllowedData为约束条件:
type Filter[T AllowedData] map[T]bool
在泛型类型 Filter 前面,声明了 T 类型参数,并指定 map 键只接受类型参数的约束 AllowedData 作为键类型。
-
现在,删除所有 FindDuplicateIntX 函数。然后使用 Go 泛型创建一个新的 FindDuplicate函数,代码如下:
func FindDuplicate[T AllowedData](data []T) bool {
inArray := Filter[T]{}
for _, datum := range data {
if inArray.has(datum) {
return true
}
inArray.add(datum)
}
return false
}
FindDuplicate 函数是一个泛型函数,添加了类型参数 T,并在函数名后面的方括号中指定了 AllowedData 约束,然后用类型参数 T 定义了切片类型的函数参数,并用类型参数 T 初始化了 inArray。
👉注:在函数中声明泛型参数时使用方括号。
-
接下来,更新 has以及add方法,如下所示。
func (r Filter[T]) add(datum T) {
r[datum] = true
}
func (r Filter[T]) has(datum T) bool {
_, ok := r[datum]
return ok
}
因为我们在定义类型 Filter 时已经声明了约束,因此方法中只包含类型参数。
最后,更新调用 FindDuplicateIntX 的调用代码,使用新的泛型函数 FindDuplicate,最终代码如下:
package main
import (
"errors"
"fmt"
"golang.org/x/exp/constraints"
)
type Filter[T AllowedData] map[T]bool
type AllowedData interface {
constraints.Ordered
}
func main() {
data := []int{1, 3, 4, 4, 5, 8, 7, 3, 2} // sample array
data32 := []int32{1, 3, 4, 4, 5, 8, 7, 3, 2} // sample array
data64 := []int64{1, 3, 4, 4, 5, 8, 7, 3, 2} // sample array
fmt.Printf("Duplicate found %t\n", FindDuplicate(data))
fmt.Printf("Duplicate found %t\n", FindDuplicate(data32))
fmt.Printf("Duplicate found %t\n", FindDuplicate(data64))
}
func (r Filter[T]) add(datum T) {
r[datum] = true
}
func (r Filter[T]) has(datum T) bool {
_, ok := r[datum]
return ok
}
func FindDuplicate[T AllowedData](data []T) bool {
inArray := Filter[T]{}
for _, datum := range data {
if inArray.has(datum) {
return true
}
inArray.add(datum)
}
return false
}
现在执行 go run main.go,程序成功编译并运行,预期输出为:
Duplicate found true
Duplicate found true
Duplicate found true
我们成功重构了代码,却没有犯复制粘贴的错误。
6.可比较(comparable)约束
可比较约束与相等运算符(即 == 和≠)相关联。
这是在 Go 1.18 中引入的一个接口,由结构体、指针、接口、管道等类似类型实现。
👉注:Comparable 不用作任何变量的类型。
func Sort[K comparable, T Data](values map[K]T) error {
for k, t := range values {
// code
}
return nil
}
7.约束类型链和类型推导
-
类型链
允许一个已定义的类型参数与另一个类型参数复合的做法被称为类型链。当在泛型结构或函数中定义辅助类型时,这种方法就派上用场了。
示例:
-
约束类型推导
前面我们详细介绍了类型推导,但与类型链无关,可以如下调用上图中的函数:
c := Example(2)
由于 ~T 是类型参数 T 与任意约束条件的复合体,因此在调用 Example 函数时可以推断出类型参数 U。
👉注:2 是整数,是 T 的底层类型。
8.多类型参数和约束
Go 泛型支持多类型参数,但有一个问题,我们看下面的另一个例子:
package main
import "fmt"
func main() {
printValues(1, 2, 3, "c")
}
func printValues[A, B any, C comparable](a, a1 A, b B, c C) {
fmt.Println(a, a1, b, c)
}
如果编译并成功运行,预期输出结果将是:
1 2 3 c
在函数方括号[]中,我们添加了多个类型参数。类型参数 A 和 B 共享同一个约束条件。在函数括号中,参数 a 和 a1 共享同一个类型参数 any 约束条件。
现在更新主函数,如下所示。
...
func main() {
printValues(1, 2.1, 3, "c")
}
...
发生了什么?
我们将 2 的值从 2 改为 2.1,如你所知,这会将 2 的数据类型从 int 改为 float。当我们再次运行程序时,编译失败:
/main.go:6:14: default type float64 of 2.1 does not match inferred type int for A
等等!我们到底有没有声明 int 类型?
原因就在这里--在编译过程中,编译器会根据函数括号中的类型参数约束进行推断。可以看到,a 和 a1 共享同一个类型参数 A,约束条件是 any(允许所有类型)。
编译器会根据调用代码的变量类型进行推断,并在编译过程中使用函数括号中的类型参数约束来检查类型。
可以看到,a 和 a1 具有相同的类型参数 A,并带有 any 约束。因此,a 和 a1 必须具有相同的类型,因为它们在用于类型推导的函数括号中共享相同的类型参数。
尽管类型参数 A 和 B 共享同一个约束条件,但 b 在函数括号中是独立的。
何时使用(或不使用)泛型
总之,请记住一点--大多数用例并不需要 Go 泛型。不过,知道什么时候需要也很有帮助,因为这样可以大大提高工作效率。
这里有一些指导原则:
何时使用 Go 泛型
-
替换多个类型执行相同逻辑的重复代码,或者替换处理切片、映射和管道等多个类型的重复代码 -
在处理容器型数据结构(如链表、树和堆)时 -
当代码逻辑需要对多种类型进行排序、比较和/或打印时
何时不使用 Go 泛型
-
当 Go 泛型会让代码变得更复杂时 -
当指定函数参数类型时 -
当有可能滥用 Go 泛型时。避免使用 Go 泛型/类型参数,除非确定有使用多种类型的重复逻辑 -
当不同类型的实现不同时 -
使用 io.Reader 等读取器时
局限性
目前,匿名函数和闭包不支持类型参数。
Go 泛型的测试
由于 Go 泛型支持编写多种类型的泛型代码,测试用例将与函数支持的类型数量成正比增长。
结论
本文介绍了 Go 中的泛型、与之相关的新术语,以及如何在类型、函数、方法和结构体中使用泛型。
希望能对大家的学习 Go 有所帮助,但请不要滥用 Go 泛型。
收获
-
如果使用得当,Go 泛型的功能会非常强大;但要谨慎,因为能力越大,责任越大。 -
Go 泛型将提高代码的灵活性和可重用性,同时保持向后兼容,从而为 Go 语言增添价值。 -
它简单易用,直接明了,学习周期短,练习有助于更好的理解 Go 泛型及其局限性。 -
过度使用、借用其他语言的泛型实现以及误解会导致 Go 社区出现反模式和复杂性,风险自担。
你好,我是俞凡,在Motorola做过研发,现在在Mavenir做技术工作,对通信、网络、后端架构、云原生、DevOps、CICD、区块链、AI等技术始终保持着浓厚的兴趣,平时喜欢阅读、思考,相信持续学习、终身成长,欢迎一起交流学习。为了方便大家以后能第一时间看到文章,请朋友们关注公众号"DeepNoMind",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的支持和动力,激励我持续写下去,和大家共同成长进步!
泛型 - 维基百科: https://en.wikipedia.org/wiki/Generic_programming#:~:text=Generic%20programming%20is%20a%20style,specific%20types%20provided%20as%20parameters.
[2]genny: https://github.com/cheekybits/genny,
[3]Leetcode: contains duplicate: https://leetcode.com/problems/contains-duplicate/
[4]golang.org/x/exp/constraints: https://golang.org/x/exp/constraints
本文由 mdnice 多平台发布
相关文章:
10分钟了解Golang泛型
泛型是Golang在1.18版本引入的强大工具,能够帮助我们在合适的场合实现简洁、可读、可维护的代码。原文: Go Generics: Everything You Need To Know 导言 可能有人会觉得Go泛型很难,因此想要借鉴其他语言(比如Java、NodeJS)的泛型…...
鸿蒙内核源码分析(Shell解析篇) | 应用窥视内核的窗口
系列篇从内核视角用一句话概括shell的底层实现为:两个任务,三个阶段。其本质是独立进程,因而划到进程管理模块。每次创建shell进程都会再创建两个任务。 客户端任务(ShellEntry): 负责接受来自终端(控制台)敲入的一个个字符&…...
TypeScript在前端项目的渐进式采用策略
渐进式采用 TypeScript 在前端项目中的策略通常包括: 引入TypeScript 如果我们有一个简单的JavaScript模块utils.js,它包含一个函数用于计算两数之和: // utils.js export function add(a, b) {return a b; }首先,我们将文件扩展名改为.t…...
C++容器常用集合(附传送门)
C常用的容器: string容器 C容器——string-CSDN博客 储存字符串的 vector容器 C容器——vector-CSDN博客 向量是动态数组,可以自动扩展以容纳更多元素。 插入和删除元素的时间复杂度取决于操作的位置 tuple容器(元组) C容器…...
基于springboot的校园资料分享平台源码数据库
基于springboot的校园资料分享平台源码数据库 随着信息互联网购物的飞速发展,国内放开了自媒体的政策,一般企业都开始开发属于自己内容分发平台的网站。本文介绍了校园资料分享平台的开发全过程。通过分析企业对于校园资料分享平台的需求,创…...
卷积神经网络(CNN)
大家好,这里是七七,今天来更新关于CNN相关的内容同了。本文是针对CNN原理的说明,但对于小白不是非常友好,建议先掌握神经网络相应知识再进行阅读哦。 一、卷积与互相关 卷积 卷积运算是对两个函数进行的一种数学运算,…...
Linux入门攻坚——22、通信安全基础知识及openssl、CA证书
Linux系统常用的加解密工具:OpenSSL,gpg(是pgp的实现) 加密算法和协议: 对称加密:加解密使用同一个秘钥; DES:Data Encryption Standard,数据加密标准&…...
无障碍Web开发:遵循WCAG标准构建包容性用户体验
无障碍Web开发旨在确保所有用户,无论其身体条件或能力如何,都能轻松、有效地访问和使用Web内容。遵循Web Content Accessibility Guidelines (WCAG) 标准是实现这一目标的关键。以下是一些基于WCAG标准的无障碍Web开发实践,以构建更具包容性的…...
Isaac Sim 3(学习笔记5.8)
Isaac Sim 利用深度学习获取mask掩码图 参考内容 Kubernetes官网 在 Linux 系统中安装并设置 kubectl | Kubernetes准备开始 kubectl 版本和集群版本之间的差异必须在一个小版本号内。 例如:v1.30 版本的客户端能与 v1.29、 v1.30 和 v1.31 版本的控制面通信。 用…...
对象定义成final类型还能改变吗
如果一个Java对象被定义为final类型,那么它的引用不能被改变,但是对象本身的状态仍然可以被修改。这意味着你可以改变final对象的属性,但是不能将其引用指向另一个对象。 例如,下面的代码中,虽然person对象被声明为fi…...
Vue Router 路由hash和history模式
文章目录 hash和history模式区别Hash 模式History 模式 在 Vue 中,路由的两种主要模式是 hash 和 history,默认的路由模式是hash模式。。这两种模式决定了 URL 的外观以及浏览器如何处理 URL 的变化。 hash和history模式区别 特性Hash 模式History 模…...
【xrframe】优化ar相机中加载模型效果
方法一:定义渲染width和height //组件生命周期:在视图层布局完成后执行ready() {const info wx.getSystemInfoSync();//在小程序中同步获取系统信息const width info.windowWidth;//获取屏幕的宽度(单位为物理像素)const heigh…...
解决 SyntaxError: Unexpected token ‘.‘ 报错问题
这个报错一般是编译问题,浏览器的版本过低没通过代码 解决办法: 在package.json文件中加上这个 "browserslist": ["> 1%","last 2 versions","not dead","not ie < 6","Android > 4&…...
谷歌插件V3知识点
1.background.js与content.js与popup.js对比: background.js 生命周期:一开始就执行,最早执行且一直执行; 作用:放置全局的、需要一直运行的代码,权限非常高几乎调用所有Chrome api,还可以发起跨域请求; content.js 生…...
webrtc windows 编译,以及peerconnection_client
webrtc windows环境编译,主要参考webrtc官方文档,自备梯子 depot tools 安装 Install depot_tools 因为我用的是windows,这里下载bundle 的安装包,然后直接解压,最后设置到环境变量PATH。 执行gn等命令不报错&…...
geotrust企业通配符证书2990
随着时代的变化,人们获取信息的方式由报纸、书籍变为手机、电脑,因此很多企事业单位用户开始在互联网中创建网站来进行宣传,吸引客户。为了维护网站安全环境,保护客户数据,企事业单位也开始使用SSL数字证书,…...
网络安全科普:保护你的数字生活
# 网络安全科普:保护你的数字生活 ## 引言 在数字化时代,网络安全已成为每个人都必须面对的问题。从个人隐私保护到金融交易安全,网络的安全性直接关系到我们的日常生活。因此,普及网络安全知识,提高公众的网络安全意…...
Java实战:递归查找指定后缀名的文件
在日常的软件开发中,经常需要处理文件操作。假设我们有一个需求:从一个包含大量JSON文件的文件夹中提取出所有的JSON文件以进行进一步处理。本文将介绍如何利用Java编写一个高效的方法来递归查找指定后缀名的文件。 代码实现: import java.i…...
Linux 操作系统网络编程1
目录 1、网络编程 1.1 OSI 网络七层模型 1.1.1 OSI 参考模型 1.1.2 网络数据传输过程 2 传输层通信协议 2.1 TCP 2.1.1 TCP的3次握手过程 2.1.2 TCP四次挥手过程 2.2 UDP 3 网络编程的IP地址 4 端口 5 套接字 1、网络编程 1.1 OSI 网络七层模型 1.1.1 OSI 参考模型…...
future wait_for()成员、shared_future
future wait_for()成员 wait_for():等待其异步操作操作完成或者超出等待,用于检查异步操作的状态。wait_for()可以接受一个std::chrono::duration类型的参数,它表示等待的最大时间,会返回一个std::future_status枚举值࿰…...
工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...
ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...
CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别
【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而,传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案,能够实现大范围覆盖并远程采集数据。尽管具备这些优势…...
宇树科技,改名了!
提到国内具身智能和机器人领域的代表企业,那宇树科技(Unitree)必须名列其榜。 最近,宇树科技的一项新变动消息在业界引发了不少关注和讨论,即: 宇树向其合作伙伴发布了一封公司名称变更函称,因…...
Pydantic + Function Calling的结合
1、Pydantic Pydantic 是一个 Python 库,用于数据验证和设置管理,通过 Python 类型注解强制执行数据类型。它广泛用于 API 开发(如 FastAPI)、配置管理和数据解析,核心功能包括: 数据验证:通过…...
MySQL体系架构解析(三):MySQL目录与启动配置全解析
MySQL中的目录和文件 bin目录 在 MySQL 的安装目录下有一个特别重要的 bin 目录,这个目录下存放着许多可执行文件。与其他系统的可执行文件类似,这些可执行文件都是与服务器和客户端程序相关的。 启动MySQL服务器程序 在 UNIX 系统中,用…...
