Go Slice的底层实现原理深度解析

文章目录

    • 切片的诞生:数组的延伸
      • 切片的结构
      • 初始化切片
    • 切片的内存管理
      • 扩容机制
    • 实例分析:切片的动态特性
    • 切片与性能
      • 性能对比
    • 切片的并发安全
      • 并发场景下的切片操作
    • 切片与接口
      • 切片与空接口
    • 切片的遍历与操作
      • 遍历切片
      • 切片的切片操作
    • 切片的垃圾回收
      • 切片的生命周期
    • 切片与性能优化
      • 预分配与扩容
      • 避免不必要的切片操作
      • 使用切片池
      • 切片与并发
      • 切片的并发访问
    • 切片与错误处理
      • 切片操作的错误检查
      • 切片的边界检查
      • 切片与panic/recover
    • 切片的高级应用
      • 切片作为队列
      • 切片与排序
    • 切片与迭代器
      • 自定义迭代器
    • 切片与反射
      • 使用反射操作切片
    • 切片与接口
      • 切片与接口的结合
    • 切片与并发映射
      • 并发安全的切片映射
    • 切片与错误处理
      • 使用`defer`​和`recover`​处理切片错误
    • 切片与算法
      • 切片排序
      • 切片搜索
    • 切片与数据流
      • 使用切片处理文件数据
      • 使用切片处理网络数据
    • 切片与数据结构
      • 切片作为数据结构的一部分
      • 使用切片实现自定义数据结构
    • 切片与标准库
      • 使用标准库处理切片
      • 切片与并发
      • 切片与错误处理

在Go语言的世界里,切片(Slice)是一种极其重要的数据结构,它以其灵活性和高效性在众多编程场景中扮演着核心角色。本文将深入探讨Go切片的底层实现原理,通过实例和源码分析,带你领略Go语言设计之美。

切片的诞生:数组的延伸

在Go中,数组是一种固定长度的数据结构,这在某些情况下限制了它的使用。为了解决这个问题,切片应运而生。切片基于数组实现,但它提供了一种动态调整大小的能力,使得数据的存储和管理更加灵活。

切片的结构

切片的内部结构在src/runtime/slice.go​中定义,它包含三个主要部分:

  • array​:指向底层数组的指针。
  • len​:切片的长度,即当前切片包含的元素数量。
  • cap​:切片的容量,即底层数组能够容纳的元素数量。

初始化切片

切片有多种初始化方式,包括直接声明、使用字面量、使用make​函数以及从已有的切片或数组中截取。这些初始化方式在底层都会调用相应的函数,如runtime.makeslice​,它负责计算所需内存大小并分配。

切片的内存管理

切片的内存管理是其高效性的关键。当切片的len​小于cap​时,我们可以通过追加元素来扩展切片,而不需要重新分配整个底层数组。这种设计使得切片在添加元素时具有很高的效率。

扩容机制

len​达到cap​时,切片需要扩容。扩容过程中,Go会分配一个新的更大的底层数组,并将原数组中的元素复制到新数组中。这个过程在runtime.growslice​函数中实现。

实例分析:切片的动态特性

让我们通过一个简单的例子来观察切片的动态特性。

package mainimport "fmt"func main() {slice1 := make([]int, 0, 5) // 初始化一个长度为0,容量为5的切片slice1 = append(slice1, 1, 2, 3) // 追加元素,此时len=3,cap=5// 当len达到cap时,扩容会发生slice1 = append(slice1, 4, 5) // 此时len=5,cap=5,扩容后len=5,cap>5fmt.Println(slice1) // 输出:[1 2 3 4 5]
}

在这个例子中,我们可以看到切片在追加元素时如何动态调整其大小。

切片与性能

切片的设计使得它在性能上具有优势。由于它基于数组,所以它提供了对数组的快速访问。同时,它的动态特性使得它在处理不确定数量的元素时更加高效。

性能对比

与其他语言中的动态数组相比,Go切片在内存管理和性能上都有显著的优势。这得益于Go语言的编译器优化和运行时的高效内存管理。

切片的并发安全

