Golang的strings.Split()踩坑記錄
背景
工作中,當(dāng)我們需要對(duì)字符串按照某個(gè)字符串切分成字符串?dāng)?shù)組數(shù)時(shí),常用到strings.Split()
最近在使用過(guò)程中踩到了個(gè)坑,后對(duì)踩坑原因做了分析,并總結(jié)了使用string.Split
可能踩到的坑。最后寫(xiě)本篇文章做復(fù)盤(pán)總結(jié)與分享
場(chǎng)景
當(dāng)時(shí)是需要取某個(gè)結(jié)構(gòu)體的某個(gè)屬性,并將其按,
切分 整體邏輯類似這樣的
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") // *** }
當(dāng)ids = ""
時(shí),控制臺(tái)打印了 ids-not-empty
,當(dāng)時(shí)百思不得其解,按理來(lái)說(shuō)應(yīng)該直接走return
這個(gè)問(wèn)題激發(fā)了我的好奇心,決定認(rèn)真排查一下
前置
在排查之前,先大概講講 Go 中string
的基本結(jié)構(gòu)
golang的string
它的運(yùn)行時(shí)的數(shù)據(jù)結(jié)構(gòu)位于reflect.StringHeader
type stringHeader struct { Data unsafe.Pointer Len int }
其中Data
指向數(shù)據(jù)數(shù)組的指針 ,Len
為數(shù)組的長(zhǎng)度
排查
驗(yàn)證
既然代碼中的 if
判斷為false
,那么就實(shí)際打印一下 isList
的長(zhǎng)度看看呢
func test3(info Info){ ids := info.Ids idList := strings.Split(ids, ",") log.Printf("idList長(zhǎng)度: [%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數(shù)組地址:[%v] ,Len字段的地址:[%p] ,Len字段值:[%v]" basePrintInfoV2 = "%s切片的指針地址:[%p],切片數(shù)組地址:[%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
經(jīng)過(guò) split
之后的數(shù)組和預(yù)期的不一樣,看來(lái)應(yīng)該是 split
源碼有特殊處理了,那追一下源碼吧
func Split(s, sep string) []string { return genSplit(s, sep, 0, -1) }
大概讀一遍源碼能夠理清楚genSplit
思路
- 預(yù)先確定s 能夠被切分成
n
份 - 創(chuàng)建長(zhǎng)度為
n
的數(shù)組 - 遍歷 s ,將每片數(shù)據(jù)放入數(shù)組中
- 返回
func genSplit(s, sep string, sepSave, n int) []string { if n == 0 { return nil } if sep == "" { return explode(s, n) } if n < 0 { // 計(jì)算 s 按照 seq 能被切成多少份 n = Count(s, sep) + 1 } a := make([]string, n) n-- i := 0 for i < n { // 定位 s里的第一個(gè) sep 所在的位置 m := Index(s, sep) if m < 0 { break } // 放入返回的數(shù)組 a[i] = s[:m+sepSave] // 切割s s = s[m+len(sep):] i++ } a[i] = s return a[:i+1] }
那么問(wèn)題應(yīng)該出就出在 Count
函數(shù)中
跟進(jìn)看看 count
函數(shù)會(huì)計(jì)算 s
字符串中包含了多少個(gè) 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
中會(huì)走 len(substr) == 1
這個(gè)邏輯,其中的CountString
計(jì)算s
中存在多少個(gè) substr[0]
,當(dāng)時(shí)跟進(jìn),返回的結(jié)果是0
,這里符合預(yù)期 。
再結(jié)合 genSplit
中的 n = Count() + 1
我們可以發(fā)現(xiàn),在genSplit
時(shí),預(yù)先創(chuàng)建的數(shù)組長(zhǎng)度就為0 + 1 = 1
! 問(wèn)題迎刃而解
類似情況
經(jīng)過(guò)查閱,這里再總結(jié)一下其他使用strings.Split
可能遇到的坑
s := strings.Split("", "") fmt.Println(s, len(s)) // [] 0 //返回空數(shù)組 s = strings.Split("abc,abc", "") fmt.Println(s, len(s)) // [a b c , a b c] 7 //返回7個(gè)數(shù)組元素 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
總結(jié)
這次小小的踩坑其實(shí)也算是繞了一點(diǎn)點(diǎn)彎路,直接讀源碼就好了 hhhhhh
到此這篇關(guān)于Golang的strings.Split()踩坑記錄的文章就介紹到這了,更多相關(guān)Golang strings.Split()內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- 一文帶你了解Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)strings的常用函數(shù)和方法
- GoLang strings.Builder底層實(shí)現(xiàn)方法詳解
- GO語(yǔ)言字符串處理Strings包的函數(shù)使用示例講解
- Golang?strings包常用字符串操作函數(shù)
- golang strings包的Replace的使用說(shuō)明
- golang 中strings包的Replace的使用說(shuō)明
- Go中strings的常用方法詳解
- Go語(yǔ)言中strings和strconv包示例代碼詳解
- go語(yǔ)言中strings包的用法匯總
- go的strings用法小結(jié)
相關(guān)文章
一文帶你了解Go語(yǔ)言fmt標(biāo)準(zhǔn)庫(kù)輸出函數(shù)的使用
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中 fmt 標(biāo)準(zhǔn)庫(kù)輸出函數(shù)的使用,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起了解一下2022-12-12golang 執(zhí)行命令行的實(shí)現(xiàn)
本文主要介紹了golang 執(zhí)行命令行的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08go語(yǔ)言實(shí)現(xiàn)兩個(gè)協(xié)程交替打印
這篇文章主要介紹了go語(yǔ)言實(shí)現(xiàn)兩個(gè)協(xié)程交替打印,文章主要分享了兩種方法使用兩個(gè)channel和使用一個(gè)channel,內(nèi)容介紹詳細(xì)具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-03-03Go語(yǔ)言里的結(jié)構(gòu)體文法實(shí)例分析
這篇文章主要介紹了Go語(yǔ)言里的結(jié)構(gòu)體文法,實(shí)例分析了結(jié)構(gòu)體文法的概念及使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02淺談Golang?Slice切片如何擴(kuò)容的實(shí)現(xiàn)
本文主要介紹了淺談Golang?Slice切片如何擴(kuò)容的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02Go編寫(xiě)定時(shí)器與定時(shí)任務(wù)詳解(附第三方庫(kù)gocron用法)
當(dāng)需要每天執(zhí)行定時(shí)任務(wù)的時(shí)候就需要定時(shí)器來(lái)處理了,周期任務(wù),倒計(jì)時(shí)任務(wù),定點(diǎn)任務(wù)等,下面這篇文章主要給大家介紹了關(guān)于Go編寫(xiě)定時(shí)器與定時(shí)任務(wù)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07Go錯(cuò)誤和異常CGO?fallthrough處理教程詳解
這篇文章主要為大家介紹了Go錯(cuò)誤和異常CGO?fallthrough使用教程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08Golang使用Zookeeper實(shí)現(xiàn)分布式鎖
分布式鎖是一種在分布式系統(tǒng)中用于控制并發(fā)訪問(wèn)的機(jī)制,ZooKeeper?和?Redis?都是常用的實(shí)現(xiàn)分布式鎖的工具,本文就來(lái)使用Zookeeper實(shí)現(xiàn)分布式鎖,希望對(duì)大家有所幫助2024-02-02