Help us understand the problem. What is going on with this article?

Goで文字列中に4バイト文字があるか探したい

More than 1 year has passed since last update.

MySQLの文字コードがutf8だと4バイト文字が登録できない。
文字コードをutf8mb4に変更すれば良いという話は置いておいて、
文字列中に4バイトの文字があるかをGoで判定したい。

今回はとにかく4バイト文字があったら困るだけなので
3バイトの絵文字はOKなゆるゆるルール。

コード

main.go
package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    check("ほげほげ")
    check("hogehoge")
    check("こんにちは😀") // ニコちゃんは4バイトの絵文字
    check("グーパンチ✊") // グーパンは3バイトの絵文字
}

func check(str string) {
    flg := false
    for _, r := range str {
        if utf8.RuneLen(r) > 3 {
            flg = true
            break
        }
    }
    if flg {
        fmt.Println("4バイト文字がいる")
    } else {
        fmt.Println("4バイト文字がいない")
    }
}

実行結果

$ go run main.go 
4バイト文字がいない
4バイト文字がいない
4バイト文字がいる
4バイト文字がいない

速度を確かめてみる

golangは標準でBenchmark取れるので使ってみます!!
https://golang.org/pkg/testing/

テスト対象のコードを用意

絵文字(4byte文字)があるかをtrue/falseで返すだけの処理を作成。

テストのパターンは以下

  • 一文字ずつループしてバイト数をチェック
  • 正規表現で😀にだけマッチするかチェック(簡単な正規表現)
  • 正規表現で😀から🙏にマッチするかチェック(ちょっと長い正規表現)

😀のUnicodeはU+1F600、🙏のUnicodeはU+1F64Fだそうな。
http://0g0.org/category/1F600-1F64F/1/

sample.go
package sample

import (
    "regexp"
    "unicode/utf8"
)

// Rune 一文字ずつループしてバイト数をチェック
func Rune(str string) bool {
    for _, r := range str {
        if utf8.RuneLen(r) > 3 {
            return true
        }
    }
    return false
}

// Regexp1 正規表現で😀にだけマッチするかチェック(簡単な正規表現)
func Regexp1(str string) bool {
    reg := regexp.MustCompile(`\x{1F600}`)
    return reg.MatchString(str)
}

// Regexp2 正規表現で😀から🙏にマッチするかチェック(ちょっと長い正規表現)
func Regexp2(str string) bool {
    reg := regexp.MustCompile(`[\x{1F600}-\x{1F64F}]`)
    return reg.MatchString(str)
}

テストコードを用意

テスト用のテキストはWikipediaさんからGo言語の説明文持ってきて一番最後でニッコリする😀
文字数は420文字くらいです。

sample_test.go
package sample

import (
    "testing"
)

var (
    str = `Goはプログラミング言語のひとつ。Googleによって開発されており[5]、設計にロブ・パイク、ケン・トンプソンらが関わっている。
主な特徴として、軽量スレッディングのための機能、Pythonのような動的型付け言語のようなプログラミングの容易性、などがある。Go処理系としてはコンパイラのみが開発されている。マスコット・キャラクターはGopher(ホリネズミ)。
発表当初はLinuxとMac OS Xのみしかサポートしていなかったが[6]、2012年3月にリリースされたバージョン1.0からはWindowsもサポートされている[7]。2014年12月にリリースされたバージョン1.4からAndroidをサポートし[8]、2015年8月19日にリリースされたバージョン1.5からiOSをサポートしている[9]。また、2011年5月10日に公開された Google App Engine 1.5.0 でも、Go言語がサポートされている[1]😀`
)

func BenchmarkRune(b *testing.B) {
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        Rune(str)
    }
}

func BenchmarkRegexp1(b *testing.B) {
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        Regexp1(str)
    }
}

func BenchmarkRegexp2(b *testing.B) {
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        Regexp2(str)
    }
}

実行結果

Goのバージョンは1.9.3です。

一文字ずつ回しているのが一番は実行速度が早いですねー

$ go version
go version go1.9.3 darwin/amd64