在并发编程中,数据结构的安全性至关重要。Go语言的切片在设计时就考虑了并发安全。虽然切片本身不是线程安全的,但是它的操作(如追加、删除等)在单线程环境下是安全的。在多线程环境下,需要开发者手动同步对切片的访问。

并发场景下的切片操作

在多线程环境中,如果多个goroutine同时对同一个切片进行操作,可能会导致竞态条件。为了避免这种情况,可以使用互斥锁(mutex)来保护对切片的访问。

package mainimport ("fmt""sync"
)func main() {var slice []intvar lock sync.Mutex// 启动两个goroutine,分别向切片中追加元素for i := 0; i < 2; i++ {go func(i int) {lock.Lock()defer lock.Unlock()slice = append(slice, i)fmt.Println("Appended", i, "to slice", slice)}(i)}// 等待goroutine完成for i := 0; i < 2; i++ {<-make(chan struct{})}fmt.Println("Final slice:", slice)
}

在这个例子中,我们使用了sync.Mutex​来确保在追加元素时不会有并发问题。

切片与接口

Go语言的切片还与接口(interface)有着紧密的联系。切片可以存储任何类型的元素,这使得它在处理异构数据时非常有用。然而,切片的元素类型必须是相同的,这是Go语言类型安全的一个体现。

切片与空接口

空接口(empty interface)interface{}​可以存储任何类型的值,包括切片。但是,当我们将切片存储在空接口中时,会丢失切片的类型信息。

package mainimport "fmt"func main() {var i interface{} = []int{1, 2, 3}fmt.Println(i) // 输出:[1 2 3]// 无法直接访问切片的元素类型信息// 需要通过类型断言来获取具体的切片类型
}

切片的遍历与操作

切片提供了多种方法来遍历和操作其元素。这些方法包括len()​、cap()​、append()​、copy()​等。这些方法使得切片的操作变得简单而直观。

遍历切片

遍历切片通常使用for​循环或者range​关键字。range​关键字可以同时获取切片的索引和值。

package mainimport "fmt"func main() {slice := []string{"apple", "banana", "cherry"}// 使用for循环遍历切片for i := 0; i < len(slice); i++ {fmt.Println("Index", i, "Value", slice[i])}// 使用range遍历切片for index, value := range slice {fmt.Println("Index", index, "Value", value)}
}

切片的切片操作

切片的切片操作允许我们创建原切片的一个子集。这在处理大型数据集时非常有用。

package mainimport "fmt"func main() {slice := []int{1, 2, 3, 4, 5}// 创建一个子切片subslice := slice[1:4]fmt.Println(subslice) // 输出:[2 3 4]
}

切片的垃圾回收

在Go语言中,垃圾回收(GC)是自动进行的。切片作为引用类型,其生命周期由垃圾回收器管理。当切片不再被任何变量引用时,它所占用的内存会被垃圾回收器回收。

切片的生命周期

package mainimport "fmt"func main() {slice := make([]int, 0, 10)defer fmt.Println("Slice is garbage collected")// 在这里,slice被创建并使用// ...// 当main函数结束时,slice的生命周期结束// 垃圾回收器会在适当的时候回收slice
}

在这个例子中,defer​语句确保了在main​函数结束时,会打印出切片被垃圾回收的信息。

切片与性能优化

在Go语言中,切片的性能优化是一个值得深入探讨的话题。由于切片在内存管理上的特殊性,它在某些情况下可能成为性能瓶颈。了解这些情况并采取相应的优化措施,可以使程序运行得更加高效。

预分配与扩容

在创建切片时,预分配足够的容量可以避免多次扩容操作。虽然Go的扩容机制已经非常高效,但在某些情况下,预先知道切片的大致大小并进行预分配,可以减少内存分配的次数,从而提高性能。

// 预分配容量的切片
slice := make([]int, 0, 100)

避免不必要的切片操作

在处理切片时,不必要的切片操作会增加额外的开销。例如,频繁地创建切片的子集,或者在循环中不断地追加元素,都可能导致性能下降。在这些情况下,考虑使用其他数据结构或者优化切片的使用方式,可能会带来更好的性能。

