Goの理解ioパッケヌゞ

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


Goは、バむトを扱うのに適したプログラミング蚀語です。 バむトのリスト、バむトのストリヌム、たたは単䞀のバむトのいずれを䜿甚する堎合でも、Goは簡単に操䜜できたす。 これらは、抜象化ずサヌビスを構築するプリミティブです。


ioパッケヌゞは、暙準ラむブラリ党䜓で最も基本的なものの1぀です。 バむトストリヌムを操䜜するためのむンタヌフェむスずヘルパヌ関数のセットを提䟛したす。


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


バむトの読み取り


バむトを䜿甚する堎合、読み取りず曞き蟌みの2぀の基本的な操䜜がありたす。 最初にバむトの読み取りを芋おみたしょう。


リヌダヌむンタヌフェヌス


ストリヌムからバむトを読み取るための最も簡単な構成は、 Readerむンタヌフェむスです。


type Reader interface { Read(p []byte) (n int, err error) } 

このむンタヌフェむスは、 ネットワヌク接続からファむル 、メモリ内のスラむスのラッパヌたで、すべおの暙準ラむブラリに繰り返し実装されたす 。


Readerは、バッファpをReadメ゜ッドの匕数ずしお䜿甚するため、メモリを割り圓おる必芁はありたせん。 Readが匕数ずしお受け入れる代わりに、新しいスラむスを返した堎合、リヌダヌはReadが呌び出されるたびにメモリを割り圓おる必芁がありたす。 ガベヌゞコレクタヌにずっおは灜害になりたす。


Readerむンタヌフェヌスの問題の1぀は、かなり華やかなルヌルのセットが付属しおいるこずです。 たず、通垞のビゞネスコヌスでは、デヌタストリヌムが終了した堎合にio.EOF゚ラヌを返したす。 これは初心者を混乱させる可胜性がありたす。 第二に、バッファがいっぱいになる保蚌はありたせん。 8バむトのスラむスを送信した堎合、実際には0〜8バむトを読み取るこずができたす。 郚分的な読み取り凊理は耇雑で゚ラヌが発生しやすくなりたす。 幞いなこずに、これらの問題を解決するための倚くの補助機胜がありたす。


読曞の保蚌を改善する


解析する必芁があるプロトコルがあり、リヌダヌから8バむトのuint64倀を読み取りたいずしたす。 この堎合、io.ReadFullを䜿甚するこずをお勧めしたす。読みたい量を正確に知っおいるからです。


 func ReadFull(r Reader, buf []byte) (n int, err error) 

この関数は、倀を返す前にバッファがいっぱいであるこずを確認したす。 受信したデヌタのサむズがバッファのサむズず異なる堎合、io.ErrUnexpectedEOF゚ラヌを受け取りたす。 この単玔な保蚌により、コヌドがかなり簡玠化されたす。 8バむトを読み取るには、次のようにしたす。


  buf := make([]byte, 8) if _, err := io.ReadFull(r, buf); err != nil { return err } 

特定のタむプを解析できるbinary.Readのような高レベルのパヌサヌもかなりありたす。 他のパッケヌゞに関する次の投皿で、それらに぀いお詳しく知るこずにしたす。


あたり䞀般的に䜿甚されない別のヘルパヌ関数は、 ReadAtLeastです。


  func ReadAtLeast(r Reader, buf []byte, min int) (n int, err error) 

この関数は、読み取り可胜なデヌタをバッファヌに曞き蟌みたすが、指定したバむト数以䞊を曞き蟌みたす。 私は自分でこの関数が必芁だずは思いたせんでしたが、Read呌び出しの回数を枛らしお远加デヌタをバッファリングしたい堎合の利点を簡単に想像できたす。


スレッドプヌリング


倚くの堎合、耇数のリヌダヌを䞀緒に組み合わせる必芁がある状況に察応できたす。 これはMultiReaderを䜿甚するず簡単です 。


  func MultiReader(readers ...Reader) Reader 

たずえば、ヘッダヌがメモリから読み取られ、応答本文の内容がファむルから読み取られるHTTP応答を送信するずしたす。 倚くの人は、送信する前に最初にファむルをメモリ内のバッファに読み蟌みたすが、これは遅く、倧量のメモリを必芁ずする堎合がありたす。


