Go语言内存管理:从理论到实战

news2024/9/23 23:31:38

1. 引言

在这里插入图片描述

1.1 内存管理的重要性

内存管理是编程中的一个重要环节,它涉及到内存的分配与释放,对于程序的性能和稳定性有着至关重要的影响。不当的内存管理会导致内存泄漏、内存溢出等问题,从而影响程序的正常运行。特别是在大型项目中,良好的内存管理能够有效提高程序的运行效率,降低系统的资源消耗。

1.2 Go语言内存管理的优势

Go语言是一种静态类型、编译型语言,它的内存管理具有以下优势:

(1)简洁明了:Go语言的内存管理机制相对简单,易于理解和掌握。

(2)自动垃圾回收:Go语言提供了自动垃圾回收机制,可以有效减少内存泄漏的问题。

(3)高效的内存分配:Go语言的内存分配速度快,可以提高程序的性能。

2. Go语言内存模型概述

在这里插入图片描述

Go语言的内存管理模型是自动化的,即开发者不需要显式地分配和释放内存。Go通过垃圾回收(Garbage Collection, GC)机制自动管理内存分配和回收。理解Go的内存模型有助于编写高效的Go程序。

2.1 内存分配机制

在Go中,内存分配主要通过堆(heap)和栈(stack)来管理。堆用于分配全局对象和动态生成的对象,而栈则用于局部变量和函数调用。

栈内存分配

栈内存分配速度快,因为它是基于LIFO(后进先出)原则的。当函数调用结束时,栈上的内存会自动释放。

package main

import "fmt"

func stackAllocation() {
    var a int = 10
    var b int = 20
    fmt.Println("a + b =", a + b)
}

func main() {
    stackAllocation()
}

在上述代码中,变量abstackAllocation函数调用时分配到栈上,函数结束时内存自动释放。

堆内存分配

堆内存分配相对较慢,但适合需要长时间存活的对象。使用newmake关键字可以分配堆内存。

package main

import "fmt"

func heapAllocation() {
    p := new(int)
    *p = 100
    fmt.Println("Value stored in heap:", *p)
}

func main() {
    heapAllocation()
}

在这个例子中,p指向一个堆上分配的整数。虽然heapAllocation函数结束,但p指向的内存仍然存在,直到垃圾回收器回收。

2.2 内存生命周期

理解内存生命周期对优化内存使用和提高程序性能至关重要。Go语言的内存生命周期可以分为以下几个阶段:

分配阶段

内存分配是指通过堆或栈分配内存空间。例如,varnewmake等关键字用于分配内存。

package main

import "fmt"

type Person struct {
    Name string
    Age  int
}

func createPerson(name string, age int) *Person {
    return &Person{name, age}
}

func main() {
    p := createPerson("Alice", 30)
    fmt.Println("Person:", p)
}

在这里,createPerson函数在堆上分配一个Person结构体实例,并返回指向它的指针。

使用阶段

使用阶段是指分配的内存被程序读写操作使用。确保在使用内存前已正确初始化,避免未定义行为。

package main

import "fmt"

func main() {
    nums := make([]int, 5)
    for i := 0; i < len(nums); i++ {
        nums[i] = i * 10
    }
    fmt.Println("Slice:", nums)
}

在这个例子中,nums切片被初始化并填充数据,然后被打印。

释放阶段

释放阶段是指内存不再被程序使用,由垃圾回收器自动回收。这对减少内存泄漏至关重要。

2.3 垃圾回收机制

Go语言的垃圾回收机制是其内存管理的核心。垃圾回收器自动回收不再使用的内存,减少了内存泄漏的风险。

标记-清除算法

Go的垃圾回收器主要基于标记-清除算法。它分为两个阶段:

  1. 标记阶段:遍历所有可达对象并标记。
  2. 清除阶段:回收未标记的对象内存。
package main

import (
    "fmt"
    "runtime"
)

func createGarbage() {
    for i := 0; i < 1000; i++ {
        _ = make([]byte, 1024*1024) // 创建大量垃圾数据
    }
}

func main() {
    var m runtime.MemStats
    runtime.ReadMemStats(&m)
    fmt.Printf("Before garbage creation: Alloc = %v MiB\n", m.Alloc/1024/1024)

    createGarbage()
    
    runtime.GC() // 强制垃圾回收
    runtime.ReadMemStats(&m)
    fmt.Printf("After garbage collection: Alloc = %v MiB\n", m.Alloc/1024/1024)
}

