hankmo.com

HANKMO.COM

🧑‍💻潜心研技术,积极品人生!🌱

194
文章
17
分类
252
标签

📝 最新文章

为什么我建议大家要学习Golang

为什么我建议大家学习Golang 大家好,我是极客老墨。 在互联网行业摸爬滚打十几年,从早期的LAMP时代,到Java的Spring全家桶,再到后来的云原生浪潮,老墨见证了技术的每一次变迁。 最近很多朋友问我:“老墨,现在的AI写代码这么厉害,大模型日新月异,智能IDE更是百花齐放,Claude Code、Cursor 简直是神,还需要专门去学一门后端语言吗?如果学,学什么比较好?Java还是Python?或者 Rust?” 这是一个非常好的问题。今天老墨就结合自己的经验,跟大家聊聊为什么在这个AI横行的时代,我依然建议大家掌握Golang,并且会手把手教你如何拿下它。 1. AI时代,为什么还要学后端语言? 很多同学觉得,现在AI不仅能生成CRUD代码,甚至能帮你完成全套编码、测试,这样的发展速度,我真的有必要学编程?我只需要会写Prompt不就行了吗? 大错特错。 AI确实能提高效率,但它目前还无法替代架构思维和底层认知。 知其然,更要知其所以然:AI生成的代码,如果出了Bug,或者性能不达标,你看不懂怎么调优? 不仅仅是Coder,更是Engineer:单纯的写代码(Coding)会被AI取代,但工程化能力(Engineering)——包括系统设计、并发处理、错误治理、服务部署,是AI很难完全掌控的。 掌握控制权:作为一名开发者,你不能做AI的傀儡。你需要有能力判断AI生成的代码是垃圾还是金子。 掌握一门强类型的、编译型的后端语言,能让你深入理解计算机的工作原理:内存管理、进程线程、网络协议。这些内功,是Prompt Engineering给不了你的。 2. 为什么是Golang? 在众多后端语言中,老墨首推 Go (Golang)。不是因为赶时髦,而是基于实用主义的考量。 Java:沉重的企业级战车 Java确实强大,生态无敌,将近30年了仍然是一门非常活跃的开发语言。但是: 太重了:Spring Boot启动一下,内存吃掉几百兆是常事。对于想要快速开发微服务或者云原生应用的极客来说,有点“大炮打蚊子”。 语法繁琐:虽然有了Lombok和新版本的语法糖,而且语法糖、新特性在一直增加,就是为了简化开发、提高效率,但比起Go的简洁,Java依然显得啰嗦。 JVM调优是玄学:GC调优、JVM参数配置,是一门高深的学问,对于初学者来说门槛较高。 Rust:陡峭的绝壁 Rust绝对是好语言,内存安全,性能极致。但是: 学习曲线太陡峭:所有权(Ownership)、借用(Borrowing)、生命周期(Lifetime),这些概念能劝退90%的初学者。老墨到现在仍然还在学习 Rust,深有体会! 开发效率:为了通过编译器的检查,你可能需要花费大量时间与编译器搏斗。对于大多数互联网业务应用来说,Rust的开发效率不如Go。 Golang:平衡的艺术 Go语言是Google出品,有着纯正的工程血统。 简单直接:只有25个关键字(早期),语法极其简洁,没有花哨的语法糖。任何Go程序员写的代码,风格都惊人的一致,这在团队协作中是巨大的优势。 天生并发:go func(),一个Goroutine开启并发,Channel进行通信。这是我见过的处理并发最优雅的方式,没有之一。 性能强悍:编译型语言,接近C/C++的性能,但开发效率接近Python。 云原生通用语:Docker、Kubernetes、Prometheus…这些云原生时代的基石,全是Go写的。学了Go,你就拿到了通往云原生世界的门票。 老墨总结: 如果你想快速构建高性能的后端服务,不想被复杂的语法和繁重的运行时拖累,Go是你的不二之选。 3. 极客老墨的Golang学习路径 很多同学这就去买书了,别急!听老墨一句劝:不要一开始就啃大骨头! 现代语言学习,讲究的是 “Learn by Doing”。 第一阶段:不仅是Syntax,更是思维转变(1-2周) 不要死记硬背语法。重点理解Go独特的概念: 接口(Interface):Duck Typing(鸭子模型),非侵入式接口,这和Java的implements完全不同。 Goroutine & Channel:不要用共享内存来通信,要用通信来共享内存。这是Go并发的核心哲学。 Defer & Panic:Go没有try-catch,适应它的错误处理机制。 第二阶段:标准库是最好的老师(2-3周) Go的标准库(Standard Library)写得非常漂亮。重点攻克: net/http:几行代码起一个Web Server。 fmt, io, bufio:理解IO操作。 encoding/json:JSON处理是后端日常。 context:重中之重! 并发控制、超时处理全靠它。 第三阶段:工程化实战(1个月) 光会写Hello World没用,你需要能干活的框架: ...

