最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Go語(yǔ)言模型:string的底層數(shù)據(jù)結(jié)構(gòu)與高效操作詳解

 更新時(shí)間:2020年12月24日 10:21:05   作者:ka__ka__  
這篇文章主要介紹了Go語(yǔ)言模型:string的底層數(shù)據(jù)結(jié)構(gòu)與高效操作詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧

Golang的string類型底層數(shù)據(jù)結(jié)構(gòu)簡(jiǎn)單,本質(zhì)也是一個(gè)結(jié)構(gòu)體實(shí)例,且是const不可變。

string的底層數(shù)據(jù)結(jié)構(gòu)

通過(guò)下面一個(gè)例子來(lái)看:

package main
import (
	"fmt"
	"unsafe"
)
// from: string.go 在GoLand IDE中雙擊shift快速找到
type stringStruct struct {
	array unsafe.Pointer // 指向一個(gè) [len]byte 的數(shù)組
	length int    // 長(zhǎng)度
}
func main() {
	test := "hello"
	p := (*str)(unsafe.Pointer(&test))
	fmt.Println(&p, p) // 0xc420070018 &{0xa3f71 5}
	c := make([]byte, p.length)
	for i := 0; i < p.length; i++ {
		tmp := uintptr(unsafe.Pointer(p.array))   // 指針類型轉(zhuǎn)換通過(guò)unsafe包
		c[i] = *(*byte)(unsafe.Pointer(tmp + uintptr(i))) // 指針運(yùn)算只能通過(guò)uintptr
	}
	fmt.Println(c)   // [104 101 108 108 111]
	fmt.Println(string(c)) // [byte] --> string, "hello"
	test2 := test + " world" // 字符串是不可變類型,會(huì)生成一個(gè)新的string實(shí)例
	p2 := (*str)(unsafe.Pointer(&test2))
	fmt.Println(&p2, p2) // 0xc420028030 &{0xc42000a2e5 11}
	fmt.Println(test2) // hello, world
}

string的拼接與修改

+操作

string類型是一個(gè)不可變類型,那么任何對(duì)string的修改都會(huì)新生成一個(gè)string的實(shí)例,如果是考慮效率的場(chǎng)景就要好好考慮一下如何修改了。先說(shuō)一下最長(zhǎng)用的+操作,同樣上面的例子,看一下+操作拼接字符串的反匯編:

25		test2 := test + " world"
 0x00000000004824d7 <+1127>:	lea 0x105a2(%rip),%rax  # 0x492a80
 0x00000000004824de <+1134>:	mov %rax,(%rsp)
 0x00000000004824e2 <+1138>:	callq 0x40dda0 <runtime.newobject> # 調(diào)用newobject函數(shù)
 0x00000000004824e7 <+1143>:	mov 0x8(%rsp),%rax
 0x00000000004824ec <+1148>:	mov %rax,0xa0(%rsp)
 0x00000000004824f4 <+1156>:	mov 0xa8(%rsp),%rax
 0x00000000004824fc <+1164>:	mov 0x8(%rax),%rcx
 0x0000000000482500 <+1168>:	mov (%rax),%rax
 0x0000000000482503 <+1171>:	mov %rax,0x8(%rsp)
 0x0000000000482508 <+1176>:	mov %rcx,0x10(%rsp)
 0x000000000048250d <+1181>:	movq $0x0,(%rsp)
 0x0000000000482515 <+1189>:	lea 0x30060(%rip),%rax  # 0x4b257c
 0x000000000048251c <+1196>:	mov %rax,0x18(%rsp)
 0x0000000000482521 <+1201>:	movq $0x6,0x20(%rsp)
 0x000000000048252a <+1210>:	callq 0x43cc00 <runtime.concatstring2> # 調(diào)用concatstring2函數(shù)

因?yàn)楫?dāng)前go[2018.11 version: go1.11]的不是遵循默認(rèn)的x86 calling convention用寄存器傳參,而是通過(guò)stack進(jìn)行傳參,所以go的反匯編不像c的那么容易理解,不過(guò)大概看懂+背后的操作還是沒(méi)問(wèn)題的,看一下runtime源碼的拼接函數(shù):