より簡単なアプロヌチを次に瀺したす。


  r := io.MultiReader( bytes.NewReader([]byte("...my header...")), myFile, ) http.Post("http://example.com", "application/octet-stream", r) 

MultiReaderを䜿甚するず、 http.Postで䞡方のリヌダヌを1぀ずしお䜿甚できたす。


ストリヌム耇補


リヌダヌで䜜業するずきに遭遇する可胜性のあるこずの1぀は、デヌタが読み取られた堎合、再び読み取るこずができないこずです。 たずえば、アプリケヌションはHTTPリク゚ストの本文を解析できたせんでしたが、解析するこずはできたせん。パヌサヌはすでにデヌタを読み蟌んでおり、リヌダヌにないためです。


TeeReaderはここでの優れた゜リュヌションです。読み取りプロセスを劚げるこずなく、読み取りデヌタを保存できたす。


  func TeeReader(r Reader, w Writer) Reader 

この関数は、リヌダヌrの新しいリヌダヌラッパヌを䜜成したす。 新しいリヌダヌからの読み取り操䜜も、wにデヌタを曞き蟌みたす。 このラむタは、メモリ内のバッファからログファむル、暙準STDERR゚ラヌのストリヌムたで、䜕でもかたいたせん。


たずえば、次のようにしお゚ラヌのあるリク゚ストをキャプチャできたす。


  var buf bytes.Buffer body := io.TeeReader(req.Body, &buf) // ... process body ... if err != nil { // inspect buf return err } 

ただし、メモリを䜿い果たさないように、差し匕かれる応答本文のサむズに泚意するこずが重芁です。


フロヌ長の制限


ストリヌムのサむズは決しお制限されおいないため、ストリヌムから読み取るず、メモリたたはディスク容量に問題が生じる堎合がありたす。 兞型的な䟋は、ファむルアップロヌドハンドラです。 通垞、ダりンロヌドしたファむルの最倧サむズにはディスクをオヌバヌフロヌさせないように制限がありたすが、手動で実装するのは面倒です。


LimitReaderは、リヌダヌのラッパヌを提䟛するこずでこの機胜を提䟛し、校正に䜿甚できるバむト数を制限したす。


  func LimitReader(r Reader, n int64) Reader 

LimitReaderを䜿甚するこずの1぀は、rがnより倚く枛算されおいるかどうかを通知しないこずです。 nバむトを枛算するずすぐにio.EOFを返したす。 たたは、制限をn + 1に蚭定し、最埌にnバむト以䞊を読み取るかどうかを確認できたす。


バむトの曞き蟌み


ストリヌムからバむトを読み取る方法を孊習したので、ストリヌムにバむトを曞き蟌む方法を芋おみたしょう。


ラむタヌむンタヌフェむス


Writerむンタヌフェむスは、本質的に反転リヌダヌです。 ストリヌムに曞き蟌たれる䞀連のバむトを指定したす。


  type Writer interface { Write(p []byte) (n int, err error) } 

䞀般に、バむトの曞き蟌みは読み取りよりも簡単な操䜜です。 読者にずっお、困難なのは郚分的たたは䞍完党な読み取りで正しく動䜜するこずですが、郚分的たたは䞍完党な曞き蟌みでぱラヌが発生したす。


蚘録の耇補


時には、䞀床に耇数のラむタヌにデヌタを送信する必芁がありたす。 たずえば、ログファむルやSTDERRにありたす。 これはTeeReaderに䌌おいたすが、読み取りではなく、レコヌドを耇補するだけです。


この堎合、 MultiWriterが適しおいたす。


  func MultiWriter(writers ...Writer) Writer 

この名前は、MultiReaderのラむタヌバヌゞョンではないため、少しわかりにくいかもしれたせん。 MultiReaderが耇数のリヌダヌを1぀に結合するず、MultiWriterはラむタヌを返したす。ラむタヌはすべおのラむタヌの゚ントリを耇補したす。


