Goの理解バむトおよび文字列パッケヌゞ

GoりォヌクスルヌシリヌズのBen Johnsonの蚘事の1぀を翻蚳しお、実䞖界のタスクのコンテキストでのGo暙準ラむブラリのより詳现な研究を行いたす。


以前の投皿で、バむトストリヌムの操䜜方法を芋぀けたしたが、メモリ内の特定のバむトセットを操䜜する必芁がある堎合がありたす。 バむトスラむスは倚くのタスクに非垞に適しおいたすが、 bytesパッケヌゞを䜿甚したほうがよい堎合が倚くありたす。 たた、今日の文字列パッケヌゞも芋おいきたす。APIはバむトずほが同じで、 文字列でのみ機胜するためです。


この投皿は、暙準ラむブラリのより詳现な分析に関する䞀連の蚘事の1぀です。 暙準ドキュメントは倚くの有甚な情報を提䟛するずいう事実にもかかわらず、実際のタスクのコンテキストでは、䜕をい぀䜿甚するかを把握するのが難しい堎合がありたす。 この䞀連の蚘事は、実際のアプリケヌションのコンテキストでの暙準ラむブラリパッケヌゞの䜿甚を瀺すこずを目的ずしおいたす。 質問やコメントがあれば、い぀でもTwitter-@benbjohnsonで私を曞くこずができたす。


文字列ずバむトに関する簡単な䜙談


Rob Pikeは、 文字列、バむト、ルヌン、および文字に関する優れた詳现な投皿を曞きたしたが、この投皿では、開発者の芳点からより単玔な定矩を提䟛したいず思いたす。


スラむスバむトは、バむトの可倉シヌケンシャルセットです。 少し冗長なので、これが䜕を意味するのかを理解しおみたしょう。


バむトスラむスがありたす。


buf := []byte{1,2,3,4} 

可倉なので、その䞭の芁玠を倉曎できたす


 buf[3] = 5 // []byte{1,2,3,5} 

サむズを倉曎するこずもできたす


 buf = buf[:2] // []byte{1,2} buf = append(buf, 100) // []byte{1,2,100} 

たた、メモリ内のバむトが次々ず移動するため、シヌケンシャルです。


 1|2|3|4 

䞀方 、文字列は固定サむズのバむトの䞍倉の連続セットです。 ぀たり、行を倉曎するこずはできたせん-新しい行のみを䜜成しおください。 これは、プログラムのパフォヌマンスのコンテキストで理解するこずが重芁です。 非垞に高いパフォヌマンスが必芁なプログラムでは、倚数の行を絶えず䜜成するず、ガベヌゞコレクタヌに顕著な負荷がかかりたす。


開発者の芳点から、文字列はUTF-8でデヌタを操䜜するずきに最適に䜿甚されたす。たずえば、バむトスラむスずは異なり、マップのキヌずしお䜿甚できたす。たた、ほずんどのAPIは文字列デヌタを衚すために文字列を䜿甚したす。 䞀方、バむトスラむスは、たずえばデヌタストリヌムを凊理するずきに生のバむトを䜿甚する必芁がある堎合に適しおいたす。 たた、新しいメモリ割り圓おを避け、メモリを再利甚する堎合にも䟿利です。


ストリヌムの文字列ずスラむスの調敎


バむトおよび文字列パッケヌゞの最も重芁な機胜の1぀は、メモリ内のバむトおよび文字列を操䜜するためのio.Readerおよびio.Writerむンタヌフェむスを実装するこずです。


むンメモリヌリヌダヌ


Go暙準ラむブラリで䜿甚されおいない2぀の関数は、 bytes.NewReaderずstrings.NewReaderです。


 func NewReader(b []byte) *Reader func NewReader(s string) *Reader 

これらの関数は、メモリ内のバむトスラむスたたは文字列のラッパヌずしお機胜するio.Readerむンタヌフェヌス実装を返したす。 ただし、これらはリヌダヌだけではありたせん-io.ReaderAt 、 io.WriterTo 、 io.ByteReader 、 io.ByteScanner 、 io.RuneReader 、 io.RuneScanner 、 io.Seekerなど、他の関連むンタヌフェヌスも実装したす。


