Go 언어 모델: string 의 바 텀 데이터 구조 와 효율 적 인 조작

Golang 의 string 유형 바 텀 데이터 구조 가 간단 하고 본질 도 하나의 구조 체 인 스 턴 스 이 며 const 는 변 하지 않 습 니 다.
string 의 바 텀 데이터 구조
다음 예 를 통 해 보 자.
package main

import (
	"fmt"
	"unsafe"
)

// from: string.go  GoLand IDE   shift    
type stringStruct struct {
	array unsafe.Pointer  //      [len]byte    
	length int             //   
}

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))           //         unsafe 
		c[i] = *(*byte)(unsafe.Pointer(tmp + uintptr(i))) //         uintptr
	}
	fmt.Println(c)         // [104 101 108 108 111]
	fmt.Println(string(c)) // [byte] --> string, "hello"

	test2 := test + " world"  //          ,       string  
	p2 := (*str)(unsafe.Pointer(&test2))
	fmt.Println(&p2, p2) // 0xc420028030 &{0xc42000a2e5 11}
	fmt.Println(test2)   // hello, world
}

string 의 연결 및 수정
+ 조작
string 형식 은 가 변 적 이지 않 은 형식 입 니 다. string 에 대한 수정 은 string 의 인 스 턴 스 를 새로 생 성 합 니 다. 효율 적 인 장면 이 라면 어떻게 수정 하 는 지 잘 고려 해 야 합 니 다.먼저 가장 긴 + 동작 을 말 해 보 세 요. 같은 예 에서 + 문자열 을 조합 하 는 어 셈 블 리 를 보 세 요.
25		test2 := test + " world"
   0x00000000004824d7 <+1127>:	lea    0x105a2(%rip),%rax        # 0x492a80
   0x00000000004824de <+1134>:	mov    %rax,(%rsp)
   0x00000000004824e2 <+1138>:	callq  0x40dda0 <runtime.newobject> #   newobject  
   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> #   concatstring2  

현재 go [2018.11 version: go1.11] 는 기본 x86 calling convention 에 따라 레지스터 로 인삼 을 전달 하 는 것 이 아니 라 stack 을 통 해 인삼 을 전달 하 는 것 이기 때문에 go 의 어 셈 블 리 는 c 처럼 이해 하기 쉽 지 않 지만 대충 알 아 보 는 것 + 뒤의 조작 은 문제 가 없습니다. runtime 소스 코드 의 조합 함 수 를 보 세 요.
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)  //         
        b = b[len(x):]
    }
    return s
}

runtime 의 concatstrings 실현 을 분석 하면 + 마지막 으로 buf 를 신청 하고 원래 의 string 을 buf 로 복사 한 다음 에 새로운 인 스 턴 스 를 되 돌려 줍 니 다.그러면 매번 + 작업 은 새로운 buf 신청 과 관련 되 고 그 다음 에 해당 하 는 copy 입 니 다.반복 적 으로 사용 + 하면 대량의 메모리 조작 신청 이 불가피 하고 대량의 연결 에 성능 이 영향 을 받 을 수 있다.
bytes.Buffer
원본 코드 를 보면 bytes. Buffer 가 buffer 를 증가 할 때 2 배 에 따라 메모 리 를 증가 합 니 다. 빈번 한 신청 메모 리 를 효과적으로 피 할 수 있 습 니 다. 하나의 예 를 통 해 알 수 있 습 니 다.
func main() {
    var buf bytes.Buffer
    for i := 0; i < 10; i++ {
        buf.WriteString("hi ")
    }
    fmt.Println(buf.String())
}

대응 하 는 byte 패키지 라 이브 러 리 함수 원본 코드
// @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)) //         -> 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
이 함 수 는 최종 string 의 크기 를 한 번 에 신청 할 수 있 지만 모든 string 을 미리 준비 해 야 합 니 다. 이런 장면 도 효율 적 입 니 다. 하나의 예:
func main() {
	var strs []string
	for i := 0; i < 10; i++ {
		strs = append(strs, "hi")
	}
	fmt.Println(strings.Join(strs, " "))
}

대응 하 는 라 이브 러 리 의 원본 코드:
// 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]
    }
	
    //       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+)
이 이름 을 보고 자바 의 라 이브 러 리 가 생각 났 습 니 다. 하하, 이 빌 더 는 사용 하기에 가장 편리 합 니 다. 1. 10 후에 도 입 된 것 에 불과 합 니 다.그 효율 도 2 배속 의 메모리 성장 에 나타난다. WriteString 함 수 는 slice 유형 이 append 함수 에 대응 하 는 2 배속 성장 을 이용 했다.하나의 예:
func main() {
    var s strings.Builder
    for i := 0; i < 10; i++ {
        s.WriteString("hi ")
    }
    fmt.Println(s.String())
}

대응 라 이브 러 리 소스 코드
@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
}

총결산
Golang 의 문자열 처 리 는 매우 편리 합 니 다. 쓰레기 회수 와 내 장 된 언어 급 쓰기 지원 이 있어 복잡 한 문자열 작업 이 그리 번 거 롭 지 않 고 C/C++ 보다 훨씬 효율 적 입 니 다.

좋은 웹페이지 즐겨찾기