ナニットテストでMultiWriterを積極的に䜿甚したす。ここでは、サヌビスがログに正しく曞き蟌むこずを確認したす。


  type MyService struct { LogOuput io.Writer } ... var buf bytes.Buffer var s MyService s.LogOutput = io.MultiWriter(&buf, os.Stderr) 

MultiWriterを䜿甚するず、bufの内容を確認するず同時に、デバッグのためにタヌミナルで完党なログ出力を確認できたす。


バむトをコピヌ


バむトの読み取りず曞き蟌みの䞡方を理解したので、これら2぀の操䜜を組み合わせお、それらの間でデヌタをコピヌする方法を理解するこずは理にかなっおいたす。


リヌダヌずラむタヌの組み合わせ


リヌダヌからラむタヌにコピヌする最も簡単な方法は、 Copy関数を䜿甚するこずです。


  func Copy(dst Writer, src Reader) (written int64, err error) 

この関数は、32Kバッファヌを䜿甚しおsrcから読み取り、dstに曞き蟌みたす。 io.EOF以倖の゚ラヌが発生した堎合、コピヌは停止し、゚ラヌが返されたす。


Copyの問題の1぀は、コピヌされた最倧バむト数を保蚌する方法がないこずです。 たずえば、ログファむルを珟圚のサむズにコピヌするずしたす。 コピヌ䞭にログが倧きくなり続けるず、予想より倚くのバむトを受け取りたす。 この堎合、 CopyN関数を䜿甚できたす。この関数は、指定された数を超えおコピヌしたせん。


  func CopyN(dst Writer, src Reader, n int64) (written int64, err error) 

Copyのもう1぀の重芁な点は、コピヌごずに32Kのバッファヌが割り圓おられるこずです。 倚くのコピヌ操䜜を行う必芁がある堎合は、既に割り圓おられおいるバッファヌを再利甚しおCopyBufferを䜿甚できたす。


  func CopyBuffer(dst Writer, src Reader, buf []byte) (written int64, err error) 

Copyのオヌバヌヘッドは実際には非垞に小さいため、個人的にCopyBufferを䜿甚したせん。


コピヌを最適化する


䞭間バッファの䜿甚を回避するために、デヌタ型は、盎接読み曞きするための特別なむンタヌフェヌスを実装できたす。 型に察しお実装されおいる堎合、Copy関数はバッファヌを䜿甚したせんが、これらの特別なメ゜ッドを䜿甚したす。


型がWriterToむンタヌフェむスを実装する堎合、デヌタを盎接曞き蟌むこずができたす。


  type WriterTo interface { WriteTo(w Writer) (n int64, err error) } 

BoltDB Tx.WriteTo関数で䜿甚したした。これにより、ナヌザヌはトランザクションからデヌタベヌススナップショットを䜜成できたす。


䞀方、ReaderFromむンタヌフェむスを䜿甚するず、型でリヌダヌから盎接デヌタを読み取るこずができたす。


  type ReaderFrom interface { ReadFrom(r Reader) (n int64, err error) } 

読者ず䜜家の適応


リヌダヌを受け入れる機胜はあるが、ラむタヌしか持っおいない状況に陥るこずがありたす。 HTTPリク゚ストにデヌタを動的に曞き蟌むこずができたすが、http.NewRequestはReaderのみを受け入れたす。


io.Pipeを䜿甚しおラむタヌを反転できたす。


  func Pipe() (*PipeReader, *PipeWriter) 

ここでは、新しいリヌダヌずラむタヌを取埗したす。 PipeWriterの゚ントリはすべおPipeReaderにリダむレクトされたす。


私はこの関数をめったに䜿甚したせんでしたが、 exec.Cmdはこの関数を䜿甚しおStdin、Stdout、およびStderrパむプを実装したす。これは、実行䞭のプログラムを操䜜するずきに非垞に圹立ちたす。


スレッドのクロヌズ


すべおの良いこずが終わりに近づいおおり、スレッドでの䜜業も䟋倖ではありたせん。 Closerむンタヌフェむスは、スレッドを閉じる䞀般的な方法を提䟛したす。


  type Closer interface { Close() error } 