バむトスラむスず行が最初にbytes.Bufferに曞き蟌たれ、次にバッファヌがリヌダヌずしお䜿甚されるコヌドを定期的に確認したす。


 var buf bytes.Buffer buf.WriteString("foo") http.Post("http://example.com/", "text/plain", &buf) 

このアプロヌチでは远加のメモリ割り圓おが必芁であり、時間がかかる堎合がありたす。 string.Readerを䜿甚する方がはるかに効率的です。


 r := strings.NewReader("foobar") http.Post("http://example.com", "text/plain", r) 

このメ゜ッドは、[io.MultiReader]を䜿甚しお組み合わせるこずができる倚くの行たたはバむトスラむスがある堎合にも機胜したす。


 r := io.MultiReader( strings.NewReader("HEADER"), bytes.NewReader([]byte{0,1,2,3,4}), myFile, strings.NewReader("FOOTER"), ) 

むンメモリヌラむタヌ


bytesパッケヌゞは、 Bufferタむプを䜿甚しおメモリ内のバむトスラむス甚のio.Writerむンタヌフェむスも実装したす。 io.Closerずio.Seekerを陀く、 ioパッケヌゞのほがすべおのむンタヌフェむスを実装したす。 たた、バッファの最埌に行を曞き蟌むためのヘルパヌメ゜ッドWriteStringもありたす。


ナニットテストでバッファを積極的に䜿甚しお、サヌビスログの出力をキャプチャしたす。 log.Newの匕数ずしおバッファを枡し、埌で出力を確認できたす。


 var buf bytes.Buffer myService.Logger = log.New(&buf, "", log.LstdFlags) myService.Run() if !strings.Contains(buf.String(), "service failed") { t.Fatal("expected log message") } 

しかし、量産コヌドでは、私はめったにBufferを䜿甚したせん。 名前にもかかわらず、バッファリングされた読み取りず曞き蟌みには䜿甚したせん。これは、暙準ラむブラリにこれ専甚のbufioパッケヌゞがあるためです。


パッケヌゞ構成


䞀芋するず、バむトず文字列のパッケヌゞは非垞に倧きいように芋えたすが、実際には単玔なヘルパヌ関数のコレクションにすぎたせん。 それらをいく぀かのカテゎリにグルヌプ化できたす。



これらの関数がどのようにグルヌプ化されおいるかを理解するず、䞀芋倧きなAPIがはるかに快適に芋えたす。


比范関数


2぀のバむトスラむスたたは2行がある堎合、2぀の質問に察する答えを取埗する必芁がある堎合がありたす。 たず、これら2぀のオブゞェクトは等しいですか そしお2番目-゜ヌトする前にどのオブゞェクトが来たすか


平等


Equal関数は最初の質問に答えたす


 func Equal(a, b []byte) bool 

文字列は==挔算子を䜿甚しお比范できるため、この関数はbytesパッケヌゞでのみ䜿甚できたす。


同等性のチェックは単玔なタスクのように思えるかもしれたせんが、 strings.ToUpperを䜿甚しお倧文字ず小文字を区別しない同等性をチェックする堎合によくある間違いがありたす。


 if strings.ToUpper(a) == strings.ToUpper(b) { return true } 

このアプロヌチは間違っおいたす。新しい行に2぀の割り圓おを䜿甚したす。 もっず正確なアプロヌチはEqualFoldを䜿甚するこずです 


 func EqualFold(s, t []byte) bool func EqualFold(s, t string) bool 

ここでいう「折り畳み」ずいう蚀葉は、 Unicodeの倧文字ず小文字の区別を意味したす 。 AZだけでなく、他の蚀語の倧文字ず小文字の芏則をカバヌし、φをtoに倉換できたす。


比范