使用切片池

在某些应用场景中,频繁创建和销毁切片可能会导致大量的内存分配和回收。为了解决这个问题,可以考虑使用切片池(slice pool)来重用切片。通过维护一个切片池,可以在需要时从池中获取切片,使用完毕后放回池中,从而减少内存分配的频率。

type slicePool struct {sync.Pool
}func (p *slicePool) Get(size int) []int {if v := p.Get(); v != nil {s := v.([]int)if len(s) >= size {return s[:size]}}return make([]int, size)
}func (p *slicePool) Put(s []int) {if len(s) < 1024 {p.Put(s)}
}

在这个例子中,我们创建了一个切片池,它可以帮助我们重用切片,减少内存分配。

切片与并发

在并发编程中,切片的共享使用需要谨慎处理。由于切片不是线程安全的,因此在多线程环境中共享切片时,需要确保对切片的访问是同步的。这可以通过互斥锁、channel或者原子操作来实现。

切片的并发访问

在Go中,使用channel来传递切片是一种安全的做法,因为channel保证了在发送和接收操作的原子性。这样可以避免在多个goroutine之间共享切片时出现竞态条件。

func worker(c chan []int) {slice := <-c// 在这里处理slice
}func main() {c := make(chan []int, 10)c <- make([]int, 0, 100) // 发送切片到channelgo worker(c)// ...
}

在这个例子中,我们通过channel安全地在goroutine之间传递切片。

切片与错误处理

在使用切片时,错误处理是一个不可忽视的方面。Go语言提供了丰富的错误处理机制,这些机制同样适用于切片操作。了解如何在切片操作中处理错误,可以帮助我们编写更健壮的代码。

切片操作的错误检查

在进行切片操作时,如索引访问、切片操作等,我们需要确保索引不会越界。Go语言的运行时会检查这些操作,一旦发现越界,程序会立即崩溃并打印堆栈跟踪。因此,合理地使用切片可以避免这类错误。

package mainimport "fmt"func main() {slice := []int{1, 2, 3}// 正确的索引访问fmt.Println(slice[1]) // 输出:2// 错误的索引访问会导致程序崩溃// fmt.Println(slice[5])
}

切片的边界检查

在Go中,没有直接的函数或方法来检查切片的边界。但是,我们可以通过比较索引与切片的长度来手动检查。在处理切片时,始终要确保索引不会超出切片的长度。

package mainimport "fmt"func main() {slice := []int{1, 2, 3}for i := range slice {if i >= len(slice) {fmt.Println("Index out of bounds")break}fmt.Println(slice[i])}
}

切片与panic/recover

在Go中,当切片操作导致越界时,程序会触发panic​。我们可以使用defer​和recover​来捕获并处理这种异常情况。

package mainimport "fmt"func main() {defer func() {if r := recover(); r != nil {fmt.Println("Recovered in main", r)}}()slice := []int{1, 2, 3}fmt.Println(slice[5]) // 这将触发panic
}

在这个例子中,我们通过defer​语句捕获了由于越界访问切片而引发的panic​。

切片的高级应用

切片不仅在日常编程中扮演着基础角色,它还可以用于实现更复杂的数据结构和算法。以下是一些切片的高级应用示例。

切片作为队列

切片可以很容易地实现队列(FIFO)的功能。通过在切片的末尾追加元素,并从前端移除元素,我们可以创建一个高效的队列。

package mainimport "fmt"type Queue struct {slice []int
}func (q *Queue) Enqueue(value int) {q.slice = append(q.slice, value)
}func (q *Queue) Dequeue() (int, bool) {if len(q.slice) == 0 {return 0, false}value := q.slice[0]q.slice = q.slice[1:]return value, true
}func main() {q := Queue{}q.Enqueue(1)q.Enqueue(2)q.Enqueue(3)for {value, ok := q.Dequeue()if !ok {break}fmt.Println(value)}
}

切片与排序

切片提供了sort.Slice​函数,它可以对切片进行排序。这个函数非常灵活,可以用于各种类型的切片排序。