在这个例子中,createGarbage函数创建大量垃圾数据,runtime.GC()函数强制进行垃圾回收,显示回收前后的内存使用情况。

3. 实战代码讲解:内存分配与释放

Go语言的内存分配主要涉及到newmake两个关键字,而内存的释放则依赖于垃圾回收机制。在本节中,我们将通过实战代码来讲解这些概念。

3.1 动态内存分配

在Go中,动态内存分配通常使用new关键字来为某个类型的零值分配内存,并返回指向该内存的指针。

package main

import "fmt"

func main() {
    // 分配一个int类型的零值,并返回其指针
    p := new(int)
    fmt.Println("Pointer to int:", p)

    // 设置指针指向的内存的值
    *p = 42
    fmt.Println("Value through pointer:", *p)
}

在这个例子中,new(int)分配了一个int类型的零值,并返回了一个指向这个零值的指针。通过*p可以访问并修改这个内存区域的值。

3.2 内存释放与垃圾回收

Go语言的垃圾回收器会自动回收不再使用的内存。通常,我们不需要手动释放内存。但是,有一种情况例外,那就是当我们需要释放一个动态分配的内存,并且希望在垃圾回收器运行之前就释放它,这时可以使用delete关键字。

package main

import "fmt"

func main() {
    // 分配一个切片
    slice := make([]int, 5)
    fmt.Println("Initial slice:", slice)

    // 分配一个新的切片,并让垃圾回收器回收旧的切片内存
    newSlice := make([]int, 10)
    fmt.Println("New slice created, old slice memory is released.")
}

在这个例子中,当我们创建一个新的切片newSlice时,旧的切片slice的内存会被自动释放。如果我们希望在创建新切片前就释放slice的内存,可以使用delete

package main

import "fmt"

func main() {
    // 分配一个切片
    slice := make([]int, 5)
    fmt.Println("Initial slice:", slice)

    // 使用delete提前释放内存
    delete(slice, 0)
    fmt.Println("Memory of slice is released.")
}

在这个例子中,通过delete(slice, 0),我们删除了slice中索引为0的元素,这会导致slice的内存被提前释放。

3.3 示例:使用动态内存分配优化数据结构

下面我们将通过一个示例来演示如何使用动态内存分配来优化数据结构。我们将创建一个动态数组,并根据需要动态地添加和删除元素。

package main

import (
    "fmt"
)

// Node 定义了一个链表节点
type Node struct {
    Value int
    Next  *Node
}

// NewNode 创建一个新的链表节点
func NewNode(value int) *Node {
    return &Node{Value: value}
}

// AddNode 向链表的末尾添加一个新的节点
func (l *Node) AddNode(value int) {
    newNode := NewNode(value)
    if l.Next == nil {
        l.Next = newNode
    } else {
        current := l
        for current.Next != nil {
            current = current.Next
        }
        current.Next = newNode
    }
}

// DeleteNode 删除链表中指定的节点
func (l *Node) DeleteNode(value int) bool {
    if l.Value == value {
        if l.Next == nil {
            return false
        }
        l = l.Next
        return true
    }
    current := l
    for current.Next != nil {
        if current.Next.Value == value {
            current.Next = current.Next.Next
            return true
        }
        current = current.Next
    }
    return false
}

func main() {
    // 创建链表
    list := &Node{Value: 1}

    // 向链表添加新节点
    list.AddNode(2)
    list.AddNode(3)
    list.AddNode(4)

    // 打印链表
    current := list
    for current != nil {
        fmt.Println(current.Value)
        current = current.Next
    }

    // 删除链表中的节点
    list.DeleteNode(3)

    // 再次打印链表
    current = list
    for current != nil {
        fmt.Println(current.Value)
        current = current.Next
    }
}

在这个示例中,我们定义了一个Node结构体来表示链表的节点。我们提供了AddNodeDeleteNode方法来动态地添加和删除节点。通过这个示例,我们看到了如何使用动态内存分配来创建和操作链表数据结构。

4. 实战代码讲解:内存池技术

内存池(Memory Pool)是一种预先分配一定数量的内存块,并管理这些内存块的分配和释放的技术。在Go语言中,虽然垃圾回收器负责自动管理内存,但使用内存池可以在某些情况下提高性能,尤其是在频繁创建和销毁小对象的场景。

