Obsah
1. Pokročilejší použití vstupně-výstupních funkcí standardní knihovny programovacího jazyka Go
2. Krátké zopakování: rozhraní io.Reader a io.Writer a jejich význam v systému jazyka Go
3. Roura (pipe) vytvořená v operační paměti
4. Rozhraní io.Seeker a jeho varianty
5. Přesun (seek) při čtení dat ze souboru
6. Přesun (seek) při zápisu dat do souboru
7. Vytvoření řídkého souboru (sparse file)
8. Použití rozhraní io.MultiWriter pro souběžné zpracování výstupních dat
9. Využití rozhraní StringWriter
10. Načítání řetězců – typ Reader z balíčku bufio
11. Chování readeru při dosažení konce souboru
12. Přečtení sekvence bajtů z bufferovaného vstupu
13. Zápis dat s jejich komprimací s využitím algoritmu Deflate
14. Konfigurace komprimačního algoritmu
15. Přímé získání zkomprimovaných dat přes rouru (pipe)
17. Využití hex dumperu (nejenom) pro ladicí účely
18. Konverze binárních dat s využitím kódování Base64
19. Repositář s demonstračními příklady
1. Pokročilejší použití vstupně-výstupních funkcí standardní knihovny programovacího jazyka Go
V předchozí části tohoto seriálu jsme si popsali základní rozhraní používaná při čtení či při zápisu dat, ať již dat binárních či textových (Unicode). Připomeňme si, že se jednalo o tato rozhraní:
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 |
Tato rozhraní, především pak io.Reader a io.Writer, jsou implementována mnoha různými způsoby, které umožňují tvořit mnohdy i poměrně složité aplikace, v nichž se však budou pro vstup a výstup používat ty samé operace:
Rozhraní | Implementované operace |
---|---|
io.MultiReader | postupné čtení z několika zdrojů |
io.MultiWriter | zápis jednoho proudu dat do několika výstupních proudů |
encoding/base64.Encoding | kódování podle BASE64 |
encoding/hex.Dumper | výstup dat v hexadecimálním formátu, například pro ladicí účely |
compress/gzip.Writer | komprimace dat s využitím algoritmu Deflate |
compress/gzip.Reader | dekomprimace dat s využitím algoritmu Deflate |
V předchozí tabulce je uvedeno pouze několik příkladů, protože ve skutečnosti obsahují balíčky encoding a compress větší množství podbalíčků.
To ovšem není vše, protože rozhraní je možné jak implementovat, tak i rozšiřovat a vytvářet z nich složitější rozhraní. To je případ rozhraní Seeker, z něhož se odvozují například rozhraní ReadSeeker, WriteSeeker a ReadWriteSeeker, s nimiž se taktéž dnes seznámíme.
2. Krátké zopakování: rozhraní io.Reader a io.Writer a jejich význam v systému jazyka Go
Nejprve si krátce zopakujeme, jak se používají rozhraní io.Reader a io.Writer. V rozhraní io.Reader je deklarována jediná metoda Read určená pro načtení sekvence bajtů do připraveného bufferu a nastavené kapacitě:
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.
V prvním demonstračním příkladu je ukázáno, jak lze postupně načíst soubor a zpracovávat ho v bufferu o předem známé a programátorem nastavené kapacitě:
package main import ( "fmt" "io" "log" "os" ) const filename = "test_input.txt" const buffer_size = 16 func main() { reader, err := os.Open(filename) if err != nil { log.Fatal(err) } defer reader.Close() buffer := make([]byte, buffer_size) for { read, err := reader.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 } } }
Po spuštění tohoto příkladu se postupně zobrazí informace o načtení jednotlivých bloků, přičemž poslední blok bude obecně kratší:
read 16 bytes [108 105 110 101 32 35 49 10 108 105 110 101 32 35 50 10] read 16 bytes [108 105 110 101 32 35 51 10 108 105 110 101 32 35 52 10] read 8 bytes [108 105 110 101 32 35 53 10] reached end of file
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) }
Ukázka použití rozhraní io.Writer je uvedena v dnešním druhém demonstračním příkladu, který po svém spuštění vytvoří nový soubor a zapíše do něj textovou zprávu převedenou na sekvenci bajtů:
package main import ( "fmt" "log" "os" ) const filename = "test_output.txt" const message = "Hello world!" func main() { writer, err := os.Create(filename) if err != nil { log.Fatal(err) } defer writer.Close() buffer := []byte(message) written, err := writer.Write(buffer) if written > 0 { fmt.Printf("written %d bytes\n", written) } if err != nil { fmt.Printf("I/O error %v\n", err) } }
3. Roura (pipe) vytvořená v operační paměti
V balíčku io nalezneme mj. i velmi užitečnou pomůcku. Jedná se o rouru (pipe) vytvořenou přímo v operační paměti. Tuto rouru lze používat prakticky stejným způsobem, jako souborovou rouru podporovanou Unixovými systémy – z jedné strany se provádí zápis dat, ze druhé strany pak její čtení. Roura se vytvoří funkcí:
func Pipe() (*PipeReader, *PipeWriter)
tato funkce vrací dvě hodnoty; první hodnota mj. implementuje io.Reader, druhá pak pochopitelně io.Writer. Typicky se roura vytvořená v operační používá pro komunikaci mezi několika gorutinami, podobně jako kanály (které však mají odlišnou sémantiku operací).
V dnešním třetím demonstračním příkladu je ukázán způsob použití roury vytvořené v operační paměti. V jedné gorutině se do roury zapíšou data (zpráva), ve druhé (konkrétně v hlavní gorutině) se data naopak přečtou:
package main import ( "fmt" "io" ) func main() { reader, writer := io.Pipe() done := make(chan bool) go func() { fmt.Fprint(writer, "Hello Mario!") writer.Close() done <- true }() buffer := make([]byte, 100) read, err := reader.Read(buffer) if err != nil { panic(err) } else { if read > 0 { fmt.Printf("read %d bytes translated into '%s'\n", read, buffer) } } <-done reader.Close() }
Po spuštění příkladu by se měla zpráva přes rouru přenést do readeru:
read 12 bytes translated into 'Hello Mario!'
Příklad si můžeme zjednodušit, pokud použijeme buffer bytes.Buffer, který zjednodušuje proces čtení dat. Upravená varianta bude vypadat následovně:
package main import ( "bytes" "fmt" "io" ) func main() { reader, writer := io.Pipe() go func() { fmt.Fprint(writer, "Hello Mario!") writer.Close() }() buffer := new(bytes.Buffer) buffer.ReadFrom(reader) fmt.Printf("Message read from pipe: '%s'\n", buffer.String()) writer.Close() }
Opět si ukažme, jak se tento příklad bude chovat po svém spuštění:
Message read from pipe: 'Hello Mario!'
4. Rozhraní io.Seeker a jeho varianty
V balíčku io nalezneme i další užitečné rozhraní nazvané jednoduše Seeker. V tomto rozhraní je předepsána jediná metoda pojmenovaná Seek, která slouží pro přesun ukazovátka aktuálně čtených či zapisovaných dat v Readeru či Writeru. Samozřejmě ne vždy je toto rozhraní implementováno, protože některé zdroje/cíle dat přesun ukazovátka neumožňují. Ovšem například u souborů je to možné:
type Seeker interface { Seek(offset int64, whence int) (int64, error) }
První parametr metody Seek určuje offset, o který se má ukazovátko posunout. Povšimněte si, že se jedná o hodnotu se znaménkem, protože posun lze provést jak dopředu, tak i dozadu. Druhým parametrem se specifikuje, od jaké hodnoty se offset počítá:
Hodnota | Význam |
---|---|
SeekStart | offset se počítá od začátku souboru (takže je absolutní) |
SeekCurrent | offset se počítá relativně k aktuální hodnotě ukazovátka |
SeekEnd | offset se počítá od konce souboru (takže je absolutní) |
Od rozhraní Seeker jsou kombinací s Reader a Writer odvozena další rozhraní:
type ReadSeeker interface { Reader Seeker } type WriteSeeker interface { Writer Seeker } type ReadWriteSeeker interface { Reader Writer Seeker }
5. Přesun (seek) při čtení dat ze souboru
Nejprve si ukažme, jak se provádí přesun (seek) ukazovátka při čtení ze souboru nebo z jiného prostředku poskytujícího data. V následujícím příkladu se provádí čtení dat z řetězce, a to po blocích o velikosti šest bajtů:
package main import ( "fmt" "io" "strings" ) const input_string = "*** Hello world! ***" const buffer_size = 6 func main() { reader := strings.NewReader(input_string) buffer := make([]byte, buffer_size) for { read, err := reader.Read(buffer) if read > 0 { fmt.Printf("read %d bytes translated into '%s'\n", read, buffer[:read]) } if err == io.EOF { fmt.Println("reached end of file") break } if err != nil { fmt.Printf("other error %v\n", err) break } } }
Výsledek je predikovatelný – prostě se postupně přečte celý obsah řetězce:
read 6 bytes translated into '*** He' read 6 bytes translated into 'llo wo' read 6 bytes translated into 'rld! *' read 2 bytes translated into '**' reached end of file
S využitím operace Seek však můžeme první čtyři bajty přeskočit (viz zvýrazněná část). Posun se počítá od začátku souboru:
package main import ( "fmt" "io" "strings" ) const input_string = "*** Hello world! ***" const buffer_size = 6 func main() { reader := strings.NewReader(input_string) buffer := make([]byte, buffer_size) reader.Seek(4, io.SeekStart) for { read, err := reader.Read(buffer) if read > 0 { fmt.Printf("read %d bytes translated into '%s'\n", read, buffer[:read]) } if err == io.EOF { fmt.Println("reached end of file") break } if err != nil { fmt.Printf("other error %v\n", err) break } } }
Výsledek:
read 6 bytes translated into 'Hello ' read 6 bytes translated into 'world!' read 4 bytes translated into ' ***' reached end of file
Naopak v dalším demonstračním příkladu se posun vypočte od konce souboru, takže je záporný:
package main import ( "fmt" "io" "strings" ) const input_string = "*** Hello world! ***" const buffer_size = 6 func main() { reader := strings.NewReader(input_string) buffer := make([]byte, buffer_size) reader.Seek(-10, io.SeekEnd) for { read, err := reader.Read(buffer) if read > 0 { fmt.Printf("read %d bytes translated into '%s'\n", read, 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 výstupu je patrné, že se skutečně načetlo jen posledních deset bajtů:
read 6 bytes translated into 'world!' read 4 bytes translated into ' ***' reached end of file
A konečně si ukažme, jak se provede relativní přeskok vypočítaný na základě aktuální polohy ukazovátka:
package main import ( "fmt" "io" "strings" ) const input_string = "****** Hello world! ******[END]" const buffer_size = 6 func main() { reader := strings.NewReader(input_string) buffer := make([]byte, buffer_size) for { read, err := reader.Read(buffer) if read > 0 { fmt.Printf("read %d bytes translated into '%s'\n", read, buffer[:read]) reader.Seek(7, io.SeekCurrent) } if err == io.EOF { fmt.Println("reached end of file") break } if err != nil { fmt.Printf("other error %v\n", err) break } } }
Přesun se povede celkově třikrát:
read 6 bytes translated into '******' read 6 bytes translated into 'world!' read 5 bytes translated into '[END]' reached end of file
6. Přesun (seek) při zápisu dat do souboru
Přesun ukazovátka lze ovšem provést i při zápisu dat do souboru (s určitým omezením pro soubory otevřené v režimu append). Jednou zapsaná data je tedy možné přepsat (posun zpět) či naopak v souboru vytvořit „díru“ (posun vpřed):
package main import ( "fmt" "io" "log" "os" ) const filename = "small.bin" func writeMark(writer io.Writer) { buffer := []byte("**") written, err := writer.Write(buffer) if written > 0 { fmt.Printf("written %d bytes\n", written) } if err != nil { fmt.Printf("I/O error %v\n", err) } } func main() { writer, err := os.Create(filename) if err != nil { log.Fatal(err) } defer writer.Close() writeMark(writer) writer.Seek(100, io.SeekCurrent) writeMark(writer) writer.Seek(100, io.SeekCurrent) writeMark(writer) }
Výsledný soubor obsahuje automaticky doplněné „díry“, což je patrné z jeho hexadecimálního zobrazení:
0000000: 2a 2a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 **.............. 0000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000060: 00 00 00 00 00 00 2a 2a 00 00 00 00 00 00 00 00 ......**........ 0000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000c0: 00 00 00 00 00 00 00 00 00 00 00 00 2a 2a ............**
7. Vytvoření řídkého souboru (sparse file)
Některé souborové systémy podporují takzvané řídké soubory neboli sparse files. Jedná se o takové soubory, jejichž celý obsah nemusí být fyzicky celý umístěn na datovém médiu ve formě bitové kopie – soubor tedy může obsahovat „díry“ a zabírat na disku méně fyzického místa, než odpovídá jeho logické velikosti. Takový řídký soubor můžeme vytvořit velmi snadno, samozřejmě za předpokladu, že jsou řídké soubory podporovány souborovým systémem:
package main import ( "fmt" "io" "log" "os" ) const filename = "huge.bin" func writeMark(writer io.Writer) { buffer := []byte("**") written, err := writer.Write(buffer) if written > 0 { fmt.Printf("written %d bytes\n", written) } if err != nil { fmt.Printf("I/O error %v\n", err) } } func main() { writer, err := os.Create(filename) if err != nil { log.Fatal(err) } defer writer.Close() writeMark(writer) writer.Seek(1000000, io.SeekCurrent) writeMark(writer) writer.Seek(1000000, io.SeekCurrent) writeMark(writer) }
Povšimněte si, že se mezi jednotlivými zápisy dvou hvězdiček provedl přesun ukazovátka o milion bajtů. Výsledný soubor by měl mít logickou velikost 2+1000000+2+1000000+2=2000006 bajtů. Jeho podobu si tedy zobrazíme jen ve zkrácené podobě (vynecháme sekvenci milionu nulových bajtů):
0000000: 2a 2a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 **.............. 0000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ ... ... ... 00f41f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00f4200: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00f4210: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00f4220: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00f4230: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00f4240: 00 00 2a 2a 00 00 00 00 00 00 00 00 00 00 00 00 ..**............ 00f4250: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00f4260: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00f4270: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00f4280: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00f4290: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ ... ... ... 01e8440: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 01e8450: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 01e8460: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 01e8470: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 01e8480: 00 00 00 00 2a 2a ....**
Fyzická velikost tohoto souboru je ovšem ve skutečnosti mnohem menší, o čemž se můžeme snadno přesvědčit, pokud příkazu ls předáme parametr -s, jenž zajistí, že se v levém sloupci zobrazí skutečná velikost souboru v kilobajtech:
$ ls -ls huge.bin 12 -rw-r--r-- 1 tester tester 2000006 dub 28 09:50 huge.bin
8. Použití rozhraní io.MultiWriter pro souběžné zpracování výstupních dat
Další užitečné rozhraní se jmenuje io.MultiWriter. Slouží například pro souběžné zpracování dat a jeho činnost se do jisté míry podobá nástroji tee – data zapsaná do vytvořené instance MultiWriteru se interně „propíšou“ do několika dalších writerů. Podívejme se na jednoduchý příklad. Nejprve vytvoříme dva objekty sloužící pro zápis dat do dvojice souborů:
writer1, err := os.Create(filename1) writer2, err := os.Create(filename2)
Zápis do souborů však nebudeme provádět přímo. Namísto toho vytvoříme instanci MultiWriteru:
writer := io.MultiWriter(writer1, writer2)
Nyní jakákoli operace writer.Write() interně zavolá writer1.Write() a souběžně i writer2.Write() (ve skutečnosti nemusí nutně dojít ke skutečně souběžnému zápisu, ovšem to jako uživatelé nijak neovlivníme).
Úplný demonstrační příklad používající MultiWriter vypadá následovně:
package main import ( "fmt" "io" "log" "os" ) const filename1 = "test_output_1.txt" const filename2 = "test_output_2.txt" const message = "Hello world!" func main() { writer1, err := os.Create(filename1) if err != nil { log.Fatal(err) } defer writer1.Close() writer2, err := os.Create(filename2) if err != nil { log.Fatal(err) } defer writer2.Close() writer := io.MultiWriter(writer1, writer2) buffer := []byte(message) written, err := writer.Write(buffer) if written > 0 { fmt.Printf("written %d bytes\n", written) } if err != nil { fmt.Printf("I/O error %v\n", err) } }
Po spuštění příkladu by měly vzniknout dva soubory se stejným obsahem.
9. Využití rozhraní StringWriter
Tato kapitola bude velmi stručná, protože si v ní pouze ukážeme použití metody WriteString pro zápis řetězce do souboru. Zavolání této metody je přímočaré a interně se samozřejmě provádí konverze z Unicode na sekvenci bajtů podle kódování UTF-8:
package main import ( "fmt" "log" "os" ) const filename = "test_output.txt" const message = "Hello world!" func main() { writer, err := os.Create(filename) if err != nil { log.Fatal(err) } defer writer.Close() written, err := writer.WriteString(message) if written > 0 { fmt.Printf("written %d bytes\n", written) } if err != nil { fmt.Printf("I/O error %v\n", err) } }
10. Načítání řetězců – typ Reader z balíčku bufio
Zajímavé je, že při čtení řetězců (například ze souborů, ale i z jiných vstupů) si již nevystačíme s možnostmi balíčku io, ale musíme využít možnosti nabízené balíčkem bufio, v němž je deklarován typ Reader, který kromě implementace rozhraní io.Reader implementuje mj. i tyto metody:
func (b *Reader) ReadString(delim byte) (string, error) func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)
Doporučuje se používat první metodu ReadString, které se předá znak tvořící oddělovač jednotlivých záznamů v řetězci. Pokud tento znak bude nastaven na konec řádku (\n), bude metoda pracovat s jednotlivými textovými řádky. Metoda vrátí načtený řetězec a případný identifikátor chyby, pokud k ní dojde (více viz navazující kapitolu).
Můžeme si to vyzkoušet na následujícím demonstračním příkladu:
package main import ( "bufio" "fmt" "io" "log" "os" ) const filename = "test_input.txt" const buffer_size = 16 func main() { reader, err := os.Open(filename) if err != nil { log.Fatal(err) } defer reader.Close() bufferedReader := bufio.NewReader(reader) for { str, err := bufferedReader.ReadString('\n') fmt.Printf("read string with size %d bytes: %s", len(str), str) if err == io.EOF { fmt.Println("\nreached end of file") break } if err != nil { fmt.Printf("\nother error %v\n", err) break } } }
Chování si můžeme otestovat na běžném textovém souboru, na jehož konci je znak konce řádku:
read string with size 8 bytes: line #1 read string with size 8 bytes: line #2 read string with size 8 bytes: line #3 read string with size 8 bytes: line #4 read string with size 8 bytes: line #5 read string with size 0 bytes: reached end of file
Zvýrazněný řádek nám naznačuje, že při posledním pokusu o čtení již došlo k indikaci konce souboru, což ovšem obecně nemusí znamenat, že přečtenou hodnotu máme zahodit – viz následující kapitolu.
11. Chování readeru při dosažení konce souboru
V předchozím příkladu jsme načítali textový soubor, jehož hexadecimální tvar je následující:
0000000: 6c 69 6e 65 20 23 31 0a 6c 69 6e 65 20 23 32 0a line #1.line #2. 0000010: 6c 69 6e 65 20 23 33 0a 6c 69 6e 65 20 23 34 0a line #3.line #4. 0000020: 6c 69 6e 65 20 23 35 0a line #5.
Povšimněte si, že posledním znakem tohoto souboru je znak s ASCII kódem 0×0a, což je (v Unixu) znak pro konec řádku. Co se ovšem stane, pokud tento znak nebude na konci posledního řádku umístěn, tj. pokud bude mít soubor tento tvar?:
0000000: 6c 69 6e 65 20 23 31 0a 6c 69 6e 65 20 23 32 0a line #1.line #2. 0000010: 6c 69 6e 65 20 23 33 0a 6c 69 6e 65 20 23 34 0a line #3.line #4. 0000020: 6c 69 6e 65 20 23 35 line #5
:set binary :set noendofline :w
Zdrojový kód příkladu bude prakticky stejný, pouze změníme vstupní soubor z test_input.txt na test_input_no_eoln.txt:
package main import ( "bufio" "fmt" "io" "log" "os" ) const filename = "test_input_no_eoln.txt" const buffer_size = 16 func main() { reader, err := os.Open(filename) if err != nil { log.Fatal(err) } defer reader.Close() bufferedReader := bufio.NewReader(reader) for { str, err := bufferedReader.ReadString('\n') fmt.Printf("read string with size %d bytes: %s", len(str), str) if err == io.EOF { fmt.Println("\nreached end of file") break } if err != nil { fmt.Printf("\nother error %v\n", err) break } } }
Povšimněte si, že při posledním čtení se přečte pouze sedm bajtů (chybí odřádkování) a následující čtení již neproběhne, protože poslední čtení nastavilo „chybu“ na hodnotu io.EOF:
read string with size 8 bytes: line #1 read string with size 8 bytes: line #2 read string with size 8 bytes: line #3 read string with size 8 bytes: line #4 read string with size 7 bytes: line #5 reached end of file
Co to znamená v praxi? I když bude při čtení platit err == nil, musíme se podívat na hodnotu chyby, aby bylo zřejmé, jaká situace nastala – zda se pouze jedná o poslední řádek či zda došlo k jiné specifikované chybě.
12. Přečtení sekvence bajtů z bufferovaného vstupu
V dalším demonstračním příkladu opět použijeme balíček bufio, ovšem nikoli pro čtení celých řádků ve formě textu (řetězců), ale pro čtení sekvence bajtů až do chvíle, kdy se narazí na specifikovaný oddělovač. Tento oddělovač opět nastavíme na ASCII hodnotu znaku pro konec řádku:
array, err := bufferedReader.ReadBytes('\n')
Samotná logika čtení je prakticky stejná, jako tomu bylo v případě řetězců; budeme tedy rozlišovat mezi koncem dat (souboru) a jinou chybou:
if err == io.EOF { fmt.Println("reached end of file") break } if err != nil { fmt.Printf("other error %v\n", err) break }
Následuje výpis zdrojového kódu tohoto demonstračního příkladu:
package main import ( "bufio" "fmt" "io" "log" "os" ) const filename = "test_input_no_eoln.txt" const buffer_size = 16 func main() { reader, err := os.Open(filename) if err != nil { log.Fatal(err) } defer reader.Close() bufferedReader := bufio.NewReader(reader) for { array, err := bufferedReader.ReadBytes('\n') fmt.Printf("read array of bytes with size %d bytes: %v\n", len(array), array) if err == io.EOF { fmt.Println("reached end of file") break } if err != nil { fmt.Printf("other error %v\n", err) break } } }
Z výpisu zpráv tohoto demonstračního příkladu je patrné, že každá načtená sekvence bajtů skutečně končí bajtem s kódem 10 (což odpovídá 0×0a neboli ‚\n‘), pochopitelně až na poslední sekvenci, která je načtena bez ohledu na to, jakým bajtem končí:
read array of bytes with size 8 bytes: [108 105 110 101 32 35 49 10] read array of bytes with size 8 bytes: [108 105 110 101 32 35 50 10] read array of bytes with size 8 bytes: [108 105 110 101 32 35 51 10] read array of bytes with size 8 bytes: [108 105 110 101 32 35 52 10] read array of bytes with size 7 bytes: [108 105 110 101 32 35 53] reached end of file
Při pokusu o přečtení souboru končícího znakem ‚\n‘ vrátí poslední čtení již jen prázdnou sekvenci a chyba bude reprezentována hodnotou io.EOF:
read array of bytes with size 8 bytes: [108 105 110 101 32 35 49 10] read array of bytes with size 8 bytes: [108 105 110 101 32 35 50 10] read array of bytes with size 8 bytes: [108 105 110 101 32 35 51 10] read array of bytes with size 8 bytes: [108 105 110 101 32 35 52 10] read array of bytes with size 8 bytes: [108 105 110 101 32 35 53 10] read array of bytes with size 0 bytes: [] reached end of file
13. Zápis dat s jejich komprimací s využitím algoritmu Deflate
Dalšími užitečnými datovými typy, které implementují nám již velmi dobře známé rozhraní io.Writer, jsou typy nacházející se v balíčku compress. Dnes se konkrétně budeme věnovat podbalíčku gzip, ovšem kromě něho zde nalezneme podbalíčky bzip2, flate, lzw a zlib. V podbalíčku compress/gzip je implementace komprimovacího algoritmu Deflate používaného v Gzipu a taktéž ve známém DOSovém komprimačním nástroji pkzip. Komprimace dat je z pohledu běžného programátora snadná, právě díky použití rozhraní io.Writer:
writer, err := os.Create(filename) gzipWriter := gzip.NewWriter(writer) buffer := []byte(message) written, err := gzipWriter.Write(buffer)
Data jsou před fyzickým zápisem automaticky zkomprimována. Úplná implementace příkladu bude vypadat následovně:
package main import ( "compress/gzip" "fmt" "log" "os" ) const filename = "test_output.txt.gz" const message = "Hello Hello Hello Hello Hello Hello Hello Hello Hello Hello " func main() { writer, err := os.Create(filename) if err != nil { log.Fatal(err) } defer writer.Close() gzipWriter := gzip.NewWriter(writer) defer gzipWriter.Close() buffer := []byte(message) written, err := gzipWriter.Write(buffer) if written >= 0 { fmt.Printf("written %d bytes\n", written) } if err != nil { fmt.Printf("I/O error %v\n", err) } }
Po jeho spuštění se objeví informace o tom, že byla zapsána zpráva o délce šedesát bajtů:
written 60 bytes
Ovšem pohledem na vytvořený soubor se můžeme přesvědčit, že jeho skutečná délka bude menší, právě kvůli použití komprimačního algoritmu:
$ ls -l test_output.txt.gz -rw-r--r-- 1 tester tester 32 dub 28 09:46 test_output.txt.gz
Obsah vytvořeného souboru bude vypadat takto:
$ xxd -g 1 test_output.txt.gz 0000000: 1f 8b 08 00 00 00 00 00 00 ff f2 48 cd c9 c9 57 ...........H...W 0000010: 20 8f 04 04 00 00 ff ff 73 a9 91 e2 3c 00 00 00 .......s...<...
Offset | Velikost | Hodnota | Význam |
---|---|---|---|
0 | 2 | 0×1f 0×8b | magické číslo identifikující formát Gzip |
2 | 1 | ? | metoda použitá při komprimaci dat |
3 | 1 | ? | příznaky souboru |
4 | 4 | ? | časové razítko |
8 | 1 | ? | další příznaky komprimačního algoritmu |
9 | 1 | ? | identifikace operačního systému, na němž byla data zkomprimována |
Pokud vás zajímá tabulka s kódy operačních systémů, ponoříme se hluboko do minulosti:
Hodnota | Operační systém (varianty) |
---|---|
0 | FAT (MS-DOS, OS/2, NT/Win32) |
1 | Amiga |
2 | VMS (OpenVMS) |
3 | Unix |
4 | VM/CMS |
5 | Atari TOS |
6 | HPFS (OS/2, NT) |
7 | Macintosh |
8 | Z-System |
9 | CP/M |
10 | TOPS-20 |
11 | NTFS filesystem (NT) |
12 | QDOS |
13 | Acorn RISCOS |
255 | neznámý |
14. Konfigurace komprimačního algoritmu
Ve skutečnosti lze instanci implementace komprimačního algoritmu Deflate vytvořit dvěma možnými způsoby. První z nich již známe – konstruktoru předáme pouze jiný writer:
gzipWriter := gzip.NewWriter(writer)
Druhý způsob umožňuje specifikovat typ komprimace a její úroveň, například můžeme nastavit nejvyšší úroveň komprimace (která pochopitelně může prodloužit dobu komprimace):
gzipWriter, err := gzip.NewWriterLevel(writer, gzip.BestCompression)
Upravený tvar demonstračního příkladu vypadá následovně:
package main import ( "compress/gzip" "fmt" "log" "os" ) const filename = "test_output.txt.gz" const message = "Hello Hello Hello Hello Hello Hello Hello Hello Hello Hello " func main() { writer, err := os.Create(filename) if err != nil { log.Fatal(err) } defer writer.Close() gzipWriter, err := gzip.NewWriterLevel(writer, gzip.BestCompression) if err != nil { log.Fatal(err) } defer gzipWriter.Close() buffer := []byte(message) written, err := gzipWriter.Write(buffer) if written >= 0 { fmt.Printf("written %d bytes\n", written) } if err != nil { fmt.Printf("I/O error %v\n", err) } }
15. Přímé získání zkomprimovaných dat přes rouru (pipe)
Ve třetí kapitole jsme se seznámili s posíláním dat přes rouru vytvořenou v operační paměti. Podobným způsobem ovšem můžeme získat i zkomprimovaná data, a to bez nutnosti jejich zápisu do souboru. Celý postup zpracování bude následující:
data → gzipWriter → roura → běžný reader → zkomprimovaná data
Opět se podívejme na způsob implementace, který není příliš složitý:
package main import ( "compress/gzip" "fmt" "io" ) const message = "Hello Hello Hello Hello Hello Hello Hello Hello Hello Hello " func main() { reader, writer := io.Pipe() gzipWriter := gzip.NewWriter(writer) go func() { buffer := []byte(message) written, err := gzipWriter.Write(buffer) if written >= 0 { fmt.Printf("written %d bytes\n", written) } if err != nil { fmt.Printf("I/O error %v\n", err) } gzipWriter.Close() writer.Close() }() buffer := make([]byte, 100) for { read, err := reader.Read(buffer) if read > 0 { fmt.Printf("read %d bytes:", read) for i := 0; i < read; i++ { fmt.Printf(" %02x", buffer[i]) } fmt.Println() } if err == io.EOF { fmt.Println("reached end of file") break } if err != nil { fmt.Printf("other error %v\n", err) break } } }
Výsledkem práce tohoto programu bude informace o uložení šedesáti nezkomprimovaných bajtů a o přečtení celkem třiceti dvou bajtů představujících zkomprimovanou zprávu:
read 10 bytes: 1f 8b 08 00 00 00 00 00 00 ff written 60 bytes read 10 bytes: f2 48 cd c9 c9 57 20 8f 04 04 read 4 bytes: 00 00 ff ff read 8 bytes: 73 a9 91 e2 3c 00 00 00 reached end of file
16. Ukázka použití MultiWriteru pro současnou komprimaci dat do více souborů s různě nastaveným algoritmem
Otestování jednotlivých nastavení algoritmu Delfate je možné provést v dalším demonstračním příkladu, v němž je využit již výše popsaný MultiWriter umožňující „rozvětvení“ toku dat. Zapisovaná zpráva je nejdříve rozvětvena a následně je každá větev zkomprimována, ovšem s odlišným nastavením algoritmu:
↗ MultiWriter → gzipWriter#1 → zkomprimovaná data#1 data → MultiWriter → gzipWriter#1 → zkomprimovaná data#1 ↘ MultiWriter → gzipWriter#1 → zkomprimovaná data#1
Samozřejmě opět následuje výpis úplného zdrojového kódu takto upraveného programu:
package main import ( "compress/gzip" "fmt" "io" "log" "os" "strings" ) const filename1 = "test_output.txt.1.gz" const filename2 = "test_output.txt.2.gz" const filename3 = "test_output.txt.3.gz" const filename4 = "test_output.txt.4.gz" func fileWriter(filename string) io.WriteCloser { writer, err := os.Create(filename) if err != nil { log.Fatal(err) } return writer } func gzipWriter(writer io.Writer, compressionLevel int) io.WriteCloser { gzipWriter, err := gzip.NewWriterLevel(writer, compressionLevel) if err != nil { log.Fatal(err) } return gzipWriter } func writeMessage(writer io.Writer, message string) { buffer := []byte(message) written, err := writer.Write(buffer) if written >= 0 { fmt.Printf("written %d bytes\n", written) } if err != nil { fmt.Printf("I/O error %v\n", err) } } func main() { message := strings.Repeat("Hello ", 100) writer1 := fileWriter(filename1) defer writer1.Close() writer2 := fileWriter(filename2) defer writer2.Close() writer3 := fileWriter(filename3) defer writer3.Close() writer4 := fileWriter(filename4) defer writer4.Close() gzipWriter1 := gzipWriter(writer1, gzip.BestCompression) defer gzipWriter1.Close() gzipWriter2 := gzipWriter(writer2, gzip.BestSpeed) defer gzipWriter2.Close() gzipWriter3 := gzipWriter(writer3, gzip.HuffmanOnly) defer gzipWriter3.Close() gzipWriter4 := gzipWriter(writer4, gzip.NoCompression) defer gzipWriter4.Close() writer := io.MultiWriter(gzipWriter1, gzipWriter2, gzipWriter3, gzipWriter4) writeMessage(writer, message) }
Výsledné soubory mají rozdílnou délku:
$ ls -l *.gz -rw-r--r-- 1 tester tester 36 dub 28 09:49 test_output.txt.1.gz -rw-r--r-- 1 tester tester 52 dub 28 09:49 test_output.txt.2.gz -rw-r--r-- 1 tester tester 228 dub 28 09:49 test_output.txt.3.gz -rw-r--r-- 1 tester tester 628 dub 28 09:49 test_output.txt.4.gz
17. Využití hex dumperu (nejenom) pro ladicí účely
V některých situacích je nutné prozkoumat binární data získaná například z dalších zdrojů (síťové protokoly atd.), popř. přímo generovaná vyvíjenou aplikací. Samozřejmě je vždy možné tato data vyexportovat do binárních souborů a použít výše zmíněný nástroj xxd, popř. jeho varianty od, hd (hexdump) apod., ovšem mnohdy je výhodnější si nechat vypsat hexadecimální podobu binárních dat přímo na standardní výstup. A právě k tomuto účelu slouží balíček encoding/hex a v něm definovaný datový typ Dumper (zkrácenina celého názvu hex dumper). Použití tohoto typu je jednoduché, protože implementuje nám již velmi dobře známé rozhraní io.Writer. Jakýkoli zápis binárních dat se převede na hexadecimální podobu, která se následně vypíše do terminálu (výsledek je kompatibilní s nástrojem hexdump při použití přepínače -C („kanonický formát“), popř. přímo při zavolání hd bez přepínače. Samotná instance typu Dumper se vytvoří konstruktorem:
func Dumper(w io.Writer) io.WriteCloser
Použití hex dumperu je snadné a nijak se neliší od dalších typů implementujících rozhraní io.Writer:
writer, err := os.Create(filename) hexDumper := hex.Dumper(writer) buffer := []byte(message) hexDumper.Write(buffer)
Podívejme se nyní na úplný zdrojový kód demonstračního příkladu, který převede řetězec „*** Hello world! ***“ (považovaný za sled bajtů) do kanonické hexadecimální podoby:
package main import ( "encoding/hex" "fmt" "log" "os" ) const filename = "test_output.hex" const message = "*** Hello world! ***" func main() { writer, err := os.Create(filename) if err != nil { log.Fatal(err) } defer writer.Close() hexDumper := hex.Dumper(writer) defer hexDumper.Close() buffer := []byte(message) written, err := hexDumper.Write(buffer) if written >= 0 { fmt.Printf("written %d bytes\n", written) } if err != nil { fmt.Printf("I/O error %v\n", err) } }
Výsledkem běhu tohoto příkladu by měla být tato dvojice řádků vypsaná na standardní výstup:
00000000 2a 2a 2a 20 48 65 6c 6c 6f 20 77 6f 72 6c 64 21 |*** Hello world!| 00000010 20 2a 2a 2a | ***|
18. Konverze binárních dat s využitím kódování Base64
V dnešním posledním demonstračním příkladu si ukážeme způsob použití dalšího datového typu implementujícího rozhraní io.Writer. Jedná se o datový typ Encoding z balíčku encoding/base64, který – jak již jeho název správně napovídá – slouží pro zakódování libovolných binárních dat podle specifikace Base64, která je používána mj. i v několika síťových protokolech. Vždy tři bajty ze vstupních dat (24 bitů) jsou rozděleny po šesti bitech a každá šestice je zakódována do jednoho výstupního znaku podle následující tabulky:
Index | Znak | Index | Znak | Index | Znak | Index | Znak |
---|---|---|---|---|---|---|---|
0 | A | 16 | Q | 32 | g | 48 | w |
1 | B | 17 | R | 33 | h | 49 | x |
2 | C | 18 | S | 34 | i | 50 | y |
3 | D | 19 | T | 35 | j | 51 | z |
4 | E | 20 | U | 36 | k | 52 | 0 |
5 | F | 21 | V | 37 | l | 53 | 1 |
6 | G | 22 | W | 38 | m | 54 | 2 |
7 | H | 23 | X | 39 | n | 55 | 3 |
8 | I | 24 | Y | 40 | o | 56 | 4 |
9 | J | 25 | Z | 41 | p | 57 | 5 |
10 | K | 26 | a | 42 | q | 58 | 6 |
11 | L | 27 | b | 43 | r | 59 | 7 |
12 | M | 28 | c | 44 | s | 60 | 8 |
13 | N | 29 | d | 45 | t | 61 | 9 |
14 | O | 30 | e | 46 | u | 62 | + |
15 | P | 31 | f | 47 | v | 63 | / |
Následně je zajišťěno případné dorovnání posledních bajtů (pokud není délka dat dělitelná třemi) znakem rovnítka.
Zakódování dat do Base64 je snadné:
writer, err := os.Create(filename) base64Encoder := base64.NewEncoder(base64.RawStdEncoding, writer) buffer := []byte(message) written, err := base64Encoder.Write(buffer)
Úplný zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/go-root/blob/master/article23/21_base64_encoder.go:
package main import ( "encoding/base64" "fmt" "log" "os" ) const filename = "test_output.base64" const message = "*** Hello world! ***" func main() { writer, err := os.Create(filename) if err != nil { log.Fatal(err) } defer writer.Close() base64Encoder := base64.NewEncoder(base64.RawStdEncoding, writer) defer base64Encoder.Close() buffer := []byte(message) written, err := base64Encoder.Write(buffer) if written >= 0 { fmt.Printf("written %d bytes\n", written) } if err != nil { fmt.Printf("I/O error %v\n", err) } }
Vstupní řetězec „*** Hello world! ***“ by se měl zkonvertovat do následující sekvence znaků z množiny definované pro BASE64:
KioqIEhlbGxvIHdvcmxkISAqKio
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
- Go Proverbs: Simple, Poetic, Pithy
https://go-proverbs.github.io/ - Handling Sparse Files on Linux
https://www.systutorials.com/136652/handling-sparse-files-on-linux/ - Gzip (Wikipedia)
https://en.wikipedia.org/wiki/Gzip - Deflate
https://en.wikipedia.org/wiki/DEFLATE - 10 tools written in Go that every developer needs to know
https://gustavohenrique.net/en/2019/01/10-tools-written-in-go-that-every-dev-needs-to-know/ - Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
https://www.root.cz/clanky/hexadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/ - Hex dump
https://en.wikipedia.org/wiki/Hex_dump - 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