package mainimport ("fmt""sort"
)type Person struct {Name stringAge  int
}func main() {people := []Person{{"Bob", 31},{"John", 42},{"Michael", 17},}// 按年龄排序sort.Slice(people, func(i, j int) bool {return people[i].Age < people[j].Age})fmt.Println(people)
}

在这个例子中,我们定义了一个Person​结构体,并使用sort.Slice​对people​切片按年龄进行了排序。

切片与迭代器

在处理大型数据集时,使用迭代器可以提高代码的可读性和效率。Go语言的切片没有内置的迭代器,但我们可以通过编写自定义函数来模拟迭代器的行为。

自定义迭代器

以下是一个简单的切片迭代器的示例,它允许我们遍历切片中的每个元素,而不需要直接操作索引。

package mainimport "fmt"// SliceIterator 是一个自定义的切片迭代器
type SliceIterator struct {slice []intindex int
}// NewSliceIterator 创建一个新的切片迭代器
func NewSliceIterator(slice []int) *SliceIterator {return &SliceIterator{slice: slice, index: 0}
}// HasNext 检查迭代器是否还有更多的元素
func (i *SliceIterator) HasNext() bool {return i.index < len(i.slice)
}// Next 返回下一个元素,并更新迭代器的索引
func (i *SliceIterator) Next() int {if i.HasNext() {value := i.slice[i.index]i.index++return value}panic("迭代器没有更多元素")
}func main() {slice := []int{1, 2, 3, 4, 5}iterator := NewSliceIterator(slice)for iterator.HasNext() {fmt.Println(iterator.Next())}
}

在这个例子中,我们创建了一个SliceIterator​结构体,它包含了切片和当前索引。通过HasNext​和Next​方法,我们可以遍历切片中的所有元素。

切片与反射

Go语言的反射(reflection)机制允许我们在运行时检查和操作数据。虽然切片的类型信息在编译时就已经确定,但我们仍然可以使用反射来操作切片。

使用反射操作切片

以下是一个使用反射来操作切片的示例。这个例子展示了如何动态地访问切片的元素类型和值。

package mainimport ("fmt""reflect"
)func main() {slice := []int{1, 2, 3, 4, 5}// 使用反射获取切片的类型信息sliceType := reflect.TypeOf(slice)fmt.Println("Slice Type:", sliceType)// 使用反射遍历切片for i := 0; i < sliceType.Len(); i++ {value := sliceType.Elem().Index(i).Int()fmt.Println("Element at index", i, ": ", value)}
}

在这个例子中,我们使用reflect.TypeOf​获取切片的类型信息,并使用reflect.Value​来遍历切片的元素。

切片与接口

切片可以与接口(interface)结合使用,这为我们提供了更多的灵活性。当我们将切片作为接口的值时,我们可以在不知道具体类型的情况下操作切片。

切片与接口的结合

以下是一个将切片与接口结合使用的示例。这个例子展示了如何将切片存储在接口中,并在需要时进行类型断言。

package mainimport "fmt"func processSlice(slice interface{}) {sliceValue := slice.([]int)fmt.Println("Processing slice:", sliceValue)
}func main() {intSlice := []int{1, 2, 3, 4, 5}processSlice(intSlice)
}

在这个例子中,我们将一个整数切片存储在接口变量中,并传递给processSlice​函数。在函数内部,我们通过类型断言来获取切片的值。

切片与并发映射

在并发编程中,映射(map)是一种常用的数据结构,用于存储键值对。切片也可以与映射结合使用,以实现更复杂的数据结构,如并发安全的映射。

并发安全的切片映射

以下是一个使用sync.Map​来存储切片的示例。sync.Map​是Go语言提供的一种并发安全的映射,它可以在多个goroutine之间安全地共享和修改数据。

package mainimport ("fmt""sync"
)func main() {var m sync.Map// 向映射中添加切片m.Store("slice1", []int{1, 2, 3})// 从映射中获取切片if slice, ok := m.Load("slice1"); ok {fmt.Println("Retrieved slice:", slice.([]int))}
}