4.1 内存池的概念与原理

内存池的基本思想是预先分配一定数量的内存块,这些内存块可以被重复使用。当需要一个新对象时,内存池中如果有空闲的内存块,就直接使用这个内存块,而不是去操作系统中申请新的内存。这样可以减少内存分配和释放的开销,提高程序的性能。

4.2 实现一个简单的内存池

下面是一个简单的内存池实现的示例:

package main

import (
	"fmt"
)

// Pool 定义了一个内存池
type Pool struct {
	pool []*int
}

// NewPool 创建一个新的内存池
func NewPool(size int) *Pool {
	return &Pool{make([]*int, size)}
}

// Get 从未使用的内存块中获取一个内存块
func (p *Pool) Get() *int {
	for i, block := range p.pool {
		if block == nil {
			p.pool[i] = block
			return block
		}
	}
	return nil
}

// Put 返回一个使用过的内存块到内存池中
func (p *Pool) Put(block *int) {
	for i, b := range p.pool {
		if b == nil {
			p.pool[i] = block
			return
		}
	}
}

func main() {
	// 创建一个内存池,预分配10个int类型的内存块
	pool := NewPool(10)

	// 使用内存池中的内存块
	block1 := pool.Get()
	*block1 = 10
	fmt.Println("Block1:", *block1)

	block2 := pool.Get()
	*block2 = 20
	fmt.Println("Block2:", *block2)

	// 返回使用过的内存块到内存池
	pool.Put(block1)
	pool.Put(block2)

	// 再次使用内存池中的内存块
	block3 := pool.Get()
	fmt.Println("Block3:", *block3)
}

在这个例子中,我们定义了一个Pool结构体,它包含一个pool切片,用于存储预分配的内存块。NewPool函数用于创建一个新的内存池,Get方法用于从内存池中获取一个内存块,而Put方法用于将一个使用过的内存块返回到内存池中。

4.3 示例:使用内存池优化程序性能

内存池技术在处理大量短生命周期的对象时特别有用。例如,在网络编程中,处理大量的HTTP请求时,每个请求可能只需要一个小内存块来存储一些数据。使用内存池可以避免每次请求都分配新的内存块,从而减少垃圾回收的开销。

下面是一个使用内存池优化程序性能的示例:

package main

import (
	"fmt"
	"sync"
)

// Request 表示一个HTTP请求
type Request struct {
	URL    string
	Method string
}

// Response 表示一个HTTP响应
type Response struct {
	Status string
	Body   string
}

// Pool 定义了一个内存池
type Pool struct {
	pool []*Request
	mu   sync.Mutex
}

// NewPool 创建一个新的内存池
func NewPool(size int) *Pool {
	return &Pool{make([]*Request, size)}
}

// Get 从未使用的内存块中获取一个内存块
func (p *Pool) Get() *Request {
	p.mu.Lock()
	defer p.mu.Unlock()
	for i, block := range p.pool {
		if block == nil {
			p.pool[i] = block
			return block
		}
	}
	return nil
}

// Put 返回一个使用过的内存块到内存池中
func (p *Pool) Put(block *Request) {
	p.mu.Lock()
	defer p.mu.Unlock()
	for i, b := range p.pool {
		if b == nil {
			p.pool[i] = block
			return
		}
	}
}

func main() {
	// 创建一个内存池,预分配100个Request类型的内存块
	pool := NewPool(100)

	// 模拟处理HTTP请求
	for i := 0; i < 1000; i++ {
		req := pool.Get()
		if req != nil {
			// 模拟处理请求...
			// 返回使用过的请求到内存池
			pool.Put(req)
		}
	}
}

在这个例子中,我们定义了一个RequestResponse结构体来表示HTTP请求和响应。我们创建了一个Pool类型来管理请求的内存分配。每个请求都从内存池中获取内存块,处理完成后,将内存块返回到内存池中。这样可以避免每次处理请求时都分配新的内存,减少垃圾回收的开销,从而提高程序的性能。

5. 实战代码讲解:逃逸分析

逃逸分析是Go语言中一种重要的性能优化手段,它可以帮助编译器判断变量的生命周期,从而做出更优化的内存分配决策。逃逸分析的目的是将变量的生命周期限制在最小范围内,以减少内存分配和垃圾回收的压力。

