MySQLの文字コードがutf8だと4バイト文字が登録できない。
文字コードをutf8mb4に変更すれば良いという話は置いておいて、
文字列中に4バイトの文字があるかをGoで判定したい。
今回はとにかく4バイト文字があったら困るだけなので
3バイトの絵文字はOKなゆるゆるルール。
コード
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/
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文字くらいです。
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しておく版
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