为了学 AI,我用 Go + Fyne 手撸了一个原生视频下载器

为了学 AI,我用 Go + Fyne 手撸了一个原生视频下载器 大家好,我是老墨。 一个写了十几年代码,发际线依然坚挺,但最近确实有点焦虑的中年程序员。 为啥焦虑?还不是因为这该死的 AI。 从 24 年初 Sora 横空出世,到 Claude 3.5 杀疯了,再到最近 DeepSeek 甚至能自己修 Bug,这世界变化快得像开了二倍速。以前我们卷算法、卷架构,现在倒好,不仅要卷提示词,还要防着被自己的 IDE 抢了饭碗。 老墨我痛定思痛,觉得不能坐以待毙。打不过就加入嘛,我也开始疯狂恶补 AI 知识。 01 一个悲伤的故事:学习资料太多也是一种烦恼 要学 AI,最好的路径是什么?这年头文档更新赶不上模型迭代,最鲜活的知识全在视频里。YouTube 上的 Andrej Karpathy 大神课,B 站上的各种论文精读、实战通过… 于是我的浏览器收藏夹很快就爆了。 但我这人有个毛病,看视频喜欢囤。一来是有些干货太硬,得反复咀嚼;二来是作为技术人,总有一种由于网络不确定性带来的"松鼠症"——好东西必须存在本地硬盘里才踏实。 这时候问题来了:市面上的下载器,怎么就没一个顺手的? 某雷:广告多是其次,关键是我想下的 YouTube 和 B 站视频它基本都解析不了。对于我们这种想看外网 AI 前沿教程的人来说,它形同虚设。 某 IDM:嗅探功能很强,但碰到 YouTube/B 站 这种音视频分离的高画质(DASH流)视频就歇菜了,经常只能下个无声画面,心累。而且 Mac 上没有原生版,体验割裂。 Electron 系工具:界面是好看了,但这动不动几百兆的内存占用,我开个 PyCharm 跑模型本来就捉襟见肘,哪还有余粮养它们? 命令行 yt-dlp:这是真神,功能无敌。用了很久,自己写脚本都写了多个。但时间久了脚本太多,每次想下个视频还得敲命令,复制粘贴 URL,还得拼代理参数… 实在是比较麻烦。 “求人不如求己,为什么不能自己写一个GUI?” 这念头一出,我就兴奋了。写了几年的 Golang,这点事情不在话下。说干就干,开搞开搞。 02 技术选型:要做就做原生的 既然决定自己干,那必须立好 Flag: 要快:启动快,下载快。 要美:虽然我是后端出身,但也不能忍受丑陋的 UI。 要轻:拒绝 Electron,拒绝 WebView,我要纯原生。 基于这个标准,技术栈基本就锁死了: ...

Go高级教程:反射 (Reflection) 实战

