一、引言

在当今高速发展的软件开发世界中,语言迁移已成为技术进化的常态。作为一名曾经的C/C++开发者,我经历了向Go语言转变的全过程,其中最大的认知挑战来自内存管理模式的根本性差异。

我记得第一次接触Go项目时的困惑:没有析构函数?不用手动释放内存?这些看似简单的变化,实则需要一套全新的思维模式。本文旨在帮助有C/C++背景、正在或即将使用Go的开发者,特别是那些已有1-2年Go经验但仍在挣扎于内存管理思维转变的朋友们。

Go语言设计的核心理念之一是简化内存管理,让开发者将更多精力集中在业务逻辑上。这种设计不仅提高了开发效率,也减少了常见的内存错误,如悬垂指针、重复释放等。然而,这并不意味着我们可以完全忽视内存管理 - 相反,我们需要以不同的方式思考它。

二、C/C++与Go内存管理模型对比

C/C++内存管理回顾:手动分配与释放

在C/C++中,内存管理就像是精心照料一座花园 - 每一株植物(内存)都需要亲手种植和清理:

// C语言中的内存管理
void process_data() {char* buffer = (char*)malloc(1024);  // 手动分配内存if (buffer == NULL) {return;  // 内存分配失败处理}// 使用buffer进行数据处理// ...free(buffer);  // 必须手动释放内存// 如果这里忘记释放,或提前返回,就会造成内存泄漏
}// C++中使用new/delete
void process_data_cpp() {int* numbers = new int[100];  // 动态分配数组// 使用numbers数组// ...delete[] numbers;  // 必须记得释放,且使用正确的形式
}

C++后来引入了RAII(资源获取即初始化)模式和智能指针,部分改善了手动内存管理的问题:

// 使用智能指针
void modern_cpp_approach() {std::unique_ptr<int[]> numbers(new int[100]);// 使用numbers// ...// 不需要手动释放,智能指针会在作用域结束时自动处理
}// 或使用标准容器
void using_containers() {std::vector<int> numbers(100);  // 内存管理由vector处理// 使用numbers// ...// vector离开作用域时自动释放内存
}

C/C++内存管理的核心特点

  • 开发者对内存有完全控制权
  • 必须手动跟踪每个对象的生命周期
  • 资源管理责任完全在开发者
  • 内存错误(泄漏、越界、使用已释放内存)是常见问题

Go的自动内存管理

Go语言则像是一个有智能园丁的花园——你只需决定种什么花,园丁会处理灌溉和清理工作:

// Go语言中的内存管理
func processData() {buffer := make([]byte, 1024)  // 分配内存// 使用buffer进行处理// ...// 无需手动释放内存// 当buffer不再被引用时,垃圾回收器会自动回收它
}

Go的垃圾回收器(GC)是一个并发的三色标记清除收集器,它会周期性地识别和回收不再使用的内存。这种设计极大地简化了内存管理,但也引入了新的考量点。

Go内存分配策略

特性栈分配堆分配
分配速度非常快相对较慢
生命周期函数返回自动释放由GC回收
适用情况局部变量且不会逃逸返回值、大对象、全局引用
性能影响几乎没有开销有GC开销

逃逸分析是Go编译器的一项重要技术,它决定一个变量应该分配在栈上还是堆上。当编译器无法确定变量在函数返回后是否还会被使用时,通常会采取保守策略,将其分配在堆上。

// 这个变量会留在栈上 - 因为它的生命周期仅限于函数内
func calculateSum() int {x := 10  // 在栈上分配y := 20  // 在栈上分配return x + y  // 返回值,不是引用
}// 这个变量会逃逸到堆上 - 因为它在函数结束后仍然可访问
func createData() *int {x := 10  // 初始在栈上,但会逃逸到堆上return &x  // 返回局部变量的指针,导致变量必须在堆上分配
}

三、思维模式转变的关键点

从C/C++迁移到Go,需要进行以下几个关键的思维转变:

从"我必须释放内存"到"让GC处理它"

在C/C++中,未释放的内存是程序员的失误,而在Go中,这是预期行为。这种转变需要建立对垃圾回收器的信任,同时了解其工作原理以避免性能问题。

