LoginSignup
80
54

More than 5 years have passed since last update.

Go で UTF-8 の文字列を扱う

Last updated at Posted at 2015-11-07

Go で UTF-8 の文字列の扱いに慣れるために練習で書いたコードをまとめました。

文字列を表示する

package main

func main() {
    str := "あいうえお"
    buf := []byte("あいうえお")
    runes := []rune("あいうえお")

    println(str)
    println(string(buf))
    println(string(runes))
}

文字列リテラルを使う

package main

func main() {
    str := "か\u3099\u3099\u3099\u3099\u3099"
    str2 := "DOG FACE \U0001F436"

    println(str)
    println(str2)
}

文字列リテラルを使う場合、次のとおり。

package main

func main() {
    str := "\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a"
    println("あいうえお" == str)
}

数値から文字列を生成する

次のようにコードポイントをあらわす数値から rune スライスをつくることができる。

package main

func main() {
    runes := []rune{0x3042, 0x3044, 0x3046, 0x3048, 0x304A}
    println(string(runes))
}

バイト列をあらわす数値から byte スライスを次のようにつくることができる。

package main

func main() {
    buf := []byte{0xe3, 0x81, 0x82, 0xe3, 0x81, 0x84, 0xe3, 0x81, 0x86, 0xe3, 0x81, 0x88, 0xe3, 0x81, 0x8a}
    println(string(buf))
}

Go の仕様

string、byte、rune の相互変換ルール

Go の仕様ドキュメントの Conversions to and from a string type にまとめられています。

符号つきもしくは符号なしの整数値を string 型に変換すると、整数の UTF-8 表現を含む文字列が生成されます。有効な Unicode コードポイントの範囲の外にある値は "\uFFFD" に変換されます。

string('a')       // "a"
string(-1)        // "\ufffd" == "\xef\xbf\xbd"
string(0xf8)      // "\u00f8" == "ø" == "\xc3\xb8"
type MyString string
MyString(0x65e5)  // "\u65e5" == "日" == "\xe6\x97\xa5"

byte のスライスを string 型に変換すると、スライスの要素が連続したバイト列である文字列が生成されます。

string([]byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'})   // "hellø"
string([]byte{})                                     // ""
string([]byte(nil))                                  // ""

type MyBytes []byte
string(MyBytes{'h', 'e', 'l', 'l', '\xc3', '\xb8'})  // "hellø"

rune のスライスを string 型に変換すると、それぞれの rune の値を文字に変換して連結した文字列が生成されます。

string([]rune{0x767d, 0x9d6c, 0x7fd4})   // "\u767d\u9d6c\u7fd4" == "白鵬翔"
string([]rune{})                         // ""
string([]rune(nil))                      // ""

type MyRunes []rune
string(MyRunes{0x767d, 0x9d6c, 0x7fd4})  // "\u767d\u9d6c\u7fd4" == "白鵬翔"

string 型の値を byte のスライスに変換すると、連続した要素が文字列のバイトであるスライスが生成されます。

[]byte("hellø")   // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}
[]byte("")        // []byte{}

MyBytes("hellø")  // []byte{'h', 'e', 'l', 'l', '\xc3', '\xb8'}

string 型の値を rune のスライスに変換すると、それぞれの文字の Unicode コードポイントを保有するスライスが生成されます。

[]rune(MyString("白鵬翔"))  // []rune{0x767d, 0x9d6c, 0x7fd4}
[]rune("")                 // []rune{}

MyRunes("白鵬翔")           // []rune{0x767d, 0x9d6c, 0x7fd4}

バイト数を求める

package main

func main() {
    str := "あいうえお"
    buf := []byte("あいうえお")
    runes := []rune("あいうえお")

    println(15 == len(str))
    println(15 == len(buf))
    println(15 == len(string(runes))) 
}

文字数を求める

package main

import "unicode/utf8"

func main() {
    str := "あいうえお"
    buf := []byte("あいうえお")
    runes := []rune("あいうえお")

    println(5 == utf8.RuneCountInString(str))
    println(5 == utf8.RuneCount(buf))
    println(5 == len(runes))
}

1文字ずつアクセスする

package main

func main() {
    str := "あいうえお"

    for index, runeValue := range str {
        println("位置:", index, "文字:", string([]rune{runeValue}))
    }
}

rune 型のスライスの場合、次のようになる。

package main

import (
    "fmt"
)

func main() {
    runes := []rune("あいうえお")

    for i := range runes {
        fmt.Printf("%c\n", runes[i])
    }
}

utf8.DecodeRuneInString を使う場合、次のように書ける。

package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    str := "あいうえお"

    for len(str) > 0 {
        r, size := utf8.DecodeRuneInString(str)
        fmt.Printf("%c\n", r)

        str = str[size:]
    }
}