5.1 逃逸分析的概念与原理

逃逸分析是一种静态分析技术,它分析变量在函数中的使用范围,以确定变量是否逃出了函数的作用域。如果变量在函数外部仍然可以访问,那么这个变量就被认为“逃逸”了。逃逸分析可以帮助编译器做出以下决策:

  • 将变量分配到堆上还是栈上。
  • 是否使用逃逸分析优化,如内联函数、分配到栈上等。

5.2 Go语言逃逸分析的实现

Go语言的逃逸分析是由编译器自动进行的,开发者不需要手动干预。编译器会分析变量在函数中的使用情况,并根据逃逸分析的结果来优化代码。

5.3 示例:使用逃逸分析优化程序性能

下面是一个使用逃逸分析优化程序性能的示例:

package main

import "fmt"

func main() {
    // 使用new分配一个字符串,这是一个逃逸引用
    s := new(string)
    *s = "hello"
    fmt.Println("String:", *s)

    // 使用make分配一个切片,这是一个逃逸引用
    slice := make([]int, 10)
    fmt.Println("Slice:", slice)

    // 局部变量,不会逃逸
    var local int
    local = 42
    fmt.Println("Local variable:", local)

    // 函数内部创建的变量,不会逃逸
    fmt.Println("Function local variable:", functionLocal())
}

// functionLocal 函数内部创建的变量不会逃逸
func functionLocal() int {
    local := 10
    return local
}

在这个例子中,sslice都是通过newmake关键字分配的,它们在函数外部仍然可以访问,因此被认为是逃逸引用。而localfunctionLocal函数内的局部变量则不会逃逸,因为它们的生命周期被限制在函数内部。

通过逃逸分析,编译器可以做出优化决策,例如将逃逸引用的对象分配到堆上,而不是栈上,这样可以提高性能。同时,逃逸分析还可以帮助编译器进行内联优化,将频繁调用的函数直接内联到调用代码中,减少函数调用的开销。

6. 实战代码讲解:切片与映射的内存管理

切片(Slice)和映射(Map)是Go语言中非常强大的数据结构,它们在内存管理方面有一些需要注意的地方。

6.1 切片的内存管理

切片是一个轻量级的数据结构,它指向一个底层数组,并提供数组的部分访问。切片本身不存储数据,而是通过指针来引用底层数组。因此,切片占用的内存比其底层数组要少得多。

切片的长度和容量

切片的长度是指切片包含的元素数量,而容量是指底层数组中切片可以访问的元素数量。切片的长度不会超过其容量。

package main

import "fmt"

func main() {
    // 创建一个切片
    s := []int{1, 2, 3, 4, 5}
    fmt.Println("Length:", len(s)) // 输出:Length: 5
    fmt.Println("Capacity:", cap(s)) // 输出:Capacity: 5

    // 修改切片的值
    s[0] = 10
    fmt.Println("Modified slice:", s) // 输出:Modified slice: [10 2 3 4 5]

    // 切片的长度和容量不会改变
    fmt.Println("Length:", len(s)) // 输出:Length: 5
    fmt.Println("Capacity:", cap(s)) // 输出:Capacity: 5
}

在这个例子中,我们创建了一个长度为5的切片,它的容量也是5。当我们修改切片的一个元素时,切片的长度和容量保持不变。

6.2 映射的内存管理

映射是一个无序的键值对集合,它的元素是随机存储的。映射的内存管理需要注意以下几点:

  • 映射的大小是根据元素的数量来确定的。
  • 映射中的元素是按键的哈希值存储的,所以元素的顺序可能会随着插入和删除操作而改变。
  • 映射中的元素在内存中是连续的,但元素之间的顺序可能不是按照键的顺序排列的。
package main

import "fmt"

func main() {
    // 创建一个映射
    m := map[string]int{"one": 1, "two": 2, "three": 3}
    fmt.Println("Map:", m) // 输出:Map:map[one:1 three:3 two:2]

    // 修改映射的值
    m["two"] = 20
    fmt.Println("Modified map:", m) // 输出:Modified map:map[one:1 three:3 two:20]

    // 映射的大小不会改变
    fmt.Println("Size of map:", len(m)) // 输出:Size of map:3
}

在这个例子中,我们创建了一个映射,并修改了一个键对应的值。映射的大小保持不变,因为我们只是修改了一个键的值,并没有添加或删除任何元素。