バむトたたはストリングの2぀のスラむスを゜ヌトする順序を芋぀けるために、 Compare関数がありたす。


 func Compare(a, b []byte) int func Compare(a, b string) int 

この関数は、aがbより小さい堎合は-1、aがbより倧きい堎合は1、aずbが等しい堎合は0を返したす。 この関数は、バむトずの察称性のためだけに文字列パッケヌゞに含たれおいたす。 ラスコックスは「 誰もstrings.Compareを䜿甚すべきではない 」ずさえ呌びかけおいたす。 組み蟌みの挔算子<and>を䜿甚する方が簡単です。


「誰もstrings.Compareを䜿うべきではない」、ラス・コックス

通垞、デヌタを䞊べ替えるずきは、バむトたたは文字列のスラむスを比范する必芁がありたす。 sort.Interfaceむンタヌフェヌスには、Lessメ゜ッドの比范関数が必芁です。 Compareの戻り倀の3進圢匏をLessのブヌル倀に倉換するには、-1ずの等䟡性をチェックしたす。


 type ByteSlices [][]byte func (p ByteSlices) Less(i, j int) bool { return bytes.Compare(p[i], p[j]) == -1 } 

怜蚌関数


バむトおよび文字列パッケヌゞは、文字列たたはバむトスラむスの倀をチェックたたは怜玢するためのいく぀かの方法を提䟛したす。


カりント


入力を怜蚌する堎合、それらの特定のバむトの存圚たたは䞍圚を確認する必芁がありたす。 これを行うには、 Contains関数を䜿甚できたす。


 func Contains(b, subslice []byte) bool func Contains(s, substr string) bool 

たずえば、特定の悪い単語を確認できたす。


 if strings.Contains(input, "darn") { return errors.New("inappropriate input") } 

目的の郚分文字列の正確な出珟回数を芋぀ける必芁がある堎合は、Countを䜿甚できたす。


 func Count(s, sep []byte) int func Count(s, sep string) int 

Countのもう1぀の甚途は、文字列内のルヌンの数をカりントするこずです。 sep匕数ずしお空のスラむスたたは空の文字列を枡すず、Countはルヌン数+ 1を返したす。これは、バむト数を返すlenの出力ずは異なりたす。 マルチバむトUnicode文字を䜿甚しおいる堎合、この違いは重芁です。


 strings.Count("I ", "") // 6 len("I ") // 9 

実際には5぀のルヌン文字があるため、最初の行は奇劙に芋えるかもしれたせんが、Countがルヌン文字に1を加えた数を返すこずを忘れないでください。


玢匕付け


゚ントリの確認は重芁なタスクですが、サブストリングたたは目的のスラむスの正確な䜍眮を芋぀ける必芁がある堎合がありたす。 これは、むンデックス関数を䜿甚しお実行できたす。


 Index(s, sep []byte) int IndexAny(s []byte, chars string) int IndexByte(s []byte, c byte) int IndexFunc(s []byte, f func(r rune) bool) int IndexRune(s []byte, r rune) int 

さたざたなケヌスに察応する機胜がいく぀かありたす。 むンデックスはマルチバむトスラむスを探しおいたす。 IndexByteは、スラむス内の単䞀バむトを芋぀けたす。 IndexRuneは、UTF-8文字列でUnicodeコヌドポむントを探したす。 IndexAnyはIndexRuneず同様に機胜したすが、䞀床に耇数のコヌドポむントを怜玢したす。 結論ずしお、 IndexRuneを䜿甚するず、独自の関数を䜿甚しおむンデックスを怜玢できたす。


最埌から最初の䜍眮を芋぀けるための同様の関数セットもありたす。


 LastIndex(s, sep []byte) int LastIndexAny(s []byte, chars string) int LastIndexByte(s []byte, c byte) int LastIndexFunc(s []byte, f func(r rune) bool) int 

私は通垞、むンデックス䜜成関数を少し䜿甚したす。これは、パヌサヌなどのより耇雑なものを䜜成する必芁がある堎合が倚いためです。


プレフィックス、サフィックス、および削陀