不正なバイト列

不正なバイト列が含まれるかチェックする

package main

import "unicode/utf8"

func main() {
    str := "あいうえお\x80"
    buf := []byte("あいうえお\x80")

    println(false == utf8.ValidString(str))
    println(false == utf8.Valid(buf))
}

不正なバイト列を代替文字の U+FFFD に置き換える

package main

func main() {
    str := "あいうえお\x80"
    buf := []byte("")

    for _, runeValue := range str {
        buf = append(buf, string(runeValue)...)
    }

    println(string(buf))
}

東アジアの文字幅

文字幅の種類を調べる

文字幅の種類をあらわす定数として EastAsianAmbiguousEastAsianWideEastAsianNarrowEastAsianFullwidthEastAsianHalfwidth が定義される。

package main

import (
    "golang.org/x/text/width"
)

func main() {
    s := "あいうえお"
    p, _ := width.LookupString(s)

    println(width.EastAsianWide == p.Kind())
}

全角文字と半角文字の相互変換

package main

import (
    "golang.org/x/text/width"
    "fmt"
)

func main() {
    s := "ハンカクカタカナ"
    w := width.Widen.String(s)
    fmt.Printf("%U: %s\n", []rune(s), s)
    fmt.Printf("%U: %s\n", []rune(w), w)
}
package main

import (
    "golang.org/x/text/width"
    "fmt"
)

func main() {
    s := "ゼンカクカタカナ"
    w := width.Narrow.String(s)
    fmt.Printf("%U: %s\n", []rune(s), s)
    fmt.Printf("%U: %s\n", []rune(w), w)
}

Unicode 照合

package main

import (
    "golang.org/x/text/collate"
    "golang.org/x/text/language"
)

func main() {
    c := collate.New(language.Japanese)

    println(0 == c.CompareString("a", "a"))
    println(-1 == c.CompareString("a", "A"))
    println(1 == c.CompareString("A", "a"))

    println(0 == c.CompareString("が", "か\u3099"))
}

Unicode 正規化

package main

import "golang.org/x/text/unicode/norm"

func main() {
    buf := norm.NFD.Bytes([]byte("がぎぐげご"))
    str := norm.NFD.String("がぎぐげご")
    expected := "か\u3099\u3099\u3099\u3099\u3099"

    println(expected == string(buf))
    println(expected == str)
}

U+FDFA に NFKD を適用すると18文字になる。Unicode 正規化を適用して長くなる文字の例は 「What are the maximum expansion factors for the different normalization forms?」(Unicode.org)の記事を参照。

package main

import (
    "golang.org/x/text/unicode/norm"
    "unicode/utf8"
)

func main() {
    str := norm.NFKD.String("\uFDFA")

    println(18 == utf8.RuneCountInString(str))
}

基底文字の後に続く装飾記号の個数が31以上である場合、正規化は適用されない。

package main

import (
    "strings"
    "golang.org/x/text/unicode/norm"
    "unicode/utf8"
)

func main() {
    str := "か" + strings.Repeat("\u3099", 30)
    ret := norm.NFC.String(str)

    println(30 == utf8.RuneCountInString(ret))

    str2 := "か" + strings.Repeat("\u3099", 32)
    ret2 := norm.NFC.String(str2)

    println(33 == utf8.RuneCountInString(ret2))
}
80
54
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
80
54