在这个例子中,我们使用sync.Map​来存储和检索切片。这种方式确保了在并发环境下对映射的访问是安全的。

切片与错误处理

在处理切片时,错误处理是一个重要的方面。Go语言提供了panic​和recover​机制来处理运行时错误。在切片操作中,我们可以通过这些机制来处理潜在的错误情况。

使用defer​和recover​处理切片错误

以下是一个使用defer​和recover​来处理切片越界错误的示例。

package mainimport ("fmt"
)func main() {defer func() {if r := recover(); r != nil {fmt.Println("Recovered from panic:", r)}}()slice := []int{1, 2, 3}// 故意越界访问切片,触发panicfmt.Println(slice[5])
}

在这个例子中,我们故意访问了一个不存在的切片索引,这会导致panic​。通过defer​语句,我们捕获了这个panic​并进行了处理。

切片与算法

切片是实现各种算法的理想选择,因为它们提供了灵活的内存管理和高效的元素访问。以下是一些使用切片实现的常见算法示例。

切片排序

切片排序是处理切片时的一个基本操作。Go标准库提供了sort.Sort​函数,它可以对切片进行排序。

package mainimport ("fmt""sort"
)func main() {intSlice := []int{3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5}fmt.Println("Original slice:", intSlice)// 使用sort.Sort对切片进行排序sort.Sort(sort.IntSlice(intSlice))fmt.Println("Sorted slice:", intSlice)
}

在这个例子中,我们使用sort.Sort​和sort.IntSlice​对整数切片进行了排序。

切片搜索

切片搜索是另一个常见的操作。Go标准库提供了sort.Search​函数,它可以在有序切片中查找特定元素的索引。

package mainimport ("fmt""sort"
)func main() {intSlice := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}fmt.Println("Original slice:", intSlice)// 使用sort.Search在切片中搜索元素index := sort.SearchInts(intSlice, 5)fmt.Println("Index of 5:", index)
}

在这个例子中,我们使用sort.SearchInts​在整数切片中搜索元素5的索引。

切片与数据流

在处理数据流时,切片可以作为一种缓冲机制,帮助我们管理数据的读取和写入。这在文件操作、网络通信等场景中尤为常见。

使用切片处理文件数据

在读取或写入文件时,我们通常会使用切片来临时存储数据块。以下是一个使用切片读取文件内容的示例。

package mainimport ("bufio""fmt""os"
)func main() {file, err := os.Open("example.txt")if err != nil {panic(err)}defer file.Close()reader := bufio.NewReader(file)// 使用切片作为缓冲区读取文件buffer := make([]byte, 1024)for {n, err := reader.Read(buffer)if err != nil {if err != nil {panic(err)}break}// 处理读取的数据fmt.Print(string(buffer[:n]))}
}

在这个例子中,我们使用bufio.Reader​来逐块读取文件,每次读取1024字节到切片buffer​中,并处理这些数据。

使用切片处理网络数据

在网络编程中,切片同样可以用来处理接收到的数据。以下是一个简单的TCP服务器示例,它使用切片来接收客户端发送的数据。