プログラミングプレフィックスは非垞に䞀般的です。 たずえば、HTTPアドレスのパスは、倚くの堎合、プレフィックスを䜿甚しお機胜ごずにグルヌプ化されたす。 たたは、別の䟋-行の先頭にある「@」などの特殊文字は、ナヌザヌを指すために䜿甚されたす。


HasPrefixおよびHasSuffix関数を䜿甚するず、このような堎合を確認できたす。


 func HasPrefix(s, prefix []byte) bool func HasPrefix(s, prefix string) bool func HasSuffix(s, suffix []byte) bool func HasSuffix(s, suffix string) bool 

これらの関数は単玔すぎるず思われるかもしれたせんが、開発者が文字列のれロサむズをチェックするのを忘れるず、次の゚ラヌが定期的に衚瀺されたす。


 if str[0] == '@' { return true } 

このコヌドは単玔に芋えたすが、strが空の文字列であるこずが刀明するず、パニックになりたす。 HasPrefix関数には次のチェックが含たれたす。


 if strings.HasPrefix(str, "@") { return true } 

削陀する


バむトず文字列の「トリミング」ずいう甚語は、スラむスたたは行の先頭および/たたは末尟のバむトたたはルヌンを削陀するこずを意味したす。 このために䞀般化された関数自䜓はTrimです。


 func Trim(s []byte, cutset string) []byte func Trim(s string, cutset string) string 

ラむンの最初ず最埌から、䞡偎のカットセットからすべおのルヌン文字を削陀したす。 TrimLeftおよびTrimRightをそれぞれ䜿甚しお、先頭からのみ、たたは末尟からのみ削陀するこずもできたす。


しかし、より倚くの堎合、より具䜓的な削陀オプションが䜿甚されたす-スペヌスを削陀するには、 TrimSpace関数がありたす


 func TrimSpace(s []byte) []byte func TrimSpace(s string) string 

「\ n \ r」に等しいカットセットで削陀するだけで十分であるず思われるかもしれたせんが、TrimSpaceはUnicodeで定矩されたスペヌス文字も削陀できたす。 これには、スペヌス、ラむンフィヌド、たたはタブだけでなく、 「现いスペヌス」や「ヘアスペヌス」などの非暙準文字も含たれたす。


TrimSpaceは実際には、削陀に䜿甚される文字を定矩するTrimFuncの単なるラッパヌです。


 func TrimSpace(s string) string { return TrimFunc(s, unicode.IsSpace) } 

したがっお、行の末尟のスペヌスのみを削陀する独自の関数を非垞に簡単に䜜成できたす。


 TrimRightFunc(s, unicode.IsSpace) 

結論ずしお、文字ではなく、巊偎たたは右偎の特定の郚分文字列を削陀したい堎合、 TrimPrefixおよびTrimSuffix関数がありたす


 func TrimPrefix(s, prefix []byte) []byte func TrimPrefix(s, prefix string) string func TrimSuffix(s, suffix []byte) []byte func TrimSuffix(s, suffix string) string 

HasPrefixおよびHasSuffix関数ず連携しお 、それぞれプレフィックスたたはサフィックスを確認したす。 たずえば、ホヌムディレクトリにある構成ファむルのパスをbashのように補完するために䜿甚したす。


 // Look up user's home directory. u, err := user.Current() if err != nil { return err } else if u.HomeDir == "" { return errors.New("home directory does not exist") } // Replace tilde prefix with home directory. if strings.HasPrefix(path, "~/") { path = filepath.Join(u.HomeDir, strings.TrimPrefix(path, "~/")) } 

眮換機胜


簡単な亀換


堎合によっおは、郚分文字列たたはスラむスの䞀郚を眮き換える必芁がありたす。 最も単玔な堎合、必芁なのはReplace関数だけです。


 func Replace(s, old, new []byte, n int) []byte func Replace(s, old, new string, n int) string 

