Hlavní navigace

Pokročilejší použití vstupně-výstupních funkcí standardní knihovny jazyka Go

Pavel Tišnovský

Dnes si ukážeme další (pokročilejší) možnosti, které nám poskytuje standardní knihovna u vstupně-výstupních operací. Popíšeme si komprimaci dat, přesun (seek) v načítaných a ukládaných souborech, tvorbu rour (pipe) atd.

Doba čtení: 41 minut

Sdílet

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)

16. Ukázka použití MultiWriteru pro současnou komprimaci dat do více souborů s různě nastaveným algoritmem

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

20. Odkazy na Internetu

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.

Poznámka: některé demonstrační příklady popsané v navazujících kapitolách vytvářejí binární soubory. Ty je možné si prohlédnout několika způsoby, typicky s využitím hexa prohlížečů, o nichž jsme se zmínili v článku Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním. V dnešním článku budeme konkrétně využívat nástroj xxd popsaný zde. Jak je z výše zmíněného odkazu patrné, je nástroj xxd součástí nástrojů postavených okolo textového editoru Vim, ovšem lze ho používat i zcela samostatně. Jeho předností je možnost obousměrné konverze dat (binární → hexadecimální, hexadecimální → binární).

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()
}
Poznámka: povšimněte si, že se s oběma konci roury skutečně pracuje stejně, jako například s otevřenými soubory atd.

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í)
Poznámka: použitím datového typu int64 se (prozatím) teoreticky omezuje maximální posun, který lze v rámci souboru provést, ovšem prakticky na stejné omezení většinou narazíme již na úrovni operačního systému a/nebo jeho souborového systému.

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
Poznámka: vidíme tedy, že je obsazeno pouze dvanáct bloků a nikoli více než 1900 bloků, které by bylo zapotřebí pro uložení celého neřídkého souboru.

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
Poznámka: takový soubor vytvoříte například ve Vimu po zadání příkazů:
: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...<...
Poznámka: první dva bajty 0×1f 0×8b tvoří magickou konstantu umožňující rozpoznání formátu dat i v případě, že se například nejedná o pojmenovaný soubor, ale o pouhou sekvenci bajtů. Další bajt 0×08 určuje typ komprimace (zde Deflate), následují příznaky souboru, případné časové razítko, a další dva příznaky. Celková délka hlavičky je v tomto případě deset bajtů, samotná zkomprimovaná data začínají až za touto hlavičkou. Hlavička vypadá takto:
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)
Poznámka: povšimněte si, že se v tomto případě kromě samotné instance writeru vrací i hodnota reprezentující případnou chybu.

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_ba­se64_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:

# Soubor Popis Cesta
1 01_io_reader_read.go čtení dat ze souboru s využitím metody Read z rozhraní Reader https://github.com/tisnik/go-root/blob/master/article23/01_i­o_reader_read.go
2 02_io_writer_write.go použití metody Write z rozhraní Writer pro zápis do souboru https://github.com/tisnik/go-root/blob/master/article23/02_i­o_writer_write.go
3 03_memory_pipe.go přenos dat s využitím roury (pipe) alokované v operační paměti https://github.com/tisnik/go-root/blob/master/article23/03_me­mory_pipe.go
4 04_memory_pipe_bytes_buffer.go vylepšení předchozího příkladu https://github.com/tisnik/go-root/blob/master/article23/04_me­mory_pipe_bytes_buffer.go
5 05_simple_file_reader.go čtení dat z řetězce po blocích https://github.com/tisnik/go-root/blob/master/article23/05_sim­ple_file_reader.go
6 06_reader_seeker_absolute.go použití operace Seek pro posun počítaný od začátku souboru https://github.com/tisnik/go-root/blob/master/article23/06_re­ader_seeker_absolute.go
7 07_reader_seeker_from_end.go použití operace Seek pro posun počítaný od konce souboru https://github.com/tisnik/go-root/blob/master/article23/07_re­ader_seeker_from_end.go
8 08_reader_seeker_relative.go použití operace Seek pro relativní posun v souboru https://github.com/tisnik/go-root/blob/master/article23/08_re­ader_seeker_relative.go
9 09_writer_seeker.go použití operace Seek při zápisu do souboru https://github.com/tisnik/go-root/blob/master/article23/09_wri­ter_seeker.go
10 10_writer_seeker_sparse_file.go vytvoření řídkého souboru https://github.com/tisnik/go-root/blob/master/article23/10_wri­ter_seeker_sparse_file.go
11 11_multi_writer.go souběžný zápis do více souborů https://github.com/tisnik/go-root/blob/master/article23/11_mul­ti_writer.go
12 12_string_writer.go zápis řetězců (StringWriter) https://github.com/tisnik/go-root/blob/master/article23/12_strin­g_writer.go
13 13_bufio_reader_read_string.go čtení řetězců pomocí funkcí z balíčku bufio https://github.com/tisnik/go-root/blob/master/article23/13_bu­fio_reader_read_string.go
14 14_bufio_reader_other_file.go čtení řetězců pomocí funkcí z balíčku bufio https://github.com/tisnik/go-root/blob/master/article23/14_bu­fio_reader_other_file.go
15 15_bufio_reader_read_bytes.go čtení sekvence bajtů s využitím funkcí z balíčku bufio https://github.com/tisnik/go-root/blob/master/article23/15_bu­fio_reader_read_bytes.go
16 16_gzip_writer.go komprimace dat metodou Deflate https://github.com/tisnik/go-root/blob/master/article23/16_gzip_wri­ter.go
17 17_gzip_writer_compression_level.go volba metody a úrovně komprimace https://github.com/tisnik/go-root/blob/master/article23/17_gzip_wri­ter_compression_level.go
18 18_gzip_writer_fifo.go přečtení zkomprimovaných dat zpět https://github.com/tisnik/go-root/blob/master/article23/18_gzip_wri­ter_fifo.go
19 19_gzip_multiwriter.go souběžná komprimace dat více metodami https://github.com/tisnik/go-root/blob/master/article23/19_gzip_mul­tiwriter.go
20 20_hex_dumper.go zobrazení libovolných binárních dat ve formě hexadecimálního výpisu https://github.com/tisnik/go-root/blob/master/article23/20_hex_dum­per.go
21 21_base64_encoder.go zakódování binárních dat do tisknutelné podoby podle Base64 https://github.com/tisnik/go-root/blob/master/article23/21_ba­se64_encoder.go