$ go test -bench . -benchmem 
goos: darwin
goarch: amd64
pkg: github.com/masakurapa/sample/sample
BenchmarkRune-4           500000          2770 ns/op           0 B/op          0 allocs/op
BenchmarkRegexp1-4        200000          6509 ns/op       40304 B/op         22 allocs/op
BenchmarkRegexp2-4        100000         22016 ns/op       40176 B/op         19 allocs/op
PASS
ok      github.com/masakurapa/sample/sample 5.235s

テスト その2

ただ先程作ったテストだと毎回regexp.MustCompile()が走っています。
そりゃ遅いだろって思うのでテストコードをちょっと直してみる。

予めCompileしておく版

sample.go
package sample

import (
    "regexp"
    "unicode/utf8"
)

var (
    reg1 = regexp.MustCompile(`\x{1F600}`)
    reg2 = regexp.MustCompile(`[\x{1F600}-\x{1F64F}]`)
)

// Rune 一文字ずつ探す
func Rune(str string) bool {
    for _, r := range str {
        if utf8.RuneLen(r) > 3 {
            return true
        }
    }
    return false
}

// Regexp1 正規表現で探す(スマイル一文字だけ)
func Regexp1(str string) bool {
    return reg1.MatchString(str)
}

// Regexp2 正規表現で探す(複数の絵文字にマッチ)
func Regexp2(str string) bool {
    return reg2.MatchString(str)
}

実行結果

単純に😀にマッチする処理は早くなっていますが
😀〜🙏にマッチする方はやはり微妙。

実際に使うときは😀だけ探すなんてことはないし
もっとたくさんの絵文字を探すとなると、やはり正規表現はより複雑になってしまうので
一文字ずつ見るのがいいと思います。

$ go test -bench . -benchmem 
goos: darwin
goarch: amd64
pkg: github.com/masakurapa/sample/sample
BenchmarkRune-4           500000          2853 ns/op           0 B/op          0 allocs/op
BenchmarkRegexp1-4      10000000           152 ns/op           0 B/op          0 allocs/op
BenchmarkRegexp2-4        100000         16971 ns/op           0 B/op          0 allocs/op
PASS
ok      github.com/masakurapa/sample/sample 5.017s

その他のパターン

テスト用の文字列を適当に直して数字見てみただけ
結果載せるだけ

一文字目に4バイト文字(絵文字)

$ go test -bench . -benchmem 
goos: darwin
goarch: amd64
pkg: github.com/masakurapa/sample/sample
BenchmarkRune-4         100000000           10.1 ns/op         0 B/op          0 allocs/op
BenchmarkRegexp1-4      10000000           117 ns/op           0 B/op          0 allocs/op
BenchmarkRegexp2-4      10000000           113 ns/op           0 B/op          0 allocs/op
PASS
ok      github.com/masakurapa/sample/sample 3.585s

真ん中らへんに4バイト文字(絵文字)

$ go test -bench . -benchmem 
goos: darwin
goarch: amd64
pkg: github.com/masakurapa/sample/sample
BenchmarkRune-4          1000000          1277 ns/op           0 B/op          0 allocs/op
BenchmarkRegexp1-4      10000000           139 ns/op           0 B/op          0 allocs/op
BenchmarkRegexp2-4        200000          7196 ns/op           0 B/op          0 allocs/op
PASS
ok      github.com/masakurapa/sample/sample 4.354s

4バイト文字(絵文字)がない

$ go test -bench . -benchmem 
goos: darwin
goarch: amd64
pkg: github.com/masakurapa/sample/sample
BenchmarkRune-4           500000          3007 ns/op           0 B/op          0 allocs/op
BenchmarkRegexp1-4      20000000           109 ns/op           0 B/op          0 allocs/op
BenchmarkRegexp2-4        100000         16074 ns/op           0 B/op          0 allocs/op
PASS
ok      github.com/masakurapa/sample/sample 5.619s
masakurapa
自分の作業メモ的なことを書く感じ。 PHP/Goで開発をしています。 家ではAWS/Svelte3/Goを触りながら色々作って遊んでます。
kaonavi
クラウド人材管理ツール「カオナビ」の製造・販売・サポートを行い、企業の人材管理にイノベーションを起こすことを目的としている会社
https://www.kaonavi.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away