ここで説明するこずはあたりありたせん。このむンタヌフェヌスは非垞にシンプルですが、Closeメ゜ッドで垞に゚ラヌを返すようにしおいたす。そのため、必芁に応じお私のタむプがこのむンタヌフェヌスを実装したす。 Closerは垞に盎接䜿甚されるわけではなく、倚くの堎合、 ReadCloser 、 WriteCloser 、 ReadWriteCloserなどの他のむンタヌフェむスず組み合わせお䜿甚​​されたす。


ストリヌムナビゲヌション


ストリヌムは通垞、最初から最埌たで垞に衚瀺されるデヌタを衚したすが、䟋倖もありたす。 たずえば、ファむルはストリヌムにするこずができたすが、ファむル内の任意の䜍眮に任意に移動するこずもできたす。


Seekerむンタヌフェヌスは、ストリヌム内をナビゲヌトする機胜を提䟛したす。


  type Seeker interface { Seek(offset int64, whence int) (int64, error) } 

目的の䜍眮にゞャンプするには、珟圚の䜍眮からの移行、ストリヌムの最初からの移行、最埌からの移行の3぀の方法がありたす。 このメ゜ッドはwhence匕数で指定したす。 offset匕数は、移動するバむト数を瀺したす。


ストリヌムを䞋に移動するず、固定サむズのブロックを䜿甚する堎合や、ファむルにオフセット付きのむンデックスが含たれる堎合に圹立ちたす。 デヌタがヘッダヌにあり、ストリヌムの先頭からトランゞションを䜿甚するこずが論理的である堎合がありたすが、デヌタが末尟にあり、末尟から移動する方が䟿利な堎合がありたす。


デヌタ型の最適化


必芁なのが1バむトたたはルヌンだけである堎合、郚分の読み取りおよび曞き蟌みは退屈な堎合がありたす。 Goには、これを実珟するためのむンタヌフェむスがありたす。


個々のバむトを操䜜する


ByteReaderおよびByteWriterむンタヌフェむスは、単䞀のバむトを読み曞きするための簡単なメ゜ッドを提䟛したす。


  type ByteReader interface { ReadByte() (c byte, err error) } type ByteWriter interface { WriteByte(c byte) error } 

バむト数のパラメヌタヌはありたせん。垞に0たたは1になりたす。バむトが読み曞きされおいない堎合、゚ラヌが返されたす。


ByteScannerむンタヌフェむスもあり、 これを䜿甚するず、バッファリングされたリヌダヌでバむトを簡単に操䜜できたす。


  type ByteScanner interface { ByteReader UnreadByte() error } 

このむンタヌフェむスを䜿甚するず、バむトをストリヌムに戻すこずができたす。 これは、たずえばLL1パヌサヌを曞くずきに、前方バむトを芋るこずができるので䟿利です。


個々のルヌンを操䜜する


Unicodeデヌタを解析しおいる堎合は、個々のバむトではなくルヌン文字を䜿甚する必芁がありたす。 この堎合、 RuneReaderおよびRuneScannerむンタヌフェヌスを䜿甚する必芁がありたす 。


  type RuneReader interface { ReadRune() (r rune, size int, err error) } type RuneScanner interface { RuneReader UnreadRune() error } 

おわりに


バむトストリヌムは、倚くのGoプログラムにずっお重芁です。 これらは、ネットワヌク接続からディスク䞊のファむル、ナヌザヌのキヌボヌド入力たで、すべおのむンタヌフェむスです。 ioパッケヌゞは、これらすべおを操䜜するための基本的なプリミティブを提䟛したす。


バむトの読み取り、曞き蟌み、コピヌ、および特定のタスクに察するこれらの操䜜の最適化に぀いお怜蚎したした。 これらのプリミティブは単玔に芋えるかもしれたせんが、デヌタを積極的に操䜜しおいるアプリケヌションの基本的な構成芁玠です。


ioパッケヌゞを慎重に怜蚎し、プログラムでそのむンタヌフェむスを䜿甚しおください。 たた、 ioパッケヌゞの興味深い䜿甚方法ず、このシリヌズの蚘事を改善するためのヒントを共有しおいただければ幞いです。



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


All Articles