20. Odkazy na Internetu

  1. Go Proverbs: Simple, Poetic, Pithy
    https://go-proverbs.github.io/
  2. Handling Sparse Files on Linux
    https://www.systutorials.com/136652/han­dling-sparse-files-on-linux/
  3. Gzip (Wikipedia)
    https://en.wikipedia.org/wiki/Gzip
  4. Deflate
    https://en.wikipedia.org/wiki/DEFLATE
  5. 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/
  6. Hexadecimální prohlížeče a editory s textovým uživatelským rozhraním
    https://www.root.cz/clanky/he­xadecimalni-prohlizece-a-editory-s-textovym-uzivatelskym-rozhranim/
  7. Hex dump
    https://en.wikipedia.org/wi­ki/Hex_dump
  8. Rozhraní io.ByteReader
    https://golang.org/pkg/io/#ByteReader
  9. Rozhraní io.RuneReader
    https://golang.org/pkg/io/#RuneReader
  10. Rozhraní io.ByteScanner
    https://golang.org/pkg/io/#By­teScanner
  11. Rozhraní io.RuneScanner
    https://golang.org/pkg/io/#Ru­neScanner
  12. Rozhraní io.Closer
    https://golang.org/pkg/io/#Closer
  13. Rozhraní io.Reader
    https://golang.org/pkg/io/#Reader
  14. Rozhraní io.Writer
    https://golang.org/pkg/io/#Writer
  15. Typ Strings.Reader
    https://golang.org/pkg/strin­gs/#Reader
  16. VACUUM (SQL)
    https://www.sqlite.org/lan­g_vacuum.html
  17. VACUUM (Postgres)
    https://www.postgresql.or­g/docs/8.4/sql-vacuum.html
  18. go-cron
    https://github.com/rk/go-cron
  19. gocron
    https://github.com/jasonlvhit/gocron
  20. clockwork
    https://github.com/whiteShtef/cloc­kwork
  21. clockwerk
    https://github.com/onatm/clockwerk
  22. JobRunner
    https://github.com/bamzi/jobrunner
  23. Rethinking Cron
    https://adam.herokuapp.com/pas­t/2010/4/13/rethinking_cron/
  24. In the Beginning was the Command Line
    https://web.archive.org/web/20180218045352/htt­p://www.cryptonomicon.com/be­ginning.html
  25. repl.it (REPL pro různé jazyky)
    https://repl.it/languages
  26. GOCUI – Go Console User Interface (celé uživatelské prostředí, nejenom input box)
    https://github.com/jroimartin/gocui
  27. Read–eval–print loop
    https://en.wikipedia.org/wi­ki/Read%E2%80%93eval%E2%80%93prin­t_loop
  28. go-prompt
    https://github.com/c-bata/go-prompt
  29. readline
    https://github.com/chzyer/readline
  30. A pure golang implementation for GNU-Readline kind library
    https://golangexample.com/a-pure-golang-implementation-for-gnu-readline-kind-library/
  31. go-readline
    https://github.com/fiorix/go-readline
  32. 4 Python libraries for building great command-line user interfaces
    https://opensource.com/article/17/5/4-practical-python-libraries
  33. prompt_toolkit 2.0.3 na PyPi
    https://pypi.org/project/prom­pt_toolkit/
  34. python-prompt-toolkit na GitHubu
    https://github.com/jonathan­slenders/python-prompt-toolkit
  35. The GNU Readline Library
    https://tiswww.case.edu/php/chet/re­adline/rltop.html
  36. GNU Readline (Wikipedia)
    https://en.wikipedia.org/wi­ki/GNU_Readline
  37. readline — GNU readline interface (Python 3.x)
    https://docs.python.org/3/li­brary/readline.html
  38. readline — GNU readline interface (Python 2.x)
    https://docs.python.org/2/li­brary/readline.html
  39. GNU Readline Library – command line editing
    https://tiswww.cwru.edu/php/chet/re­adline/readline.html
  40. gnureadline 6.3.8 na PyPi
    https://pypi.org/project/gnureadline/
  41. Editline Library (libedit)
    http://thrysoee.dk/editline/
  42. Comparing Python Command-Line Parsing Libraries – Argparse, Docopt, and Click
    https://realpython.com/comparing-python-command-line-parsing-libraries-argparse-docopt-click/
  43. libedit or editline
    http://www.cs.utah.edu/~bi­gler/code/libedit.html
  44. WinEditLine
    http://mingweditline.sourceforge.net/
  45. rlcompleter — Completion function for GNU readline
    https://docs.python.org/3/li­brary/rlcompleter.html
  46. rlwrap na GitHubu
    https://github.com/hanslub42/rlwrap
  47. rlwrap(1) – Linux man page
    https://linux.die.net/man/1/rlwrap
  48. readline(3) – Linux man page
    https://linux.die.net/man/3/readline
  49. history(3) – Linux man page
    https://linux.die.net/man/3/history
  50. Dokumentace k balíčku oglematchers
    https://godoc.org/github.com/ja­cobsa/oglematchers
  51. Balíček oglematchers
    https://github.com/jacobsa/o­glematchers
  52. Dokumentace k balíčku ogletest
    https://godoc.org/github.com/ja­cobsa/ogletest
  53. Balíček ogletest
    https://github.com/jacobsa/ogletest
  54. Dokumentace k balíčku assert
    https://godoc.org/github.com/stret­chr/testify/assert
  55. Testify – Thou Shalt Write Tests
    https://github.com/stretchr/testify/
  56. package testing
    https://golang.org/pkg/testing/
  57. Golang basics – writing unit tests
    https://blog.alexellis.io/golang-writing-unit-tests/
  58. An Introduction to Programming in Go / Testing
    https://www.golang-book.com/books/intro/12
  59. An Introduction to Testing in Go
    https://tutorialedge.net/golang/intro-testing-in-go/
  60. Advanced Go Testing Tutorial
    https://tutorialedge.net/go­lang/advanced-go-testing-tutorial/
  61. GoConvey
    http://goconvey.co/
  62. Testing Techniques
    https://talks.golang.org/2014/tes­ting.slide
  63. 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
  64. Afinní transformace
    https://cs.wikibooks.org/wi­ki/Geometrie/Afinn%C3%AD_tran­sformace_sou%C5%99adnic
  65. package gg
    https://godoc.org/github.com/fo­gleman/gg
  66. Generate an animated GIF with Golang
    http://tech.nitoyon.com/en/blog/2016/01/07/­go-animated-gif-gen/
  67. Generate an image programmatically with Golang
    http://tech.nitoyon.com/en/blog/2015/12/31/­go-image-gen/
  68. The Go image package
    https://blog.golang.org/go-image-package
  69. Balíček draw2D: 2D rendering for different output (raster, pdf, svg)
    https://github.com/llgcode/draw2d
  70. Draw a rectangle in Golang?
    https://stackoverflow.com/qu­estions/28992396/draw-a-rectangle-in-golang
  71. YAML
    https://yaml.org/
  72. edn
    https://github.com/edn-format/edn
  73. Smile
    https://github.com/FasterXML/smile-format-specification
  74. Protocol-Buffers
    https://developers.google.com/protocol-buffers/
  75. Marshalling (computer science)
    https://en.wikipedia.org/wi­ki/Marshalling_(computer_sci­ence)
  76. Unmarshalling
    https://en.wikipedia.org/wi­ki/Unmarshalling
  77. Introducing JSON
    http://json.org/
  78. Package json
    https://golang.org/pkg/encoding/json/
  79. The Go Blog: JSON and Go
    https://blog.golang.org/json-and-go
  80. Go by Example: JSON
    https://gobyexample.com/json
  81. Writing Web Applications
    https://golang.org/doc/articles/wiki/
  82. Golang Web Apps
    https://www.reinbach.com/blog/golang-webapps-1/
  83. Build web application with Golang
    https://legacy.gitbook.com/bo­ok/astaxie/build-web-application-with-golang/details
  84. Golang Templates – Golang Web Pages
    https://www.youtube.com/wat­ch?v=TkNIETmF-RU
  85. Simple Golang HTTPS/TLS Examples
    https://github.com/denji/golang-tls
  86. Playing with images in HTTP response in golang
    https://www.sanarias.com/blog/1214Pla­yingwithimagesinHTTPrespon­seingolang
  87. MIME Types List
    https://www.freeformatter.com/mime-types-list.html
  88. Go Mutex Tutorial
    https://tutorialedge.net/golang/go-mutex-tutorial/
  89. Creating A Simple Web Server With Golang
    https://tutorialedge.net/go­lang/creating-simple-web-server-with-golang/
  90. Building a Web Server in Go
    https://thenewstack.io/building-a-web-server-in-go/
  91. How big is the pipe buffer?
    https://unix.stackexchange­.com/questions/11946/how-big-is-the-pipe-buffer
  92. How to turn off buffering of stdout in C
    https://stackoverflow.com/qu­estions/7876660/how-to-turn-off-buffering-of-stdout-in-c
  93. setbuf(3) – Linux man page
    https://linux.die.net/man/3/setbuf
  94. setvbuf(3) – Linux man page (stejný obsah jako předchozí stránka)
    https://linux.die.net/man/3/setvbuf
  95. Select waits on a group of channels
    https://yourbasic.org/golang/select-explained/
  96. Rob Pike: Simplicity is Complicated (video)
    http://www.golang.to/posts/dotgo-2015-rob-pike-simplicity-is-complicated-youtube-16893
  97. Algorithms to Go
    https://yourbasic.org/
  98. Využití knihovny Pygments (nejenom) pro obarvení zdrojových kódů
    https://www.root.cz/clanky/vyuziti-knihovny-pygments-nejenom-pro-obarveni-zdrojovych-kodu/
  99. 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/
  100. Go Defer Simplified with Practical Visuals
    https://blog.learngoprogram­ming.com/golang-defer-simplified-77d3b2b817ff
  101. 5 More Gotchas of Defer in Go — Part II
    https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa
  102. The Go Blog: Defer, Panic, and Recover
    https://blog.golang.org/defer-panic-and-recover
  103. The defer keyword in Swift 2: try/finally done right
    https://www.hackingwithswift.com/new-syntax-swift-2-defer
  104. Swift Defer Statement
    https://andybargh.com/swift-defer-statement/
  105. Modulo operation (Wikipedia)
    https://en.wikipedia.org/wi­ki/Modulo_operation
  106. Node.js vs Golang: Battle of the Next-Gen Languages
    https://www.hostingadvice­.com/blog/nodejs-vs-golang/
  107. The Go Programming Language (home page)
    https://golang.org/
  108. GoDoc
    https://godoc.org/
  109. Go (programming language), Wikipedia
    https://en.wikipedia.org/wi­ki/Go_(programming_langua­ge)
  110. Go Books (kniha o jazyku Go)
    https://github.com/dariubs/GoBooks
  111. The Go Programming Language Specification
    https://golang.org/ref/spec
  112. Go: the Good, the Bad and the Ugly
    https://bluxte.net/musings/2018/04/10/go-good-bad-ugly/
  113. Package builtin
    https://golang.org/pkg/builtin/
  114. Package fmt
    https://golang.org/pkg/fmt/
  115. The Little Go Book (další kniha)
    https://github.com/dariubs/GoBooks
  116. The Go Programming Language by Brian W. Kernighan, Alan A. A. Donovan
    https://www.safaribookson­line.com/library/view/the-go-programming/9780134190570/e­book_split010.html
  117. Learning Go
    https://www.miek.nl/go/
  118. Go Bootcamp
    http://www.golangbootcamp.com/
  119. Programming in Go: Creating Applications for the 21st Century (další kniha o jazyku Go)
    http://www.informit.com/sto­re/programming-in-go-creating-applications-for-the-21st-9780321774637
  120. Introducing Go (Build Reliable, Scalable Programs)
    http://shop.oreilly.com/pro­duct/0636920046516.do
  121. Learning Go Programming
    https://www.packtpub.com/application-development/learning-go-programming
  122. The Go Blog
    https://blog.golang.org/
  123. Getting to Go: The Journey of Go's Garbage Collector
    https://blog.golang.org/ismmkeynote
  124. Go (programovací jazyk, Wikipedia)
    https://cs.wikipedia.org/wi­ki/Go_(programovac%C3%AD_ja­zyk)
  125. Rychle, rychleji až úplně nejrychleji s jazykem Go
    https://www.root.cz/clanky/rychle-rychleji-az-uplne-nejrychleji-s-jazykem-go/
  126. Installing Go on the Raspberry Pi
    https://dave.cheney.net/2012/09/25/in­stalling-go-on-the-raspberry-pi
  127. 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
  128. Niečo málo o Go – Golang (slovensky)
    http://golangsk.logdown.com/
  129. How Many Go Developers Are There?
    https://research.swtch.com/gop­hercount
  130. Most Popular Technologies (Stack Overflow Survery 2018)
    https://insights.stackover­flow.com/survey/2018/#most-popular-technologies
  131. Most Popular Technologies (Stack Overflow Survery 2017)
    https://insights.stackover­flow.com/survey/2017#techno­logy
  132. JavaScript vs. Golang for IoT: Is Gopher Winning?
    https://www.iotforall.com/javascript-vs-golang-iot/
  133. The Go Programming Language: Release History
    https://golang.org/doc/de­vel/release.html
  134. Go 1.11 Release Notes
    https://golang.org/doc/go1.11
  135. Go 1.10 Release Notes
    https://golang.org/doc/go1.10
  136. Go 1.9 Release Notes (tato verze je stále používána)
    https://golang.org/doc/go1.9
  137. Go 1.8 Release Notes (i tato verze je stále používána)
    https://golang.org/doc/go1.8
  138. Go on Fedora
    https://developer.fedorapro­ject.org/tech/languages/go/go-installation.html
  139. Writing Go programs
    https://developer.fedorapro­ject.org/tech/languages/go/go-programs.html
  140. The GOPATH environment variable
    https://tip.golang.org/doc/co­de.html#GOPATH
  141. Command gofmt
    https://tip.golang.org/cmd/gofmt/
  142. The Go Blog: go fmt your code
    https://blog.golang.org/go-fmt-your-code
  143. C? Go? Cgo!
    https://blog.golang.org/c-go-cgo
  144. 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/
  145. 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
  146. Gofmt No Longer Allows Spaces. Tabs Only
    https://news.ycombinator.com/i­tem?id=7914523
  147. Why does Go „go fmt“ uses tabs instead of whitespaces?
    https://www.quora.com/Why-does-Go-go-fmt-uses-tabs-instead-of-whitespaces
  148. Interactive: The Top Programming Languages 2018
    https://spectrum.ieee.org/sta­tic/interactive-the-top-programming-languages-2018
  149. Go vs. Python
    https://www.peterbe.com/plog/govspy
  150. PackageManagementTools
    https://github.com/golang/go/wi­ki/PackageManagementTools
  151. A Tour of Go: Type inference
    https://tour.golang.org/basics/14
  152. Go Slices: usage and internals
    https://blog.golang.org/go-slices-usage-and-internals
  153. Go by Example: Slices
    https://gobyexample.com/slices
  154. What is the point of slice type in Go?
    https://stackoverflow.com/qu­estions/2098874/what-is-the-point-of-slice-type-in-go
  155. The curious case of Golang array and slices
    https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335
  156. Introduction to Slices in Golang
    https://www.callicoder.com/golang-slices/
  157. Golang: Understanding ‚null‘ and nil
    https://newfivefour.com/golang-null-nil.html
  158. What does nil mean in golang?
    https://stackoverflow.com/qu­estions/35983118/what-does-nil-mean-in-golang
  159. nils In Go
    https://go101.org/article/nil.html
  160. Go slices are not dynamic arrays
    https://appliedgo.net/slices/
  161. Go-is-no-good (nelze brát doslova)
    https://github.com/ksimka/go-is-not-good
  162. Rust vs. Go
    https://news.ycombinator.com/i­tem?id=13430108
  163. Seriál Programovací jazyk Rust
    https://www.root.cz/seria­ly/programovaci-jazyk-rust/
  164. Modern garbage collection: A look at the Go GC strategy
    https://blog.plan99.net/modern-garbage-collection-911ef4f8bd8e
  165. Go GC: Prioritizing low latency and simplicity
    https://blog.golang.org/go15gc
  166. Is Golang a good language for embedded systems?
    https://www.quora.com/Is-Golang-a-good-language-for-embedded-systems
  167. Running GoLang on an STM32 MCU. A quick tutorial.
    https://www.mickmake.com/post/running-golang-on-an-mcu-a-quick-tutorial
  168. Go, Robot, Go! Golang Powered Robotics
    https://gobot.io/
  169. Emgo: Bare metal Go (language for programming embedded systems)
    https://github.com/ziutek/emgo
  170. UTF-8 history
    https://www.cl.cam.ac.uk/~mgk25/uc­s/utf-8-history.txt
  171. Less is exponentially more
    https://commandcenter.blog­spot.com/2012/06/less-is-exponentially-more.html
  172. Should I Rust, or Should I Go
    https://codeburst.io/should-i-rust-or-should-i-go-59a298e00ea9
  173. Setting up and using gccgo
    https://golang.org/doc/install/gccgo
  174. Elastic Tabstops
    http://nickgravgaard.com/elastic-tabstops/
  175. Strings, bytes, runes and characters in Go
    https://blog.golang.org/strings
  176. Datový typ
    https://cs.wikipedia.org/wi­ki/Datov%C3%BD_typ
  177. Seriál o programovacím jazyku Rust: Základní (primitivní) datové typy
    https://www.root.cz/clanky/pro­gramovaci-jazyk-rust-nahrada-c-nebo-slepa-cesta/#k09
  178. 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
  179. Seriál o programovacím jazyku Rust: Řezy (slice) vektoru
    https://www.root.cz/clanky/prace-s-vektory-v-programovacim-jazyku-rust/#k05
  180. Printf Format Strings
    https://www.cprogramming.com/tu­torial/printf-format-strings.html
  181. Java: String.format
    https://docs.oracle.com/ja­vase/8/docs/api/java/lang/Strin­g.html#format-java.lang.String-java.lang.Object…-
  182. Java: format string syntax
    https://docs.oracle.com/ja­vase/8/docs/api/java/util/For­matter.html#syntax
  183. Selectors
    https://golang.org/ref/spec#Selectors
  184. Calling Go code from Python code
    http://savorywatt.com/2015/09/18/ca­lling-go-code-from-python-code/
  185. Go Data Structures: Interfaces
    https://research.swtch.com/interfaces
  186. How to use interfaces in Go
    http://jordanorelli.com/pos­t/32665860244/how-to-use-interfaces-in-go
  187. Interfaces in Go (part I)
    https://medium.com/golangspec/in­terfaces-in-go-part-i-4ae53a97479c
  188. Part 21: Goroutines
    https://golangbot.com/goroutines/
  189. Part 22: Channels
    https://golangbot.com/channels/
  190. [Go] Lightweight eventbus with async compatibility for Go
    https://github.com/asaske­vich/EventBus
  191. What about Trait support in Golang?
    https://www.reddit.com/r/go­lang/comments/8mfykl/what_a­bout_trait_support_in_golan­g/
  192. 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/
  193. Control Flow
    https://en.wikipedia.org/wi­ki/Control_flow
  194. Structured programming
    https://en.wikipedia.org/wi­ki/Structured_programming
  195. Control Structures
    https://www.golang-book.com/books/intro/5
  196. Control structures – Go if else statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-if-else-statement.html
  197. Control structures – Go switch case statement
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-switch-case.html
  198. Control structures – Go for loop, break, continue, range
    http://golangtutorials.blog­spot.com/2011/06/control-structures-go-for-loop-break.html
  199. Single Function Exit Point
    http://wiki.c2.com/?Single­FunctionExitPoint
  200. Entry point
    https://en.wikipedia.org/wi­ki/Entry_point
  201. Why does Go have a GOTO statement?!
    https://www.reddit.com/r/go­lang/comments/kag5q/why_do­es_go_have_a_goto_statemen­t/
  202. Effective Go
    https://golang.org/doc/ef­fective_go.html
  203. GoClipse: an Eclipse IDE for the Go programming language
    http://goclipse.github.io/
  204. GoClipse Installation
    https://github.com/GoClip­se/goclipse/blob/latest/do­cumentation/Installation.md#in­stallation