0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[golang] string と rune の関係 や strings.Builder の使い方について

Last updated at Posted at 2024-12-16

目的

string と rune の関係 や strings.Builder の使い方について理解する。

説明

main.go
package main

import "fmt"

// go run .
func main() {
	fmt.Println(" ============================== (1)")
	pra_string_my_1()
	fmt.Println(" ============================== (2)")
	pra_string_my_2()
	fmt.Println(" ============================== (3)")
	pra_string_my_3()
	fmt.Println(" ============================== (4)")
	pra_string_my_4()
	fmt.Println(" ============================== (5)")
	pra_string_my_5()
}

pra_string_my.go
package main

import (
	"fmt"
	"strings"
)

// //////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////// (1)
// string はバイト長とバイト配列をフィールドに持つ構造体である。
// string を for ループで回すと、index は各ルーン(Unicode のコードポイント)の先頭のバイトの位置を示す。
// myString[i] のようにアクセスすると、i 番目のバイトを取得する。
// UTF-8 は全てのルーン(Unicode のコードポイント) を 1 ~ 4 バイトで表現する。
// 「漢」など、UTF-8 にエンコードする際に1バイトで表現できないルーンがある場合、myString[i] のようにアクセスすると、そのルーンの部分的なバイトにアクセスすることになるので意図しない挙動になる。
//
//go:noinline
func pra_string_my_1() {
	myString := "Hello漢World!"
	fmt.Printf("len(myString): %d\n", len(myString)) // ルーンの個数ではなくて、バイト長を取得する。
	for i, r := range myString {
		fmt.Printf("index: %d, ", i)                 // 各ルーンの先頭のバイトの位置を示す。
		fmt.Printf("rune: %c, ", r)                  // ルーン(Unicode のコードポイント)を取得する。
		fmt.Printf("byte: %c, ", myString[i])        // i 番目のバイトを取得する。
		fmt.Printf("byte 16進数: 0x%x\n", myString[i]) // i 番目のバイトを 16 進数で表示する。
	}
}

// //////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////// (2)
// string を []rune に変換することで直観的な操作が可能になる。
// ただし、この変換は完全なコピーであり、O(n) の計算量がかかる。
//
//go:noinline
func pra_string_my_2() {
	myString := "Hello漢World!"
	runes := []rune(myString)
	fmt.Printf("len(runes): %d\n", len(runes))
	for i, r := range runes {
		fmt.Printf("index: %d, ", i)
		fmt.Printf("rune: %c, ", r)
		fmt.Printf("rune: %c\n", runes[i])
	}
}

// //////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////// (3)
// あるstringスライスを、一個おきに結合して、新たに string を生成したいとする。
// 下記は非推奨の方法である。
// なぜなら、文字列は不変であり、文字列の結合は新たな文字列を生成する(新たなメモリ割り当てを行う)からである。
//
//go:noinline
func pra_string_my_3() {
	myStringArray := []string{"Hello", "漢", "World", "字", "Good", "日本", "Morning", "語", "Good", "にほん", "Evening", "ご", "Good", "ニホン", "Night", "ゴ"}
	result := ""
	for i, ele := range myStringArray {
		if i%2 == 0 {
			result += ele
		}
	}
	fmt.Println(result)
}

// //////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////// (4)
// strings.Builder は、文字列の結合を行う際に、新たなメモリ割り当てを行わずに、効率的に文字列を結合することができる。
// ただし、後述するが、内部的にバイトスライスを持っているので、使い方によっては余計なメモリ割り当てを発生させてしまうので、スライスの特徴を理解しておく必要がある。
//
//go:noinline
func pra_string_my_4() {
	myStringArray := []string{"Hello", "漢", "World", "字", "Good", "日本", "Morning", "語", "Good", "にほん", "Evening", "ご", "Good", "ニホン", "Night", "ゴ"}
	var sb strings.Builder
	for i, ele := range myStringArray {
		if i%2 == 0 {
			sb.WriteString(ele)
		}
	}
	fmt.Println(sb.String())
}

// //////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////// (5)
// 事前に連結後の文字列のバイト長がわかっている場合、strings.Builder の Grow メソッドを使って、メモリ割り当てを事前に行うべきである。
// なぜなら、strings.Builder は内部的にバイトスライスを持っており、スライスの特徴として、容量が足りなくなると、その時点の容量の 2 倍の容量を持つ新しいスライスのコピーを作成するからである。
// ちなみに、1024 バイトを超える場合、その時点の容量の 2 倍ではなくて、その時点の容量の 1.25 倍の容量を持つ新しいスライスのコピーを作成する。
// この 2 倍の容量を持つ新しいスライスのコピーを作成する処理は、コピー元のスライスは必要ないわけなので、明らかに余計なメモリ割り当てを行っていることは分かるだろう。
//
//go:noinline
func pra_string_my_5() {
	myStringArray := []string{"Hello", "漢", "World", "字", "Good", "日本", "Morning", "語", "Good", "にほん", "Evening", "ご", "Good", "ニホン", "Night", "ゴ"}
	totalByteLen := 0
	for i, ele := range myStringArray {
		if i%2 == 0 {
			totalByteLen += len(ele)
		}
	}
	// var sb strings.Builder は nil である一方、strings.Builder{} は strings.Builder のゼロ値を生成するので、ほんの少しだけ余計なメモリ割り当てを行ってしまうと思う。
	// 後で検証したい。
	// sb := strings.Builder{}
	var sb strings.Builder
	sb.Grow(totalByteLen)
	for index, element := range myStringArray {
		if index%2 == 0 {
			sb.WriteString(element)
		}
	}
	fmt.Println(sb.String())
}

~/praprago$ go run .
 ============================== (1)
len(myString): 14
index: 0, rune: H, byte: H, byte 16進数: 0x48
index: 1, rune: e, byte: e, byte 16進数: 0x65
index: 2, rune: l, byte: l, byte 16進数: 0x6c
index: 3, rune: l, byte: l, byte 16進数: 0x6c
index: 4, rune: o, byte: o, byte 16進数: 0x6f
index: 5, rune: 漢, byte: æ, byte 16進数: 0xe6
index: 8, rune: W, byte: W, byte 16進数: 0x57
index: 9, rune: o, byte: o, byte 16進数: 0x6f
index: 10, rune: r, byte: r, byte 16進数: 0x72
index: 11, rune: l, byte: l, byte 16進数: 0x6c
index: 12, rune: d, byte: d, byte 16進数: 0x64
index: 13, rune: !, byte: !, byte 16進数: 0x21
 ============================== (2)
len(runes): 12
index: 0, rune: H, rune: H
index: 1, rune: e, rune: e
index: 2, rune: l, rune: l
index: 3, rune: l, rune: l
index: 4, rune: o, rune: o
index: 5, rune: 漢, rune: 漢
index: 6, rune: W, rune: W
index: 7, rune: o, rune: o
index: 8, rune: r, rune: r
index: 9, rune: l, rune: l
index: 10, rune: d, rune: d
index: 11, rune: !, rune: !
 ============================== (3)
HelloWorldGoodMorningGoodEveningGoodNight
 ============================== (4)
HelloWorldGoodMorningGoodEveningGoodNight
 ============================== (5)
HelloWorldGoodMorningGoodEveningGoodNight

参考

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?