func concatstring2(buf *tmpBuf, a [2]string) string {
 return concatstrings(buf, a[:])
}
// concatstrings implements a Go string concatenation x+y+z+...
// The operands are passed in the slice a.
// If buf != nil, the compiler has determined that the result does not
// escape the calling function, so the string data can be stored in buf
// if small enough.
func concatstrings(buf *tmpBuf, a []string) string {
 idx := 0
 l := 0
 count := 0
 for i, x := range a {
  n := len(x)
  if n == 0 {
   continue
  }
  if l+n < l {
   throw("string concatenation too long")
  }
  l += n
  count++
  idx = i
 }
 if count == 0 {
  return ""
 }
 // If there is just one string and either it is not on the stack
 // or our result does not escape the calling frame (buf != nil),
 // then we can return that string directly.
 if count == 1 && (buf != nil || !stringDataOnStack(a[idx])) {
  return a[idx]
 }
 s, b := rawstringtmp(buf, l)
 for _, x := range a {
  copy(b, x) // 最關(guān)鍵的拷貝操作
  b = b[len(x):]
 }
 return s
}

分析runtime的concatstrings實(shí)現(xiàn),可以看出+最后新申請(qǐng)buf,拷貝原來(lái)的string到buf,最后返回新實(shí)例。那么每次的+操作,都會(huì)涉及新申請(qǐng)buf,然后是對(duì)應(yīng)的copy。如果反復(fù)使用+,就不可避免有大量的申請(qǐng)內(nèi)存操作,對(duì)于大量的拼接,性能就會(huì)受到影響了。

bytes.Buffer

通過(guò)看源碼,bytes.Buffer 增長(zhǎng)buffer時(shí)是按照2倍來(lái)增長(zhǎng)內(nèi)存,可以有效避免頻繁的申請(qǐng)內(nèi)存,通過(guò)一個(gè)例子來(lái)看:

func main() {
 var buf bytes.Buffer
 for i := 0; i < 10; i++ {
  buf.WriteString("hi ")
 }
 fmt.Println(buf.String())
}

對(duì)應(yīng)的byte包庫(kù)函數(shù)源碼

// @file: buffer.go
func (b *Buffer) WriteString(s string) (n int, err error) {
 b.lastRead = opInvalid
 m, ok := b.tryGrowByReslice(len(s))
 if !ok {
  m = b.grow(len(s)) // 高效的增長(zhǎng)策略 -> let capacity get twice as large
 }
 return copy(b.buf[m:], s), nil
}
// @file: buffer.go
// let capacity get twice as large !!!
func (b *Buffer) grow(n int) int {
 m := b.Len()
 // If buffer is empty, reset to recover space.
 if m == 0 && b.off != 0 {
  b.Reset()
 }
 // Try to grow by means of a reslice.
 if i, ok := b.tryGrowByReslice(n); ok {
  return i
 }
 // Check if we can make use of bootstrap array.
 if b.buf == nil && n <= len(b.bootstrap) {
  b.buf = b.bootstrap[:n]
  return 0
 }
 c := cap(b.buf)
 if n <= c/2-m {
  // We can slide things down instead of allocating a new
  // slice. We only need m+n <= c to slide, but
  // we instead let capacity get twice as large so we
  // don't spend all our time copying.
  copy(b.buf, b.buf[b.off:])
 } else if c > maxInt-c-n {
  panic(ErrTooLarge)
 } else {
  // Not enough space anywhere, we need to allocate.
  buf := makeSlice(2*c + n)
  copy(buf, b.buf[b.off:])
  b.buf = buf
 }
 // Restore b.off and len(b.buf).
 b.off = 0
 b.buf = b.buf[:m+n]
 return m
}

string.join

這個(gè)函數(shù)可以一次申請(qǐng)最終string的大小,但是使用得預(yù)先準(zhǔn)備好所有string,這種場(chǎng)景也是高效的,一個(gè)例子:

func main() {
 var strs []string
 for i := 0; i < 10; i++ {
 strs = append(strs, "hi")
 }
 fmt.Println(strings.Join(strs, " "))
}

對(duì)應(yīng)庫(kù)的源碼:

// Join concatenates the elements of a to create a single string. The separator string
// sep is placed between elements in the resulting string.
func Join(a []string, sep string) string {
 switch len(a) {
 case 0:
  return ""
 case 1:
  return a[0]
 case 2:
  // Special case for common small values.
  // Remove if golang.org/issue/6714 is fixed
  return a[0] + sep + a[1]
 case 3:
  // Special case for common small values.
  // Remove if golang.org/issue/6714 is fixed
  return a[0] + sep + a[1] + sep + a[2]
 }
 
 // 計(jì)算好最終的string的大小
 n := len(sep) * (len(a) - 1) //
 for i := 0; i < len(a); i++ {
  n += len(a[i])
 }
 b := make([]byte, n)
 bp := copy(b, a[0])
 for _, s := range a[1:] {
  bp += copy(b[bp:], sep)
  bp += copy(b[bp:], s)
 }
 return string(b)
}

strings.Builder (go1.10+)

看到這個(gè)名字,就想到了Java的庫(kù),哈哈,這個(gè)Builder用起來(lái)是最方便的,不過(guò)是在1.10后引入的。其高效也是體現(xiàn)在2倍速的內(nèi)存增長(zhǎng), WriteString函數(shù)利用了slice類型對(duì)應(yīng)append函數(shù)的2倍速增長(zhǎng)。

一個(gè)例子:

func main() {
 var s strings.Builder
 for i := 0; i < 10; i++ {
  s.WriteString("hi ")
 }
 fmt.Println(s.String())
}

對(duì)應(yīng)庫(kù)的源碼

@file: builder.go
// WriteString appends the contents of s to b's buffer.
// It returns the length of s and a nil error.
func (b *Builder) WriteString(s string) (int, error) {
 b.copyCheck()
 b.buf = append(b.buf, s...)
 return len(s), nil
}

總結(jié)

Golang的字符串處理還是挺方便的,有垃圾回收和一些內(nèi)置的語(yǔ)言級(jí)寫(xiě)法支持,讓復(fù)雜字符串操作沒(méi)有那么繁瑣了,比起C/C++高效了不少。

補(bǔ)充:go string的內(nèi)部實(shí)現(xiàn)

go string 內(nèi)部實(shí)現(xiàn)

這個(gè)string的探索

來(lái)來(lái)個(gè)例子

func boo(a int, b int)(int, string){
 return a + b, "abcd"
}
81079 000000000044dfa0 <main.boo>:
81080 44dfa0:>------48 c7 44 24 18 00 00 >--movq $0x0,0x18(%rsp)
81081 44dfa7:>------00 00- 
81082 44dfa9:>------0f 57 c0    >--xorps %xmm0,%xmm0
81083 44dfac:>------0f 11 44 24 20  >--movups %xmm0,0x20(%rsp)
81084 44dfb1:>------48 8b 44 24 08  >--mov 0x8(%rsp),%rax
81085 44dfb6:>------48 03 44 24 10  >--add 0x10(%rsp),%rax
81086 44dfbb:>------48 89 44 24 18  >--mov %rax,0x18(%rsp)
81087 44dfc0:>------48 8d 05 d4 eb 01 00 >--lea 0x1ebd4(%rip),%rax  # 46cb9b <go.string.*+0xbb>
81088 44dfc7:>------48 89 44 24 20  >--mov %rax,0x20(%rsp)
81089 44dfcc:>------48 c7 44 24 28 04 00 >--movq $0x4,0x28(%rsp)
81090 44dfd3:>------00 00- 
81091 44dfd5:>------c3     >--retq---

其中

81087 44dfc0:>------48 8d 05 d4 eb 01 00 >--lea 0x1ebd4(%rip),%rax  # 46cb9b <go.string.*+0xbb>
81088 44dfc7:>------48 89 44 24 20  >--mov %rax,0x20(%rsp)
81089 44dfcc:>------48 c7 44 24 28 04 00 >--movq $0x4,0x28(%rsp)
81090 44dfd3:>------00 00- 
81091 44dfd5:>------c3     >--retq---
lea 0x1ebd4(%rip),%rax得到char*, mov %rax,0x20(%rsp)復(fù)制給返回值, movq $0x4,0x28(%rsp)把長(zhǎng)度也填進(jìn)去,

其實(shí)可以看到string就是c里面的char* 和len的組合

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。

