Golang的strings.Split()踩坑记录

作者:酒红 时间:2024-02-22 11:34:05 

背景

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

最近在使用过程中踩到了个坑,后对踩坑原因做了分析,并总结了使用string.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")
  // ***
}

Golang的strings.Split()踩坑记录

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

前置

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

golang的string它的运行时的数据结构位于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])
   }    
  // ***
}

Golang的strings.Split()踩坑记录

打印底层信息

好奇心加深,打印一下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)
}

Golang的strings.Split()踩坑记录

追源码

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

来源:https://juejin.cn/post/7102663514510065672

标签:Go,strings.Split()
0
投稿

猜你喜欢

  • 带你轻松了解 SQL Server数据库的组成

    2009-02-05 15:53:00
  • np.concatenate()函数的具体使用

    2023-06-21 11:15:19
  • pyqt5让图片自适应QLabel大小上以及移除已显示的图片方法

    2022-02-26 16:18:37
  • 运用TensorFlow进行简单实现线性回归、梯度下降示例

    2022-03-02 10:30:15
  • mysql limit查询优化分析

    2023-11-16 00:51:04
  • 讲解MySQL数据库字符集出错的解决方法

    2008-12-02 14:32:00
  • js实现适用于素材网站的黑色多级菜单导航条效果

    2024-04-18 10:09:37
  • ASP 循环导入导出数据处理 不使用缓存

    2010-07-02 12:31:00
  • MySQL表LEFT JOIN左连接与RIGHT JOIN右连接的实例教程

    2024-01-24 05:14:17
  • 解决Jupyter因卸载重装导致的问题修复

    2023-09-30 18:22:31
  • ASP JSON类文件的使用方法

    2011-04-30 16:39:00
  • 几个小技巧帮你实现Golang永久阻塞

    2024-02-14 10:37:29
  • Python常用数据分析模块原理解析

    2023-07-12 03:46:31
  • vue阻止页面回退的实现方法(浏览器适用)

    2024-06-07 15:24:10
  • python time时间库详解

    2023-10-09 03:20:57
  • SQL SERVER 建立索引

    2010-07-02 21:01:00
  • python中count函数知识点浅析

    2023-05-21 18:41:30
  • CentOS Linux更改MySQL数据库目录位置具体操作

    2024-01-23 18:13:49
  • FCKEditor网页编辑器 几点使用心得

    2022-06-25 17:09:34
  • jQuery获取radio选中项的值实例

    2024-04-09 19:45:16
  • asp之家 网络编程 m.aspxhome.com