10分钟了解Golang泛型

泛型是Golang在1.18版本引入的强大工具,能够帮助我们在合适的场合实现简洁、可读、可维护的代码。原文: Go Generics: Everything You Need To Know

alt
导言

可能有人会觉得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 博客 2020 调查结果

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 泛型对其进行重构。

alt

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{134458732}     // sample array
 data32 := []int32{134458732// sample array
 data64 := []int64{134458732// 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 行,分别提供了 intint32int64 类型数据的示例数组。

在第 5、6 和 7 行,分别创建了关键字类型为 intint32int64map 类型 FilterIntFilterInt32FilterInt64

所有类型 map 的值都是布尔值,所有类型都有相同的 hasadd 方法。从本质上讲,add 方法将接受 datum 参数,并在 map 中创建值为 true 的键。根据 map 是否包含作为 datum 传入的键,has 方法将返回 truefalse

现在,第 18 行的函数 FindDuplicateInt、第 29 行的函数 FindDuplicateInt32 和第 40 行的函数 FindDuplicateInt64 实现了相同的逻辑,即验证所提供的数据中是否存在重复数据,如果发现重复数据,则返回 true,否则返回 false

看看这些重复代码。

有没有让你感到恶心🤕?

总之,如果我们在终端运行项目根目录下的 go run leetcode.go,就会编译成功并运行。输出结果应该与此类似:

Duplicate found true
Duplicate found true
Duplicate found true

如果我们要查找 float32float64 或字符串的重复内容,该怎么办?

我们可以为每种类型编写一个实现,为不同类型明确编写多个函数,或者使用接口,或者通过包生成"泛型"代码。这就是"泛型"诞生的过程。

通过泛型,我们可以编写泛型函数来替代多个函数,或使用带有类型转换的接口。

接下来我们用泛型来重构代码,但首先需要熟悉一些术语和概念。

泛型基础知识
1.类型参数
类型参数的可视化表示
类型参数的可视化表示

上图描述的是泛型函数 FindDuplicateT 是类型参数,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 泛型函数的一个很好的示例:

FindDuplicate 泛型函数示例
FindDuplicate 泛型函数示例

我们可以忽略调用代码中的 [int],因为编译器会推断出[int],但我更倾向于加入[int]以提高代码的可读性。

3.约束

在引入泛型之前,Go 接口用于定义方法集。然而,随着泛型约束的引入,接口现在既可以定义类型集,也可以定义方法集。

约束是用于指定允许使用的泛型的接口,在上述 FindDuplicate 函数中使用了 any 约束。

❗️Pro 提示:除非必要,否则避免使用 any 接口约束。

在底层实现上,any关键字只是一个空接口,这意味着可以用 interface{} 替换,编译时不会出现任何错误。

Go 泛型中约束的可视化表示
Go 泛型中约束的可视化表示

上述接口约束允许使用 intint16int32int64 类型。这些类型是约束联合体,用管道符 | 分隔类型。

约束在以下几个方面有好处:

  • 通过类型参数定义了一组允许的类型
  • 明确发现泛型函数的误用
  • 提高代码可读性
  • 有助于编写更具可维护性、可重用性和可测试性的代码

☝️ 简单提一下:使用约束时有一个小问题

请看下面的代码:

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)

尽管自定义类型 CustomTypeint16 类型,但 printValue 泛型函数的类型参数约束无法识别。

鉴于函数约束不允许使用该类型,这也是合理的。不过,可以修改 printValue 函数,使其接受我们的自定义类型。

现在,更新 printValue 函数如下:

func printValue[T int16 | CustomType](value T) { 
    fmt.Println(value)
}

使用管道操作符,我们将自定义类型 CustomType 添加到 printValue 泛型函数类型参数的约束中,现在有了一个联合约束。

如果我们再次运行该程序,编译和运行都不会出现任何错误。

但是,等等!为什么需要 int16 类型和"int16"类型的约束联合?

alt

我们将在下一节介绍波浪线 ~ 运算符。

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{134458732}     // sample array
 data32 := []int32{134458732// sample array
 data64 := []int64{134458732// 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 comparableT 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(123"c")
}

func printValues[AB anyC comparable](a, a1 A, b B, c C) {
 fmt.Println(a, a1, b, c)
}

如果编译并成功运行,预期输出结果将是:

1 2 3 c

在函数方括号[]中,我们添加了多个类型参数。类型参数 AB 共享同一个约束条件。在函数括号中,参数 aa1 共享同一个类型参数 any 约束条件。

现在更新主函数,如下所示。

...
func main() {
 printValues(12.13"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 类型?

原因就在这里--在编译过程中,编译器会根据函数括号中的类型参数约束进行推断。可以看到,aa1 共享同一个类型参数 A,约束条件是 any(允许所有类型)。

编译器会根据调用代码的变量类型进行推断,并在编译过程中使用函数括号中的类型参数约束来检查类型。

可以看到,aa1 具有相同的类型参数 A,并带有 any 约束。因此,aa1 必须具有相同的类型,因为它们在用于类型推导的函数括号中共享相同的类型参数。

尽管类型参数 AB 共享同一个约束条件,但 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",并设个星标吧,如果能一键三连(转发、点赞、在看),则能给我带来更多的支持和动力,激励我持续写下去,和大家共同成长进步!

参考资料
[1]

泛型 - 维基百科: 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 多平台发布

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

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

相关文章

基于STM32单片机的室内温湿度及PM2.5浓度监测报警系统

基于STM32单片机的室内温湿度及PM2.5浓度监测报警系统 摘要: 本文设计并实现了一个基于STM32单片机的室内温湿度及PM2.5浓度监测报警系统。该系统通过集成温湿度传感器和PM2.5传感器,实时监测室内环境参数,并将数据通过液晶显示屏实时显示。…

docker修改默认安装路径

docker安装之后默认在 /etc/docker 在/etc/docker 文件下有一个daemon -json 没有就新增 {"registry-mirrors": ["https://kfwkfulq.mirror.aliyuncs.com","https://2lqq34jg.mirror.aliyuncs.com","https://pee6w651.mirror.aliyuncs.c…

【计算机网络】物理层传输介质 习题3

双绞线是用两根绝缘导线绞合而成的,绞合的目的是( )。 A.减少干扰 B.提高传输速度 C.增大传输距离 D.增大抗拉强度 在电缆中采用屏蔽技术带来的好处主要是( ) A.减少信号衰减 B. 减少电磁干扰辐射 C.减少物理损坏 D. 减少电缆的阻抗 利用一根同轴电缆互连主机构成…

Linux-页(page)和页表

本文在页表方面参考了这篇博客,特别鸣谢! 【Linux】页表的深入分析 1. 页帧和页框 页帧(page frame)是内存的最小可分配单元,也开始称作页框,Linux下页帧的大小为4KB。 内核需要将他们用于所有的内存需求&a…

Linux(Ubuntu24.04) 安装 MinIO

本文所使用的 Ubuntu 系统版本是 Ubuntu 24.04 ! # 1、下载 MinIO wget https://dl.min.io/server/minio/release/linux-amd64/minio# 2、添加可执行权限 chmod x minio# 3、导出环境变量,用于设置账号密码,我设置的账号和密码都是 minioadmin export MI…

[蓝桥杯]真题讲解:AB路线(BFS+分层图)

[蓝桥杯]真题讲解&#xff1a;AB路线&#xff08;BFS分层图&#xff09; 一、视频讲解二、正解代码1、C2、python33、Java 一、视频讲解 [蓝桥杯]真题讲解&#xff1a;AB路线&#xff08;BFS分层图&#xff09; 二、正解代码 1、C #include<bits/stdc.h> #define INF …

PyCharm 集成 Git

目录 1、配置 Git 忽略文件 2、定位Git 3、使用pycharm本地提交 3.1、初始化本地库 3.2、添加到暂存区 3.3、提交到本地库 3.4、切换版本 4、分支操作 4.1、创建分支 4.2、切换分支 4.3、合并分支 5、解决冲突 1、配置 Git 忽略文件 作用&#xff1a;与项目的实际…

问题:幂等性 分布式session

web项目中请求线程到service层的时候远程调用服务之前是串行化执行每个任务都要get阻塞等待任务完成&#xff0c;举例当用户在购物车页面点击去结算就会请求后台toTrade请求获取订单确认的详情数据并渲染到订单详情页&#xff0c;现在在toTrade请求中使用异步任务编排Completab…

Linux下安装mysql8.0(以tar.xz包安装--编译安装)

前言&#xff1a;原文在我的博客网站中&#xff0c;持续更新数通、系统方面的知识&#xff0c;欢迎来访&#xff01; Linux下安装mysql8.0&#xff08;以tar.xz包安装--编译安装&#xff09;https://myweb.myskillstree.cn/126.html 目录 一、下载对应自己glic版本的MySQL …

1.基于python的单细胞数据预处理-降维可视化

目录 降维的背景PCAt-sneUMAP检查质量控制中的指标 参考&#xff1a; [1] https://github.com/Starlitnightly/single_cell_tutorial [2] https://github.com/theislab/single-cell-best-practices 降维的背景 虽然特征选择已经减少了维数&#xff0c;但为了可视化&#xff0…

C++语法|智能指针的实现及智能指针的浅拷贝问题、auto_ptr、scoped_ptr、unique_ptr、shared_ptr和weak_ptr详细解读

文章目录 1.自己实现智能指针智能指针引起的浅拷贝问题尝试定义自己的拷贝构造函数解决浅拷贝 2.不带引用计数的智能指针auto_ptrscoped_ptrunique_ptr&#xff08;推荐&#xff09; 3.带引用计数的智能指针模拟实现引用计数shared_ptr和weak_ptr循环引用&#xff08;交叉引用&…

DDD架构理论详解

文章目录 一、概念入门1. 概念简介2. DDD的核心理念3. 范式4. 模型5. 框架6. 方法论7. 软件设计的主要活动 二、DDD核心理论1. Domain领域层都包含什么&#xff1f;2. 聚合、实体和值对象3. 仓储&#xff0c;封装持久化数据4. 适配&#xff08;端口&#xff09;&#xff0c;调用…

AI应用案例:新闻文本分类

随着科学技术的不断发展&#xff0c;互联网技术得以快速的发展和普及&#xff0c;并已在各行各业得到了广泛的应用&#xff0c;从中致使了网络上的信息呈现出爆炸式的增长状态&#xff0c;达到了“足不出户&#xff0c;万事皆知”的境况&#xff0c;充分体现了互联网新闻给生活…

Java实现自定义注解,实现不需要token 验证就可以访问接口

目录 1 问题2 实现 1 问题 一个springboot 项目&#xff0c;需要token 验证&#xff0c;前端传过来token ,我们一般在项目全局写一个过滤器&#xff0c;去验证前端传过来的token ,如果有哪些接口不需要token验证&#xff0c;那么就排除这些接口&#xff0c;这个也需要配置。 …

前端 | TED打卡号分类查询

文章目录 &#x1f4da;实现效果&#x1f4da;模块实现解析&#x1f407;html&#x1f407;css&#x1f407;javascript &#x1f4da;实现效果 提供完整TED打卡号对应TED标题的查询列表 根据分类按需查询 &#x1f4da;模块实现解析 &#x1f407;html 搭框架<div cl…

Android Studio连接MySQL8.0

【序言】 移动平台这个课程要做一个app的课设&#xff0c;我打算后期增加功能改成毕设&#xff0c;就想要使用MySQL来作为数据库&#xff0c;相对于SQLlite来说&#xff0c;我更熟悉MySQL一点。 【遇到的问题】 一直无法连接上数据库&#xff0c;开始的时候查了很多资料&#…

海外云手机解决海外社交媒体运营难题

随着全球数字化浪潮的推进&#xff0c;海外社交媒体已成为外贸企业拓展市场、提升品牌影响力的重要阵地。Tiktok、Facebook、领英、twitter等平台以其庞大的用户基础和高度互动性&#xff0c;为企业提供了前所未有的营销机会。本文将介绍如何通过海外云手机&#xff0c;高效、快…

eNSP中小型园区网络拓扑搭建(下)

→b站直通车&#xff0c;感谢大佬← →eNSP中小型园区网络拓扑搭建&#xff08;上&#xff09;← 不带配置命令的拓扑图已上传~ 配置ospf SW5 # ospf 1 router-id 5.5.5.5area 0.0.0.0network 192.168.51.5 0.0.0.0network 192.168.52.5 0.0.0.0area 0.0.0.10network 192.1…

elk + filebeat 8.4.3 收集nginx日志(docker部署)

ELK filebeat docker部署 一、 elasticsearch部署1、运行elasticsearch临时配置容器2、拷贝文件目录到本地3、检查elasticsearch.yml4、删除之前elastic&#xff0c;运行正式容器5、docker logs记录启动日志 二、部署kibana1、运行kibana临时配置容器2、docker拷贝配置文件到本…

数据链路层——计算机网络学习笔记三

使用点对点信道的数据链路层 前言&#xff1a; 1.数据链路层的重要性&#xff1a;网络中的主机、路由器都必须实现数据连输层&#xff1b; 2.数据链路层中使用的信道&#xff1a; 点对点信道&#xff1a;这种信道是一对一的通信方式&#xff1b; 广播信道&#xff1a;使用一对多…