经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Go语言 » 查看文章
Golang的strings.Split()踩坑记录
来源:jb51  时间:2022/5/30 9:34:00  对本文有异议

背景

工作中,当我们需要对字符串按照某个字符串切分成字符串数组数时,常用到strings.Split()

最近在使用过程中踩到了个坑,后对踩坑原因做了分析,并总结了使用string.Split可能踩到的坑。最后写本篇文章做复盘总结与分享

场景

当时是需要取某个结构体的某个属性,并将其按,切分 整体逻辑类似这样的

  1. type Info struct{
  2. Ids string // Ids: 123,456
  3. }
  4.  
  5. func test3(info Info){
  6. ids := info.Ids
  7. idList := strings.Split(ids , ",")
  8. if len(idList) < 1 {
  9. return
  10. }
  11. log.Println("ids-not-empty")
  12. // ***
  13. }

ids = "" 时,控制台打印了 ids-not-empty ,当时百思不得其解,按理来说应该直接走return 这个问题激发了我的好奇心,决定认真排查一下

前置

在排查之前,先大概讲讲 Go 中string的基本结构

golang的string它的运行时的数据结构位于reflect.StringHeader

  1. type stringHeader struct {
  2. Data unsafe.Pointer
  3. Len int
  4. }

其中Data指向数据数组的指针 ,Len为数组的长度

排查

验证

既然代码中的 if 判断为false,那么就实际打印一下 isList的长度看看呢

  1. func test3(info Info){
  2. ids := info.Ids
  3. idList := strings.Split(ids, ",")
  4. log.Printf("idList长度: [%d], idList: [%v]", len(idList), idList)
  5. for index, _ := range idList {
  6. log.Printf("idList[%d]:[%v]", index, idList[index])
  7. }
  8. // ***
  9. }

打印底层信息

好奇心加深,打印一下idsidList的信息

  1. const (
  2. basePrintInfoV3 = "%s 字符串的指针地址:[%v],字符串buf数组地址:[%v] ,Len字段的地址:[%p] ,Len字段值:[%v]"
  3. basePrintInfoV2 = "%s切片的指针地址:[%p],切片数组地址:[%p], Len字段的地址:[%p], Len字段的值:[%v]"
  4. )
  5.  
  6. func test3(info Info) {
  7. ids := info.Ids
  8. idList := strings.Split(ids, ",")
  9. getStringPtr("ids ", &ids)
  10. getStringSliceAllPtr("idList ", &idList)
  11. // ***
  12. }
  13. func getStringPtr(name string, str *string) {
  14. s2 := (*reflect.StringHeader)(unsafe.Pointer(str))
  15. log.Printf(basePrintInfoV3, name, unsafe.Pointer(str), unsafe.Pointer(s2.Data), unsafe.Pointer(&s2.Len), s2.Len)
  16. }
  17.  
  18. func getStringSliceAllPtr(name string, s1 *[]string) {
  19. s2 := (*reflect.StringHeader)(unsafe.Pointer(s1))
  20. log.Printf(basePrintInfoV2, name, unsafe.Pointer(&s1), unsafe.Pointer(s2.Data), unsafe.Pointer(&s2.Len), s2.Len)
  21. }

追源码

ids 经过 split 之后的数组和预期的不一样,看来应该是 split 源码有特殊处理了,那追一下源码吧

  1. func Split(s, sep string) []string { return genSplit(s, sep, 0, -1) }

大概读一遍源码能够理清楚genSplit思路

  • 预先确定s 能够被切分成n
  • 创建长度为n的数组
  • 遍历 s ,将每片数据放入数组中
  • 返回
  1. func genSplit(s, sep string, sepSave, n int) []string {
  2. if n == 0 {
  3. return nil
  4. }
  5. if sep == "" {
  6. return explode(s, n)
  7. }
  8. if n < 0 {
  9. // 计算 s 按照 seq 能被切成多少份
  10. n = Count(s, sep) + 1
  11. }
  12.  
  13. a := make([]string, n)
  14. n--
  15. i := 0
  16. for i < n {
  17. // 定位 s里的第一个 sep 所在的位置
  18. m := Index(s, sep)
  19. if m < 0 {
  20. break
  21. }
  22. // 放入返回的数组
  23. a[i] = s[:m+sepSave]
  24. // 切割s
  25. s = s[m+len(sep):]
  26. i++
  27. }
  28. a[i] = s
  29. return a[:i+1]
  30. }

那么问题应该出就出在 Count 函数中

跟进看看 count 函数会计算 s 字符串中包含了多少个 subStr

  1. func Count(s, substr string) int {
  2. // special case
  3. if len(substr) == 0 {
  4. return utf8.RuneCountInString(s) + 1
  5. }
  6. if len(substr) == 1 {
  7. return bytealg.CountString(s, substr[0])
  8. }
  9. n := 0
  10. for {
  11. i := Index(s, substr)
  12. if i == -1 {
  13. return n
  14. }
  15. n++
  16. s = s[i+len(substr):]
  17. }
  18. }

Count 中会走 len(substr) == 1这个逻辑,其中的CountString计算s中存在多少个 substr[0],当时跟进,返回的结果是0 ,这里符合预期 。

再结合 genSplit 中的 n = Count() + 1 我们可以发现,在genSplit时,预先创建的数组长度就为0 + 1 = 1 ! 问题迎刃而解

类似情况

经过查阅,这里再总结一下其他使用strings.Split可能遇到的坑

  1. s := strings.Split("", "")
  2. fmt.Println(s, len(s)) // [] 0 //返回空数组
  3.  
  4. s = strings.Split("abc,abc", "")
  5. fmt.Println(s, len(s)) // [a b c , a b c] 7 //返回7个数组元素
  6.  
  7. s = strings.Split("", ",")
  8. fmt.Println(s, len(s)) // [] 1
  9.  
  10. s = strings.Split("abc,abc", ",")
  11. fmt.Println(s, len(s)) // [abc abc] 2
  12.  
  13. s = strings.Split("abc,abc", "|")
  14. fmt.Println(s, len(s)) // [abc,abc] 1
  15.  
  16. fmt.Println(len("")) // 0
  17. fmt.Println(len([]string{""})) // 1
  18.  
  19. str := ""
  20. fmt.Println(str[0]) // panic

总结

这次小小的踩坑其实也算是绕了一点点弯路,直接读源码就好了 hhhhhh

到此这篇关于Golang的strings.Split()踩坑记录的文章就介绍到这了,更多相关Golang strings.Split()内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号