Obsah
1. Vstupně-výstupní funkce standardní knihovny programovacího jazyka Go
2. Nejdůležitější rozhraní používaná vstupně-výstupním systémem
4. Vylepšení testu na zdetekovaný konec dat
8. Zápis jednotlivých znaků metodou WriteRune
10. Vícenásobné vrácení bajtu do vstupu
13. Čtení bloku bajtů ze standardního vstupu a z řetězce
14. Spojení více vstupů – multireader
17. Načtení celého řádku ze standardního vstupu
18. Skládání rozhraní souvisejících se vstupně-výstupními operacemi
19. Repositář s demonstračními příklady
1. Vstupně-výstupní funkce standardní knihovny programovacího jazyka Go
Již v předchozích částech seriálu o programovacím jazyku Go jsme se seznámili s některými balíčky patřícími do standardní knihovny tohoto jazyka. Ve skutečnosti je standardní knihovna poměrně rozsáhlá a obsahuje funkce a datové typy používané především pro tvorbu síťových služeb, webových služeb, mikroslužeb, utilit používaných systémovými administrátory, aplikací ovládaných z příkazové řádky (CLI) apod. Naopak ve standardní knihovně nenalezneme například podporu pro tvorbu plnohodnotných desktopových aplikací s grafickým uživatelským rozhraním, protože (alespoň prozatím) používá většina Go aplikací s uživatelským rozhraním možností poskytovaných moderními webovými prohlížeči (což ostatně pro jazyk vyvinutý primárně ve společnosti Google pravděpodobně dává smysl, i když se v delším časovém horizontu může jednat o dvousečnou zbraň – ostatně historie IT zná už mnoho jednoúčelových programovacích jazyků, které se mimo svou niku nijak významně nerozšířily).
V dnešním článku si přiblížíme některé další možnosti poskytované standardní knihovnou. Nejprve se budeme zabývat systémem používaným pro vstup a výstup dat (a zdaleka se nejedná pouze o operace nad soubory). Mohlo by se možná zdát, že se jedná o triviální téma, ovšem například zkušenosti z jiných jazyků (příkladem může být Java a jejich několik generací IO a NIO knihoven) ukazují, že dobře navržená a především pak rozšiřitelná IO knihovna je pro další rozvoj jazyka velmi užitečná a důležitá. V jazyku Go je celý systém vstupně-výstupních operací založen na několika rozhraních, jejichž metody mohou být (a ve skutečnosti i jsou) implementovány hned několika různými způsoby a použít je lze pro různé účely. Většina těchto rozhraní obsahuje pouze jedinou metodu, což je ovšem v programovacím jazyku Go velmi často používané řešení, s nímž se setkáme nejenom ve standardní knihovně.
To, že mnoho dále popsaných rozhraní obsahuje předpis pouze jediné metody má i další praktický dopad – pokud budeme potřebovat vytvořit další mechanismus používaný z pohledu programátora stejným způsobem, jako další vstupně-výstupní prostředky (soubory, síť, obsah archivu atd.), postačuje nadeklarovat nový datový typ a pro něj jedinou metodu z vybraného rozhraní (například ByteReader, Reader či ReadWriter. Nový takto navržený mechanismus bude od této chvíle možné do aplikací zařadit bez nutnosti jejich dalších modifikací.
2. Nejdůležitější rozhraní používaná vstupně-výstupním systémem
V následující tabulce jsou zmíněny základní informace o rozhraních, s nimiž se prakticky vždy setkáme při práci se vstupně-výstupním systémem základní knihovny programovacího jazyka Go:
Typ operace | Čtení | Zápis |
---|---|---|
jednotlivých bajtů | io.ByteReader | io.ByteWriter |
jednotlivých znaků | io.RuneReader | × |
jednotlivých bajtů s bufferem | io.ByteScanner | × |
jednotlivých znaků s bufferem | io.RuneScanner | × |
bloku bajtů | io.Reader | io.Writer |
3. Rozhraní io.ByteReader
Základním rozhraním, které může být implementováno pro prakticky libovolné vstupní či vstupně-výstupní zařízení, je rozhraní nazvané ByteReader. Toto rozhraní předepisuje jedinou metodu nazvanou ReadByte, která pochopitelně slouží k načtení jednoho bajtu ze vstupu. Pokud už další bajt nelze z nějakého důvodu načíst, například proto, že se dosáhlo konce dat nebo došlo k nějaké vstupně-výstupní chybě (výpadek připojení, odpojení souborového systému, skutečná chyba na fyzické datové vrstvě…), vrátí se ve druhé návratové hodnotě informace o chybě a první vrácená hodnota z metody ReadByte není obecně definována (musíme tedy počítat s tím, že může obsahovat jakoukoli hodnotu):
type ByteReader interface { ReadByte() (byte, error) }
Demonstrační příklad používající rozhraní nazvané ByteReader a jeho metodu ReadByte je ve skutečnosti velmi jednoduchý. Využijeme zde faktu, že metoda ReadByte je implementována i datovým typem nazvaným Reader, tentokrát ovšem definovaným v balíčku strings a nikoli v balíčku io. Konstruktor tohoto datového typu se volá funkcí NewReader, které předáme řetězec použitý jako zdroj dat:
reader := strings.NewReader("Hello world!")
Povšimněte si, jakým způsobem je realizován test na to, zda řetězec, z něhož načítáme jednotlivé bajty, ještě obsahuje nějaká data či zda jsme došli až na konec řetězce:
b, err := reader.ReadByte() if err == nil { fmt.Printf("%c", b) } else { fmt.Printf("\nerror %v", err) break }
Následuje výpis úplného zdrojového kódu tohoto demonstračního příkladu:
package main import ( "fmt" "strings" ) func main() { reader := strings.NewReader("Hello world!") for { b, err := reader.ReadByte() if err == nil { fmt.Printf("%c", b) } else { fmt.Printf("\nerror %v", err) break } } }
Příklad výstupu:
Hello world! error EOF
4. Vylepšení testu na zdetekovaný konec dat
Ve skutečnosti ovšem můžeme test na to, zda jsme na vstupu došli až na konec dat, napsat daleko explicitněji, a to kontrolou, jaká konkrétní chybová hodnota se vrátila z metody ReadByte. V případě, že se vrátila hodnota odpovídající konstantě io.EOF, nejedná se o skutečnou chybu, ale o „obyčejný“ konec dat, který většinou budeme chtít zpracovat jiným způsobem, než odlišný typ chyby. Tato úprava zdrojového kódu je provedena v další verzi demonstračního příkladu, jenž je vypsán pod tímto odstavcem:
package main import ( "fmt" "io" "strings" ) func main() { reader := strings.NewReader("Hello world!") for { b, err := reader.ReadByte() if err == io.EOF { fmt.Println("\nEOF detected") break } if err == nil { fmt.Printf("%c", b) } else { fmt.Printf("\nerror %v", err) break } } }
Opět si ukažme příklad výstupu, který bude od prvního příkladu nepatrně odlišný:
Hello world! EOF detected
5. Bajty versus znaky
Další komplikace při praktickém používání vstupně-výstupních operací může nastat v případě, kdy budeme potřebovat ze vstupu načítat celé znaky a nikoli jednotlivé dále nezpracovávané bajty. Připomeňme si, že v programovacím jazyku Go existuje striktní rozlišení mezi bajty a znaky, přičemž znaky jsou reprezentovány datovým typem nazvaným rune, protože je nutné rozpoznávat celý rozsah Unicode a nikoli už pouhé ASCII, které je jen nepatrnou (i když velmi důležitou) podmnožinou Unicode. Nejdříve se podívejme na způsob, jakým se vlastně zpracovávají jednotlivé bajty metodou pojmenovanou ReadByte v tom případě, kdy zdroj dat obsahuje znaky Unicode (samotný zdrojový kód napsaný v Go přitom používá kódování UTF-8, tj. jedno z nejčastějších kódování Unicode). Postačí nám nepatrná úprava předchozího demonstračního příkladu do modifikovaného tvaru:
package main import ( "fmt" "io" "strings" ) func main() { reader := strings.NewReader("* ěščř ½µ§я¤ *") for { b, err := reader.ReadByte() if err == io.EOF { fmt.Println("\nEOF detected") break } if err == nil { fmt.Printf("%02x ", b) } else { fmt.Printf("\nerror %v", err) break } } }
Výsledkem by měla být sekvence bajtů vypsaných v hexadecimální soustavě:
2a 20 c4 9b c5 a1 c4 8d c5 99 20 c2 bd c2 b5 c2 a7 d1 8f c2 a4 20 2a EOF detected
6. Rozhraní io.RuneReader
V případě, že skutečně budeme chtít postupně načítat jednotlivé znaky a nikoli bajty, je výhodnější namísto rozhraní ByteReader využít spíše rozhraní nazvané RuneReader s jedinou předepsanou metodou, jejíž jméno je ReadRune. Tato metoda vrací jak načtený znak, tak i jeho velikost reprezentovanou v bajtech (přičemž znaky z původní ASCII jsou reprezentovány jedním bajtem, ostatní znaky pak více bajty). V případě, že se načtení dalšího znaku nezdaří, vrátí se ve třetí návratové hodnotě chyba (a první dvě návratové hodnoty by se neměly dále zpracovávat):
type RuneReader interface { ReadRune() (r rune, size int, err error) }
Samozřejmě si opět všechno vyzkoušíme v demonstračním příkladu, v němž pro jednoduchost opět použijeme objekt typu Reader, jehož zdrojem dat je obyčejný řetězec:
package main import ( "fmt" "io" "strings" ) func main() { reader := strings.NewReader("* ěščř ½µ§я¤ *") for { c, size, err := reader.ReadRune() if err == io.EOF { fmt.Println("\nEOF detected") break } if err == nil { fmt.Printf("%c %d\n", c, size) } else { fmt.Printf("\nerror %v", err) break } } }
V tomto případě by měl výsledek práce tohoto demonstračního příkladu vypadat následovně:
* 1 1 ě 2 š 2 č 2 ř 2 1 ½ 2 µ 2 § 2 я 2 ¤ 2 1 * 1 EOF detected
7. Rozhraní io.ByteWriter
Opakem rozhraní ByteReader, s nímž jsme se seznámili v předchozích kapitolách, je rozhraní, které se – což asi nebude příliš překvapující – jmenuje ByteWriter. Toto rozhraní předepisuje pouze jedinou metodu s názvem WriteByte, která slouží k zápisu bajtu do výstupního zařízení (či libovolného výstupního mechanismu) a vrací hodnotu obsahující popis případné chyby, která může při zápisu nastat:
type ByteWriter interface { WriteByte(c byte) error }
Toto rozhraní je implementováno například datovým typem se jménem Buffer ze standardního balíčku bytes. Samotný buffer umožňuje zápis a čtení na úrovni jednotlivých bajtů či znaků do operační paměti, přičemž velikost bufferu se dynamicky podle potřeb mění. V následujícím demonstračním příkladu je buffer zpočátku prázdný a hodnoty (jednotlivé bajty) do něj zapíšeme právě metodou WriteByte a zpětně je pro kontrolu přečteme metodou ReadByte, kterou již dobře známe z předchozího textu:
package main import ( "bytes" "fmt" ) func main() { buffer := bytes.Buffer{} buffer.WriteByte(65) b, _ := buffer.ReadByte() fmt.Printf("%02x\n", b) buffer.WriteByte(0xff) b, _ = buffer.ReadByte() fmt.Printf("%02x\n", b) }
Příklad výstupu:
41 ff
V předchozím odstavci jsme si řekli, že buffer umožňuje, aby se čtení a zápis prováděly na úrovni jednotlivých bajtů či znaků. To nám umožňuje i kombinaci obou přístupů, tj. například zápis bajtů, které se následně pokusíme přečíst jako jednotlivé znaky Unicode zakódované pomocí UTF-8. Vyzkoušejme si to na dalším příkladu:
package main import ( "bytes" "fmt" ) func main() { buffer := bytes.Buffer{} buffer.WriteByte(65) c, size, err := buffer.ReadRune() fmt.Printf("%c %d %v\n", c, size, err) buffer.WriteByte(0xc4) buffer.WriteByte(0x9b) c, size, err = buffer.ReadRune() fmt.Printf("%c %d %v\n", c, size, err) c, size, err = buffer.ReadRune() fmt.Printf("%c %d %v\n", c, size, err) }
Výsledek činnosti tohoto demonstračního příkladu by měl vypadat následovně:
A 1 <nil> ě 2 <nil> 0 EOF
8. Zápis jednotlivých znaků metodou WriteRune
Vzhledem k tomu, že existují standardní rozhraní io.ByteWriter, io.ByteReader a io.RuneReader, mohlo by se očekávat, že bude existovat i rozhraní pojmenované io.RuneWriter. Ve skutečnosti tomu tak není, i když například již výše zmíněný typ Buffer z balíčku bytes obsahuje metodu nazvanou WriteRune, která by byla předepsána právě teoretickým rozhraním io.RuneWriter. Ostatně si chování této metody můžeme velmi snadno otestovat, a to překladem a spuštěním dalšího demonstračního příkladu, jehož zdrojový kód vypadá takto:
package main import ( "bytes" "fmt" ) func main() { buffer := bytes.Buffer{} buffer.WriteRune('a') buffer.WriteRune('ě') buffer.WriteRune('я') for { b, err := buffer.ReadByte() if err == nil { fmt.Printf("%02x ", b) } else { fmt.Printf("\nerror %v", err) break } } }
Tento příklad by měl po svém spuštění vypsat následující trojici řádků:
A 1 <nil> ě 2 <nil> 0 EOF
9. Rozhraní io.ByteScanner
Další dvojice rozhraní, s nimiž se v dnešním článku alespoň ve stručnosti seznámíme, se jmenuje io.ByteScanner a io.RuneScanner. Rozhraní io.ByteScanner je založeno na již popsaném rozhraní io.ByteReader, ovšem k metodě ReadByte je navíc přidána i metoda pojmenovaná UnreadByte:
type ByteScanner interface { ByteReader UnreadByte() error }
Tato metoda – pokud ji samozřejmě nějaký datový typ implementuje – zajistí, že další volání metody ReadByte vrátí stejný bajt, jako předchozí volání této metody. Jak je popsané chování interně zajištěno, již pochopitelně do značné míry závisí na tom, jaké zařízení či vstupní mechanismus je použit. Někde je možné použít vstupní buffer, jindy se zapamatuje pouze poslední načítaný znak atd. Většinou není možné metodu UnreadByte zavolat vícekrát za sebou, a to právě z toho důvodu, že její interní implementace si pamatuje pouze poslední čtený znak.
Tato dvě rozhraní jsou velmi užitečná, protože v mnoha algoritmech se při zpracování vstupních dat dozvíme, že například nějaký blok již skončil, až ve chvíli, kdy přečteme následující bajt či znak, který již má logicky patřit do dalšího bloku. Čistě programové řešení by většinou bylo zbytečně komplikované, takže použití rozhraní ByteScanner a RuneScanner je snadnější a čistější.
V dalším demonstračním příkladu si ukážeme použití metody UnreadByte při čtení bajtů (nikoli celých znaků!) z řetězce představujícího zdroj dat pro objekt typu strings.Reader. Úplný zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/go-root/blob/master/article22/08_unread_byte.go:
package main import ( "fmt" "strings" ) func main() { reader := strings.NewReader("Hello world!") cnt := 0 for { b, err := reader.ReadByte() if err == nil { fmt.Printf("%c", b) } else { fmt.Printf("\nerror %v", err) break } cnt++ if cnt == 5 || cnt == 10 || cnt == 14 || cnt == 15 { reader.UnreadByte() } } }
Z výpisu produkovaného tímto příkladem je patrné, že se skutečně podařilo zdvojit pátý, desátý, čtrnáctý a patnáctý bajt v řetězci:
Helloo worrld!!! error EOF
10. Vícenásobné vrácení bajtu do vstupu
Objekt typu strings.Reader ve skutečnosti umožňuje několikanásobné zpětné vložení bajtu, protože se interně jedná pouze o posun ukazatele ve vstupních datech. Můžeme tedy napsat například:
if cnt == 6 { for i := 0; i <= 6; i++ { reader.UnreadByte() } }
Chování takto upraveného příkladu si samozřejmě můžeme otestovat; zde je jeho úplný zdrojový kód:
package main import ( "fmt" "strings" ) func main() { reader := strings.NewReader("Hello world!") cnt := 0 for { b, err := reader.ReadByte() if err == nil { fmt.Printf("%c", b) } else { fmt.Printf("\nerror %v", err) break } cnt++ if cnt == 6 { for i := 0; i <= 6; i++ { reader.UnreadByte() } } } }
Výsledek činnosti tohoto demonstračního příkladu by měl vypadat následovně:
Hello Hello world!
func (r *Reader) UnreadByte() error { r.prevRune = -1 if r.i <= 0 { return errors.New("strings.Reader.UnreadByte: at beginning of string") } r.i-- return nil }
11. Rozhraní io.RuneScanner
Další rozhraní se stejnou filozofií, jako má výše popsané rozhraní io.ByteScanner, se jmenuje RuneScanner. Toto rozhraní je odvozeno od io.RuneReader, ovšem navíc je do něj přidána metoda určená pro vrácení posledně čteného znaku/runy:
type RuneScanner interface { RuneReader UnreadRune() error }
Použití tohoto rozhraní je snadné, což si ostatně můžeme ukázat v pořadí již desátém příkladu:
package main import ( "fmt" "io" "strings" ) func main() { reader := strings.NewReader("* ěščř ½µ§я¤ *") cnt := 0 for { c, size, err := reader.ReadRune() if err == io.EOF { fmt.Println("\nEOF detected") break } if err == nil { fmt.Printf("%c %d\n", c, size) } else { fmt.Printf("\nerror %v", err) break } cnt++ if cnt == 5 || cnt == 10 || cnt == 14 || cnt == 15 { reader.UnreadRune() } } }
Na výstupu můžeme vidět, že se některé znaky/runy skutečně načetly a vypsaly vícekrát. Zdvojené a ztrojené znaky jsou zvýrazněny:
* 1 1 ě 2 š 2 č 2 č 2 ř 2 1 ½ 2 µ 2 µ 2 § 2 я 2 ¤ 2 ¤ 2 ¤ 2 1 * 1 EOF detected
12. Rozhraní Reader
Ve standardní knihovně programovacího jazyka Go nalezneme i další dvě důležitá rozhraní, s nimiž se dokonce v praxi setkáme většinou mnohem častěji, než s výše zmíněnými rozhraními ByteReader a ByteWriter. První z těchto rozhraní předepisuje jedinou metodu určenou pro bufferované čtení dat z prakticky libovolného vstupního mechanismu (zařízení, souboru, roury atd.), u něhož se předpokládá, že dokáže načítat blok dat a rozeznávat jejich případný konec. Toto rozhraní se jmenuje jednoduše Reader a jediná metoda tohoto rozhraní má jméno Read:
type Reader interface { Read(p []byte) (n int, err error) }
Počet načítaných bajtů závisí na velikosti/kapacitě pole bajtů a taktéž na tom, kolik bajtů se ještě na vstupu nachází. Ideálně se vždy načte tolik bajtů, kolik odpovídá kapacitě předaného pole, ovšem například na konci souboru (pokud provádíme načítání ze souboru) to bude méně. Počet skutečně načtených bajtů získáme snadno – z první návratové hodnoty.
Podívejme se nyní na typické použití tohoto rozhraní při načítání dat ze vstupního souboru. Soubor nejdříve otevřeme a zajistíme jeho uzavření na konci funkce:
fin, err := os.Open(filename) if err != nil { log.Fatal(err) } defer fin.Close()
Dále vytvoříme buffer pro čtení dat:
const buffer_size = 16 buffer := make([]byte, buffer_size)
Další čtení (v našem případě po šestnácti bajtech) je již snadné:
for { read, err := fin.Read(buffer) if read > 0 { fmt.Printf("read %d bytes\n", read) fmt.Println(buffer[:read]) } if err != nil { fmt.Printf("other error %v\n", err) break } }
Úplná implementace čtení bajtů ze souboru po blocích pevné délky vypadá takto:
package main import ( "fmt" "io" "log" "os" ) const filename = "test_input.txt" const buffer_size = 16 func main() { fin, err := os.Open(filename) if err != nil { log.Fatal(err) } defer fin.Close() buffer := make([]byte, buffer_size) for { read, err := fin.Read(buffer) if read > 0 { fmt.Printf("read %d bytes\n", read) fmt.Println(buffer[:read]) } if err == io.EOF { fmt.Println("reached end of file") break } if err != nil { fmt.Printf("other error %v\n", err) break } } }
13. Čtení bloku bajtů ze standardního vstupu a z řetězce
Nepatrnou úpravou předchozího příkladu můžeme zajistit čtení bloku bajtů ze standardního vstupu. Ten je představován objektem os.Stdin, který pochopitelně nemusíme ani otevírat ani uzavírat. Podívejme se tedy jen na způsob implementace:
package main import ( "fmt" "io" "os" ) const buffer_size = 16 func main() { buffer := make([]byte, buffer_size) for { read, err := os.Stdin.Read(buffer) if read > 0 { fmt.Printf("read %d bytes\n", read) fmt.Println(buffer[:read]) } if err == io.EOF { fmt.Println("reached end of file") break } if err != nil { fmt.Printf("other error %v\n", err) break } } }
Podobně můžeme využít objekt strings.Reader pro čtení bloku bajtů z řetězce, což je implementováno v dnešním třináctém demonstračním příkladu:
package main import ( "fmt" "io" "strings" ) const input_string = "Hello world!" const buffer_size = 4 func main() { r := strings.NewReader(input_string) buffer := make([]byte, buffer_size) for { read, err := r.Read(buffer) if read > 0 { fmt.Printf("read %d bytes\n", read) fmt.Println(buffer[:read]) } if err == io.EOF { fmt.Println("reached end of file") break } if err != nil { fmt.Printf("other error %v\n", err) break } } }
Načtené bloky bajtů se můžeme pokusit převést na řetězec konstruktorem string. Opět si ukažme celý zdrojový kód takto upraveného příkladu:
package main import ( "fmt" "io" "strings" ) const input_string = "Hello world!" const buffer_size = 4 func main() { r := strings.NewReader(input_string) buffer := make([]byte, buffer_size) for { read, err := r.Read(buffer) if read > 0 { fmt.Printf("read %d bytes\n", read) fmt.Println(string(buffer[:read])) } if err == io.EOF { fmt.Println("reached end of file") break } if err != nil { fmt.Printf("other error %v\n", err) break } } }
14. Spojení více vstupů – multireader
Konečně se dostáváme k zajímavějším způsobům použití standardních vstupně-výstupních rozhraní. Dobrým příkladem může být funkce io.MultiReader, které se na vstup předá libovolné množství objektů implementujících rozhraní io.Reader a výsledkem bude opět objekt typu io.Reader, který ovšem bude vracet spojené vstupní proudy. To je potenciálně velmi užitečné a především – toto spojení dat se provede zcela automaticky a bez toho, abychom museli nějakým způsobem měnit logiku aplikace. V dalším příkladu je ukázáno, jak se spojí tři objekty typu Reader:
package main import ( "fmt" "io" "strings" ) const input_string_1 = "Hello" const input_string_2 = "world" const input_string_3 = "!" const buffer_size = 4 func main() { r1 := strings.NewReader(input_string_1) r2 := strings.NewReader(input_string_2) r3 := strings.NewReader(input_string_3) r := io.MultiReader(r1, r2, r3) buffer := make([]byte, buffer_size) for { read, err := r.Read(buffer) if read > 0 { fmt.Printf("read %d bytes\n", read) fmt.Println(string(buffer[:read])) } if err == io.EOF { fmt.Println("reached end of file") break } if err != nil { fmt.Printf("other error %v\n", err) break } } }
Příklad výstupu:
read 4 bytes Hell read 1 bytes o read 4 bytes worl read 1 bytes d read 1 bytes ! reached end of file
15. Rozhraní io.Writer
Opakem rozhraní io.Reader je pochopitelně rozhraní pojmenované io.Writer. Toto rozhraní předepisuje jedinou metodu určenou pro zápis bloku bajtů do libovolného výstupu. Metoda vrací počet skutečně zapsaných bajtů (ten se může lišit od kapacitu bufferu, například při chybě) a případnou hodnotu reprezentující chybu:
type Writer interface { Write(p []byte) (n int, err error) }
Typicky se tato metoda využívá při zápisu do souborů s binárním obsahem, ovšem můžeme ji pochopitelně použít i v případě, že potřebujeme pracovat s textovými soubory obsahujícími pouze ASCII znaky. A právě tento způsob použití je použit v dalším demonstračním příkladu:
package main import ( "fmt" "log" "os" ) const filename = "test_output.txt" const message = "Hello world!" func main() { fout, err := os.Create(filename) if err != nil { log.Fatal(err) } defer fout.Close() buffer := []byte(message) written, err := fout.Write(buffer) if written > 0 { fmt.Printf("written %d bytes\n", written) } if err != nil { fmt.Printf("I/O error %v\n", err) } }
16. Kopie souborů po blocích
Ukažme si ještě, jak zajistit kopii souborů po blocích nějaké pevně zadané délky. Samotná kopie po blocích samozřejmě není nic složitého, pouze musíme zajistit, aby se správně pracovalo s posledním blokem, který pochopitelně nemusí být zcela zaplněn. To je zajištěno zvýrazněným výrazem:
read, err := src.Read(buffer) if read > 0 { fmt.Printf("read %d bytes\n", read) written, err := dst.Write(buffer[:read]) if written > 0 { fmt.Printf("written %d bytes\n", written) } if err != nil { fmt.Printf("write error %v\n", err) return copied, err } copied += int64(written) }
Následuje výpis celého zdrojového kódu tohoto příkladu:
package main import ( "fmt" "io" "os" ) func closeFile(file *os.File) { fmt.Printf("Closing file '%s'\n", file.Name()) file.Close() } func copyFile(srcName, dstName string) (written int64, err error) { src, err := os.Open(srcName) if err != nil { panic(err) } defer closeFile(src) dst, err := os.Create(dstName) if err != nil { panic(err) } defer closeFile(dst) buffer := make([]byte, 16) copied := int64(0) for { read, err := src.Read(buffer) if read > 0 { fmt.Printf("read %d bytes\n", read) written, err := dst.Write(buffer[:read]) if written > 0 { fmt.Printf("written %d bytes\n", written) } if err != nil { fmt.Printf("write error %v\n", err) return copied, err } copied += int64(written) } if err == io.EOF { fmt.Println("reached end of file") break } if err != nil { fmt.Printf("other error %v\n", err) return copied, err } } return copied, nil } func testCopyFile(srcName, dstName string) { copied, err := copyFile(srcName, dstName) if err != nil { fmt.Printf("copyFile('%s', '%s') failed!!!\n", srcName, dstName) } else { fmt.Printf("Copied %d bytes\n", copied) } fmt.Println() } func main() { testCopyFile("test_input.txt", "output.txt") }
17. Načtení celého řádku ze standardního vstupu
V dnešním posledním demonstračním příkladu je ukázáno jiné použití standardních vstupně-výstupních funkcí a metod. Využijeme zde balíček bufio, v němž se mj. nachází i objekt typu Reader obsahující metodu ReadString, které se předá znak, jenž reprezentuje konec dat. V případě, že metodě předáme znak konce řádku, načte tato metoda celý textový řádek a vrátí ho ve formě řetězce, tedy s využitím základních konverzních funkcí pro převod bajtů na Unicode:
package main import ( "bufio" "os" ) func main() { reader := bufio.NewReader(os.Stdin) print("Login: ") login, err := reader.ReadString('\n') if err != nil { println("Error reading login") } print("Password: ") password, err := reader.ReadString('\n') if err != nil { println("Error reading password") } println(login) println(password) }
18. Skládání rozhraní souvisejících se vstupně-výstupními operacemi
Ve standardní knihovně nalezneme i rozhraní, která jsou složena z některých rozhraní jednodušších. Příkladem může být především rozhraní se jménem io.ReadWriter s oběma metodami Read i Write. Tyto metody ovšem nejsou předepsány přímo, ale nové rozhraní je skutečně složeno z rozhraní jednodušších, o čemž se ostatně můžeme snadno přesvědčit:
type ReadWriter interface { Reader Writer }
Podobně nalezneme ve standardní knihovně i další podobná rozhraní vycházející ze dvou či tří rozhraní s původně jedinou metodou, zejména pak:
type ReadCloser interface { Reader Closer } type WriteCloser interface { Writer Closer } type ReadWriteCloser interface { Reader Writer Closer } type ReadSeeker interface { Reader Seeker } type WriteSeeker interface { Writer Seeker } type ReadWriteSeeker interface { Reader Writer Seeker }
19. Repositář s demonstračními příklady
Zdrojové kódy všech dnes popsaných demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně dva megabajty), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:
20. Odkazy na Internetu
- Rozhraní io.ByteReader
https://golang.org/pkg/io/#ByteReader - Rozhraní io.RuneReader
https://golang.org/pkg/io/#RuneReader - Rozhraní io.ByteScanner
https://golang.org/pkg/io/#ByteScanner - Rozhraní io.RuneScanner
https://golang.org/pkg/io/#RuneScanner - Rozhraní io.Closer
https://golang.org/pkg/io/#Closer - Rozhraní io.Reader
https://golang.org/pkg/io/#Reader - Rozhraní io.Writer
https://golang.org/pkg/io/#Writer - Typ Strings.Reader
https://golang.org/pkg/strings/#Reader - VACUUM (SQL)
https://www.sqlite.org/lang_vacuum.html - VACUUM (Postgres)
https://www.postgresql.org/docs/8.4/sql-vacuum.html - go-cron
https://github.com/rk/go-cron - gocron
https://github.com/jasonlvhit/gocron - clockwork
https://github.com/whiteShtef/clockwork - clockwerk
https://github.com/onatm/clockwerk - JobRunner
https://github.com/bamzi/jobrunner - Rethinking Cron
https://adam.herokuapp.com/past/2010/4/13/rethinking_cron/ - In the Beginning was the Command Line
https://web.archive.org/web/20180218045352/http://www.cryptonomicon.com/beginning.html - repl.it (REPL pro různé jazyky)
https://repl.it/languages - GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
https://github.com/jroimartin/gocui - Read–eval–print loop
https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop - go-prompt
https://github.com/c-bata/go-prompt - readline
https://github.com/chzyer/readline - A pure golang implementation for GNU-Readline kind library
https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/ - go-readline
https://github.com/fiorix/go-readline - 4 Python libraries for building great command-line user interfaces
https://opensource.com/article/17/5/4-practical-python-libraries - prompt_toolkit 2.0.3 na PyPi
https://pypi.org/project/prompt_toolkit/ - python-prompt-toolkit na GitHubu
https://github.com/jonathanslenders/python-prompt-toolkit - The GNU Readline Library
https://tiswww.case.edu/php/chet/readline/rltop.html - GNU Readline (Wikipedia)
https://en.wikipedia.org/wiki/GNU_Readline - readline — GNU readline interface (Python 3.x)
https://docs.python.org/3/library/readline.html - readline — GNU readline interface (Python 2.x)
https://docs.python.org/2/library/readline.html - GNU Readline Library – command line editing
https://tiswww.cwru.edu/php/chet/readline/readline.html - gnureadline 6.3.8 na PyPi
https://pypi.org/project/gnureadline/ - Editline Library (libedit)
http://thrysoee.dk/editline/ - Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/ - libedit or editline
http://www.cs.utah.edu/~bigler/code/libedit.html - WinEditLine
http://mingweditline.sourceforge.net/ - rlcompleter — Completion function for GNU readline
https://docs.python.org/3/library/rlcompleter.html - rlwrap na GitHubu
https://github.com/hanslub42/rlwrap - rlwrap(1) – Linux man page
https://linux.die.net/man/1/rlwrap - readline(3) – Linux man page
https://linux.die.net/man/3/readline - history(3) – Linux man page
https://linux.die.net/man/3/history - Dokumentace k balíčku oglematchers
https://godoc.org/github.com/jacobsa/oglematchers - Balíček oglematchers
https://github.com/jacobsa/oglematchers - Dokumentace k balíčku ogletest
https://godoc.org/github.com/jacobsa/ogletest - Balíček ogletest
https://github.com/jacobsa/ogletest - Dokumentace k balíčku assert
https://godoc.org/github.com/stretchr/testify/assert - Testify – Thou Shalt Write Tests
https://github.com/stretchr/testify/ - package testing
https://golang.org/pkg/testing/ - Golang basics – writing unit tests
https://blog.alexellis.io/golang-writing-unit-tests/ - An Introduction to Programming in Go / Testing
https://www.golang-book.com/books/intro/12 - An Introduction to Testing in Go
https://tutorialedge.net/golang/intro-testing-in-go/ - Advanced Go Testing Tutorial
https://tutorialedge.net/golang/advanced-go-testing-tutorial/ - GoConvey
http://goconvey.co/ - Testing Techniques
https://talks.golang.org/2014/testing.slide - 5 simple tips and tricks for writing unit tests in #golang
https://medium.com/@matryer/5-simple-tips-and-tricks-for-writing-unit-tests-in-golang-619653f90742 - Afinní transformace
https://cs.wikibooks.org/wiki/Geometrie/Afinn%C3%AD_transformace_sou%C5%99adnic - package gg
https://godoc.org/github.com/fogleman/gg - Generate an animated GIF with Golang
http://tech.nitoyon.com/en/blog/2016/01/07/go-animated-gif-gen/ - Generate an image programmatically with Golang
http://tech.nitoyon.com/en/blog/2015/12/31/go-image-gen/ - The Go image package
https://blog.golang.org/go-image-package - Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
https://github.com/llgcode/draw2d - Draw a rectangle in Golang?
https://stackoverflow.com/questions/28992396/draw-a-rectangle-in-golang - YAML
https://yaml.org/ - edn
https://github.com/edn-format/edn - Smile
https://github.com/FasterXML/smile-format-specification - Protocol-Buffers
https://developers.google.com/protocol-buffers/ - Marshalling (computer science)
https://en.wikipedia.org/wiki/Marshalling_(computer_science) - Unmarshalling
https://en.wikipedia.org/wiki/Unmarshalling - Introducing JSON
http://json.org/ - Package json
https://golang.org/pkg/encoding/json/ - The Go Blog: JSON and Go
https://blog.golang.org/json-and-go - Go by Example: JSON
https://gobyexample.com/json - Writing Web Applications
https://golang.org/doc/articles/wiki/ - Golang Web Apps
https://www.reinbach.com/blog/golang-webapps-1/ - Build web application with Golang
https://legacy.gitbook.com/book/astaxie/build-web-application-with-golang/details - Golang Templates – Golang Web Pages
https://www.youtube.com/watch?v=TkNIETmF-RU - Simple Golang HTTPS/TLS Examples
https://github.com/denji/golang-tls - Playing with images in HTTP response in golang
https://www.sanarias.com/blog/1214PlayingwithimagesinHTTPresponseingolang - MIME Types List
https://www.freeformatter.com/mime-types-list.html - Go Mutex Tutorial
https://tutorialedge.net/golang/go-mutex-tutorial/ - Creating A Simple Web Server With Golang
https://tutorialedge.net/golang/creating-simple-web-server-with-golang/ - Building a Web Server in Go
https://thenewstack.io/building-a-web-server-in-go/ - How big is the pipe buffer?
https://unix.stackexchange.com/questions/11946/how-big-is-the-pipe-buffer - How to turn off buffering of stdout in C
https://stackoverflow.com/questions/7876660/how-to-turn-off-buffering-of-stdout-in-c - setbuf(3) – Linux man page
https://linux.die.net/man/3/setbuf - setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
https://linux.die.net/man/3/setvbuf - Select waits on a group of channels
https://yourbasic.org/golang/select-explained/ - Rob Pike: Simplicity is Complicated (video)
http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893 - Algorithms to Go
https://yourbasic.org/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/ - Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů: vlastní filtry a lexery
https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu-vlastni-filtry-a-lexery/ - Go Defer Simplified with Practical Visuals
https://blog.learngoprogramming.com/golang-defer-simplified-77d3b2b817ff - 5 More Gotchas of Defer in Go — Part II
https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa - The Go Blog: Defer, Panic, and Recover
https://blog.golang.org/defer-panic-and-recover - The defer keyword in Swift 2: try/finally done right
https://www.hackingwithswift.com/new-syntax-swift-2-defer - Swift Defer Statement
https://andybargh.com/swift-defer-statement/ - Modulo operation (Wikipedia)
https://en.wikipedia.org/wiki/Modulo_operation - Node.js vs Golang: Battle of the Next-Gen Languages
https://www.hostingadvice.com/blog/nodejs-vs-golang/ - The Go Programming Language (home page)
https://golang.org/ - GoDoc
https://godoc.org/ - Go (programming language), Wikipedia
https://en.wikipedia.org/wiki/Go_(programming_language) - Go Books (kniha o jazyku Go)
https://github.com/dariubs/GoBooks - The Go Programming Language Specification
https://golang.org/ref/spec - Go: the Good, the Bad and the Ugly
https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/ - Package builtin
https://golang.org/pkg/builtin/ - Package fmt
https://golang.org/pkg/fmt/ - The Little Go Book (další kniha)
https://github.com/dariubs/GoBooks - The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
https://www.safaribooksonline.com/library/view/the-go-programming/9780134190570/ebook_split010.html - Learning Go
https://www.miek.nl/go/ - Go Bootcamp
http://www.golangbootcamp.com/ - Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
http://www.informit.com/store/programming-in-go-creating-applications-for-the-21st-9780321774637 - Introducing Go (Build Reliable, Scalable Programs)
http://shop.oreilly.com/product/0636920046516.do - Learning Go Programming
https://www.packtpub.com/application-development/learning-go-programming - The Go Blog
https://blog.golang.org/ - Getting to Go: The Journey of Go's Garbage Collector
https://blog.golang.org/ismmkeynote - Go (programovací jazyk, Wikipedia)
https://cs.wikipedia.org/wiki/Go_(programovac%C3%AD_jazyk) - Rychle, rychleji až úplně nejrychleji s jazykem Go
https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/ - Installing Go on the Raspberry Pi
https://dave.cheney.net/2012/09/25/installing-go-on-the-raspberry-pi - How the Go runtime implements maps efficiently (without generics)
https://dave.cheney.net/2018/05/29/how-the-go-runtime-implements-maps-efficiently-without-generics - Niečo málo o Go – Golang (slovensky)
http://golangsk.logdown.com/ - How Many Go Developers Are There?
https://research.swtch.com/gophercount - Most Popular Technologies (Stack Overflow Survery 2018)
https://insights.stackoverflow.com/survey/2018/#most-popular-technologies - Most Popular Technologies (Stack Overflow Survery 2017)
https://insights.stackoverflow.com/survey/2017#technology - JavaScript vs. Golang for IoT: Is Gopher Winning?
https://www.iotforall.com/javascript-vs-golang-iot/ - The Go Programming Language: Release History
https://golang.org/doc/devel/release.html - Go 1.11 Release Notes
https://golang.org/doc/go1.11 - Go 1.10 Release Notes
https://golang.org/doc/go1.10 - Go 1.9 Release Notes (tato verze je stále používána)
https://golang.org/doc/go1.9 - Go 1.8 Release Notes (i tato verze je stále používána)
https://golang.org/doc/go1.8 - Go on Fedora
https://developer.fedoraproject.org/tech/languages/go/go-installation.html - Writing Go programs
https://developer.fedoraproject.org/tech/languages/go/go-programs.html - The GOPATH environment variable
https://tip.golang.org/doc/code.html#GOPATH - Command gofmt
https://tip.golang.org/cmd/gofmt/ - The Go Blog: go fmt your code
https://blog.golang.org/go-fmt-your-code - C? Go? Cgo!
https://blog.golang.org/c-go-cgo - Spaces vs. Tabs: A 20-Year Debate Reignited by Google’s Golang
https://thenewstack.io/spaces-vs-tabs-a-20-year-debate-and-now-this-what-the-hell-is-wrong-with-go/ - 400,000 GitHub repositories, 1 billion files, 14 terabytes of code: Spaces or Tabs?
https://medium.com/@hoffa/400–000-github-repositories-1-billion-files-14-terabytes-of-code-spaces-or-tabs-7cfe0b5dd7fd - Gofmt No Longer Allows Spaces. Tabs Only
https://news.ycombinator.com/item?id=7914523 - Why does Go „go fmt“ uses tabs instead of whitespaces?
https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces - Interactive: The Top Programming Languages 2018
https://spectrum.ieee.org/static/interactive-the-top-programming-languages-2018 - Go vs. Python
https://www.peterbe.com/plog/govspy - PackageManagementTools
https://github.com/golang/go/wiki/PackageManagementTools - A Tour of Go: Type inference
https://tour.golang.org/basics/14 - Go Slices: usage and internals
https://blog.golang.org/go-slices-usage-and-internals - Go by Example: Slices
https://gobyexample.com/slices - What is the point of slice type in Go?
https://stackoverflow.com/questions/2098874/what-is-the-point-of-slice-type-in-go - The curious case of Golang array and slices
https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335 - Introduction to Slices in Golang
https://www.callicoder.com/golang-slices/ - Golang: Understanding ‚null‘ and nil
https://newfivefour.com/golang-null-nil.html - What does nil mean in golang?
https://stackoverflow.com/questions/35983118/what-does-nil-mean-in-golang - nils In Go
https://go101.org/article/nil.html - Go slices are not dynamic arrays
https://appliedgo.net/slices/ - Go-is-no-good (nelze brát doslova)
https://github.com/ksimka/go-is-not-good - Rust vs. Go
https://news.ycombinator.com/item?id=13430108 - Seriál Programovací jazyk Rust
https://www.root.cz/serialy/programovaci-jazyk-rust/ - Modern garbage collection: A look at the Go GC strategy
https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e - Go GC: Prioritizing low latency and simplicity
https://blog.golang.org/go15gc - Is Golang a good language for embedded systems?
https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems - Running GoLang on an STM32 MCU. A quick tutorial.
https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial - Go, Robot, Go! Golang Powered Robotics
https://gobot.io/ - Emgo: Bare metal Go (language for programming embedded systems)
https://github.com/ziutek/emgo - UTF-8 history
https://www.cl.cam.ac.uk/~mgk25/ucs/utf-8-history.txt - Less is exponentially more
https://commandcenter.blogspot.com/2012/06/less-is-exponentially-more.html - Should I Rust, or Should I Go
https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9 - Setting up and using gccgo
https://golang.org/doc/install/gccgo - Elastic Tabstops
http://nickgravgaard.com/elastic-tabstops/ - Strings, bytes, runes and characters in Go
https://blog.golang.org/strings - Datový typ
https://cs.wikipedia.org/wiki/Datov%C3%BD_typ - Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
https://www.root.cz/clanky/programovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09 - Seriál o programovacím jazyku Rust: Vytvoření „řezu“ z pole
https://www.root.cz/clanky/prace-s-poli-v-programovacim-jazyku-rust/#k06 - Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05 - Printf Format Strings
https://www.cprogramming.com/tutorial/printf-format-strings.html - Java: String.format
https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#format-java.lang.String-java.lang.Object…- - Java: format string syntax
https://docs.oracle.com/javase/8/docs/api/java/util/Formatter.html#syntax - Selectors
https://golang.org/ref/spec#Selectors - Calling Go code from Python code
http://savorywatt.com/2015/09/18/calling-go-code-from-python-code/ - Go Data Structures: Interfaces
https://research.swtch.com/interfaces - How to use interfaces in Go
http://jordanorelli.com/post/32665860244/how-to-use-interfaces-in-go - Interfaces in Go (part I)
https://medium.com/golangspec/interfaces-in-go-part-i-4ae53a97479c - Part 21: Goroutines
https://golangbot.com/goroutines/ - Part 22: Channels
https://golangbot.com/channels/ - [Go] Lightweight eventbus with async compatibility for Go
https://github.com/asaskevich/EventBus - What about Trait support in Golang?
https://www.reddit.com/r/golang/comments/8mfykl/what_about_trait_support_in_golang/ - Don't Get Bitten by Pointer vs Non-Pointer Method Receivers in Golang
https://nathanleclaire.com/blog/2014/08/09/dont-get-bitten-by-pointer-vs-non-pointer-method-receivers-in-golang/ - Control Flow
https://en.wikipedia.org/wiki/Control_flow - Structured programming
https://en.wikipedia.org/wiki/Structured_programming - Control Structures
https://www.golang-book.com/books/intro/5 - Control structures – Go if else statement
http://golangtutorials.blogspot.com/2011/06/control-structures-if-else-statement.html - Control structures – Go switch case statement
http://golangtutorials.blogspot.com/2011/06/control-structures-go-switch-case.html - Control structures – Go for loop, break, continue, range
http://golangtutorials.blogspot.com/2011/06/control-structures-go-for-loop-break.html - Single Function Exit Point
http://wiki.c2.com/?SingleFunctionExitPoint - Entry point
https://en.wikipedia.org/wiki/Entry_point - Why does Go have a GOTO statement?!
https://www.reddit.com/r/golang/comments/kag5q/why_does_go_have_a_goto_statement/ - Effective Go
https://golang.org/doc/effective_go.html - GoClipse: an Eclipse IDE for the Go programming language
http://goclipse.github.io/ - GoClipse Installation
https://github.com/GoClipse/goclipse/blob/latest/documentation/Installation.md#installation