Golang的strings.Split()坑怎么解决

2024-03-14

这篇文章主要介绍“Golang的strings.Split()坑怎么解决”,在日常操作中,相信很多人在Golang的strings.Split()坑怎么解决问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Golang的strings.Split()坑怎么解决”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

    场景

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

    type Info struct{
       Ids string // Ids: 123,456
    }
    
    func test3(info Info){
       ids := info.Ids
       idList := strings.Split(ids , ",")
       if len(idList) < 1 {
          return
       }
       log.Println("ids-not-empty")
       // ***
    }

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

    前置

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

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

    type stringHeader struct {
       Data unsafe.Pointer
       Len  int
    }

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

    排查

    验证

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

    func test3(info Info){  
        ids := info.Ids
        idList := strings.Split(ids, ",")
        log.Printf("idList长度: [%d], idList: [%v]", len(idList), idList)
        for index, _ := range idList {
           log.Printf("idList[%d]:[%v]", index, idList[index])
        }    
       // ***
    }

    打印底层信息

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

    const (
      basePrintInfoV3 = "%s 字符串的指针地址:[%v],字符串buf数组地址:[%v] ,Len字段的地址:[%p] ,Len字段值:[%v]"
      basePrintInfoV2 = "%s切片的指针地址:[%p],切片数组地址:[%p], Len字段的地址:[%p], Len字段的值:[%v]"
    )
    
    func test3(info Info) {
      ids := info.Ids
      idList := strings.Split(ids, ",")
      getStringPtr("ids ", &ids)
      getStringSliceAllPtr("idList ", &idList)
      // ***
    }
    func getStringPtr(name string, str *string) {
       s2 := (*reflect.StringHeader)(unsafe.Pointer(str))
       log.Printf(basePrintInfoV3, name, unsafe.Pointer(str), unsafe.Pointer(s2.Data), unsafe.Pointer(&s2.Len), s2.Len)
    }
    
    func getStringSliceAllPtr(name string, s1 *[]string) {
       s2 := (*reflect.StringHeader)(unsafe.Pointer(s1))
       log.Printf(basePrintInfoV2, name, unsafe.Pointer(&s1), unsafe.Pointer(s2.Data), unsafe.Pointer(&s2.Len), s2.Len)
    }

    追源码

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

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

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

    • 预先确定s 能够被切分成n

    • 创建长度为n的数组

    • 遍历 s ,将每片数据放入数组中

    • 返回

    func genSplit(s, sep string, sepSave, n int) []string {
       if n == 0 {
          return nil
       }
       if sep == "" {
          return explode(s, n)
       }
       if n < 0 {
          // 计算 s 按照 seq 能被切成多少份
          n = Count(s, sep) + 1
       }
    
       a := make([]string, n)
       n--
       i := 0
       for i < n {
          // 定位 s里的第一个 sep 所在的位置
          m := Index(s, sep)
          if m < 0 {
             break
          }
          // 放入返回的数组
          a[i] = s[:m+sepSave]
          // 切割s
          s = s[m+len(sep):]
          i++
       }
       a[i] = s
       return a[:i+1]
    }

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

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

    func Count(s, substr string) int {
       // special case
       if len(substr) == 0 {
          return utf8.RuneCountInString(s) + 1
       }
       if len(substr) == 1 {
          return bytealg.CountString(s, substr[0])
       }
       n := 0
       for {
          i := Index(s, substr)
          if i == -1 {
             return n
          }
          n++
          s = s[i+len(substr):]
       }
    }

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

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

    类似情况

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

    s := strings.Split("", "")
    fmt.Println(s, len(s)) // [] 0 //返回空数组
    
    s = strings.Split("abc,abc", "")
    fmt.Println(s, len(s)) // [a b c , a b c] 7 //返回7个数组元素
    
    s = strings.Split("", ",")
    fmt.Println(s, len(s)) // [] 1 
    
    s = strings.Split("abc,abc", ",")
    fmt.Println(s, len(s)) // [abc abc] 2
    
    s = strings.Split("abc,abc", "|")
    fmt.Println(s, len(s)) // [abc,abc] 1
    
    fmt.Println(len("")) // 0
    fmt.Println(len([]string{""})) // 1 
    
    str := ""
    fmt.Println(str[0]) // panic

    到此,关于“Golang的strings.Split()坑怎么解决”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注本站网站,小编会继续努力为大家带来更多实用的文章!

    《Golang的strings.Split()坑怎么解决.doc》

    下载本文的Word格式文档,以方便收藏与打印。