6.3 示例:优化切片与映射的使用

切片和映射的使用通常不需要特别优化,因为它们已经很高效了。但在某些情况下,我们可以通过一些技巧来进一步优化它们的使用。

优化切片

使用append函数时,如果切片的容量不足以容纳新元素,append会分配一个新的更大的底层数组。在这种情况下,我们可以预先分配一个足够大的切片来避免频繁的底层数组扩容。

package main

import "fmt"

func main() {
    // 预分配一个足够大的切片
    s := make([]int, 10, 100)
    for i := 0; i < 100; i++ {
        s = append(s, i)
    }
    fmt.Println("Slice:", s)
}

在这个例子中,我们预先分配了一个容量为100的切片,这样在后续的append操作中就不会频繁地扩容了。

优化映射

在创建映射时,如果知道映射中元素的数量,可以使用make函数来指定映射的大小,这样可以避免映射在初始化时占用过多的内存。

package main

import "fmt"

func main() {
    // 指定映射的大小
    m := make(map[string]int, 10)
    for i := 0; i < 10; i++ {
        m[fmt.Sprintf("key%d", i)] = i
    }
    fmt.Println("Map:", m)
}

在这个例子中,我们使用make(map[string]int, 10)来创建一个大小为10的映射,这样在初始化时就不会占用过多的内存。

7. 实战代码讲解:内存泄漏与排查

内存泄漏(Memory Leak)是指程序在申请内存后,由于某种原因未能释放,导致内存占用持续增加,最终可能导致内存不足。在Go语言中,虽然垃圾回收器(Garbage Collector, GC)会自动回收不再使用的内存,但仍然可能会出现内存泄漏的情况。

7.1 内存泄漏的概念与原因

内存泄漏是指程序中已经不需要的内存没有被释放,导致内存占用不断增加。在Go语言中,内存泄漏通常是由于以下原因造成的:

  • 全局变量或长生命周期的变量持有对短生命周期对象的引用。
  • 循环引用,导致对象无法被垃圾回收器回收。
  • 使用了new关键字分配内存,但没有使用delete关键字来释放内存。

7.2 Go语言内存泄漏的检测与修复

Go语言提供了几种方法来检测和修复内存泄漏:

  • 使用debug包中的PrintStack函数来打印堆栈信息,帮助定位内存泄漏的位置。
  • 使用pprof包来进行内存分析,通过go tool pprof命令生成内存使用报告。
  • 编写单元测试,通过断言来检查内存使用是否符合预期。

7.3 示例:使用Go语言检测与修复内存泄漏

下面是一个检测与修复内存泄漏的示例:

package main

import (
	"fmt"
	"runtime"
)

// Leak 创建一个长生命周期的对象,并持有对短生命周期对象的引用
func Leak() *int {
	value := 42
	return &value
}

func main() {
	// 创建一个长生命周期的变量,持有对短生命周期对象的引用
	longLived := Leak()
	fmt.Println("Leaked memory:", *longLived)

	// 使用debug包打印堆栈信息
	var buf [1024]byte
	runtime.Stack(buf[:], false)
	fmt.Println("Stack trace:", string(buf[:]))

	// 运行GC,尝试回收未使用的内存
	runtime.GC()

	// 再次打印堆栈信息,检查是否有变化
	runtime.Stack(buf[:], false)
	fmt.Println("After GC:", string(buf[:]))
}

在这个例子中,我们定义了一个Leak函数,它创建一个短生命周期的对象,并返回一个指向这个对象的指针。在main函数中,我们创建了一个长生命周期的变量,并持有对短生命周期对象的引用。这样就造成了内存泄漏。

通过使用debug包中的PrintStack函数,我们可以打印出堆栈信息,帮助定位内存泄漏的位置。同时,我们运行了GC,尝试回收未使用的内存,并再次打印堆栈信息来检查是否有变化。

8. 总结

Go语言的内存管理主要依赖于垃圾回收器,这使得开发者可以专注于业务逻辑,而不必过多关注内存的分配和释放。但在某些情况下,了解内存管理的细节对于编写高效的程序是非常有帮助的。