package mainimport ("bufio""fmt""net""strings"
)func main() {listener, err := net.Listen("tcp", "localhost:8080")if err != nil {panic(err)}defer listener.Close()for {conn, err := listener.Accept()if err != nil {panic(err)}go handleConnection(conn)}
}func handleConnection(conn net.Conn) {defer conn.Close()reader := bufio.NewReader(conn)for {buffer := make([]byte, 1024)n, err := reader.Read(buffer)if err != nil {break}// 处理接收到的数据message := string(buffer[:n])fmt.Println("Received message:", message)}
}

在这个例子中,我们创建了一个TCP服务器,它使用bufio.Reader​来接收客户端发送的数据,并将数据存储在切片buffer​中。

切片与数据结构

切片可以与其他数据结构结合使用,以实现更复杂的数据结构。例如,切片可以作为其他数据结构的一部分,或者用于实现自定义的数据结构。

切片作为数据结构的一部分

以下是一个使用切片实现的简单栈(Stack)数据结构示例。

package mainimport "fmt"type Stack struct {items []interface{}
}func (s *Stack) Push(item interface{}) {s.items = append(s.items, item)
}func (s *Stack) Pop() interface{} {if len(s.items) == 0 {return nil}item := s.items[len(s.items)-1]s.items = s.items[:len(s.items)-1]return item
}func main() {stack := Stack{}stack.Push(1)stack.Push("hello")stack.Push(true)for {item := stack.Pop()if item == nil {break}fmt.Println(item)}
}

在这个例子中,我们定义了一个Stack​结构体,它包含一个切片items​,用于存储栈中的元素。我们实现了Push​和Pop​方法来操作栈。

使用切片实现自定义数据结构

切片也可以用于实现更复杂的自定义数据结构。例如,我们可以使用切片来实现一个二叉搜索树(BST)。

// 这里只是一个简单的BST节点定义,实际实现会更复杂
type BSTNode struct {Value intLeft *BSTNodeRight *BSTNode
}// BSTInsert 用于向BST中插入新值
func BSTInsert(root *BSTNode, value int) {if root == nil {return &BSTNode{Value: value}}if value < root.Value {root.Left = BSTInsert(root.Left, value)} else if value > root.Value {root.Right = BSTInsert(root.Right, value)}return root
}// BSTSearch 用于在BST中搜索特定值
func BSTSearch(root *BSTNode, value int) bool {if root == nil {return false}if root.Value == value {return true}if value < root.Value {return BSTSearch(root.Left, value)}return BSTSearch(root.Right, value)
}func main() {root := &BSTNode{Value: 5}BSTInsert(root, 3)BSTInsert(root, 7)fmt.Println(BSTSearch(root, 3)) // 输出:truefmt.Println(BSTSearch(root, 6)) // 输出:false
}

在这个例子中,我们定义了一个BSTNode​结构体来表示二叉搜索树的节点,并实现了插入和搜索功能。

切片与标准库

Go语言的标准库提供了许多与切片相关的功能,这些功能可以帮助我们更高效地处理数据。了解这些功能对于编写高效的Go代码至关重要。

使用标准库处理切片

标准库中的sort​和strings​包提供了丰富的切片处理功能。例如,sort​包可以用来对切片进行排序,而strings​包则提供了字符串切片的处理方法。

package mainimport ("fmt""sort""strings"
)func main() {// 使用sort包对整数切片进行排序intSlice := []int{3, 1, 4, 1, 5, 9, 2, 6, 5, 3}fmt.Println("Original int slice:", intSlice)sort.Ints(intSlice)fmt.Println("Sorted int slice:", intSlice)// 使用strings包处理字符串切片strSlice := []string{"banana", "apple", "cherry"}fmt.Println("Original string slice:", strSlice)sort.Strings(strSlice)fmt.Println("Sorted string slice:", strSlice)
}

在这个例子中,我们展示了如何使用sort.Ints​和sort.Strings​对整数和字符串切片进行排序。

切片与并发

在并发编程中,切片的使用需要特别注意,因为它们可能被多个goroutine共享。为了确保数据的一致性和安全性,我们通常需要使用互斥锁或其他同步机制。

package mainimport ("fmt""sync"
)func main() {slice := []int{1, 2, 3}var lock sync.Mutex// 启动一个goroutine来修改切片go func() {lock.Lock()defer lock.Unlock()slice[0] = 42}()// 在主goroutine中打印切片lock.Lock()defer lock.Unlock()fmt.Println("Slice after goroutine:", slice)
}

在这个例子中,我们使用sync.Mutex​来确保对切片的修改是线程安全的。

切片与错误处理

在处理切片时,我们可能会遇到各种错误,例如索引越界。Go语言提供了panic​和recover​机制来处理这类错误。

package mainimport ("fmt"
)func main() {slice := []int{1, 2, 3}defer func() {if r := recover(); r != nil {fmt.Println("Recovered from panic:", r)}}()// 故意触发索引越界错误_ = slice[5]
}

在这个例子中,我们通过defer​和recover​捕获并处理了由于索引越界引起的panic​。


参考资料:

  • Go Slice的底层实现原理 - CSDN博客
  • Go语言官方文档

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

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

相关文章

年轻人怎么搞钱?

年轻人想要搞钱&#xff0c;可以考虑以下几个方面&#xff1a; 1. 创业&#xff1a;年轻人可以通过自己的创意&#xff0c;找到一个市场的空缺&#xff0c;开创自己的业务。可以从比较小的项目开始&#xff0c;逐渐扩大范围&#xff0c;积累经验和财富。 2. 投资&#xff1a;…

成为大佬之路--linux软件安装使用第000000021篇--linux安装docker

简介 Docker 是一个开源项目&#xff0c;诞生于 2013 年初&#xff0c;最初是 dotCloud 公司内部的一个业余项目。它基于 Google 公司推出的 Go 语言实现。 项目后来加入了 Linux 基金会&#xff0c;遵从了 Apache 2.0 协议&#xff0c;项目代码在 [GitHub](https://github.co…

Hadoop之HDFS——【模块二】数据管理

一、Namespace的概述 1.1.集群与命名空间的关系 类似于大集群与小集群之间的关系,彼此之间独立又相互依存。每个namespace彼此独立,Namespace工作时只负责维护本区域的数据,同时所有的namespace维护的文件都可以共用DataNode节点,为了区分数据属于哪些Namespace,DataNode…

强大而灵活的python装饰器

装饰器&#xff08;Decorators&#xff09; 一、概述 在Python中&#xff0c;装饰器是一种特殊类型的函数&#xff0c;它允许我们修改或增强其他函数的功能&#xff0c;而无需修改其源代码。装饰器在函数定义之后立即调用&#xff0c;并以函数对象作为参数。装饰器返回一个新…

力扣151--反转字符串中的单词(优)

清晰易懂&#xff0c;简单高效&#xff01; 大体思路&#xff1a; 每次截取到想要的单词&#xff0c;拼接到新的sb中&#xff0c;过程中伴随双指针进行空格位置指向控制&#xff0c; 其中如果start指针如果0的情况要放在第一个判断条件防止边界条件失效&#xff0c;并且这种…

Linux系统运维脚本:shell脚本查看一定网段范围在线网络设备的ip地址和不在线的网络设备的数量(查看在线和不在线网络设备)

目 录 一、需求说明 二、解决方案 &#xff08;一&#xff09;解决思路 &#xff08;二&#xff09;方案 三、脚本程序实现 &#xff08;一&#xff09;脚本代码和解释 1、脚本代码 2、代码解释 &#xff08;二&#xff09;脚本验证 1、脚本编辑…

CrossOver 24下载-CrossOver 24 for Mac下载 v24.0.0中文永久版

CrossOver 24是一款可以让mac用户能够自由运行和游戏windows游戏软件的虚拟机类应用&#xff0c;虽然能够虚拟windows但是却并不是一款虚拟机&#xff0c;也不需要重启系统或者启动虚拟机&#xff0c;类似于一种能够让mac系统直接运行windows软件的插件。它以其出色的跨平台兼容…

NVMe开发——PCIe复位

简介 PCIe中有4种复位机制&#xff0c;早期的3种被称为传统复位(Conventional Reset)。传统复位中的前2种又称为基本复位(Fundamental Resets)&#xff0c;分别为冷复位(Cold Reset)&#xff0c;暖复位(Warm Reset)。第3种复位为热复位(Hot Reset)。第4种复位被称为功能级复位…

js 正则记录

正则表达式 正则表达式创建一个正则表达式修饰符常用的特殊字符使用正则表达式的方法replace指定字符串作为替换项使用场景&#xff1a;交换字符串中的两个单词将"-"链接的方式改为驼峰式(忽略开头的-)将华氏温度转换为响应的摄氏温度 常用正则示例判断输入是否是正确…

使用docker安装dolphinscheduler

1、前提是安装docker和docker-compose 2、#mkdir /data/dolphinscheduler 3、镜像 docker load -i dolphinscheduler-mysql-driver.tar docker pull zookeeper:3.6.2:3.6.2 docker tag a7 bitnami/zookeeper:3.6.2 理论上postgresql也可以在线pull&#xff0c;但是在线do…

179基于matlab的2D-VMD处理图像

基于matlab的2D-VMD处理图像&#xff0c;将图片进行VMD分解&#xff0c;得到K个子模态图&#xff0c;将每个模态图进行重构&#xff0c;得到近似的原图。可以利用这点进行图像去噪。程序已调通&#xff0c;可直接运行。 179 2D-VMD 图像分解重构 图像处理 (xiaohongshu.com)

每日五道java面试题之spring篇(九)

目录&#xff1a; 第一题. 说一下Spring的事务传播行为第二题. 说一下 spring 的事务隔离&#xff1f;第三题. Spring AOP and AspectJ AOP 有什么区别&#xff1f;AOP 有哪些实现方式&#xff1f;第四题. JDK动态代理和CGLIB动态代理的区别第五题. 解释一下Spring AOP里面的几…

Hyperf crontab 定时任务组件

composer require hyperf/crontab autoload里面配置process和crontab&#xff0c;这里主要是对crontab配置里的其他任务属性配置进行尝试。 onOneServer singleton onOneServer 和 singleton 在crontab源码中src/Strategy/Executor.php 中decorateRunnable是控制是否执行的。…

【白嫖8k买的机构vip教程】Appium自动化(3):Appium-Desktop界面介绍

Appium-Desktop主界面包含三个菜单Simple、Advanced、Presets Simple界面&#xff1a; Host设置Appium server的ip地址&#xff0c;本地调试可以将ip地址修改为127.0.0.1&#xff1b;Port设置端口号&#xff0c;默认是4723不用修改Start Server 启动 Appium serverEdit Confi…

重生奇迹MU玩家容易遇到的问题

1、玩家可以在画面左上角座标旁找到「奇迹助手」的小按钮&#xff0c;用它来开启介面。 2、打怪范围&#xff1a;自动寻找所设定范围内的怪物&#xff0c;勾选后角色搜索范围内若无可攻击的目标&#xff0c;将会随机移动位置直到有攻击目标为止&#xff0c;但移动范围不超出所…

搭建LNMP环境并搭建论坛和博客

目录 一、LNMP架构原理 二、编译安装Nginx 三、编译安装MySQL 四、编译安装PHP 五、配置Nginx支持PHP解析 六、安装论坛 七、安装博客 一、LNMP架构原理 LNMP架构&#xff0c;是指在Linux平台下&#xff0c;由运行Nginx的web服务器&#xff0c;运行PHP的动态页面解析程序…

Node.js_基础知识(http模块)

网络基础 URL的组成结构&#xff1a;协议名: // 主机名 [:端口号] [/路径] [?查询字符串]协议默认端口&#xff1a; http&#xff1a;80&#xff0c;开发常用端口有 3000、8080、8090、9000https: 443 如果端口被其他程序占用&#xff0c;可以使用 资源监视器 找到占用端口的…

Python:练习:编写一个程序,录入一个美元数量(int),然后显示出增加%5税率后的相应金额。

案例&#xff1a; 编写一个程序&#xff0c;录入一个美元数量&#xff08;int&#xff09;&#xff0c;然后显示出增加%5税率后的相应金额。格式如下所示&#xff1a; Enter an amount:100 With tax added:$105.0 思考&#xff1a; 1、录入一个美元数量&#xff0c;录入&am…

解决GitHub无法访问的问题:手动修改hosts文件与使用SwitchHosts工具

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua&#xff0c;在这里我会分享我的知识和经验。&#x…

【C++】一个求数组中最大元素的函数模板

题目 设计一个分数类 F r a c t i o n Fraction Fraction&#xff0c;再设计一个名为 M a x e l e m e n t Max_element Maxe​lement 的函数模板&#xff0c;能够求数组中最大的元素&#xff0c;并用该模板求一个 F r a c t i o n Fraction Fraction 数组中的最大元素。 C…