Go 高级教程:反射 (Reflection) 实战 “反射是魔鬼。” —— 某些性能洁癖者 “没有反射,就没有现代 Web 框架。” —— 现实主义开发者 反射 (Reflection) 赋予了程序在 运行时 (Runtime) 检查和修改自身状态的能力。从 JSON 解析到 ORM 框架(如 GORM),再到依赖注入,它们的底层都离不开反射。 1. 核心概念:Type 和 Value 在 reflect 包中,有两位绝对主角: reflect.Type:这是啥?(类型信息,如 int, string, User) reflect.Value:这值多少?(具体的数据,如 42, “hello”, User{Name:“Hank”}) 一切反射操作的起点都是 interface{}。 1package main 2 3import ( 4 "fmt" 5 "reflect" 6) 7 8func main() { 9 x := 3.14 10 11 // 1. 获取类型 12 t := reflect.TypeOf(x) 13 fmt.Println("Type:", t) // float64 14 15 // 2. 获取值 16 v := reflect.ValueOf(x) 17 fmt.Println("Value:", v) // 3.14 18} graph LR subgraph iface ["interface{}"] direction TB TypePtr["_type pointer"] DataPtr["data pointer"] end TypePtr -->|"reflect.TypeOf"| RType["reflect.Type"] DataPtr -->|"reflect.ValueOf"| RValue["reflect.Value"] style iface fill:#f9f9f9,stroke:#333,stroke-width:2px,color:#333 style TypePtr fill:#e1f5fe,stroke:#01579b,color:#01579b style DataPtr fill:#e1f5fe,stroke:#01579b,color:#01579b style RType fill:#fff9c4,stroke:#fbc02d,color:#333 style RValue fill:#fff9c4,stroke:#fbc02d,color:#333 2. 三大反射定律 Go 的反射有三条铁律(出自 Rob Pike): ...

Go高级教程:其他并发工具

Go 高级教程:其他并发工具 如果说 Goroutine 和 Channel 是 Go 并发的“常规武器”,那么 sync 包里的工具就是“特种装备”。虽然不常用,但关键时刻能救命(榨干 CPU 的最后一点性能)。 除了 这里 介绍的诸多基础并发工具外,Go 标准库还提供了一些高级并发工具,下面介绍几个比较常用的。 1. 减轻 GC 压力:sync.Pool 我们在讲 GC 的时候提过,如果你频繁申请和销毁大对象(比如 HTTP Response 对象,或者大的 byte buffer),GC 会鸭梨山大。 sync.Pool 就是为了对象复用而生的。 1.1 示例代码 1package main 2 3import ( 4 "fmt" 5 "sync" 6) 7 8// 定义池子 9var bufPool = sync.Pool{ 10 // New 函数:当池子里没存货时,调用它创建一个新的 11 New: func() interface{} { 12 fmt.Println("Creating new buffer") 13 return make([]byte, 1024) 14 }, 15} 16 17func main() { 18 // 1.Get(): 借一个对象 19 buf := bufPool.Get().([]byte) 20 21 // 用完它... 22 23 // 2. Put(): 还回去,下次给别人用 24 // 注意:还之前最好重置一下状态(比如清空) 25 bufPool.Put(buf) 26 27 // 再次 Get,就不会触发 New,而是直接复用刚才那个 28 buf2 := bufPool.Get().([]byte) 29 _ = buf2 30} 1.2 注意事项 sync.Pool 里的对象随时可能被 GC 回收!所以绝对不要用它存数据库连接、Socket 连接这种必须长久保持的资源。它只适合存“临时垃圾”。 ...

Go高级教程:深入理解 GMP 调度器

Go 高级教程:深入理解 GMP 调度器 为什么 Go 语言能轻松支撑百万并发? 为什么 Goroutine 切换成本这么低? 这一切的背后,都站着一位神秘的大管家 —— GMP 调度器。 1. 为什么需要 GMP? 在很久很久以前(其实也就几十年前),我们写代码都是直接跟 线程 (Thread) 打交道。线程是操作系统(OS)调度的最小单位。 但是,线程这玩意儿太“贵”了: 内存占用高:一个线程栈大概要几 MB。 切换成本大:线程切换需要陷入内核态,保存寄存器、上下文,这简直就是“劳民伤财”。 这时候,Go 语言的设计师们拍案而起:“我们要造一种更轻量的线程!” 于是,Goroutine (协程) 诞生了。它初始只要几 KB,切换成本极低。 这就带来了一个问题:操作系统只认识线程,不认识 Goroutine。谁来负责把成千上万个 Goroutine 分配给 CPU 跑呢? 这就需要一个“中间商” —— Go 运行时调度器 (Scheduler)。 图示: Thread 与 Goroutine 的区别 2. GMP 模型大揭秘 GMP 其实是三个角色的缩写: G (Goroutine):我们写的代码任务,也就是协程。 M (Machine):工作线程(Thread),对应操作系统的真实线程。它是真正的干活人(搬砖工)。 P (Processor):逻辑处理器(Context),可以理解为“调度上下文”或“资源”。它是包工头,负责管理 G,并把 G 交给 M 去执行。 形象的比喻 想象一个大型搬砖工地: G (砖头):待搬运的任务。 M (工人):负责搬砖的劳动力。 P (手推车):工人必须推着车才能搬砖(因为车里装着搬砖工具和任务清单)。 如果没有 P(手推车),M(工人)就不知道该干啥。 ...

Go 项目工程结构最佳实践

好的项目结构能让代码更易理解、易维护、易扩展。Go 社区有一套被广泛认可的标准布局。 1. 标准项目布局 参考:golang-standards/project-layout 1myproject/ 2├── cmd/ # 主程序入口 3│ ├── server/ 4│ │ └── main.go # 服务端入口 5│ └── cli/ 6│ └── main.go # 命令行工具入口 7├── internal/ # 私有代码(不可被外部导入) 8│ ├── handler/ 9│ ├── service/ 10│ └── repository/ 11├── pkg/ # 公共库(可被外部导入) 12│ ├── util/ 13│ └── validator/ 14├── api/ # API 定义(OpenAPI/Swagger) 15│ └── openapi.yaml 16├── web/ # Web 静态资源 17│ ├── static/ 18│ └── templates/ 19├── configs/ # 配置文件 20│ ├── config.yaml 21│ └── config.prod.yaml 22├── scripts/ # 脚本(构建、部署等) 23│ ├── build.sh 24│ └── deploy.sh 25├── test/ # 额外的测试数据 26│ └── fixtures/ 27├── docs/ # 文档 28│ └── API.md 29├── go.mod 30├── go.sum 31├── Makefile # 构建脚本 32├── Dockerfile 33└── README.md 2. 核心目录详解 2.1 cmd/ 存放主程序入口,每个子目录对应一个可执行文件。 ...

环境变量与配置管理

环境变量与配置管理 同一份代码需要在开发、测试、生产等不同环境运行。配置管理让我们能够灵活切换环境,而不需要修改代码。 1. 环境变量基础 1.1 读取环境变量 1import ( 2 "fmt" 3 "os" 4) 5 6func main() { 7 // 读取环境变量 8 dbHost := os.Getenv("DB_HOST") 9 if dbHost == "" { 10 dbHost = "localhost" // 默认值 11 } 12 13 fmt.Println("DB Host:", dbHost) 14 15 // 检查环境变量是否存在 16 port, exists := os.LookupEnv("PORT") 17 if !exists { 18 port = "8080" 19 } 20} 1.2 设置环境变量 1// 在程序中设置(仅影响当前进程) 2os.Setenv("API_KEY", "secret123") 3 4// 在 shell 中设置 5// export DB_HOST=localhost 6// export DB_PORT=3306 2. godotenv:.env 文件 2.1 安装 1go get -u github.com/joho/godotenv 2.2 使用 创建 .env 文件: 1DB_HOST=localhost 2DB_PORT=3306 3DB_USER=root 4DB_PASSWORD=secret 5API_KEY=your-api-key 6DEBUG=true 加载配置: ...

模糊测试入门 (Fuzzing)

单元测试只能测试你想到的情况,而模糊测试能帮你发现你没想到的边界情况。 1. 什么是模糊测试? 模糊测试 (Fuzzing) 是一种自动化测试技术,通过生成大量随机或半随机的输入数据来测试程序,寻找崩溃、panic、死循环等异常。 传统测试 vs 模糊测试: 1// 传统单元测试:测试已知的输入 2func TestAdd(t *testing.T) { 3 if Add(2, 3) != 5 { 4 t.Error("2 + 3 should be 5") 5 } 6} 7 8// 模糊测试:测试大量随机输入 9func FuzzAdd(f *testing.F) { 10 f.Fuzz(func(t *testing.T, a, b int) { 11 result := Add(a, b) 12 // 检查属性而不是具体值 13 if result < a && result < b { 14 t.Errorf("Add(%d, %d) = %d, should be >= both", a, b, result) 15 } 16 }) 17} 2. 编写 Fuzz 测试 2.1 基础示例 假设我们有一个解析 URL 的函数: 1// url.go 2package myurl 3 4import ( 5 "fmt" 6 "strings" 7) 8 9func ParseURL(rawURL string) (string, error) { 10 if !strings.HasPrefix(rawURL, "http://") && !strings.HasPrefix(rawURL, "https://") { 11 return "", fmt.Errorf("invalid protocol") 12 } 13 return rawURL, nil 14} 编写 Fuzz 测试: ...

日志管理:从 log 到 zap

日志是排查问题的第一手段。一个好的日志系统应该:结构化、高性能、可配置、易于检索。 1. 标准库 log 1.1 基础使用 1package main 2 3import ( 4 "log" 5) 6 7func main() { 8 log.Println("This is a log message") 9 log.Printf("User %s logged in", "admin") 10 11 // log.Fatal 会调用 os.Exit(1) 12 // log.Fatal("Fatal error") 13 14 // log.Panic 会触发 panic 15 // log.Panic("Panic error") 16} 1.2 自定义 Logger 1import ( 2 "log" 3 "os" 4) 5 6func main() { 7 // 创建自定义 Logger 8 logger := log.New( 9 os.Stdout, // 输出目标 10 "[MyApp] ", // 前缀 11 log.Ldate|log.Ltime|log.Lshortfile, // 标志 12 ) 13 14 logger.Println("Custom logger message") 15 // 输出:[MyApp] 2025/12/18 10:00:00 main.go:15: Custom logger message 16} 1.3 写入文件 1func main() { 2 file, err := os.OpenFile("app.log", 3 os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) 4 if err != nil { 5 panic(err) 6 } 7 defer file.Close() 8 9 log.SetOutput(file) 10 log.Println("This goes to file") 11} 2. slog:结构化日志 (Go 1.21+) 2.1 基础使用 1import ( 2 "log/slog" 3 "os" 4) 5 6func main() { 7 // 创建 JSON 格式的 Logger 8 logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) 9 10 logger.Info("User logged in", 11 "user_id", 123, 12 "username", "admin", 13 "ip", "192.168.1.1") 14 15 // 输出: 16 // {"time":"2025-12-18T10:00:00Z","level":"INFO","msg":"User logged in","user_id":123,"username":"admin","ip":"192.168.1.1"} 17} 2.2 日志级别 1func main() { 2 logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ 3 Level: slog.LevelInfo, // 只输出 Info 及以上级别 4 })) 5 6 logger.Debug("Debug message") // 不会输出 7 logger.Info("Info message") // 会输出 8 logger.Warn("Warning message") // 会输出 9 logger.Error("Error message") // 会输出 10} 2.3 上下文日志 1func main() { 2 logger := slog.Default() 3 4 // 创建带有固定字段的子 Logger 5 requestLogger := logger.With( 6 "request_id", "abc123", 7 "user_id", 456, 8 ) 9 10 requestLogger.Info("Processing request") 11 requestLogger.Info("Request completed") 12 13 // 两条日志都会自动包含 request_id 和 user_id 14} 3. zap:高性能日志库 3.1 安装 1go get -u go.uber.org/zap 3.2 快速开始 1import ( 2 "go.uber.org/zap" 3) 4 5func main() { 6 // 开发环境:易读的格式 7 logger, _ := zap.NewDevelopment() 8 defer logger.Sync() // 刷新缓冲区 9 10 logger.Info("User logged in", 11 zap.String("username", "admin"), 12 zap.Int("user_id", 123)) 13 14 // 生产环境:JSON 格式 15 prodLogger, _ := zap.NewProduction() 16 defer prodLogger.Sync() 17 18 prodLogger.Info("Server started", 19 zap.String("port", "8080")) 20} 3.3 自定义配置 1func main() { 2 config := zap.NewProductionConfig() 3 4 // 设置日志级别 5 config.Level = zap.NewAtomicLevelAt(zap.InfoLevel) 6 7 // 设置输出路径 8 config.OutputPaths = []string{ 9 "stdout", 10 "./logs/app.log", 11 } 12 13 // 设置错误日志路径 14 config.ErrorOutputPaths = []string{ 15 "stderr", 16 "./logs/error.log", 17 } 18 19 logger, _ := config.Build() 20 defer logger.Sync() 21 22 logger.Info("Application started") 23} 3.4 性能对比 1// zap 提供了 SugaredLogger,牺牲一点性能换取更简洁的 API 2logger, _ := zap.NewProduction() 3sugar := logger.Sugar() 4 5// 结构化日志(最快) 6logger.Info("User logged in", 7 zap.String("username", "admin")) 8 9// 格式化日志(稍慢,但更方便) 10sugar.Infof("User %s logged in", "admin") 4. 日志分级 4.1 日志级别 从低到高: ...

文件与 IO 操作实战

文件操作是编程中最基础也最常用的需求。Go 语言的 io 包设计优雅,通过 io.Reader 和 io.Writer 接口实现了高度的抽象和复用。 1. 文件读取 1.1 一次性读取整个文件 1package main 2 3import ( 4 "fmt" 5 "os" 6) 7 8func main() { 9 // 方法一:os.ReadFile (推荐,Go 1.16+) 10 data, err := os.ReadFile("test.txt") 11 if err != nil { 12 panic(err) 13 } 14 fmt.Println(string(data)) 15 16 // 方法二:ioutil.ReadFile (已废弃,但仍可用) 17 // data, err := ioutil.ReadFile("test.txt") 18} 1.2 逐行读取(大文件) 1import ( 2 "bufio" 3 "fmt" 4 "os" 5) 6 7func main() { 8 file, err := os.Open("large.txt") 9 if err != nil { 10 panic(err) 11 } 12 defer file.Close() // 确保文件关闭 13 14 scanner := bufio.NewScanner(file) 15 for scanner.Scan() { 16 line := scanner.Text() 17 fmt.Println(line) 18 } 19 20 if err := scanner.Err(); err != nil { 21 panic(err) 22 } 23} 1.3 按块读取 1func main() { 2 file, _ := os.Open("data.bin") 3 defer file.Close() 4 5 buffer := make([]byte, 1024) // 每次读 1KB 6 for { 7 n, err := file.Read(buffer) 8 if err == io.EOF { 9 break // 文件读完 10 } 11 if err != nil { 12 panic(err) 13 } 14 fmt.Printf("Read %d bytes\n", n) 15 // 处理 buffer[:n] 16 } 17} 2. 文件写入 2.1 一次性写入 1func main() { 2 data := []byte("Hello, World!\n") 3 4 // 写入文件(会覆盖原文件) 5 // 0644 是文件权限:rw-r--r-- 6 err := os.WriteFile("output.txt", data, 0644) 7 if err != nil { 8 panic(err) 9 } 10} 2.2 追加写入 1func main() { 2 file, err := os.OpenFile("log.txt", 3 os.O_APPEND|os.O_CREATE|os.O_WRONLY, // 追加模式 4 0644) 5 if err != nil { 6 panic(err) 7 } 8 defer file.Close() 9 10 file.WriteString("New log entry\n") 11} 2.3 使用 bufio 缓冲写入 1func main() { 2 file, _ := os.Create("output.txt") 3 defer file.Close() 4 5 writer := bufio.NewWriter(file) 6 7 for i := 0; i < 1000; i++ { 8 writer.WriteString(fmt.Sprintf("Line %d\n", i)) 9 } 10 11 writer.Flush() // 重要:将缓冲区内容写入文件 12} 3. 文件与目录操作 3.1 检查文件是否存在 1func fileExists(path string) bool { 2 _, err := os.Stat(path) 3 return !os.IsNotExist(err) 4} 3.2 创建目录 1// 创建单层目录 2os.Mkdir("mydir", 0755) 3 4// 创建多层目录(类似 mkdir -p) 5os.MkdirAll("path/to/mydir", 0755) 3.3 遍历目录 1import ( 2 "fmt" 3 "os" 4 "path/filepath" 5) 6 7func main() { 8 // 方法一:filepath.Walk 9 filepath.Walk(".", func(path string, info os.FileInfo, err error) error { 10 if err != nil { 11 return err 12 } 13 if !info.IsDir() { 14 fmt.Println("File:", path) 15 } 16 return nil 17 }) 18 19 // 方法二:os.ReadDir (Go 1.16+) 20 entries, _ := os.ReadDir(".") 21 for _, entry := range entries { 22 fmt.Println(entry.Name(), entry.IsDir()) 23 } 24} 3.4 删除文件/目录 1// 删除文件 2os.Remove("file.txt") 3 4// 删除目录及其所有内容(类似 rm -rf) 5os.RemoveAll("mydir") 4. io.Copy 文件拷贝 4.1 复制文件 1func copyFile(src, dst string) error { 2 source, err := os.Open(src) 3 if err != nil { 4 return err 5 } 6 defer source.Close() 7 8 destination, err := os.Create(dst) 9 if err != nil { 10 return err 11 } 12 defer destination.Close() 13 14 // io.Copy 高效复制 15 _, err = io.Copy(destination, source) 16 return err 17} 4.2 显示进度的复制 1type ProgressReader struct { 2 reader io.Reader 3 total int64 4 read int64 5} 6 7func (pr *ProgressReader) Read(p []byte) (int, error) { 8 n, err := pr.reader.Read(p) 9 pr.read += int64(n) 10 11 // 打印进度 12 fmt.Printf("\rProgress: %.2f%%", float64(pr.read)/float64(pr.total)*100) 13 14 return n, err 15} 5. 网络文件下载 1import ( 2 "io" 3 "net/http" 4 "os" 5) 6 7func downloadFile(url, filepath string) error { 8 // 发起 HTTP GET 请求 9 resp, err := http.Get(url) 10 if err != nil { 11 return err 12 } 13 defer resp.Body.Close() 14 15 // 创建文件 16 out, err := os.Create(filepath) 17 if err != nil { 18 return err 19 } 20 defer out.Close() 21 22 // 将响应体复制到文件 23 _, err = io.Copy(out, resp.Body) 24 return err 25} 26 27func main() { 28 downloadFile("https://example.com/file.zip", "file.zip") 29} 6. io.Reader 和 io.Writer 接口 6.1 理解接口 1type Reader interface { 2 Read(p []byte) (n int, err error) 3} 4 5type Writer interface { 6 Write(p []byte) (n int, err error) 7} 实现了这两个接口的类型: ...

📚 文章分类