4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-02-17

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
4
1
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
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?