-
大家好哦 ,三月份的干货合集来了,这次包含又redis,MySQL,HarmonyOS,Linux,Python,GoLang,Nginx,spring等多方面内容,希望可以帮到大家1.Redis Key的数量上限及优化策略分享【转】https://bbs.huaweicloud.com/forum/thread-02127178647758557099-1-1.html2.MySQL多列IN查询的实现【转】https://bbs.huaweicloud.com/forum/thread-0282178647688443077-1-1.html3.MySQL新增字段后Java实体未更新的潜在问题与解决方案【转】https://bbs.huaweicloud.com/forum/thread-0211178647622373117-1-1.html4.浅谈mysql的sql_mode可能会限制你的查询【转】https://bbs.huaweicloud.com/forum/thread-02127178647532278098-1-1.html5.MySQL使用SHOW PROCESSLIST的实现【转】https://bbs.huaweicloud.com/forum/thread-0238178647442172080-1-1.html6.HarmonyOS Next音乐播放器技术栈详解【转】https://bbs.huaweicloud.com/forum/thread-0213178647328545104-1-1.html7.Linux上设置Ollama服务配置(常用环境变量)【转】https://bbs.huaweicloud.com/forum/thread-0274178647201397098-1-1.html8.GORM中Model和Table的区别及使用【转】https://bbs.huaweicloud.com/forum/thread-0238178647121910079-1-1.html9. Python 的 ultralytics 库功能及安装方法【转】https://bbs.huaweicloud.com/forum/thread-0211178647036676116-1-1.html10.Python如何在Word中查找并替换文本【转】https://bbs.huaweicloud.com/forum/thread-0213178646924252103-1-1.html?fid=56811.GoLand 中设置默认项目文件夹的实现【转】https://bbs.huaweicloud.com/forum/thread-0210178646835711094-1-1.html12.Python Geopy库地理编码和地理距离计算案例展示【转】https://bbs.huaweicloud.com/forum/thread-0282178646750927076-1-1.html13.Java RMI技术详解与案例分析https://bbs.huaweicloud.com/forum/thread-0274178534386630091-1-1.html14.Volatile不保证原子性及解决方案https://bbs.huaweicloud.com/forum/thread-0274178534309336090-1-1.html15.Redis数据结构—跳跃表 skiplist 实现源码分析https://bbs.huaweicloud.com/forum/thread-0282178533434493072-1-1.html16.Java Executors类的9种创建线程池的方法及应用场景分析https://bbs.huaweicloud.com/forum/thread-0210178533186291086-1-1.html17.Nginx性能调优5招35式不可不知的策略实战https://bbs.huaweicloud.com/forum/thread-0213178533127218096-1-1.html18.Tomcat的配置文件中有哪些关键的配置项,它们分别有什么作用?https://bbs.huaweicloud.com/forum/thread-0210178533048188084-1-1.html19.深度长文解析SpringWebFlux响应式框架15个核心组件源码 https://bbs.huaweicloud.com/forum/thread-0282178532893901071-1-1.html20.对比传统数据库,TiDB 强在哪?谈谈 TiDB 的适应场景和产品能力https://bbs.huaweicloud.com/forum/thread-02127178532544750088-1-1.html
-
1. Model 的作用与特点1.1 核心用途Model 方法用于将 Go 结构体(Model)与数据库表进行关联。它隐式地基于结构体的定义推断表名、字段映射以及关系。1.2 行为特点• 自动表名解析:默认使用结构体名的复数形式作为表名(如 User → users),也可以通过 TableName() 方法自定义。• 字段映射:结构体的字段通过标签(如 gorm:"column:user_name")与数据库列关联,查询时会自动转换命名风格(如驼峰→蛇形)。• 链式方法集成:后续的查询条件(如 Where)可以直接使用结构体的字段名,而非数据库列名。• 模型钩子生效:若结构体实现了 BeforeSave、AfterFind 等钩子方法,操作时会自动触发。1.3 示例代码123456789type User struct { ID uint Name string `gorm:"column:username"`} // 自动对应 users 表,查询时使用结构体字段名var user Userdb.Model(&User{}).Where("name = ?", "john").First(&user)// SQL: SELECT * FROM users WHERE username = 'john' LIMIT 1;2. Table 的作用与特点2.1 核心用途Table 方法直接指定数据库表名,适用于非结构化查询(如动态表名、复杂 Join)或未定义 Model 的场景。2.2 行为特点• 显式表名指定:直接使用传入的字符串作为表名,忽略模型定义。• 原生列名操作:查询条件需使用数据库列名,而非结构体字段名。• 无模型关联:不会触发模型钩子,也不依赖结构体的 TableName() 方法。• 灵活性高:适合动态表名(如分表)或复杂 SQL 拼接。2.3 示例代码1234// 直接操作 users 表,需使用列名 usernamevar result map[string]interface{}db.Table("users").Where("username = ?", "john").Find(&result)// SQL: SELECT * FROM users WHERE username = 'john';3. 对比总结特性ModelTable表名来源结构体推断或 TableName() 方法直接传入字符串字段/列名映射自动转换(支持标签)需使用数据库列名模型钩子生效不生效动态表名需通过 TableName() 动态返回直接传入动态字符串(如分表场景)适用场景结构化查询、关联操作原生 SQL、动态表名、复杂查询4. 何时选择 Model 或 Table?4.1 使用 Model 的场景• 需要模型与表自动关联(如操作 CRUD)。• 依赖结构体字段名自动生成查询条件。• 需要触发钩子方法(如自动记录更新时间)。• 进行关联查询(Preload、Joins)。4.2 使用 Table 的场景• 操作未定义 Model 的表。• 动态表名(如按日期分表)。• 执行复杂 SQL(如子查询、UNION)。• 查询结果映射到 map 或非模型结构体。5. 高级技巧5.1 混合使用 Model 和 Table123// 使用 Model 定义字段映射,但重写表名db.Model(&User{}).Table("admin_users").Find(&results)// SQL: SELECT * FROM admin_users;5.2 查询到非结构体类型123456// 将结果扫描到 map 或切片var userMap map[string]interface{}db.Model(&User{}).First(&userMap) var results []map[string]interface{}db.Table("users").Find(&results)6. 总结Model 和 Table 是 GORM 中两种不同的表操作入口:• Model 更适合结构化、模型驱动的场景,强调约定优于配置。• Table 则提供更高的灵活性,适合动态需求或原生 SQL 操作。根据实际需求选择合适的方法,可以显著提升代码的可维护性和执行效率。
-
在使用 GoLand 进行开发时,设置一个默认的项目文件夹可以大大提高工作效率。默认项目文件夹会在你打开或新建项目时自动预选,避免每次都需要手动导航到目标目录。本文将详细介绍如何在 GoLand 中设置默认项目文件夹。步骤一:打开系统设置首先,打开 GoLand,点击顶部菜单栏中的 File(文件),然后选择 Settings(设置)。在 Windows 和 Linux 系统中,你也可以使用快捷键 Ctrl + Alt + S 来快速打开设置。步骤二:导航到外观与行为在设置窗口中,找到并点击 Appearance & Behavior(外观与行为)选项。这个选项通常位于设置窗口的顶部。步骤三:设置默认项目目录在 Appearance & Behavior 下,找到 System Settings(系统设置)并点击。然后,你会看到一个名为 Default project directory(默认项目目录)的选项。点击旁边的 ... 按钮,选择你希望设置为默认的项目文件夹路径。例如,你可以选择 D:\Data\code\GolandProjects。步骤四:应用更改选择好路径后,点击 Apply(应用)和 OK(确定)保存更改。现在,每当你使用 Open...(打开…)或 New | Project...(新建 | 项目…)对话框时,GoLand 都会自动预选你设置的默认项目目录。其他相关设置在设置默认项目文件夹的同时,你还可以配置一些其他有用的选项:自动保存:你可以启用 Save files if the IDE is idle for 15 seconds(如果 IDE 空闲 15 秒则保存文件)和 Save files when switching to another application or built-in terminal(切换到其他应用程序或内置终端时保存文件)来确保你的工作不会丢失。同步外部更改:启用 Synchronize external changes when switching to the IDE window or opening an editor tab(切换到 IDE 窗口或打开编辑器标签页时同步外部更改)和 Periodically when the IDE is inactive (experimental)(IDE 处于非活动状态时定期同步)可以帮助你保持项目文件的最新状态。总结通过设置默认项目文件夹,你可以在 GoLand 中更高效地管理你的项目。这个简单的设置可以节省你每次打开或新建项目时的时间,让你更专注于代码编写。希望这篇文章对你有所帮助,祝你在 GoLand 中开发愉快!
-
如题请问 2025 年比赛可以使用 Go 语言吗?
-
大家好,2025年开年的第一篇合集,本次带来的是Python,Java,MySql,Golang,JSON,等等希望可以帮到大家。1.Python判断for循环最后一次的方法【转】https://bbs.huaweicloud.com/forum/thread-0248173698858425071-1-1.html2.使用Python实现高效的端口扫描器【转】https://bbs.huaweicloud.com/forum/thread-0248173699028101072-1-1.html3.使用Python实现操作mongodb详解【转】https://bbs.huaweicloud.com/forum/thread-02109173699263711070-1-1.html4.一文详解Python中数据清洗与处理的常用方法【转】https://bbs.huaweicloud.com/forum/thread-02109173699342905071-1-1.html5.Go中sync.Once源码的深度讲解【转】https://bbs.huaweicloud.com/forum/thread-0271173699402065058-1-1.html6.从源码解析golang Timer定时器体系【转】https://bbs.huaweicloud.com/forum/thread-0251173701525255062-1-1.html7.golang1.23版本之前 Timer Reset方法无法正确使用【转】https://bbs.huaweicloud.com/forum/thread-02127173701584637057-1-1.html8.Python文件读写实用方法小结【转】https://bbs.huaweicloud.com/forum/thread-02104173701685566070-1-1.html9.mysql外键创建不成功/失效如何处理【转】https://bbs.huaweicloud.com/forum/thread-02109173701958630072-1-1.html10.Redis的Zset类型及相关命令详细讲解【转】https://bbs.huaweicloud.com/forum/thread-02109173702031434073-1-1.html11.大数据小内存排序问题如何巧妙解决【转】https://bbs.huaweicloud.com/forum/thread-02127173702077058058-1-1.html12.Redis多种内存淘汰策略及配置技巧分享【转】https://bbs.huaweicloud.com/forum/thread-0272173702166312062-1-1.html13.MySQL通过binlog实现恢复数据【转】https://bbs.huaweicloud.com/forum/thread-02109173702268081074-1-1.html14.MySQL如何将一个表的字段更新到另一个表中【转】https://bbs.huaweicloud.com/forum/thread-0272173702328248063-1-1.html15.JSON字符串转成java的Map对象详细步骤【转】https://bbs.huaweicloud.com/forum/thread-02109173702572327075-1-1.html
-
,为了防止文档更新而导致内容变动,这里粘贴出来:Before Go 1.23, the only safe way to use Reset was to [Stop] and explicitly drain the timer first. See the NewTimer documentation for more details. 在Go 1.23 之前,唯一安全使用Reset函数的方式是:在使用之前调用Stop函数并且明确的从timer的channel中抽取出东西。虽然文档中已经告诉了正确使用的方式,但是实际上在真正的代码中无法达到这个要求,参考下方代码//consumer go func() { // try to read from channel, block at most 5s. // if timeout, print time event and go on loop. // if read a message which is not the type we want(we want true, not false), // retry to read. timer := time.NewTimer(time.Second * 5) for { // timer may be not active, and fired if !timer.Stop() { select { case <-timer.C: //try to drain from the channel,尝试抽取,由于使用select,因此这里可以保证:不阻塞 + 一定抽取成功 default: } } timer.Reset(time.Second * 5) //重置定时器 select { case b := <-c: if b == false { fmt.Println(time.Now(), ":recv false. continue") continue } //we want true, not false fmt.Println(time.Now(), ":recv true. return") return case <-timer.C: fmt.Println(time.Now(), ":timer expired") continue } } }()在上面的代码中,我们按照文档的要求,在 timer.Reset 之前已经调用了 Stop 函数,且如果 Stop 成功(返回 true),还尝试抽取 timer,看起来似乎没问题的代码中仍然存在问题。问题的关键在于:当 Ticket 触发的时候,设置定时器状态的操作和发送 channel 的操作并不是原子的golang1.23 之前到底应该如何正确的使用 Reset实际上简洁点就这么写,每次一个新的局部变量 Timer 结构体没压力,非要复用使用 Reset 的可读性太差了,对维护者不友好,而且习惯了不好的写法,哪天一不小心就写出问题了~go func() {for {func() {timer := time.NewTimer(time.Second * 2)defer timer.Stop() select {case b := <-c:if !b {fmt.Println(time.Now(), "work...")}case <-timer.C: // BBB: normal receive from channel timeout eventfmt.Println(time.Now(), "timeout")}}()}}()
-
Timer、Ticker使用及其注意事项在刚开始学习golang语言的时候就听说Timer、Ticker的使用要尤其注意,很容易出现问题,这次就来一探究竟。本文主要脉络:介绍定时器体系,并介绍常用使用方式和错误使用方式源码解读timer、ticker是什么?timer和ticker都是定时器,不同的是:timer是一次性的定时器ticker是循环定时器,在底层实现上是timer触发后又重新设置下一次触发时间来实现的正确的使用姿势Timer对于Timer,可以通过三种函数创建:time.NewTimer、time.AfterFunc、time.After。其使用范例如下:// FnTimer1 Timer的使用用法func FnTimer1() {timer := time.NewTimer(time.Second * 5) //返回生成的timerfmt.Printf("timer 的启动时间为:%v\n", time.Now()) expire := <-timer.C //注意这个channel是一个容量为1的channel!fmt.Printf("timer 的触发时间为:%v\n", expire)} func FnTimer2() {ch1 := make(chan int, 1)select {case e1 := <-ch1://如果ch1通道成功读取数据,则执行该case处理语句fmt.Printf("1th case is selected. e1=%v",e1)case <- time.After(2 * time.Second): //time.After直接返回生成的timer的channel,所以一般用于超时控制fmt.Println("Timed out")}} func FnTimer3() {_ = time.AfterFunc(time.Second*5, func() { //返回的也是timer,不过可以自己传入函数进行执行fmt.Printf("定时器触发了,触发时间为%v\n", time.Now())}) fmt.Printf("timer 的启动时间为:%v\n", time.Now())time.Sleep(10 * time.Second) // 确保定时器触发} 在底层原理上,三种不同的创建方式都是调用的time.NewTimer,不同的是:time.After直接返回的是生成的timer的channel,而time.NewTimer是返回生成的timer。time.NewTimer定时触发channel的原理就是定时调用一个sendTime函数,这个函数负责向channel中发送触发时间;time.AfterFunc就是将定时调用的sendTime函数换成了一个自定义的函数。补充:返回的channel的容量为1,因此是一个asynchronous的channel,即在定时器fired(触发)、stop(停止)、reset(重启)之后,仍然有可能能收到数据~垃圾回收:在go1.23之前,对于处于active(没有触发且没有显式调用Stop)的Timer,gc是无法回收的,Ticket也是同样的道理。因此在高并发的场景下需要显式搭配defer time.Stop()来解决暂时的“内存泄露”的问题。上述两点在Golang 1.23得到了解决,且可能在未来的版本中channel的容量会改为0(由asynchronous改成sync),在Go 1.23相关源码的注释部分有对应的说明。Ticketticket相比于timer,其会循环触发,因此通常用于循环的干一些事情,like:// FnTicket2 Ticker的正确的使用方法!!func FnTicket2() {ticker := time.NewTicker(1 * time.Second)stopTicker := make(chan struct{})defer ticker.Stop()go func() {for {select {case now := <-ticker.C:// do somethingfmt.Printf("ticker 的触发时间为:%v\n", now)case <-stopTicker:fmt.Println("ticker 结束")return}}}() time.Sleep(4 * time.Second)stopTicker <- struct{}{}}注意:代码块中使用stopTicker + select的方式是必须的,由于Stop函数只是修改定时器的状态,并不会主动关闭channel,因此如果直接使用循环(见我的github仓库 )会直接导致Ticker永远不退出而导致内存泄露!补充:TIcket = 触发时自动设置下一次时间的Timer,因此上面提到的Go1.23之前的Timer存在的问题依然存在。返回的channel容量为1垃圾回收:由于Ticket会一直循环触发,因此如果不显式调用time.Stop方法的话,会永久内存泄露。此外,对于Ticket,还有一些额外的注意事项:需要使用一个额外的channel+select的方式来正确停止Ticket。Reset函数的问题,由于比较长,单独整理在golang1.23版本之前 Timer Reset无法正确使用的问题,这里说个结论 :Reset函数由于内部非原子,因此无法完美的使用,建议使用goroutine+Timer的方式替代!使用的注意事项由于Golang 1.23版本对定时器timer、ticker做了很大的改进,因此要分成1.23之前和1.23及其之后的版本分开考虑:以下是1.23版本关于timer、ticker部分的修改:未停止的定时器和不再被引用的计时器可以进行垃圾回收。在 Go 1.23 之前,未停止的定时器无法被垃圾回收,直到定时器超时,而未停止的计时器永远无法被垃圾回收。Go 1.23 的实现避免了在不使用 t.Stop 的程序中出现资源泄漏。定时器通道现在是同步的(无缓冲的),这使 t.Reset 和 t.Stop 方法具有更强的保证:在其中一个方法返回后,将来从定时器通道接收到的任何值都不会观察到与旧定时器配置相对应的陈旧时间值。在 Go 1.23 之前,无法使用 t.Reset 避免陈旧值,而使用 t.Stop 避免陈旧值需要仔细使用 t.Stop 的返回值。Go 1.23 的实现完全消除了这种担忧。总结一下:1.23版本改进了timer、ticker的垃圾回收和 停止、重置的相关方法(Reset、Stop)。这也就意味着在1.23版本之前,我们在使用的时候要注意:垃圾回收和停止、重置相关方法的使用。由于Reset、Stop方法的外在表现本质上上是跟缓冲区由 有缓冲 改为 无缓冲 相关,因此如果有涉及读取缓冲区我们也需要注意相关特性。具体来说,对于1.23之前:垃圾回收:TImer的回收只会在定时器触发(expired)或者Stop之后;Ticker只显式触发Stop之后才会回收;Reset、Stop使用:对于Timer,没有完美的做法,无论怎么样Reset和Stop都可能存在一些问题;对于Ticker,记得使用完之后显式的Stop;源码解读源码解读版本:release-branch.go1.8运作原理timer(ticket)的运作依赖于struct p,源码位置:src/runtime/runtime2.go:603。所有的计时器timer都以最小四叉堆的形式存储在struct p的timers字段中。并且也是交给了计时器都交由处理器的网络轮询器和调度器触发,这种方式能够充分利用本地性、减少上下文的切换开销,也是目前性能最好的实现方式。一般来说管理定时器的结构有3种:双向链表、最小堆、时间轮(很少)。双向链表:插入|修改时间:需要遍历去寻找插入|修改的位置,时间复杂度O(N);触发:链表头触发即可,时间复杂度O(1)。最小堆:插入|修改时间:O(logN)的时间复杂度;触发:队头触发,但是触发之后需要调整堆以保证堆的特性,O(logN)的时间复杂度。时间轮:插入|修改时间|触发的时间复杂度都是O(1)。但是需要额外维护时间轮的结构,其占据空间随着需要维护的未来时间长度、时间精度增加。涉及结构体定时器体系有两种timer:一次性触发的Timer和循环触发的Ticker,这两种的底层结构是相同的,触发逻辑是类似的。毕竟Ticker的功能包含Timer的功能。
-
概念sync.Once是Go语言标准库中的一个同步原语,用于确保某个操作只执行一次。它在多线程环境中非常有用,尤其是在需要初始化共享资源或执行某些一次性任务时。简单示例当我们在web服务访问某个路由时,如果需要事先获取某些配置,往往会写一个loadConfig函数,获取一个cfg配置项。多次路由访问所需要获取的配置项通常是相同的,如果对于每次路由访问,都加载一次loadConfig函数,会导致产生一些不必要的开销。如果loadConfig涉及到读取文件、解析配置、网络请求时,有可能会额外增加的请求响应时间,降低服务的吞吐量。使用sync.Once包提供的Do函数,就可以只在第一次请求时调用loadConfig函数加载配置,之后的请求都复用第一次请求的配置,缩短响应时间。package mainimport ( "log" "net/http" "sync")type Config struct { APIKey string LogLevel string}var ( config *Config once sync.Once)func loadConfig() { // 模拟从文件或环境变量加载配置 config = &Config{ APIKey: "secret-key", LogLevel: "debug", } log.Println("Configuration loaded")}func GetConfig() *Config { once.Do(loadConfig) // 仅第一次访问时会执行loadConfig函数 return config}func handler(w http.ResponseWriter, r *http.Request) { cfg := GetConfig() log.Printf("Request handled with API key: %s", cfg.APIKey) w.Write([]byte("OK"))}func main() { http.HandleFunc("/", handler) log.Fatal(http.ListenAndServe(":8888", nil))}源码解读源码文件:src/sync/once.go (go 1.23 版本)package syncimport ( "sync/atomic")type Once struct { done atomic.Uint32 // 是否已执行标识位,0-未执行 1-已执行 m Mutex // 互斥锁,确保并发安全}func (o *Once) Do(f func()) { // 第一次执行Do函数时,原子操作检查o.done==0,执行doSlow函数后,o.done==1 // 第二次及之后执行Do函数,原子操作检查o.done标识位为1,Do函数不执行任何功能,确保了f函数只在第一次被执行 if o.done.Load() == 0 { o.doSlow(f) // 调用doSlow函数执行f方法。第一次执行时,同一时间可能有多个goroutine尝试同时执行doSlow函数 }}func (o *Once) doSlow(f func()) { o.m.Lock() // 加锁保护,避免多个goroutine同时绕过之前的原子操作检查,并发修改o.done的值 defer o.m.Unlock() // 二次检查o.done的值,同一时间并发执行doSlow函数的goroutine,在第一个goroutine将o.done置为1并解除互斥锁后, // 剩下的goroutine识别到自身的o.done已经被设为1,无法绕过二次检查 if o.done.Load() == 0 { defer o.done.Store(1) // 需要在f()函数执行完成之后,原子性地将o.done设为1 f() // 执行f方法,一定只有一个goroutine会调用这个方法 }}可以看到,once.go文件的代码非常精炼。仅定义了一个含2个非导出字段done和m的结构体Once,并提供了一个doSlow方法用于执行f函数。当我们调用Do方法时,程序经历了几个关键步骤:判断done标志位是否等于0,如果是,说明f函数还没有被执行,执行doSlow方法mu互斥锁加锁,防止多个goroutine并发操作double-check done标志位是否等于0,如果是,说明f函数还没有被执行,执行f函数f函数执行完成之后,再将done标志位原子性设为1。使用原子操作是从内存可见性的角度出发,如果done使用uint32而不是atomic.Uint32,done修改可能不会立即被其它goroutine感知,解锁后仍有可能存在goroutine的done等于0,重复执行f函数mu互斥锁解锁。此时进入到doSlow函数的其它goroutine也感知到了o.done等于1,不会重复执行f函数了
-
Go 语言压缩文件处理在现代的应用开发中,处理压缩文件(如 .zip 格式)是常见的需求。Go 语言提供了内置的 archive/zip 包来处理 .zip 文件的读写,但有时我们需要封装一些常用操作,使得代码更加简洁、易用。本文将介绍如何使用 Go 语言封装一个 ziputil 包,来处理文件的压缩和解压操作。1. 压缩文件:Zip函数在 Go 语言中,压缩文件通常需要使用 archive/zip 包。我们将对文件夹或文件进行遍历,创建一个新的 .zip 文件,并将文件或文件夹逐个添加到压缩包中。 package ziputil import ( "archive/zip" "go-admin/app/brush/utils" "sync" "io" "os" "path/filepath" log "github.com/go-admin-team/go-admin-core/logger" ) // Zip 将指定的文件夹或文件压缩为 .zip 文件 func Zip(source, zipFile string) error { // 创建一个新的 zip 文件 zipFileWriter, err := os.Create(zipFile) if err != nil { return err } defer func(zipFileWriter *os.File) { err := zipFileWriter.Close() if err != nil { log.Errorf("关闭 zip 文件失败: %s", err) } }(zipFileWriter) // 创建 zip 写入器 zipWriter := zip.NewWriter(zipFileWriter) defer func(zipWriter *zip.Writer) { err := zipWriter.Close() if err != nil { log.Errorf("关闭 zip 写入器失败: %s", err) } }(zipWriter) // 获取源文件的绝对路径 absSource, err := filepath.Abs(source) if err != nil { return err } // 遍历文件夹并添加到 zip 文件中 return filepath.Walk(absSource, func(path string, info os.FileInfo, err error) error { if err != nil { return err } // 计算文件相对路径 relPath, err := filepath.Rel(absSource, path) if err != nil { return err } // 如果是目录,则在 zip 文件中创建一个目录项 if info.IsDir() { if relPath != "." { _, err := zipWriter.Create(relPath + "/") if err != nil { return err } } return nil } // 否则将文件添加到 zip 文件 return addFileToZip(zipWriter, path, relPath) }) } // addFileToZip 将单个文件添加到 zip 写入器 func addFileToZip(zipWriter *zip.Writer, file string, relPath string) error { f, err := os.Open(file) if err != nil { return err } defer func(f *os.File) { err := f.Close() if err != nil { log.Errorf("关闭文件失败: %s", err) } }(f) // 在 zip 文件中创建该文件 writer, err := zipWriter.Create(relPath) if err != nil { return err } // 将文件内容写入 zip _, err = io.Copy(writer, f) if err != nil { return err } return nil } 2. 解压文件:UnZip 函数解压 .zip 文件时,我们需要将 .zip 文件中的每个文件提取到指定的目录中。UnZip 函数不仅能够提取文件,还能够处理文件夹结构,保证提取后的目录结构不丢失。 // UnZip 解压 zip 文件到目标目录 func UnZip(zipFile, destDir string) error { log.Debugf("解压文件: %s 到 %s", zipFile, destDir) r, err := zip.OpenReader(zipFile) if err != nil { return err } defer func(r *zip.ReadCloser) { err := r.Close() if err != nil { log.Errorf("关闭 zip 文件失败: %s", err) } }(r) log.Debugf("总共 %d 个文件", len(r.File)) // 并发解压每个文件 wg := sync.WaitGroup{} for _, f := range r.File { wg.Add(1) go func(rf *zip.File, w *sync.WaitGroup) { defer w.Done() if err := unzipFile(rf, destDir); err != nil { log.Errorf("解压文件 [%s] 失败: %v", rf.Name, err) } }(f, &wg) } wg.Wait() return nil } // unzipFile 解压单个文件到目标目录 func unzipFile(f *zip.File, destDir string) error { // 将文件名转换为 UTF-8 filename := utils.ConvertToUTF8([]byte(f.Name)) filePath := filepath.Join(destDir, filename) // 创建文件夹 if f.FileInfo().IsDir() { return os.MkdirAll(filePath, os.ModePerm) } // 创建文件的父目录 if err := os.MkdirAll(filepath.Dir(filePath), os.ModePerm); err != nil { log.Errorf("创建目录 [%s] 失败: %v", filepath.Dir(filePath), err) return err } // 打开文件 file, err := f.Open() if err != nil { log.Errorf("打开文件 [%s] 失败: %v", filePath, err) return err } defer func(file io.ReadCloser) { err := file.Close() if err != nil { log.Errorf("关闭文件 [%s] 失败: %v", filePath, err) } }(file) // 创建文件 outFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) if err != nil { log.Errorf("创建文件 [%s] 失败: %v", filePath, err) return err } defer func(outFile *os.File) { err := outFile.Close() if err != nil { log.Errorf("关闭文件 [%s] 失败: %v", filePath, err) } }(outFile) // 将文件内容写入 _, err = io.Copy(outFile, file) if err != nil { log.Errorf("复制文件 [%s] 失败: %v", filePath, err) return err } return nil } 3. 小结通过 ziputil 包,我们可以方便地进行文件和文件夹的压缩和解压操作。该包使用了 Go 内置的 archive/zip 包来处理 .zip 文件,并通过 sync.WaitGroup 实现了解压过程的并发处理,提高了解压效率。对于较大的压缩文件或包含大量文件的压缩包,使用并发处理可以显著提升性能。
-
使用 Go 语言中的 Context 取消协程执行在 Go 语言中,协程(goroutine)是一种轻量级的线程,非常适合处理并发任务。然而,如何优雅地取消正在运行的协程是一个常见的问题。本文将通过一个具体的例子来展示如何使用 context 包来取消协程的执行,特别是处理嵌套任务中的取消问题。问题描述假设我们有一个长时间运行的任务,该任务包含一个外层循环和一个内层任务。我们需要在外层循环接收到取消信号时,能够立即终止内层任务。以下是一个示例代码: package main import ( "context" "fmt" "time" ) // longRunningTask 是一个模拟长时间运行的任务。 func longRunningTask(ctx context.Context) { for { select { case <-ctx.Done(): // 监听 ctx.Done() 以获取取消信号 fmt.Println("任务被取消:", ctx.Err()) return // 接收到取消信号后退出 default: currentTime := time.Now().Format("2006-01-02 15:04:05") // 获取并格式化当前时间 fmt.Printf("任务进行中... 当前时间:%s\n", currentTime) for { fmt.Printf("111") time.Sleep(1 * time.Second) // } } } } func main() { // 创建一个可以取消的 context ctx, cancel := context.WithCancel(context.Background()) // 启动一个新的 goroutine 执行任务 go longRunningTask(ctx) // 模拟一段时间后取消任务 time.Sleep(3 * time.Second) fmt.Println("取消任务...") cancel() // 发送取消信号 // 等待一段时间让任务有时间处理取消信号并退出 time.Sleep(10 * time.Second) } 在这个示例中,当我们取消任务时,外层循环会接收到取消信号并退出,但内层循环会继续运行,因为我们没有在内层循环中检查取消信号。解决方案为了确保内层任务也能响应取消信号,我们需要在内层任务中也检查 ctx.Done() 通道。以下是修改后的代码: package main import ( "context" "fmt" "time" ) // longRunningTask 是一个模拟长时间运行的任务。 func longRunningTask(ctx context.Context) { for { select { case <-ctx.Done(): // 监听 ctx.Done() 以获取取消信号 fmt.Println("任务被取消:", ctx.Err()) return // 接收到取消信号后退出 default: currentTime := time.Now().Format("2006-01-02 15:04:05") // 获取并格式化当前时间 fmt.Printf("任务进行中... 当前时间:%s\n", currentTime) // 启动内层任务 runInnerTask(ctx) } } } // runInnerTask 是一个模拟内层长时间运行的任务。 func runInnerTask(ctx context.Context) { for { select { case <-ctx.Done(): // 内层任务也监听 ctx.Done() fmt.Println("内层任务被取消:", ctx.Err()) return // 接收到取消信号后退出 default: fmt.Printf("111") time.Sleep(1 * time.Second) } } } func main() { // 创建一个可以取消的 context ctx, cancel := context.WithCancel(context.Background()) // 启动一个新的 goroutine 执行任务 go longRunningTask(ctx) // 模拟一段时间后取消任务 time.Sleep(3 * time.Second) fmt.Println("取消任务...") cancel() // 发送取消信号 // 等待一段时间让任务有时间处理取消信号并退出 time.Sleep(10 * time.Second) } 解释外层循环:外层循环使用 select 语句来监听 ctx.Done() 通道。如果接收到取消信号,任务会打印一条消息并退出。内层任务:内层任务也使用 select 语句来监听 ctx.Done() 通道。如果接收到取消信号,内层任务会打印一条消息并退出。通过这种方式,我们可以确保无论是在外层循环还是内层任务中,任务都能响应取消信号并优雅地退出。总结在 Go 语言中,使用 context 包来管理协程的生命周期是非常重要的。通过在每个需要响应取消信号的地方检查 ctx.Done() 通道,我们可以确保任务能够及时响应取消信号并优雅地退出。这对于构建健壮和可靠的并发应用程序至关重要。
-
大家好,四季度的第一个干货合集来了,这次带来的东西主要涉及到golang,python,MySQL,redis,PostgreSQL,docker,minio,鸿蒙等等,希望可以帮助到到家 1. golang gin ShouldBind的介绍和使用示例详解【转载】 https://bbs.huaweicloud.com/forum/thread-02127165743740541019-1-1.html 2.golang flag介绍和使用示例【转载】 https://bbs.huaweicloud.com/forum/thread-0205165743832841012-1-1.html 3.Go语言中的格式化输出占位符的用法详解【转】 https://bbs.huaweicloud.com/forum/thread-0242165743874465013-1-1.html 4.Python中格式化字符串的方法总结【转载】 https://bbs.huaweicloud.com/forum/thread-02107165744051753023-1-1.html 5.Python使用进程池并发执行SQL语句的操作代码【转载】 https://bbs.huaweicloud.com/forum/thread-02111165744119162009-1-1.html 6.Mysql转PostgreSQL注意事项及说明【转】 https://bbs.huaweicloud.com/forum/thread-0205165745689855016-1-1.html 7.一文彻底讲清该如何处理mysql的死锁问题【转载】 https://bbs.huaweicloud.com/forum/thread-02111165745597896010-1-1.html 8.Redis实现分布式事务的示例【转载】 https://bbs.huaweicloud.com/forum/thread-0242165745553696016-1-1.html 9.MySQL服务无法启动且服务没有报告任何错误解决办法【转载】 https://bbs.huaweicloud.com/forum/thread-02127165745354539021-1-1.html 10.Python+OpenCV实现火焰检测【转载】 https://bbs.huaweicloud.com/forum/thread-0205165744941700014-1-1.html 11.Python Word实现批量替换文本并生成副本【转载】 https://bbs.huaweicloud.com/forum/thread-02127165744886949020-1-1.html 12.Python实现OFD文件转PDF【转载】 https://bbs.huaweicloud.com/forum/thread-0286165744755315017-1-1.html 13.Python中将文件从一个服务器复制到另一个服务器的4种方法【转】 https://bbs.huaweicloud.com/forum/thread-0242165744680560015-1-1.html 14.Python实现将pdf文档保存成图片格式【转载】 https://bbs.huaweicloud.com/forum/thread-0242165744483484014-1-1.html 15.Python通过keyboard库实现模拟和监听键盘【转载】 https://bbs.huaweicloud.com/forum/thread-0205165744195219013-1-1.html 16.docker 配置国内镜像源 https://bbs.huaweicloud.com/forum/thread-0286165634815059009-1-1.html 17.Typora 代码块Mac风格化 https://bbs.huaweicloud.com/forum/thread-02111165634917052004-1-1.html 18.MinIO上传和下载文件及文件完整性校验 https://bbs.huaweicloud.com/forum/thread-0286165634713872008-1-1.html 19.鸿蒙系统特性 https://bbs.huaweicloud.com/forum/thread-02127165634384647014-1-1.html 20.Java EasyExcel 导出报内存溢出如何解决 https://bbs.huaweicloud.com/forum/thread-02111165634289077003-1-1.html
-
在 Go 语言中,格式化输出是一个非常常用的功能,特别是在处理字符串、数字和其他数据类型时。Go 提供了丰富的格式化选项,通过占位符来控制输出的格式。本文将详细介绍 Go 语言中常用的格式化占位符及其用法。基本格式化函数Go 语言提供了几个内置的格式化函数,这些函数主要位于 fmt 包和 log 包中:fmt 包fmt.Printf(format string, a ...interface{}):格式化输出到标准输出。fmt.Sprintf(format string, a ...interface{}):格式化输出到字符串。fmt.Fprintf(w io.Writer, format string, a ...interface{}):格式化输出到 io.Writer。log 包log.Printf(format string, v ...interface{}):格式化输出到日志。常用的格式化占位符字符串格式化占位符描述示例%s格式化字符串fmt.Printf("%s", "Hello")%q格式化字符串,用双引号包围fmt.Printf("%q", "Hello")数字格式化整数占位符描述示例%d十进制整数fmt.Printf("%d", 123)%b二进制整数fmt.Printf("%b", 123)%o八进制整数fmt.Printf("%o", 123)%x十六进制整数(小写字母)fmt.Printf("%x", 123)%X十六进制整数(大写字母)fmt.Printf("%X", 123)%cASCII 码对应的字符fmt.Printf("%c", 65)%UUnicode 码点fmt.Printf("%U", 'A')浮点数占位符描述示例%f浮点数fmt.Printf("%f", 123.456)%e科学记数法(小写字母 e)fmt.Printf("%e", 123.456)%E科学记数法(大写字母 E)fmt.Printf("%E", 123.456)%g根据值的大小选择 %e 或 %ffmt.Printf("%g", 123.456)%G根据值的大小选择 %E 或 %ffmt.Printf("%G", 123.456)布尔值占位符描述示例%t布尔值fmt.Printf("%t", true)其他类型占位符描述示例%p指针地址fmt.Printf("%p", &value)%T类型名称fmt.Printf("%T", value)特殊格式化占位符描述示例%v格式化任何值,默认格式fmt.Printf("%v", "Hello")%+v格式化任何值,包含结构体字段fmt.Printf("%+v", struct{ Name string }{"Alice"})%#v格式化任何值,Go 语法格式fmt.Printf("%#v", "Hello")自定义宽度和精度占位符描述示例%wd设置最小宽度,不足部分用空格填充fmt.Printf("%5d", 123)%0wd设置最小宽度,不足部分用零填充fmt.Printf("%05d", 123)%wf设置浮点数的小数位数fmt.Printf("%.2f", 123.456)对齐方式占位符描述示例%-w左对齐fmt.Printf("%-5d", 123)%+w右对齐(默认)fmt.Printf("%+5d", 123)
-
在 Go 语言中,flag 包用于解析命令行标志。它提供了一种简单的方法来处理程序的输入参数。以下是对 flag 包的介绍和使用示例。1. 基本概念标志(Flag):命令行参数,通常以短划线 - 开头,用于控制程序的行为。解析(Parse):读取和解析命令行参数。2. 常用函数flag.StringVar:定义一个字符串标志。flag.IntVar:定义一个整数标志。flag.BoolVar:定义一个布尔标志。flag.Parse():解析命令行参数。3. 示例代码下面是一个简单的示例,演示如何使用 flag 包:123456789101112131415161718192021222324package mainimport ( "flag" "fmt")type Options struct { Name string Age int DB bool}func main() { // 创建一个 Options 结构体实例 var option Options // 定义标志 flag.StringVar(&option.Name, "name", "Guest", "用户名称") flag.IntVar(&option.Age, "age", 18, "用户年龄") flag.BoolVar(&option.DB, "db", false, "初始化数据库") // 解析命令行参数 flag.Parse() // 输出参数 fmt.Printf("Name: %s\n", option.Name) fmt.Printf("Age: %d\n", option.Age) fmt.Printf("DB initialized: %v\n", option.DB)}4. 如何运行假设文件名为 main.go,可以通过命令行运行:1go run main.go -name=John -age=30 -db5. 输出结果运行以上命令后,输出将会类似于:123Name: JohnAge: 30DB initialized: true6. 帮助信息可以通过添加 -h 或 --help 参数查看帮助信息:1go run main.go -h输出将显示所有定义的标志及其说明。7. 小结flag 包提供了一种方便的方式来处理命令行参数。使用 flag 可以定义不同类型的标志,并在解析后使用这些参数。记得调用 flag.Parse() 来解析命令行参数。
-
在 Go 语言的 Gin 框架中,ShouldBind 是用于将请求中的数据绑定到结构体的一个方法。它简化了从请求中提取参数的过程,支持多种数据格式(如 JSON、表单、查询参数等)。以下是 ShouldBind 的介绍和使用示例。1. 基本概念ShouldBind: 这个方法根据请求的 Content-Type 自动选择合适的绑定方式,将请求数据绑定到指定的结构体上。如果绑定成功,它返回 nil,否则返回错误信息。2. 支持的数据格式JSON: 适用于 application/json 的请求。表单数据: 适用于 application/x-www-form-urlencoded 的请求。查询参数: 适用于 URL 中的查询参数。3. 使用示例以下是一个简单的示例,展示如何使用 ShouldBind 绑定 JSON 数据到结构体。示例代码package main import ( "github.com/gin-gonic/gin" "net/http" ) // 定义一个结构体用于绑定请求数据 type User struct { Name string `json:"name" binding:"required"` Email string `json:"email" binding:"required,email"` } func main() { router := gin.Default() router.POST("/user", func(c *gin.Context) { var user User // 使用 ShouldBind 绑定请求数据 if err := c.ShouldBindJSON(&user); err != nil { // 如果绑定失败,返回错误信息 c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // 绑定成功,返回成功响应 c.JSON(http.StatusOK, gin.H{"message": "用户创建成功", "user": user}) }) // 启动服务器 router.Run(":8080") }4. 运行示例将上述代码保存为 main.go。在终端中运行 go run main.go 启动服务器。使用工具(如 Postman 或 curl)发送 POST 请求到 http://localhost:8080/user,并在请求体中包含 JSON 数据,例如:1234{ "name": "John Doe", "email": "john@example.com"}5. 响应如果请求成功,你将收到如下响应:1234567{ "message": "用户创建成功", "user": { "name": "John Doe", "email": "john@example.com" }}如果请求数据不符合要求(如缺少字段或格式错误),将返回相应的错误信息。6. 小结ShouldBind 是 Gin 中用于简化请求数据绑定的强大工具。支持多种数据格式,可以根据请求的 Content-Type 自动选择合适的绑定方式。通过结构体标签可以轻松定义验证规则,提高数据处理的安全性和可靠性。虽然 ShouldBind 可以处理多种类型的请求数据,但 ShouldBindUri、ShouldBindJSON 和 ShouldBindQuery 这些方法仍然有其独特的用途和优势。以下是它们存在的原因及各自的优点:1. 更明确的绑定方式ShouldBindUri:专门用于从 URL 路径参数中提取数据,适用于处理 RESTful API 中的动态路由。明确表示你只想从 URI 中获取参数,避免了可能的混淆。ShouldBindJSON:专门处理 JSON 数据,适合 Content-Type 为 application/json 的请求。提供了更好的错误提示和特定的绑定逻辑,确保 JSON 数据的正确解析。专门处理 URL 查询参数,适合 GET 请求中的参数解析。明确表示你只想从查询字符串中获取参数,便于阅读和维护。2. 提高代码可读性使用特定的绑定方法(如 ShouldBindJSON 和 ShouldBindQuery)可以让代码的意图更明确,使后续维护和阅读更容易。其他开发者可以迅速理解这段代码是处理什么类型的数据。3. 错误处理和反馈各个专用方法能够提供更详细的错误信息。例如,如果 JSON 解析失败,ShouldBindJSON 能够提供关于 JSON 格式的问题,而 ShouldBindQuery 则会专注于查询参数的错误。4. 性能优化虽然在大多数情况下性能差异不明显,但特定的绑定方法可能在某些场景下提供更优的性能,因为它们只关注特定的数据源。ShouldBindQuery:
-
公平锁和非公平锁是计算机科学中的两种锁机制,它们主要用于多线程编程,以控制对共享资源的访问。一、公平锁 (Fair Lock)1. 概念公平锁是一种按照请求顺序授予锁的机制,即先请求锁的线程会先获得锁,后请求锁的线程会后获得锁。这种锁通过维护一个队列来管理等待的线程,确保每个线程都能公平地获取到锁。2. 优点避免饥饿:所有线程都有机会获得锁,不会出现某些线程长期得不到锁的情况。可预测性:锁的获取是按顺序进行的,具有较好的可预测性。3. 缺点性能开销:由于需要维护一个队列,公平锁在管理上有一定的性能开销。上下文切换增加:由于公平锁可能需要频繁地切换线程,导致上下文切换的次数增加,影响性能。二、非公平锁 (Unfair Lock)1. 概念非公平锁是一种不按照请求顺序授予锁的机制,即任何线程都有可能在任何时候获得锁,而不考虑请求顺序。这种锁通常会优先考虑当前已经持有锁的线程,以提高系统的吞吐量。2. 优点高性能:由于没有队列管理的开销,非公平锁通常性能较高,特别是在高并发场景下。减少上下文切换:非公平锁可以减少线程之间的上下文切换,提升效率。3. 缺点可能导致饥饿:某些线程可能长时间得不到锁,导致线程饥饿。不可预测性:锁的获取是随机的,具有较低的可预测性。三、Go语言中的实现Go语言中的锁主要通过sync包提供,常用的锁有Mutex(互斥锁)和RWMutex(读写互斥锁)。Go的sync.Mutex默认实现的是一种非公平锁,但也可以实现公平锁。1. 非公平锁的实现Go标准库中的sync.Mutex是非公平锁的实现。它的主要结构和实现方式如下: type Mutex struct { state int32 sema uint32 } func (m *Mutex) Lock() { // 快速路径:尝试直接获取锁 if atomic.CompareAndSwapInt32(&m.state, 0, 1) { return } // 慢速路径:获取不到锁时,调用lockSlow方法 m.lockSlow() } func (m *Mutex) Unlock() { // 快速路径:尝试直接释放锁 if atomic.CompareAndSwapInt32(&m.state, 1, 0) { return } // 慢速路径:释放锁时,调用unlockSlow方法 m.unlockSlow() } 2. 公平锁的实现Go标准库不直接提供公平锁的实现,但我们可以通过其他方式实现公平锁,比如通过条件变量(sync.Cond)来维护等待的队列,从而实现公平锁。 type FairMutex struct { mu sync.Mutex cond *sync.Cond waiting []chan struct{} } func NewFairMutex() *FairMutex { fm := &FairMutex{} fm.cond = sync.NewCond(&fm.mu) return fm } func (fm *FairMutex) Lock() { fm.mu.Lock() defer fm.mu.Unlock() ch := make(chan struct{}) fm.waiting = append(fm.waiting, ch) if len(fm.waiting) > 1 { <-ch } } func (fm *FairMutex) Unlock() { fm.mu.Lock() defer fm.mu.Unlock() if len(fm.waiting) > 0 { fm.waiting = fm.waiting[1:] if len(fm.waiting) > 0 { close(fm.waiting[0]) } } } 四、总结公平锁:按请求顺序授予锁,避免饥饿,维护队列,开销较大。非公平锁:随机授予锁,高性能,可能导致饥饿。
上滑加载中