资讯详情

资讯详情

建站行业动态 · 设计趋势 · 数字化升级干货

脱离Java 思维学 Go:go mod、切片、nil map、defer 那些容易忽略的问题

脱离Java 思维学 Go:go mod、切片、nil map、defer 那些容易忽略的问题 这周我额外学了一门 Go把它当作后端岗的加分技能。Java 和 Go 看着都是写业务逻辑的语言真上手才发现差别大得离谱没有 class、没有 public/private 关键字、循环只剩一个 for。这篇是我第一周的学习复盘把从环境到写出第一个小项目的过程用最直白的话讲一遍给同样想给简历加点料的 Java 后端同学避避坑。一、先把厨房搭起来GOPATH 和 go mod 到底差在哪刚装好 Go教程就让我敲go version验证。能打印出go version go1.x.x就说明装好了。然后一堆文章开始讲 GOROOT、GOPATH、PATH我一开始很懵。后来我把自己代入进去了GOROOT 就像 Go 自带的标准厨房编译器、标准库都在里面装语言时自动放好平时不用管。PATH 就是让终端认识 go 这个命令的设置装完一般自动配好。最坑的是 GOPATH。GOPATH 是早期 Go 的公共大仓库模式你写的所有代码、所有第三方库全塞进同一个全局目录。结果就是项目 A 和项目 B 的依赖互相打架版本冲突到崩溃。Go 1.11 之后进了 Module 时代现在每个项目自带一个go.mod当私人小仓库依赖版本锁得死死的项目之间互不污染。这就是为什么现在写 Go 基本不用管 GOPATH 了go mod 才是日常。旧时代 GOPATH所有代码塞一个全局目录依赖版本冲突项目互相污染新时代 go mod每个项目自带 go.mod依赖版本锁定项目互相独立这张图说的是老办法把所有菜放一个冰箱会串味新办法每人一个独立冰箱干净。新建项目就是一句话go mod init bookcli它会生成go.mod文件以后你 import 的第三方库版本都记在里面。二、package main 和大写小写Go 的入口和权限很特别第一个程序长这样packagemainimportfmtfuncmain(){fmt.Println(Hello Go)}这里有两个 Java 里没有的概念我得单独拎出来说。第一package main 是程序的开机键。普通 package 像零件箱别人 import 进来拿零件用而 package main 是带总开关的机器只有它里面有 func main()你 go run 时 Go 才知道从哪启动。没有 main 的包只是库自己跑不起来。第二Go 没有 public/private 关键字靠首字母大小写决定能不能被外面访问。这个设计第一次见真的反直觉。首字母大写 导出相当于 public包外能访问首字母小写 包内私有相当于 private只有同包内能用比如结构体字段Name string能被别的包读到name string就只能自己包里用。记住这不是风格是语法规则。我写第一个项目就因为字段名小写别的包死活读不到查了半天才反应过来。三、变量声明三兄弟var、:、const谁该什么时候用Java 里我只有一种写法int x 10;。Go 给了我三种而且语义完全不同这是面试官爱挖的点。var x int显式声明包级和函数内都能用x : 10短声明仅函数内自动推导const PI常量编译期定死不可改生活化理解var 像先挂牌子再放货可以只声明不赋值: 像边放货边自动贴标签Go 看 10 是 int 就标成 int但只能在函数里用const 像焊死的水泥墩编译期就定死运行期谁也改不了。写法能用在哪里要不要立刻赋值典型用途var x int包级和函数内都行不用可后置包级变量、先声明后赋值x : 10只能在函数里必须且自动推导类型函数内临时变量const PI 3.14包级和函数内必须配置项、枚举值还有两个第一天没讲但第二天很重要的事。零值机制Java 里局部变量不初始化就用会报错Go 不一样变量声明了就有默认值。数值是 0字符串是空串布尔是 false指针/切片/map 是 nil。这比 Java 友好不会出现随机垃圾值。iota 枚举Java 有 enumGo 没有这个关键字靠const iota模拟。iota 是编译器在 const 块里自动从 0 递增的计数器const(Moniota1// 1Tue// 2Wed// 3)强类型Go 比 Java 还狠完全没有隐式转换。哪怕都是数字int 和 int32 是两种类型必须显式写int32(x)。刚开始写别扭但能挡掉一堆隐蔽 bug。处理中文更要小心字符串底层是 UTF-8 字节中文占 3 字节遍历中文要用 rune 或 range用 []byte 会切开汉字乱码。四、切片Go 用得最多的数据结构也是第一个大坑数组我 quickly 过了一下定长、值类型赋值/传参是整个拷贝、长度还参与类型[5]int和[10]int是两种类型。实际开发几乎不用数组真正天天用的是切片 slice。切片是数组的动态视图底层就三个字段指向底层数组的指针 ptr、当前长度 len、容量 cap。它是引用类型多个切片可以指向同一块底层数组。切片变量 sptr 指向底层数组len 当前长度cap 容量第一个坑append 扩容。往切片加元素用 append。当 len 等于 cap底层数组满了append 会分配一块更大的新数组通常翻倍、拷贝旧数据、写入新元素、让切片指向新数组。所以一定要用s append(s, x)把返回值接回来不接的话如果发生了扩容你改的是旧数组新数据丢了。第二个坑更隐蔽切片截取共享底层数组。s[1:3]这种截取不会复制数据而是生成一个新切片和原切片共享同一块底层数组。你改了截取出来的切片原数组也跟着变。还有个隐藏内存坑从超大底层数组里截取一小片长期持有这一小片会拖住整个大数组无法被 GC 回收。解决方法是s2 append([]int{}, s1...)强制拷贝一份。修改 s1 会影响原数组底层数组 1 2 3 4 5s1 取第2到4个元素s2 取第1到3个元素原数组和 s2 也跟着变这张图说的是两个切片看着独立其实共用一个冰箱动一个另一个也变。我做的那个练手项目几万台设备、温度振动数据下面简称平台里切片是主力。一次从缓存批量拉回几万台设备的温度读数直接塞进 []float64动态扩容让我不用预先知道设备数量。但截取共享底层数组的坑真实存在从全天数据大切片里截取一小段做实时展示并长期缓存这一小段会拖住全天数组不被回收内存悄悄涨。实战里要么拷贝要么用 copy() 显式拷贝。对比项数组 array切片 slice长度固定写死动态能长能短类型[5]int和[10]int是不同类型[]int就一种传参行为值类型整体拷贝引用类型共享底层数组日常使用几乎不用天天用五、mapnil map 写入直接崩这个坑我记一辈子map 就是键值对抽屉柜按标签直接拿东西。三个核心特性无序、引用类型、nil map 不能写。最易错的是 nil map 和空 map 的区别两种空状态nil map未分配底层结构空 map已 make 可写写入会 panic可正常增删改查var m map[string]int是 nil读可以返回零值写必 panic。m : make(map[string]int)或m : map[string]int{}是空但可用。结论凡要写 map先 make 或给字面量别裸声明。map 的 key 必须可比较能做 所以 int/string/bool/可比较结构体可以当 keyslice/map/func 不行。还有个设计Go 故意让 map 遍历顺序随机防止你写代码时以为有序。需要有序就自己把 key 取出来排序。查一个 key 在不在用v, ok : m[k]的 ok 模式别把零值误当有值。平台里map 是缓存主力。用map[string]float64key 设备IDvalue 最新温度做设备最新状态缓存O(1) 查某个设备当前温度。nil map 的坑真实存在函数返回了一个没 make 的 map 然后别人往里写直接线上崩溃。团队规范是函数内要返回的 map 先 make。六、函数与 deferGo 的身份证和自动收尾Go 和 Java 最大的差异之一是函数可以返回多个值。最经典的就是 (result, error)funcdiv(a,bint)(int,error){ifb0{return0,errors.New(除数不能为0)}returna/b,nil}正常返回v, nil出错返回0, err调用方用v, err : f()且必查 err。这是 Go 错误即值哲学的落地比 Java 抛异常更可控。控制结构也有意思Go 只有 for 一个循环没有 while。用for cond {}当 whilefor {}当死循环。switch-case 执行完自动 break不用手写防穿透。最让我印象深的是 defer面试必考。defer 把语句推迟到函数返回前执行多个 defer 按栈的顺序后进先出defer 入栈defer A 最先注册defer B 最后注册函数返回时B 先执行 然后 A这张图说的是defer 像叠盘子最后叠的先拿。典型用途是资源释放打开文件/连接/加锁后立刻 defer close()保证函数退出时一定释放不会漏。还有 init 和 main 的顺序包级变量初始化 → 各包 init() → main 包 init() → main()。init 常做配置加载、连接池初始化这些启动前必须准备好的事。七、Day6 实战不看教程写一个图书管理 CLI前面五天都是听讲解跟敲第六天反过来合上教程从零写个图书管理命令行小程序。要求用 go mod init 起项目定义书的结构体用切片或 map 存书做个循环菜单实现增删改查。这是我第一次不查资料独立写卡了几次比如删除时忘了处理id 不存在查不到时返回零值没提示。写完跑起来把每个功能点一遍才算过关。这个 cli 里我把前五天学的都用上了结构体、var/:、slice、for/switch、多返回值思路。下面是我写的简化版核心脱敏只留结构packagemainimportfmttypeBookstruct{IDintTitlestringAuthorstringPricefloat64}funcmain(){books:make(map[int]Book)// 用 map 按 id 查得快// 循环打印菜单输入数字选择直到选退出// 1 添加 2 删除 3 修改 4 查询 5 列出全部 6 退出_books fmt.Println(图书管理 CLI 跑通了)}写完这版我对切片增删改查 map 按 key 查 for 循环菜单这套组合拳才算真正内化了。Java 后端加学 Go 的速查表Java 习惯Go 对应写法注意点class 方法struct 独立函数Go 没有类public / private首字母大小写大写才算导出try-catch多返回值 (T, error)必须查 errwhile 循环for cond {}只有 forHashMapmap[K]Vnil 不能写自动类型转换完全没有必须显式写这周的整体感受第一周最大的冲击是Go 把 Java 里很多写起来啰嗦但明确的东西砍掉了换成了更笨拙但更安全的规则。没有 class 我用 struct 加函数没有 public/private 我用大小写没有 try-catch 我用多返回值 (result, err)。初写别扭但写顺了发现它逼你写出更干净的代码。切片和 map 的两个坑截取共享底层数组、nil map 写入 panic是这周最值钱的经验光看教程记不住自己写项目踩一遍才刻进脑子。下周进第 2 周开始学 struct 的方法、接口 interface以及 Go 的错误处理惯用法。接口那块据说和 Java 的接口完全不同已经有点期待踩坑了。

相关资讯