相關(guān)文章

  • Go語(yǔ)言如何利用Mutex保障數(shù)據(jù)讀寫(xiě)正確

    Go語(yǔ)言如何利用Mutex保障數(shù)據(jù)讀寫(xiě)正確

    這篇文章主要介紹了互斥鎖的實(shí)現(xiàn)機(jī)制,以及?Go?標(biāo)準(zhǔn)庫(kù)的互斥鎖?Mutex?的基本使用方法,文中的示例代碼講解詳細(xì),需要的小伙伴可以參考一下
    2023-05-05
  • Kubernetes上使用Jaeger分布式追蹤基礎(chǔ)設(shè)施詳解

    Kubernetes上使用Jaeger分布式追蹤基礎(chǔ)設(shè)施詳解

    這篇文章主要為大家介紹了Kubernetes上使用Jaeger分布式追蹤基礎(chǔ)設(shè)施詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-03-03
  • Go?Ginrest實(shí)現(xiàn)一個(gè)RESTful接口

    Go?Ginrest實(shí)現(xiàn)一個(gè)RESTful接口

    這篇文章主要為大家介紹了Go?Ginrest實(shí)現(xiàn)一個(gè)RESTful接口示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • Golang如何快速刪除map所有元素

    Golang如何快速刪除map所有元素

    這篇文章主要介紹了Golang如何快速刪除map所有元素問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-11-11
  • GoFrame框架gset使用對(duì)比PHP?Java?Redis優(yōu)勢(shì)

    GoFrame框架gset使用對(duì)比PHP?Java?Redis優(yōu)勢(shì)

    這篇文章主要為大家介紹了GoFrame框架gset對(duì)比PHP?Java?Redis的使用優(yōu)勢(shì)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • Go語(yǔ)言中的逃逸分析究竟是什么?

    Go語(yǔ)言中的逃逸分析究竟是什么?

    這篇文章主要介紹了Go語(yǔ)言中的逃逸,套喲究竟是什么呢?通俗來(lái)講,當(dāng)一個(gè)對(duì)象的指針被多個(gè)方法或線程引用時(shí),我們稱這個(gè)指針發(fā)生了“逃逸”。下面文章將詳細(xì)介紹Go語(yǔ)言中的逃逸,需要的朋友可以參考一下
    2021-09-09
  • golang實(shí)現(xiàn)讀取excel數(shù)據(jù)并導(dǎo)入數(shù)據(jù)庫(kù)

    golang實(shí)現(xiàn)讀取excel數(shù)據(jù)并導(dǎo)入數(shù)據(jù)庫(kù)

    Go 語(yǔ)言是一門(mén)適合用于編寫(xiě)高效且并發(fā)的 Web 應(yīng)用程序的編程語(yǔ)言,同時(shí)也可以使用它進(jìn)行數(shù)據(jù)處理和分析,本文主要介紹了如何通過(guò)go語(yǔ)言實(shí)現(xiàn)讀取excel數(shù)據(jù)并導(dǎo)入數(shù)據(jù)庫(kù),感興趣的小伙伴可以了解下
    2025-04-04
  • go中string、int、float相互轉(zhuǎn)換方式

    go中string、int、float相互轉(zhuǎn)換方式

    這篇文章主要介紹了go中string、int、float相互轉(zhuǎn)換方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-07-07
  • 詳解golang中模板的常用語(yǔ)法

    詳解golang中模板的常用語(yǔ)法

    這篇文章主要介紹了golang模板中的常用語(yǔ)法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-08-08
  • 詳解Golang中interface接口的原理和使用技巧

    詳解Golang中interface接口的原理和使用技巧

    interface?接口在?Go?語(yǔ)言里面的地位非常重要,是一個(gè)非常重要的數(shù)據(jù)結(jié)構(gòu)。本文主要介紹了Golang中interface接口的原理和使用技巧,希望對(duì)大家有所幫助
    2022-11-11

最新評(píng)論

金坛市| 镇雄县| 巴中市| 梧州市| 昂仁县| 武山县| 迁西县| 泊头市| 咸阳市| 类乌齐县| 邯郸县| 黎平县| 保靖县| 望城县| 武穴市| 贵溪市| 东港市| 江油市| 阳谷县| 秀山| 花莲县| 衡阳县| 澎湖县| 济南市| 旬邑县| 吉林市| 新邵县| 会泽县| 宿松县| 河池市| 安徽省| 竹山县| 昂仁县| 涞源县| 桦川县| 襄城县| 金川县| 石景山区| 塔河县| 新郑市| 宽甸|