1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

【Go言語】stringsパッケージとbytesパッケージ

Last updated at Posted at 2024-07-15

文字列の連結

Goには文字列を連結する方法は2通りある。

シンプルな連結(+=で連結する)

スライスの全ての文字列を連結するconcat関数を作成する。

Go Playground

func concat(values []string) string {
    s := ""
    for _, value := range values {
        s += value
    }
    return s
}

これだと反復ごとにsは更新されず、新たな文字列がメモリから再割り当てされる。

strings.Builderを使用する方法(事前割り当てなし)

stringsパッケージのBuilderを用いることで上記の問題が解決し、メモリ上のコピーを最小限にする。

func concat(values []string) string {
	sb := strings.Builder{}
	for _, value := range values {
		_, _ = sb.WriteString(value)
	}
	return sb.String()
}

WriteString()は、nil以外のエラーを返すことはない。しかし、strings.Builderは、単一のメソッドを含むio.StringWriterインターフェイスを実装しており、WriteString(s string)(n int, err error)となっているため、エラーを返す必要がある。

string.Builderは内部的にバイトスライスを保持しており、`WriteString{を呼び出すたびに、appendが呼び出される。この構造体は並行的に使うべきではない。また、スライスの長さがわかっている場合は、事前に割り当てる必要がある。スライスの初期化のベストプラクティス

strings.Builderを使用する方法(事前割り当てあり)

strings.BuildGrow(n int)メソッドを使用すると、nバイトの領域を別に保証することができる。

func concat(values []string) string {
    total := 0
    for i := 0; i < len(values); i++ {
        total += len(values[i])
    }

    sb := strings.Builder{}
    sb.Grow(total)
    for _, value := range values {
        _, _ = sb.WriteString(value)
    }
    return sb.String()
}

結局どれがいいのか

ベンチマークテストでは、3つ目の方法が一番速くなっている。(参考文献から)
反復処理を2回行っても、スライスの初期化で長さを割り当てているので、速くなる。

ただし、2,3個のの文字列を連結するだけなら、strings.Builderを使用すると可読性が悪くなるので、オススメしない。(5個以上の連結であればstrings.Builderを使用する方が良い。)

文字列とバイト

GoではほとんどのI/Oは、文字列ではなく、[]byteで行われる。
例えば、io.Reader, io.Writer, io.ReadAllなどが挙げられる。

type Reader interface {
	Read(p []byte) (n int, err error)
}

io.Readerを入力とし、sanitize関数を呼び出すgetBytes関数を作成する。(サニタイズは先頭と末尾の空白を全て取り除くこと。)

stringを使用した実装

func getBytes(reader io.Reader) ([]byte, error) {
	b, err := io.ReadAll(reader)
	if err != nil {
		return nil, err
	}
	return []byte(sanitize(string(b))), nil
}

func sanitize(s string) string {
	return strings.TrimSpace(s)
}

[]byte(sanitize(string(b)))で[]byteを文字列に変換して、文字列を[]byteに変換するという冗長な書き方をしている。また、メモリも変換に余分な割り当てが発生する。

[]byteを使用した実装

[]byteを使用すると、不要な変換を行わずに実装できる。

func getBytes(reader io.Reader) ([]byte, error) {
	b, err := io.ReadAll(reader)
	if err != nil {
		return nil, err
	}
	return sanitize(b), nil
}

func sanitize(b []byte) []byte {
	return bytes.TrimSpace(b)
}

stringsパッケージのSplit, Count, Contains, Indexなどはbytesパッケージにも存在する。

文字列の不変性

[]byteから文字列を作成すると、コピーが作成される。

func main() {
	b := []byte{'a', 'b', 'c'}
	s := string(b)
	b[1] = 'x'
	fmt.Println(s) // abc
}

Goでは、文字列はイミュータブルである。

スライド

参考文献

  1. 100 Go Mistakes and How to Avoid Them
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?