脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Golang - Golang的strings.Split()踩坑记录

Golang的strings.Split()踩坑记录

2022-10-11 12:25酒红 Golang

工作中,当我们需要对字符串按照某个字符串切分成字符串数组数时,常用到strings.Split(),本文主要介绍了Golang的strings.Split()踩坑记录,感兴趣的可以了解一下

背景

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

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

场景

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
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

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

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

排查

验证

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

?
1
2
3
4
5
6
7
8
9
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的信息

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 源码有特殊处理了,那追一下源码吧

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

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

  • 预先确定s 能够被切分成n
  • 创建长度为n的数组
  • 遍历 s ,将每片数据放入数组中
  • 返回
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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可能遇到的坑

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

总结

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

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

原文链接:https://juejin.cn/post/7102663514510065672

延伸 · 阅读

精彩推荐
  • GolangGolang实现的聊天程序服务端和客户端代码分享

    Golang实现的聊天程序服务端和客户端代码分享

    这篇文章主要介绍了Golang实现的聊天程序服务端和客户端代码分享,本文先是讲解了实现逻辑,然后给出了实现代码,需要的朋友可以参考下 ...

    junjie5482020-04-09
  • GolangGo语言操作redis用法实例

    Go语言操作redis用法实例

    这篇文章主要介绍了Go语言操作redis用法,实例分析了Go语言操作redis的技巧,具有一定参考借鉴价值,需要的朋友可以参考下 ...

    niuniu3682020-04-20
  • GolangGo Java算法之单词搜索示例详解

    Go Java算法之单词搜索示例详解

    这篇文章主要为大家介绍了Go Java算法之单词搜索示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪...

    黄丫丫5522022-08-23
  • Golang使用Lumberjack+zap进行日志切割归档操作

    使用Lumberjack+zap进行日志切割归档操作

    这篇文章主要介绍了使用Lumberjack+zap进行日志切割归档操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    线条君10652021-03-01
  • GolangGolang的第一个程序-Hello World

    Golang的第一个程序-Hello World

    这篇文章主要介绍了第一个Go程序-Hello World,在编写第一个go程序之前,我们要将系统的环境变量配好,下面来看具体的编一过程吧,需要的小伙伴可以参考...

    小叶柏杉3632022-08-31
  • Golang用Go+WebSocket快速实现一个chat服务

    用Go+WebSocket快速实现一个chat服务

    这篇文章主要介绍了用Go+WebSocket快速实现一个chat服务,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们...

    kevwan10912021-04-22
  • Golang彻底理解golang中什么是nil

    彻底理解golang中什么是nil

    这篇文章主要介绍了golang中的nil用法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    raoxiaoya11112021-06-07
  • GolangMac OS系统安装golang教程

    Mac OS系统安装golang教程

    这篇文章主要介绍了Mac OS系统安装golang教程,本文还同时介绍了Sublime Text开发工具的配置,需要的朋友可以参考下 ...

    junjie1972020-04-12