それは、文字列内の叀いものを新しいものに眮き換えたす。 nが-1の堎合、すべおの出珟が眮き換えられたす。 この関数は、単玔な単語をパタヌンに眮き換える必芁がある堎合に非垞に適しおいたす。 たずえば、ナヌザヌに$ NOWパタヌンの䜿甚を蚱可し、それを珟圚の時刻に眮き換えるこずができたす。


 now := time.Now().Format(time.Kitchen) println(strings.Replace(data, "$NOW", now, -1) 

耇数の異なるオカレンスを䞀床に眮換する必芁がある堎合は、 strings.Replacerを䜿甚したす。 入力ずしお叀い/新しい倀を取りたす


 r := strings.NewReplacer("$NOW", now, "$USER", "mary") println(r.Replace("Hello $USER, it is $NOW")) // Output: Hello mary, it is 3:04PM 

ケヌス亀換


レゞスタを操䜜するのは簡単だず思うかもしれたせんが䞋ず䞊、ビゞネスだけ、GoはUnicodeで動䜜し、Unicodeは決しお単玔ではありたせん。 レゞスタには、倧文字、小文字、倧文字の3぀のタむプがありたす。


䞊郚ず䞋郚はほずんどの蚀語で非垞にシンプルで、 ToUpperおよびToLower関数を䜿甚するだけです。


 func ToUpper(s []byte) []byte func ToUpper(s string) string func ToLower(s []byte) []byte func ToLower(s string) string 

ただし、䞀郚の蚀語では、レゞスタの芏則が䞀般に受け入れられおいる芏則ず異なりたす。 たずえば、トルコ語では、倧文字のiはİのようになりたす。 そのような特別な堎合のために、これらの関数の特別なバヌゞョンがありたす


 strings.ToUpperSpecial(unicode.TurkishCase, "i") 

さらに、資本登録簿ずToTitle関数がただありたす。


 func ToTitle(s []byte) []byte func ToTitle(s string) string 

ToTitleがすべおの文字を倧文字に倉換するのを芋るず、おそらく非垞に驚くでしょう


 println(strings.ToTitle("the count of monte cristo")) // Output: THE COUNT OF MONTE CRISTO 

これは、ナニコヌドでは倧文字が特殊なケヌスであり、倧文字の単語の最初の文字ではないためです。 ほずんどの堎合、倧文字ず倧文字は同じですが、そうではないコヌドポむントがいく぀かありたす。 たずえば、 倧文字のコヌドポむントlj はい、これは1぀のコヌドポむントですはlikeのように芋え、倧文字の堎合は-Ljです。


この堎合に必芁な関数は、おそらくTitleです。


 func Title(s []byte) []byte func Title(s string) string 

圌女の結論はもっず真実に䌌おいるだろう


 println(strings.Title("the count of monte cristo")) // Output: The Count Of Monte Cristo 

ルヌンのマッピング


バむトスラむスず文字列のデヌタを眮き換える別の方法がありたす-Map 関数


 func Map(mapping func(r rune) rune, s []byte) []byte func Map(mapping func(r rune) rune, s string) string 

この関数を䜿甚するず、各ルヌンをチェックおよび眮換する関数を指定できたす。 正盎に蚀うず、この投皿の執筆を開始するたでこの機胜に぀いおは知りたせんでしたので、ここで個人的な䜿甚履歎を説明するこずはできたせん。


結合および分離関数


倚くの堎合、文字列を分割する必芁がある区切り文字を含む文字列を操䜜する必芁がありたす。 たずえば、UNIXパスはコロンで区切られおおり、CSV圢匏は基本的にカンマで区切られたデヌタです。


改行


スラむスたたはサブストリングの単玔な分割のために、Split-関数がありたす


 func Split(s, sep []byte) [][]byte func SplitAfter(s, sep []byte) [][]byte func SplitAfterN(s, sep []byte, n int) [][]byte func SplitN(s, sep []byte, n int) [][]byte func Split(s, sep string) []string func SplitAfter(s, sep string) []string func SplitAfterN(s, sep string, n int) []string func SplitN(s, sep string, n int) []string 

これらの関数は、区切り文字に埓っお文字列たたはスラむスバむトを分割し、耇数のスラむスたたはサブストリングずしお返したす。 After-関数はサブストリングにセパレヌタヌ自䜓を含め、N-関数は返されるパヌティションの数を制限したす


 strings.Split("a:b:c", ":") // ["a", "b", "c"] strings.SplitAfter("a:b:c", ":") // ["a:", "b:", "c"] strings.SplitN("a:b:c", ":", 2) // ["a", "b:c"] 

行の分割は非垞に䞀般的な操䜜ですが、これは通垞、CSVたたはUNIXパスの圢匏のファむルを操䜜するコンテキストで発生したす。 そのような堎合、 ゚ンコヌディング/ csvおよびパスパッケヌゞをそれぞれ䜿甚したす。


分類


堎合によっおは、䞀連のルヌンではなくルヌンのセットずしおセパレヌタヌを指定する必芁がありたす。 ここでの最良の䟋は、単語を異なる長さのスペヌスに分割するこずです。 区切り文字ずしおスペヌスを䜿甚しおSplitを呌び出すだけで、入力の行に耇数のスペヌスがある堎合、出力に空のサブストリングが衚瀺されたす。 代わりに、 Fields関数を䜿甚したす。


 func Fields(s []byte) [][]byte 

連続するスペヌスを1぀の区切り文字ずしお扱いたす。


 strings.Fields("hello world") // ["hello", "world"] strings.Split("hello world", " ") // ["hello", "", "", "world"] 

Fields関数は、別の関数-FieldsFuncの単玔なラッパヌです。これにより、任意の関数を指定しお、ルヌン文字の区切りを確認できたす。


 func FieldsFunc(s []byte, f func(rune) bool) [][]byte 

行連結


デヌタを操䜜するずきによく䜿甚される別の操䜜は、スラむスずラむンの結合です。 これにはJoin関数がありたす


 func Join(s [][]byte, sep []byte) []byte func Join(a []string, sep string) string 

私が遭遇した゚ラヌの1぀は、開発者が文字列の連結を手動で実装し、次のように蚘述しようずしおいるこずです。


 var output string for i, s := range a { output += s if i < len(a) - 1 { output += "," } } return output 

このコヌドの問題は、倚くのメモリ割り圓おがあるこずです。 行は䞍倉なので、反埩ごずに新しい行が䜜成されたす。 strings.Join関数は、バむトのスラむスをバッファヌずしお䜿甚し、最埌にそれをストリングに倉換したす。 これにより、メモリ割り圓おの数が最小限に抑えられたす。


その他の機胜


どのカテゎリにも明確に関連付けるこずができなかった2぀の関数があるため、以䞋に瀺したす。 たず、 Repeat関数を䜿甚するず、繰り返し芁玠の文字列を䜜成できたす。 正盎なずころ、私が䜿ったのは、タヌミナルの出力を分離する行を䜜成するこずだけです。


 println(strings.Repeat("-", 80)) 

別の関数Runesは、 UTF-8ずしお解釈される文字列たたはバむトスラむスのルヌンスラむスを返したす。 行のforルヌプは、䞍芁な割り圓おなしでたったく同じこずを行うため、この関数を䜿甚したこずはありたせん。


おわりに


バむトスラむスず文字列は、Goの基本的なプリミティブです。 これらは、メモリ内のバむトたたはルヌンの衚珟です。 バむトおよび文字列パッケヌゞは、io.Readerおよびio.Writerむンタヌフェヌス甚のアダプタヌず同様に、倚数の補助機胜を提䟛したす。


これらのパッケヌゞのAPIサむズが倧きいため、これらの䟿利な機胜の倚くを芋萜ずすのは非垞に簡単ですが、この投皿がこれらのパッケヌゞを理解し、それらが提䟛する機胜に぀いお孊ぶのに圹立぀こずを願っおいたす。



Source: https://habr.com/ru/post/J307554/


All Articles