8.1 Go语言内存管理的要点回顾

  1. 内存分配:Go语言使用new关键字来分配内存,返回的是指针类型。
  2. 垃圾回收:Go语言的垃圾回收器会自动回收不再使用的内存。
  3. 逃逸分析:编译器会分析变量的生命周期,以决定是分配到栈上还是堆上。
  4. 切片与映射:切片和映射是Go语言中强大的数据结构,但需要注意它们在内存管理上的特殊性。
  5. 内存泄漏:虽然Go语言的垃圾回收器可以自动回收内存,但仍然需要注意避免内存泄漏。

8.2 内存管理在实际开发中的应用

  1. 优化性能:通过合理使用内存池、逃逸分析等手段,可以优化程序的性能。
  2. 避免内存泄漏:通过使用debug包、pprof包等工具来检测和修复内存泄漏。
  3. 内存安全:合理管理内存可以避免内存安全问题,如缓冲区溢出等。
  4. 并发编程:在并发编程中,正确管理内存可以避免数据竞争和死锁等问题。

在实际开发中,内存管理是一个非常重要的方面,它直接影响到程序的性能和稳定性。通过理解Go语言的内存管理机制,我们可以更好地编写出高效、稳定的Go程序。同时,我们也应该熟练掌握内存管理的工具和技巧,以便在实际开发中灵活运用。

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

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

相关文章

ttwid、x-bogus、a-bogus、msToken、byted_acrawler等直播参数说明和获取方式

可以参考开源项目&#xff1a;https://github.com/Sjj1024/douyin-live 桌面端的直播间项目&#xff1a;https://github.com/Sjj1024/LiveBox ttwid参数 ttwid类似客户端id&#xff0c;即便是游客模式&#xff0c;也可以对页面数据进行埋点统计&#xff0c;通过收集ttwid下的…

R9000P 双系统安装 win11 和 ubuntu

网上了解到一堆关于 r9000p 安装较老的ubuntu系统&#xff0c;会有一堆问题 可能是电脑硬件比较新&#xff0c;较老的系统相关方面不兼容 那么干脆直接装新一点的系统 我安装了 Ubuntu 22.04 1 根据相关教程利用u盘制作系统盘 ultraISO 推荐使用清华源 速度快一点 https://…

【Linux】系列入门摘抄笔记-5-管理、创建、移动文件目录及文件搜索命令

touch 创建新的空文件 touch [选项] [参数] 描述:创建新的空文件,可以一次性创建多个文件。touch命令还可以用于修改文件的时间属性,不加时间戳则默认修改为当前时间。 如果文件不存在,则会建立空文件,如果文件已存在,则会修改文件的时间戳。 在linux中,文件没有创建时…

MTK Android12 SystemUI 手势导航 隐藏导航栏底部布局

问题:android12 平台手势导航情况下,app页面未设置全屏情况下,底部导航栏会有一个高度的颜色,底部导航会有一个手势导航提示条 需求:去掉手势导航情况下底部的导航栏和手势提示条 文章目录 相关资源修改问题描述解决方案代码跟踪中间提醒小方块代码查找底部手势导航条跟踪…

Dell R750 R760 H755安装SuSE12SP5 并识别Intel E810 NIC

新的机器对老的版本的OS支持不是非常好&#xff0c;好在有一些方法是可以获得老的驱动&#xff0c;并可以进行安装的。 1 需要有H755阵列卡对应的驱动 这里可以在博科的网站上进行下载&#xff0c;里面是有相关的驱动 截一些图片 按e键进行操作 后续继续安装即可,安装 安装…

【漏洞复现】LiveBos UploadFile 任意文件上传漏洞

声明&#xff1a;本文档或演示材料仅用于教育和教学目的。如果任何个人或组织利用本文档中的信息进行非法活动&#xff0c;将与本文档的作者或发布者无关。 一、漏洞描述 LiveBOS&#xff0c;由顶点软件股份有限公司开发的对象型业务架构中间件及其集成开发工具&#xff0c;是…

实现关系运算符的重载

全局函数的实现法&#xff1a; 成员函数实现法&#xff1a;

vscode配置代码片段生成快捷键

一开始还以为是装个插件的事&#xff0c;没想到是自己定义的快捷键。 以vue3代码片段为例 在vscode左下角点击红框处 选择新建全局代码片段文件 输入快捷键名称 vue3&#xff08;可以自定义&#xff09;&#xff0c;以.json结尾&#xff0c;回车 在打开的文件里编写需要的代…

MySQL把逗号分隔的id转换成逗号分隔的文字