// C++思维方式(在Go中不必要)
func processDataCppStyle(data []byte) []byte {result := make([]byte, len(data)*2)// 处理数据...// 不需要也不应该尝试"释放"result// defer free(result) // 这在Go中是错误的思维return result // 返回result,让调用者使用,GC会在适当时机回收
}

从"对象所有权"到"对象生命周期"

C++开发者习惯于思考"谁拥有这个对象",而Go开发者应该思考"这个对象的引用存在多久"。

// 在Go中,我们关注引用而非所有权
type Resource struct {data []byte
}func NewResource() *Resource {return &Resource{data: make([]byte, 1024),}
}func processResource() {r := NewResource()// 使用r// ...// 不需要显式释放r// 当没有任何引用指向r时,它会被自动回收
}

从"最小化内存分配"到"合理设计对象"

在C/C++中,每次内存分配都是需要权衡的,而在Go中,我们应该更关注对象和数据结构的合理设计,而非纠结于每次分配。

内存管理思维对比图

在这里插入图片描述

注意:上图是概念性的,表达了C/C++与Go在内存管理思维上的差异。

从"手动内存池管理"到"理解GC工作方式"

不再需要创建复杂的对象池来避免内存分配(除非在特定的高性能场景),而是需要了解GC的工作方式,避免给它增加不必要的负担。

四、实际项目中的内存优化经验

虽然Go有自动内存管理,但在大型项目中,依然需要关注内存使用效率。以下是一些实战经验:

大型对象处理策略

当处理大型对象时,频繁的分配和回收会给GC带来显著压力。对于这类场景,可以考虑:

// 避免在热路径中频繁创建大对象
// 不推荐的方式
func ProcessRequests(requests []Request) {for _, req := range requests {// 每个请求都创建一个大型缓冲区buffer := make([]byte, 10*1024*1024)processWithBuffer(req, buffer)}
}// 推荐的方式
func ProcessRequestsOptimized(requests []Request) {// 创建一次,重复使用buffer := make([]byte, 10*1024*1024)for _, req := range requests {processWithBuffer(req, buffer)// 可以在这里清空buffer,而不是重新分配}
}

内存池复用与sync.Pool应用场景

对于需要频繁创建和销毁的临时对象,sync.Pool提供了一种重用对象的机制,可有效减少GC压力:

var bufferPool = &sync.Pool{New: func() interface{} {// 创建一个默认大小的缓冲区return make([]byte, 8192)},
}func ProcessRequest(data []byte) []byte {// 从池中获取一个缓冲区buffer := bufferPool.Get().([]byte)// 确保无论如何都将缓冲区放回池中defer bufferPool.Put(buffer)// 重置buffer或调整大小buffer = buffer[:0] // 清空但保留容量if cap(buffer) < len(data)*2 {// 如果容量不够,创建新的buffer = make([]byte, 0, len(data)*2)}// 使用buffer处理数据// ...return result // 注意返回的是结果,不是buffer本身
}

重要提示sync.Pool不提供内存所有权保证,对象可能随时被回收,因此不适合用来管理需要长期持有的资源。它最适合处理生命周期短暂的临时对象。

切片和映射的预分配与重用技巧

预分配足够的容量可以减少内存重新分配和数据复制:

// 不推荐:会导致多次扩容和内存复制
func buildSliceInefficient(n int) []int {result := []int{}  // 容量为0for i := 0; i < n; i++ {result = append(result, i)  // 可能多次触发扩容}return result
}// 推荐:预分配容量
func buildSliceEfficient(n int) []int {result := make([]int, 0, n)  // 预分配足够容量for i := 0; i < n; i++ {result = append(result, i)  // 不会触发扩容}return result
}// 同样适用于map
func buildMapEfficient(n int) map[string]int {result := make(map[string]int, n)  // 预估容量for i := 0; i < n; i++ {result[fmt.Sprintf("key-%d", i)] = i}return result
}

避免不必要的堆分配

了解Go的逃逸分析规则,可以帮助我们减少不必要的堆分配:

// 会导致堆分配的函数
func createBufferEscape() *bytes.Buffer {buf := new(bytes.Buffer)  // 会分配在堆上,因为返回了指针buf.WriteString("hello")return buf
}// 避免堆分配的版本
func createBufferNoEscape() bytes.Buffer {var buf bytes.Buffer  // 在调用者的栈上分配buf.WriteString("hello")return buf  // 返回值,而非指针,可能在栈上处理
}// 当返回值较大时,编译器可能还是会选择堆分配
// 这时我们可以考虑传入预分配的缓冲区
func writeToBuffer(buf *bytes.Buffer) {buf.WriteString("hello")// 不返回任何东西,调用者已持有buf
}

五、常见内存问题诊断与处理

尽管Go有GC,但我们仍然需要诊断和处理内存问题。

内存泄漏排查工具与方法

Go中的内存泄漏通常由于某些对象被长期引用但不再使用导致的:

// 潜在的内存泄漏示例
var globalCache = make(map[string]*largeObject)func processAndCache(key string, data []byte) {obj := processData(data)  // 创建大对象globalCache[key] = obj    // 存入全局缓存// 问题:从不清理cache,导致内存持续增长
}// 改进版本
var (globalCache = make(map[string]*largeObject)cacheMutex  = &sync.Mutex{}
)func processAndCacheImproved(key string, data []byte) {cacheMutex.Lock()defer cacheMutex.Unlock()// 检查缓存大小,必要时清理if len(globalCache) > maxCacheSize {// 清理部分缓存,例如按LRU策略evictOldEntries()}obj := processData(data)globalCache[key] = obj
}

使用pprof进行内存分析

import ("net/http"_ "net/http/pprof"  // 注册pprof handlers"runtime/pprof""os"
)func main() {// 在后台启动pprof服务go func() {http.ListenAndServe("localhost:6060", nil)}()// 在关键点记录堆内存分析f, _ := os.Create("heap.prof")defer f.Close()pprof.WriteHeapProfile(f)// 应用主逻辑...
}

通过访问http://localhost:6060/debug/pprof/可以查看各种性能指标,或使用命令行工具分析:

go tool pprof http://localhost:6060/debug/pprof/heap

GC调优参数与实战经验

Go的GC相对黑盒,但我们可以通过环境变量和运行时参数进行有限调整:

import "runtime"func configureGC() {// 设置GC目标百分比:默认是100,意味着使用内存是上次GC后的2倍时触发// 调低这个值会导致GC更频繁,但每次停顿更短// 调高这个值会减少GC次数,但可能增加单次停顿时间和内存使用量runtime.SetGCPercent(100)// 手动触发GC(通常不建议,但在某些场景有用)runtime.GC()// 查看当前内存统计var stats runtime.MemStatsruntime.ReadMemStats(&stats)log.Printf("Alloc = %v MiB", stats.Alloc / 1024 / 1024)
}

GC实战经验

  • 在CPU密集型应用中,可以考虑调高GOGC值减少GC频率
  • 在内存受限环境中,适当调低GOGC值减少峰值内存使用
  • 对延迟敏感的服务,可以在请求低谷期手动触发GC

六、案例分析:从C++到Go的重构实践

我曾参与将一个高性能网络服务器从C++重构为Go的项目,分享一些经验和代码对比:

网络服务框架重构案例

C++版本的网络服务器

// C++版本 (简化)
class Connection {
private:std::vector<char> receiveBuffer_;std::vector<char> sendBuffer_;public:Connection() : receiveBuffer_(8192), sendBuffer_(8192) {}~Connection() {// 关闭连接,清理资源close();}void processRequests() {while (isConnected()) {// 分配内存用于新请求std::unique_ptr<Request> req(new Request());// 接收和解析请求if (!receiveRequest(req.get())) {continue;}// 处理请求std::unique_ptr<Response> resp(processRequest(req.get()));// 发送响应sendResponse(resp.get());// 智能指针自动释放内存}}
};

Go版本的网络服务器

// Go版本 (简化)
type Connection struct {conn net.Conn
}func NewConnection(conn net.Conn) *Connection {return &Connection{conn: conn}
}func (c *Connection) ProcessRequests(ctx context.Context) error {// 预分配一次,重复使用recvBuf := make([]byte, 8192)for {select {case <-ctx.Done():return ctx.Err()default:// 接收请求n, err := c.conn.Read(recvBuf)if err != nil {return err}// 解析请求req, err := ParseRequest(recvBuf[:n])if err != nil {continue}// 处理请求并获取响应resp := ProcessRequest(req)// 发送响应if err := SendResponse(c.conn, resp); err != nil {return err}// 无需手动释放req和resp,GC会处理}}
}

重构后的变化

  1. 代码更加简洁,不需要显式内存管理
  2. 错误处理更加自然,通过返回值而非异常
  3. 通过context支持更优雅的超时和取消
  4. 性能接近C++版本,但开发效率显著提高
  5. 内存使用更可预测,避免了C++版本中的一些细微内存泄漏

七、最佳实践与经验总结

基于我们的实践经验,总结出以下Go内存管理最佳实践:

数据结构设计原则

  1. 优先考虑值类型:对于小型对象,使用值类型而非指针可以减少GC压力。
// 不推荐 - 小结构体使用指针传递
type Point struct {X, Y int
}
func (p *Point) Move(dx, dy int) {p.X += dxp.Y += dy
}// 推荐 - 小结构体使用值传递
func (p Point) MoveBy(dx, dy int) Point {return Point{p.X + dx, p.Y + dy}
}
  1. 考虑内存布局:紧凑的内存布局有利于缓存局部性。
// 结构体字段顺序会影响内存对齐
// 不优化的结构体
type UserInfo struct {Name    string  // 16字节Age     int     // 8字节Active  bool    // 1字节 + 7字节填充Address string  // 16字节
}// 优化后的结构体 - 减少填充
type UserInfoOptimized struct {Name    string  // 16字节Address string  // 16字节Age     int     // 8字节Active  bool    // 1字节 + 7字节填充
}

大对象处理策略

  1. 分块处理:将大数据集分割成小块处理,避免一次性分配大量内存。
// 处理大文件时,使用缓冲区读取
func ProcessLargeFile(filename string) error {file, err := os.Open(filename)if err != nil {return err}defer file.Close()buffer := make([]byte, 32*1024) // 32KB缓冲区for {n, err := file.Read(buffer)if err == io.EOF {break}if err != nil {return err}// 处理缓冲区中的数据ProcessChunk(buffer[:n])}return nil
}
  1. 考虑使用mmap:对于超大文件,考虑使用内存映射。
import "golang.org/x/exp/mmap"func ProcessWithMMap(filename string) error {reader, err := mmap.Open(filename)if err != nil {return err}defer reader.Close()// 直接访问映射内存,无需加载整个文件data := make([]byte, 100)_, err = reader.ReadAt(data, 0)return err
}

并发场景下的内存考量

  1. 避免全局对象过度共享:减少锁竞争,考虑分片或本地缓存。
// 不推荐:所有goroutine共享一个map,高并发下锁竞争严重
var (globalCache = make(map[string]interface{})cacheMutex  = &sync.RWMutex{}
)// 推荐:使用分片减少锁竞争
type ShardedCache struct {shards    [256]shardhashFunc  func(string) uint8
}type shard struct {items map[string]interface{}mu    sync.RWMutex
}func (c *ShardedCache) Get(key string) interface{} {shardIndex := c.hashFunc(key)shard := &c.shards[shardIndex]shard.mu.RLock()defer shard.mu.RUnlock()return shard.items[key]
}
  1. 控制并发度:过高的并发会导致过多的内存分配。
// 使用有界工作池控制并发度
func ProcessItems(items []Item) {const maxWorkers = 100semaphore := make(chan struct{}, maxWorkers)var wg sync.WaitGroupfor _, item := range items {wg.Add(1)semaphore <- struct{}{} // 获取令牌go func(item Item) {defer func() {<-semaphore // 释放令牌wg.Done()}()ProcessItem(item)}(item)}wg.Wait()
}

八、常见误区与注意事项

从C/C++转到Go的开发者经常会陷入以下误区:

Go并非没有内存泄漏

即使有GC,Go程序仍然可能出现内存泄漏,尤其是以下情况:

// 泄漏1:goroutine泄漏
func leakyFunction() {ch := make(chan int) // 无缓冲通道go func() {val := <-ch // 永远阻塞,因为没有人发送fmt.Println(val)}()// goroutine会泄漏,因为通道永远不会关闭
}// 泄漏2:忘记关闭文件/网络连接
func leakyResourceHandling() {file, _ := os.Open("data.txt")// 忘记 defer file.Close()data, _ := ioutil.ReadAll(file)process(data)// 文件句柄泄漏
}// 泄漏3:不断增长的缓存
var cache = map[string][]byte{}
var mutex = &sync.Mutex{}func addToCache(key string, value []byte) {mutex.Lock()defer mutex.Unlock()cache[key] = value// 永不清理的缓存最终会耗尽内存
}

过度优化的陷阱

有时候过度关注内存优化反而会适得其反:

// 过度优化:复杂的对象池
type complexObjectPool struct {pool     []*ComplexObjectpoolLock sync.Mutex
}func (p *complexObjectPool) Get() *ComplexObject {p.poolLock.Lock()defer p.poolLock.Unlock()if len(p.pool) == 0 {return &ComplexObject{}}obj := p.pool[len(p.pool)-1]p.pool = p.pool[:len(p.pool)-1]return obj
}// 更好的选择:使用标准库
var stdPool = sync.Pool{New: func() interface{} {return &ComplexObject{}},
}

忽视GC开销的问题

在某些高性能场景下,GC暂停可能成为性能瓶颈:

// 问题代码:频繁分配大量临时对象
func ProcessLargeDataset(data []byte) []Result {var results []Result// 处理每个数据块for i := 0; i < len(data); i += chunkSize {chunk := data[i:min(i+chunkSize, len(data))]// 每次迭代产生大量临时对象intermediateResults := process(chunk)// 合并结果results = append(results, intermediateResults...)}return results
}// 改进:减少临时对象,预分配内存
func ProcessLargeDatasetImproved(data []byte) []Result {// 预估结果大小results := make([]Result, 0, len(data)/averageResultSize)// 重用临时对象tmp := make([]byte, maxTempSize)for i := 0; i < len(data); i += chunkSize {chunk := data[i:min(i+chunkSize, len(data))]// 使用预分配的临时缓冲区count := processInto(chunk, tmp)// 只分配实际需要的结果newResults := processResults(tmp[:count])results = append(results, newResults...)}return results
}

迁移过程中的思维惯性问题

C/C++的一些最佳实践在Go中可能反而是反模式:

// C++思维:手动管理连接池
type ConnectionPool struct {connections []*Connectionmutex       sync.Mutex
}func (p *ConnectionPool) GetConnection() *Connection {p.mutex.Lock()defer p.mutex.Unlock()if len(p.connections) == 0 {return newConnection()}conn := p.connections[len(p.connections)-1]p.connections = p.connections[:len(p.connections)-1]return conn
}func (p *ConnectionPool) ReturnConnection(conn *Connection) {p.mutex.Lock()defer p.mutex.Unlock()p.connections = append(p.connections, conn)
}// Go思维:使用标准库和上下文控制
import "database/sql"// 使用标准库的连接池
db, err := sql.Open("postgres", connStr)
db.SetMaxOpenConns(100)
db.SetMaxIdleConns(25)
db.SetConnMaxLifetime(5 * time.Minute)// 使用上下文控制生命周期
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()rows, err := db.QueryContext(ctx, "SELECT * FROM users")

九、未来展望与结论

Go内存管理的发展趋势

Go语言的内存管理正在不断演进:

  1. GC改进:每个版本都在提高GC性能,减少停顿时间
  2. 编译器优化:更智能的逃逸分析和内联决策
  3. 泛型支持:Go 1.18+的泛型功能可能影响内存使用模式
  4. 更多运行时控制:未来可能提供更细粒度的GC控制

个人成长与技术选择建议

  1. 接受不同的思维模式:不要试图将C++的模式强加于Go
  2. 理解而非规避GC:了解GC工作原理,与之合作而非对抗
  3. 优先考虑可读性:Go的哲学是简洁明了,不要过度优化
  4. 衡量再优化:使用基准测试验证优化的必要性和效果

总结关键思维转变要点

  1. 自动内存管理不等于无需关注内存:理解GC的工作方式和限制
  2. 从手动控制到合理设计:设计合理的数据结构和算法更重要
  3. 从所有权模型到引用跟踪:理解对象生命周期
  4. 从精细控制到适度放手:信任语言运行时,专注业务逻辑

从C/C++迁移到Go的过程中,内存管理思维的转变可能是最大的挑战,但也带来了巨大的回报。通过拥抱Go的设计理念,我们可以编写出更简洁、更可靠、更易维护的代码,同时保持接近C/C++的性能水平。

希望本文能帮助你平稳完成这一思维转变,充分发挥Go语言的潜力!

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

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

相关文章

正确设置 FreeRTOS 与 STM32 的中断优先级

在裸机开发&#xff08;非 RTOS&#xff09;时&#xff0c;大多数 STM32 外设的中断优先级通常不需要手动配置&#xff0c;原因如下&#xff1a; ✅ 裸机开发中默认中断优先级行为 特点说明默认中断优先级为 0如果你不设置&#xff0c;STM32 HAL 默认设置所有外设中断为 0&…

EasyExcel之SheetWriteHandler:解锁Excel写入的高阶玩法

引言在 EasyExcel 强大的功能体系中&#xff0c;SheetWriteHandler 接口是一个关键的组成部分。它允许开发者在写入 Excel 的 Sheet 时进行自定义处理&#xff0c;为实现各种复杂的业务需求提供了强大的支持。通过深入了解和运用 SheetWriteHandler 接口&#xff0c;我们能够更…

Python单例模式魔法方法or属性

1.单例模式概念定义:单例模式(Singleton Pattern)是一种创建型设计模式&#xff0c;它确保一个类只能有一个实例&#xff0c;并提供一个全局访问点来获取该实例。这种模式在需要控制资源访问、配置管理或协调系统操作时特别有用。核心特点:私有构造函数&#xff1a;防止外部通过…

【Kubernetes系列】Kubernetes 资源请求(Requests)

博客目录 引言一、资源请求的基本概念1.1 什么是资源请求1.2 请求与限制的区别 二、CPU 请求的深入解析2.1 CPU 请求的单位与含义2.2 CPU 请求的调度影响2.3 CPU 请求与限制的关系 三、内存请求的深入解析3.1 内存请求的单位与含义3.2 内存请求的调度影响3.3 内存请求的特殊性 …

大型语言模型中的自动化思维链提示

摘要 大型语言模型&#xff08;LLMs&#xff09;能够通过生成中间推理步骤来执行复杂的推理任务。为提示演示提供这些步骤的过程被称为思维链&#xff08;CoT&#xff09;提示。CoT提示有两种主要范式。一种使用简单的提示语&#xff0c;如“让我们一步一步思考”&#xff0c;…

Private Set Generation with Discriminative Information(2211.04446v1)

1. 遇到什么问题&#xff0c;解决了什么遇到的问题现有差分隐私生成模型受限于高维数据分布建模的复杂性&#xff0c;合成样本实用性不足。深度生成模型训练依赖大量数据&#xff0c;加入隐私约束后更难优化&#xff0c;且不保证下游任务&#xff08;如分类&#xff09;的最优解…

C++编程语言入门指南

一、C语言概述 C是由丹麦计算机科学家Bjarne Stroustrup于1979年在贝尔实验室开发的一种静态类型、编译式、通用型编程语言。最初被称为"C with Classes"(带类的C)&#xff0c;1983年更名为C。它既具有高级语言的抽象特性&#xff0c;又保留了底层硬件操作能力&…

ZED相机与Foxglove集成:加速机器人视觉调试效率的实用方案

随着机器人技术的发展&#xff0c;实时视觉数据流的高效传输和可视化成为提升系统性能的重要因素。通过ZED相机&#xff08;包括ZED 2i和ZED X&#xff09;与Foxglove Studio平台的结合&#xff0c;开发者能够轻松访问高质量的2D图像、深度图和点云数据&#xff0c;从而显著提高…

目标检测新纪元:DETR到Mamba实战解析

&#x1f680;【实战分享】目标检测的“后 DEⱯ”时代&#xff1a;DETR/DINO/RT-DETR及新型骨干网络探索&#xff08;含示例代码&#xff09; 目标检测从 YOLO、Faster R-CNN 到 Transformer 结构的 DETR&#xff0c;再到 DINO、RT-DETR&#xff0c;近两年出现了许多新趋势&am…

【IOS】XCode创建firstapp并运行(成为IOS开发者)

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍XCode创建firstapp并运行 学其所用&#xff0c;用其所学。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xff0c;下次更新不迷路…

class类和style内联样式的绑定 + 事件处理 + uniapp创建自定义页面模板

目录 一.class类的绑定 1.静态编写 2.动态编写 二.style内联样式的绑定 三.事件处理 1.案例1 2.案例2 四.uniapp创建自定义页面模板 1.为什么要这么做&#xff1f; 2.步骤 ①打开新建页面的界面 ②在弹出的目录下&#xff0c;新建模板文件 ③用HBuilderX打开该模板…

android 卡顿和丢帧区别

Android 卡顿&#xff08;Jank&#xff09;与丢帧&#xff08;Frame Drop&#xff09;的核心区别在于问题本质与用户感知&#xff0c;以下是分层解析&#xff1a; ️ 一、本质差异 维度卡顿&#xff08;Jank&#xff09;丢帧&#xff08;Frame Drop&#xff09;定义用户可感知…

【python实用小脚本-125】基于 Python 的 Gmail 邮件发送工具:实现高效邮件自动化

引言 在现代办公和开发环境中&#xff0c;邮件通信是一种重要的沟通方式。自动化发送邮件可以大大提高工作效率&#xff0c;例如发送通知、报告或文件。本文将介绍一个基于 Python 的 Gmail 邮件发送工具&#xff0c;它能够通过 Gmail 的 SMTP 服务器发送邮件&#xff0c;并支持…

gateway断言配置详解

一、Predicate - 断⾔ 1、简单用法 spring:cloud:gateway:routes:- id: after_routeuri: https://example.orgpredicates:- After2017-01-20T17:42:47.789-07:00[America/Denver] 2、自定义断言 新建类VipRoutePredicateFactory&#xff0c;注意VipRoutePredicateFactory名字…

基于大模型的尿毒症全流程预测与诊疗方案研究报告

目录 一、引言 1.1 研究背景与意义 1.2 研究目的与方法 1.3 国内外研究现状 二、尿毒症相关理论基础 2.1 尿毒症的定义、病因与发病机制 2.2 尿毒症的症状与诊断标准 2.3 尿毒症的治疗方法概述 三、大模型技术原理与应用 3.1 大模型的基本概念与发展历程 3.2 大模型…

裸金属服务器租用平台-青蛙云

企业对服务器性能与灵活性的要求与日俱增。青蛙云M-启强裸金属服务器租用平台应运而生&#xff0c;为企业提供了一种兼具物理机性能和云计算弹性的解决方案。裸金属服务器租用平台的优势​(一)高配性能&#xff0c;无虚拟化开销​裸金属服务器直接运行在物理硬件之上&#xff0…

[Terence Tao访谈] AlphaProof系统 | AI嗅觉 | 研究生学习 | 庞加莱猜想(高维) | 复杂问题简单化

玩这些有趣的东西。通常情况下什么也得不到&#xff0c;你必须学会说&#xff1a;“好吧&#xff0c;再试一次&#xff0c;什么都没发生&#xff0c;我会继续前进。” DeepMind的AlphaProof系统 Q&#xff1a;DeepMind的AlphaProof系统是通过强化学习训练的&#xff0c;使用的…

Aseprite工具入门教程4之动画导入Unity

1、时间轴功能 &#xff08;1&#xff09;眼睛图标 显示/隐藏图层图层隐藏时无法绘制 &#xff08;2&#xff09;锁定图标 锁定后无法移动或编辑图层防止意外在错误图层上绘制 &#xff08;3&#xff09;单元格图标 两个点代表帧分开&#xff0c;一个椭圆代表帧统一。分开就…

移动硬盘频繁提示格式化?解决异常故障的正确方法

移动硬盘作为数据存储的重要工具&#xff0c;不少人都习惯将照片、文档、项目资料甚至整台电脑的备份都放在里面。但有时&#xff0c;一件令人头疼的事悄然发生&#xff1a; 插上硬盘&#xff0c;系统却突然提示&#xff1a;“使用驱动器中的光盘之前需要将其格式化。是否要将…

Java泛型笔记

1 为什么需要泛型 Java5之前&#xff0c;是没有泛型的。通过两段代码我们就可以知道为何我们需要泛型 public int addInt(int a, int b) {return a b; }public double addDouble(double a, double b) {return a b; } 实际开发中&#xff0c;经常有数值类型求和的需求&…