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")
// ***
}
当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])
}
// ***
}
打印底层信息
好奇心加深,打印一下ids
和idList
的信息
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
来源:https://juejin.cn/post/7102663514510065672
猜你喜欢
- SQL中Case的使用方法Case具有两种格式。简单Case函数和Case搜索函数。--简单Case函数CASE sex &nbs
- 其实发这篇博感觉并没有什么用,太简单了,会的人不屑看,不会的人自已动动脑子也想到了。但是看着自已的博客已经这么久没更,真心疼~。粗略算下一篇
- 上一章讲数据的处理,这一章讲数据处理之后呈现的结果,即你有可能看到Loss的走向等,这样方便我们调试代码。1.Tensorboard有两个常
- 译者按:原文写于2011年末,虽然文中关于Python 3的一些说法可以说已经不成立了,但是作为一篇面向从其他语言转型到Python的程序员
- 层次分析法(The analytic hierarchy process)简称AHP,在20世纪70年代中期由美国运筹学家托马斯.塞蒂(T.
- MySQL带AND关键字的多条件查询,MySQL中,使用AND关键字,可以连接两个或者多个查询条件,只有满足所有条件的记录,才会被返回。SE
- 作者: wyh草样出处:https://www.cnblogs.com/wyh0923/p/14047466.html1、数据库配置类 Mo
- Anaconda 本质上是一个软件发行版,包含了 conda、Python 等 180 多个科学包及其依赖项。因为包含了大量的科学包,Ana
- 简述生活中经常要用到各种要求的证件照电子版,红底,蓝底,白底等,大部分情况我们只有其中一种,所以通过技术手段进行合成,用ps处理证件照,由于
- MySQL启用SSD存储的实例详解有时OS读写慢会降低MySQL服务器的性能,尤其是OS与MySQL使用同一磁盘时。故最好是让MySQL使用
- 1 圆点选择选项设置效果展示代码参考#!/usr/bin/python# -*- coding:utf-8 -*-import sysfro
- 描述random() 方法返回随机生成的一个实数,它在[0,1)范围内。import randomhelp(random)FUNCTIONS
- 核心技术:Python3.7GUI技术:Tkinter (Python已经内置)好多文章写Python GUI之tkinter窗口视窗教程大
- 若对于同一数据库实例中的两个数据库进行同步则直接对数据库表创建Trigger。SQL Server 2005的联机帮助:Trigger on
- 在本文上两篇中,我们学习了脚本语言 VBScript 的变量、函数、过程和条件语句,本篇将继续给大家介绍 VBScipt 的循环语句,并对脚
- 本文主要讨论了python中getpass模块的相关内容,具体如下。getpass模块昨天跟学弟吹牛b安利Python标准库官方文档的时候偶
- 前面的例子中,点击事件都是通过click()方法实现鼠标的点击事件。其实在WebDriver中,提供了许多鼠标操作的方法,这些操作方法都封装
- Keras运行迭代一定代数以后,速度越来越慢,经检查是因为在循环迭代过程中增加了新的计算节点,导致计算节点越来越多,内存被占用完,速度变慢。
- 一般学习java和部署项目都是在本地部署,但是生产环境一般都是在linux环境下,部署和安装环境都是在控制台下进行操作的,没有windows
- <form action="calscore.asp?action=do" met