有些列的值是逗号分隔的id&#xff0c;例如用户id&#xff1a;1,2,3&#xff0c;想要把这些id转成对应的文字&#xff0c;本文介绍转换方法。 方法一&#xff1a; 借助mysql.help_topic&#xff0c;参考我的另一篇博客&#xff1a;MySQL列转行-CSDN博客 方法二&#xff1a; …

二叉搜索树+set和map

前言 现在我们开始进行对树的学习,这一节我们主要讲二叉搜索树和set和map的使用&#xff0c;这两个的使用我们只讲一些&#xff0c;然后就是一些练习题&#xff0c;综合使用stl 1. key类型的二叉搜索树的实现 //实现二叉搜索树 template<class K> struct BSNode {BSNo…

nginx.conf alias 静态资源 别名 nginx配置

Linux系统Bug 报权限不足错误 user root; 解决server_name太长时报错的问题 #解决server_name太长时报错的问题server_names_hash_bucket_size 64; 解决文件上传默认限制1M的问题 #解决文件上传默认限制1M的问题client_max_body_size 100m; 监听所有端口 server_name _; a…

ABAP小白开发操作手册+(九)ABAP调用http

开发类型&#xff1a; 新增ABAP通过调用http的方式来发送业务数据到其他系统 开发申请&#xff1a; &#xff08;这里业务的开发申请没写完整SAP对应外部系统字段的对应关系&#xff0c;没关系&#xff0c;我们可以看接口文档&#xff09; 外围系统提供的接口文档&#xff1…

java之网络编程篇

前言 网络编程就是计算机和计算机之间通过网络进行数据传输&#xff0c;下面介绍一些概念和如何实现UDP和TCP两种模式的传输。 一、常见的软件架构C/S和B/S C/S架构需要一个客户端软件程序服务器 B/S只需要打开网页服务器 C/S架构的优缺点和应用场景 优点&#xff1a;画面可以…

看图学sql之sql的执行顺序

学完前面的内容&#xff0c;我们已经掌握了基本的sql语法了&#xff0c;那我们学的 select, distinct, from, where,group by, having, order by, limit 他们具体的执行顺序是什么样的呢&#xff1f; 语法&#xff1a; SELECT distinct column1, column2 FROM table1 join …

关系型数据库管理系统--MySQL

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

【经验总结】ShardingSphere5.2.1 + Springboot 快速开始

Sharding Sphere 官方文档地址&#xff1a; https://shardingsphere.apache.org/document/current/cn/overview/maven仓库&#xff1a;https://mvnrepository.com/artifact/org.apache.shardingsphere/shardingsphere-jdbc 官方的文档写的很详尽到位&#xff0c;这里会截取部分…

基于Retina+PFLD+CNN人脸关键点及表情识别

对图片/视频中的人脸进行检测&#xff0c;并绘制人脸框。然后对检测到的人脸进行关键点识别&#xff0c;并进行绘制。最后根据人脸关键点&#xff0c;裁剪出人脸&#xff0c;判断该人脸的表情。 基于此&#xff0c;分别使用retina进行人脸检测&#xff0c;PFLD进行人脸关键点识…

软硬链接详解

目录 一、软硬链接命令 二、特征 三、相关知识 一、软硬链接命令 ln -s target link_name-s&#xff1a;选项表示创建一个符号链接&#xff08;软链接&#xff09;&#xff0c;如果没有-s就是硬链接target&#xff1a;是要指向的目标文件或目录的路径。link_name&#xff1…

「HarmonyNextOS」页面路由跳转Router更换为Navigation

前言 前段时间&#xff0c;鸿蒙发布了HarmonyNextOS系统&#xff0c;API直接升级到了12&#xff0c;许多API都发生了改变&#xff0c;页面跳转页从当初推荐的Router变换成Navigation&#xff0c;并且从API Version 10之后&#xff0c;都推荐使用NavPathStack来实现页面路由&am…

Go语言项目实战班04 Go语言课程管理系统项目实战 20240807 课程笔记和上课代码

预览 课程特色 本教程录制于2024年8月8日&#xff0c;使用Go1.22版本&#xff0c;基于Goland2024进行开发&#xff0c;采用的技术栈比较新。 每节课控制在十分钟以内&#xff0c;课时精简&#xff0c;每节课都是一个独立的知识点&#xff0c;如果有遗忘&#xff0c;完全可以当…