Hlavní navigace

Vstupně-výstupní funkce standardní knihovny programovacího jazyka Go

24. 4. 2019
Doba čtení: 34 minut

Sdílet

 Autor: Go Lang
V dnešním článku o jazyku Go si přiblížíme další možnosti poskytované jeho standardní knihovnou. Nejprve se budeme zabývat systémem pro vstup a výstup dat, což ve skutečnosti není tak triviální téma, jak by se mohlo zdát.

Obsah

1. Vstupně-výstupní funkce standardní knihovny programovacího jazyka Go

2. Nejdůležitější rozhraní používaná vstupně-výstupním systémem

3. Rozhraní io.ByteReader

4. Vylepšení testu na zdetekovaný konec dat

5. Bajty versus znaky

6. Rozhraní io.RuneReader

7. Rozhraní io.ByteWriter

8. Zápis jednotlivých znaků metodou WriteRune

9. Rozhraní io.ByteScanner

10. Vícenásobné vrácení bajtu do vstupu

11. Rozhraní io.RuneScanner

12. Rozhraní io.Reader

13. Čtení bloku bajtů ze standardního vstupu a z řetězce

14. Spojení více vstupů – multireader

15. Rozhraní io.Writer

16. Kopie souborů po blocích

17. Načtení celého řádku ze standardního vstupu

18. Skládání rozhraní souvisejících se vstupně-výstupními operacemi

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Vstupně-výstupní funkce standardní knihovny programovacího jazyka Go

Již v předchozích částech seriálu o programovacím jazyku Go jsme se seznámili s některými balíčky patřícími do standardní knihovny tohoto jazyka. Ve skutečnosti je standardní knihovna poměrně rozsáhlá a obsahuje funkce a datové typy používané především pro tvorbu síťových služeb, webových služeb, mikroslužeb, utilit používaných systémovými administrátory, aplikací ovládaných z příkazové řádky (CLI) apod. Naopak ve standardní knihovně nenalezneme například podporu pro tvorbu plnohodnotných desktopových aplikací s grafickým uživatelským rozhraním, protože (alespoň prozatím) používá většina Go aplikací s uživatelským rozhraním možností poskytovaných moderními webovými prohlížeči (což ostatně pro jazyk vyvinutý primárně ve společnosti Google pravděpodobně dává smysl, i když se v delším časovém horizontu může jednat o dvousečnou zbraň – ostatně historie IT zná už mnoho jednoúčelových programovacích jazyků, které se mimo svou niku nijak významně nerozšířily).

V dnešním článku si přiblížíme některé další možnosti poskytované standardní knihovnou. Nejprve se budeme zabývat systémem používaným pro vstup a výstup dat (a zdaleka se nejedná pouze o operace nad soubory). Mohlo by se možná zdát, že se jedná o triviální téma, ovšem například zkušenosti z jiných jazyků (příkladem může být Java a jejich několik generací IO a NIO knihoven) ukazují, že dobře navržená a především pak rozšiřitelná IO knihovna je pro další rozvoj jazyka velmi užitečná a důležitá. V jazyku Go je celý systém vstupně-výstupních operací založen na několika rozhraních, jejichž metody mohou být (a ve skutečnosti i jsou) implementovány hned několika různými způsoby a použít je lze pro různé účely. Většina těchto rozhraní obsahuje pouze jedinou metodu, což je ovšem v programovacím jazyku Go velmi často používané řešení, s nímž se setkáme nejenom ve standardní knihovně.

To, že mnoho dále popsaných rozhraní obsahuje předpis pouze jediné metody má i další praktický dopad – pokud budeme potřebovat vytvořit další mechanismus používaný z pohledu programátora stejným způsobem, jako další vstupně-výstupní prostředky (soubory, síť, obsah archivu atd.), postačuje nadeklarovat nový datový typ a pro něj jedinou metodu z vybraného rozhraní (například ByteReader, Reader či ReadWriter. Nový takto navržený mechanismus bude od této chvíle možné do aplikací zařadit bez nutnosti jejich dalších modifikací.

2. Nejdůležitější rozhraní používaná vstupně-výstupním systémem

V následující tabulce jsou zmíněny základní informace o rozhraních, s nimiž se prakticky vždy setkáme při práci se vstupně-výstupním systémem základní knihovny programovacího jazyka Go:

Typ operace Čtení Zápis
jednotlivých bajtů io.ByteReader io.ByteWriter
jednotlivých znaků io.RuneReader ×
     
jednotlivých bajtů s bufferem io.ByteScanner ×
jednotlivých znaků s bufferem io.RuneScanner ×
     
bloku bajtů io.Reader io.Writer
Poznámka: připomeňme si, že v programovacím jazyce Go pro implementaci nějakého rozhraní postačuje pouze vytvořit metodu, jejíž hlavička odpovídá metodě předepsané v rozhraní. Nemusíme tedy nikde uvádět (ostatně neexistující) klíčové slovo implements tak, jako je tomu v některých jiných programovacích jazycích.

3. Rozhraní io.ByteReader

Základním rozhraním, které může být implementováno pro prakticky libovolné vstupní či vstupně-výstupní zařízení, je rozhraní nazvané ByteReader. Toto rozhraní předepisuje jedinou metodu nazvanou ReadByte, která pochopitelně slouží k načtení jednoho bajtu ze vstupu. Pokud už další bajt nelze z nějakého důvodu načíst, například proto, že se dosáhlo konce dat nebo došlo k nějaké vstupně-výstupní chybě (výpadek připojení, odpojení souborového systému, skutečná chyba na fyzické datové vrstvě…), vrátí se ve druhé návratové hodnotě informace o chybě a první vrácená hodnota z metody ReadByte není obecně definována (musíme tedy počítat s tím, že může obsahovat jakoukoli hodnotu):

type ByteReader interface {
        ReadByte() (byte, error)
}
Poznámka: povšimněte si, že toto rozhraní nepředpokládá žádné další vlastnosti vstupního zařízení – to nemusí umět například vrátit bajt zpátky do vstupního bufferu (pokud takový buffer vůbec existuje) a už vůbec není zapotřebí umět v datech provádět operaci typu seek nebo rozpoznávat jednotlivé znaky (v Unicode, konkrétně v kódování UTF-8). Z tohoto důvodu nalezneme metodu ReadByte (a tím pádem i celé rozhraní ByteReader) například v implementacích dekomprimačních algoritmů, u některých síťových protokolů atd. Dále se explicitně nepředpokládá operace typu Close; tu je nutné v případě potřeby implementovat přes další rozhraní.

Demonstrační příklad používající rozhraní nazvané ByteReader a jeho metodu ReadByte je ve skutečnosti velmi jednoduchý. Využijeme zde faktu, že metoda ReadByte je implementována i datovým typem nazvaným Reader, tentokrát ovšem definovaným v balíčku strings a nikoli v balíčku io. Konstruktor tohoto datového typu se volá funkcí NewReader, které předáme řetězec použitý jako zdroj dat:

reader := strings.NewReader("Hello world!")

Povšimněte si, jakým způsobem je realizován test na to, zda řetězec, z něhož načítáme jednotlivé bajty, ještě obsahuje nějaká data či zda jsme došli až na konec řetězce:

b, err := reader.ReadByte()
if err == nil {
        fmt.Printf("%c", b)
} else {
        fmt.Printf("\nerror %v", err)
        break
}

Následuje výpis úplného zdrojového kódu tohoto demonstračního příkladu:

package main
 
import (
        "fmt"
        "strings"
)
 
func main() {
        reader := strings.NewReader("Hello world!")
        for {
                b, err := reader.ReadByte()
                if err == nil {
                        fmt.Printf("%c", b)
                } else {
                        fmt.Printf("\nerror %v", err)
                        break
                }
        }
}

Příklad výstupu:

Hello world!
error EOF

4. Vylepšení testu na zdetekovaný konec dat

Ve skutečnosti ovšem můžeme test na to, zda jsme na vstupu došli až na konec dat, napsat daleko explicitněji, a to kontrolou, jaká konkrétní chybová hodnota se vrátila z metody ReadByte. V případě, že se vrátila hodnota odpovídající konstantě io.EOF, nejedná se o skutečnou chybu, ale o „obyčejný“ konec dat, který většinou budeme chtít zpracovat jiným způsobem, než odlišný typ chyby. Tato úprava zdrojového kódu je provedena v další verzi demonstračního příkladu, jenž je vypsán pod tímto odstavcem:

package main
 
import (
        "fmt"
        "io"
        "strings"
)
 
func main() {
        reader := strings.NewReader("Hello world!")
        for {
                b, err := reader.ReadByte()
                if err == io.EOF {
                        fmt.Println("\nEOF detected")
                        break
                }
                if err == nil {
                        fmt.Printf("%c", b)
                } else {
                        fmt.Printf("\nerror %v", err)
                        break
                }
        }
}

Opět si ukažme příklad výstupu, který bude od prvního příkladu nepatrně odlišný:

Hello world!
EOF detected

5. Bajty versus znaky

Další komplikace při praktickém používání vstupně-výstupních operací může nastat v případě, kdy budeme potřebovat ze vstupu načítat celé znaky a nikoli jednotlivé dále nezpracovávané bajty. Připomeňme si, že v programovacím jazyku Go existuje striktní rozlišení mezi bajty a znaky, přičemž znaky jsou reprezentovány datovým typem nazvaným rune, protože je nutné rozpoznávat celý rozsah Unicode a nikoli už pouhé ASCII, které je jen nepatrnou (i když velmi důležitou) podmnožinou Unicode. Nejdříve se podívejme na způsob, jakým se vlastně zpracovávají jednotlivé bajty metodou pojmenovanou ReadByte v tom případě, kdy zdroj dat obsahuje znaky Unicode (samotný zdrojový kód napsaný v Go přitom používá kódování UTF-8, tj. jedno z nejčastějších kódování Unicode). Postačí nám nepatrná úprava předchozího demonstračního příkladu do modifikovaného tvaru:

package main
 
import (
        "fmt"
        "io"
        "strings"
)
 
func main() {
        reader := strings.NewReader("* ěščř ½µ§я¤ *")
        for {
                b, err := reader.ReadByte()
                if err == io.EOF {
                        fmt.Println("\nEOF detected")
                        break
                }
                if err == nil {
                        fmt.Printf("%02x ", b)
                } else {
                        fmt.Printf("\nerror %v", err)
                        break
                }
        }
}

Výsledkem by měla být sekvence bajtů vypsaných v hexadecimální soustavě:

2a 20 c4 9b c5 a1 c4 8d c5 99 20 c2 bd c2 b5 c2 a7 d1 8f c2 a4 20 2a
EOF detected
Poznámka: počet vypsaných hodnot a tedy i počet načtených bajtů je samozřejmě v tomto případě numericky vyšší, než počet znaků ve vstupním řetězci. Obě hodnoty by byly shodné jen ve chvíli, kdyby řetězec obsahoval pouze ASCII znaky.

6. Rozhraní io.RuneReader

V případě, že skutečně budeme chtít postupně načítat jednotlivé znaky a nikoli bajty, je výhodnější namísto rozhraní ByteReader využít spíše rozhraní nazvané RuneReader s jedinou předepsanou metodou, jejíž jméno je ReadRune. Tato metoda vrací jak načtený znak, tak i jeho velikost reprezentovanou v bajtech (přičemž znaky z původní ASCII jsou reprezentovány jedním bajtem, ostatní znaky pak více bajty). V případě, že se načtení dalšího znaku nezdaří, vrátí se ve třetí návratové hodnotě chyba (a první dvě návratové hodnoty by se neměly dále zpracovávat):

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

Samozřejmě si opět všechno vyzkoušíme v demonstračním příkladu, v němž pro jednoduchost opět použijeme objekt typu Reader, jehož zdrojem dat je obyčejný řetězec:

package main
 
import (
        "fmt"
        "io"
        "strings"
)
 
func main() {
        reader := strings.NewReader("* ěščř ½µ§я¤ *")
        for {
                c, size, err := reader.ReadRune()
                if err == io.EOF {
                        fmt.Println("\nEOF detected")
                        break
                }
                if err == nil {
                        fmt.Printf("%c %d\n", c, size)
                } else {
                        fmt.Printf("\nerror %v", err)
                        break
                }
        }
}

V tomto případě by měl výsledek práce tohoto demonstračního příkladu vypadat následovně:

* 1
  1
ě 2
š 2
č 2
ř 2
  1
½ 2
µ 2
§ 2
я 2
¤ 2
  1
* 1
 
EOF detected
Poznámka: povšimněte si, že ASCII znaky jsou skutečně reprezentovány jediným bajtem, kdežto ostatní znaky více bajty.

7. Rozhraní io.ByteWriter

Opakem rozhraní ByteReader, s nímž jsme se seznámili v předchozích kapitolách, je rozhraní, které se – což asi nebude příliš překvapující – jmenuje ByteWriter. Toto rozhraní předepisuje pouze jedinou metodu s názvem WriteByte, která slouží k zápisu bajtu do výstupního zařízení (či libovolného výstupního mechanismu) a vrací hodnotu obsahující popis případné chyby, která může při zápisu nastat:

type ByteWriter interface {
        WriteByte(c byte) error
}

Toto rozhraní je implementováno například datovým typem se jménem Buffer ze standardního balíčku bytes. Samotný buffer umožňuje zápis a čtení na úrovni jednotlivých bajtů či znaků do operační paměti, přičemž velikost bufferu se dynamicky podle potřeb mění. V následujícím demonstračním příkladu je buffer zpočátku prázdný a hodnoty (jednotlivé bajty) do něj zapíšeme právě metodou WriteByte a zpětně je pro kontrolu přečteme metodou ReadByte, kterou již dobře známe z předchozího textu:

package main
 
import (
        "bytes"
        "fmt"
)
 
func main() {
        buffer := bytes.Buffer{}
        buffer.WriteByte(65)
 
        b, _ := buffer.ReadByte()
        fmt.Printf("%02x\n", b)
 
        buffer.WriteByte(0xff)
 
        b, _ = buffer.ReadByte()
        fmt.Printf("%02x\n", b)
}

Příklad výstupu:

41
ff

V předchozím odstavci jsme si řekli, že buffer umožňuje, aby se čtení a zápis prováděly na úrovni jednotlivých bajtů či znaků. To nám umožňuje i kombinaci obou přístupů, tj. například zápis bajtů, které se následně pokusíme přečíst jako jednotlivé znaky Unicode zakódované pomocí UTF-8. Vyzkoušejme si to na dalším příkladu:

package main
 
import (
        "bytes"
        "fmt"
)
 
func main() {
        buffer := bytes.Buffer{}
        buffer.WriteByte(65)
 
        c, size, err := buffer.ReadRune()
        fmt.Printf("%c %d %v\n", c, size, err)
 
        buffer.WriteByte(0xc4)
        buffer.WriteByte(0x9b)
 
        c, size, err = buffer.ReadRune()
        fmt.Printf("%c %d %v\n", c, size, err)
 
        c, size, err = buffer.ReadRune()
        fmt.Printf("%c %d %v\n", c, size, err)
}

Výsledek činnosti tohoto demonstračního příkladu by měl vypadat následovně:

A 1 <nil>
ě 2 <nil>
 0 EOF
Poznámka: povšimněte si, že na posledním řádku se ve skutečnosti žádný znak nevrátil – naopak se vrátila chybová hodnota oznamující, že buffer je prázdný a čtení tedy nelze provést.

8. Zápis jednotlivých znaků metodou WriteRune

Vzhledem k tomu, že existují standardní rozhraní io.ByteWriter, io.ByteReader a io.RuneReader, mohlo by se očekávat, že bude existovat i rozhraní pojmenované io.RuneWriter. Ve skutečnosti tomu tak není, i když například již výše zmíněný typ Buffer z balíčku bytes obsahuje metodu nazvanou WriteRune, která by byla předepsána právě teoretickým rozhraním io.RuneWriter. Ostatně si chování této metody můžeme velmi snadno otestovat, a to překladem a spuštěním dalšího demonstračního příkladu, jehož zdrojový kód vypadá takto:

package main
 
import (
        "bytes"
        "fmt"
)
 
func main() {
        buffer := bytes.Buffer{}
        buffer.WriteRune('a')
        buffer.WriteRune('ě')
        buffer.WriteRune('я')
 
        for {
                b, err := buffer.ReadByte()
                if err == nil {
                        fmt.Printf("%02x ", b)
                } else {
                        fmt.Printf("\nerror %v", err)
                        break
                }
        }
}

Tento příklad by měl po svém spuštění vypsat následující trojici řádků:

A 1 <nil>
ě 2 <nil>
 0 EOF
Poznámka: namísto rozhraní RuneWriter se většinou používají funkce a metody sloužící pro zápis celých řetězců, popř. je možné znaky před jejich zápisem převést na sekvenci bajtů. S tímto přístupem se setkáme později.

9. Rozhraní io.ByteScanner

Další dvojice rozhraní, s nimiž se v dnešním článku alespoň ve stručnosti seznámíme, se jmenuje io.ByteScanner a io.RuneScanner. Rozhraní io.ByteScanner je založeno na již popsaném rozhraní io.ByteReader, ovšem k metodě ReadByte je navíc přidána i metoda pojmenovaná UnreadByte:

type ByteScanner interface {
        ByteReader
        UnreadByte() error
}

Tato metoda – pokud ji samozřejmě nějaký datový typ implementuje – zajistí, že další volání metody ReadByte vrátí stejný bajt, jako předchozí volání této metody. Jak je popsané chování interně zajištěno, již pochopitelně do značné míry závisí na tom, jaké zařízení či vstupní mechanismus je použit. Někde je možné použít vstupní buffer, jindy se zapamatuje pouze poslední načítaný znak atd. Většinou není možné metodu UnreadByte zavolat vícekrát za sebou, a to právě z toho důvodu, že její interní implementace si pamatuje pouze poslední čtený znak.

Poznámka: některé implementace, například ty založené na typu Buffer, naopak umožňují prakticky libovolné volání metody UnreadByte, protože je tato metoda implementována pouhým posunem indexu či ukazatele v poli reprezentujícím vstupní data. Jedná se však spíše o výjimku, než o pravidlo a na možnost vícenásobného volání UnreadByte se není obecně možné spoléhat.

Tato dvě rozhraní jsou velmi užitečná, protože v mnoha algoritmech se při zpracování vstupních dat dozvíme, že například nějaký blok již skončil, až ve chvíli, kdy přečteme následující bajt či znak, který již má logicky patřit do dalšího bloku. Čistě programové řešení by většinou bylo zbytečně komplikované, takže použití rozhraní ByteScanner a RuneScanner je snadnější a čistější.

V dalším demonstračním příkladu si ukážeme použití metody UnreadByte při čtení bajtů (nikoli celých znaků!) z řetězce představujícího zdroj dat pro objekt typu strings.Reader. Úplný zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/go-root/blob/master/article22/08_un­read_byte.go:

package main
 
import (
        "fmt"
        "strings"
)
 
func main() {
        reader := strings.NewReader("Hello world!")
        cnt := 0
 
        for {
                b, err := reader.ReadByte()
                if err == nil {
                        fmt.Printf("%c", b)
                } else {
                        fmt.Printf("\nerror %v", err)
                        break
                }
                cnt++
                if cnt == 5 || cnt == 10 || cnt == 14 || cnt == 15 {
                        reader.UnreadByte()
                }
        }
}

Z výpisu produkovaného tímto příkladem je patrné, že se skutečně podařilo zdvojit pátý, desátý, čtrnáctý a patnáctý bajt v řetězci:

Helloo worrld!!!
error EOF

10. Vícenásobné vrácení bajtu do vstupu

Objekt typu strings.Reader ve skutečnosti umožňuje několikanásobné zpětné vložení bajtu, protože se interně jedná pouze o posun ukazatele ve vstupních datech. Můžeme tedy napsat například:

if cnt == 6 {
        for i := 0; i <= 6; i++ {
                reader.UnreadByte()
        }
}

Chování takto upraveného příkladu si samozřejmě můžeme otestovat; zde je jeho úplný zdrojový kód:

package main
 
import (
        "fmt"
        "strings"
)
 
func main() {
        reader := strings.NewReader("Hello world!")
        cnt := 0
 
        for {
                b, err := reader.ReadByte()
                if err == nil {
                        fmt.Printf("%c", b)
                } else {
                        fmt.Printf("\nerror %v", err)
                        break
                }
                cnt++
                if cnt == 6 {
                        for i := 0; i <= 6; i++ {
                                reader.UnreadByte()
                        }
                }
        }
}

Výsledek činnosti tohoto demonstračního příkladu by měl vypadat následovně:

Hello Hello world!
Poznámka: povšimněte si, že se v tomto případě ve skutečnosti skutečně vrátila zpět operace čtení bajtu, nedošlo tedy k několikanásobnému vložení stejného bajtu do řetězce (což by platilo, pokud by čtení nenávratně mazalo vstupní data a systém by si pamatoval jen poslední načtený bajt), ale k posunu ukazatele (r.i) na jeho začátek.
func (r *Reader) UnreadByte() error {
        r.prevRune = -1
        if r.i <= 0 {
                return errors.New("strings.Reader.UnreadByte: at beginning of string")
        }
        r.i--
        return nil
}

11. Rozhraní io.RuneScanner

Další rozhraní se stejnou filozofií, jako má výše popsané rozhraní io.ByteScanner, se jmenuje RuneScanner. Toto rozhraní je odvozeno od io.RuneReader, ovšem navíc je do něj přidána metoda určená pro vrácení posledně čteného znaku/runy:

type RuneScanner interface {
        RuneReader
        UnreadRune() error
}

Použití tohoto rozhraní je snadné, což si ostatně můžeme ukázat v pořadí již desátém příkladu:

package main
 
import (
        "fmt"
        "io"
        "strings"
)
 
func main() {
        reader := strings.NewReader("* ěščř ½µ§я¤ *")
        cnt := 0
 
        for {
                c, size, err := reader.ReadRune()
                if err == io.EOF {
                        fmt.Println("\nEOF detected")
                        break
                }
                if err == nil {
                        fmt.Printf("%c %d\n", c, size)
                } else {
                        fmt.Printf("\nerror %v", err)
                        break
                }
                cnt++
                if cnt == 5 || cnt == 10 || cnt == 14 || cnt == 15 {
                        reader.UnreadRune()
                }
        }
}

Na výstupu můžeme vidět, že se některé znaky/runy skutečně načetly a vypsaly vícekrát. Zdvojené a ztrojené znaky jsou zvýrazněny:

* 1
  1
ě 2
š 2
č 2
č 2
ř 2
  1
½ 2
µ 2
µ 2
§ 2
я 2
¤ 2
¤ 2
¤ 2
  1
* 1
 
EOF detected

12. Rozhraní Reader

Ve standardní knihovně programovacího jazyka Go nalezneme i další dvě důležitá rozhraní, s nimiž se dokonce v praxi setkáme většinou mnohem častěji, než s výše zmíněnými rozhraními ByteReader a ByteWriter. První z těchto rozhraní předepisuje jedinou metodu určenou pro bufferované čtení dat z prakticky libovolného vstupního mechanismu (zařízení, souboru, roury atd.), u něhož se předpokládá, že dokáže načítat blok dat a rozeznávat jejich případný konec. Toto rozhraní se jmenuje jednoduše Reader a jediná metoda tohoto rozhraní má jméno Read:

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

Počet načítaných bajtů závisí na velikosti/kapacitě pole bajtů a taktéž na tom, kolik bajtů se ještě na vstupu nachází. Ideálně se vždy načte tolik bajtů, kolik odpovídá kapacitě předaného pole, ovšem například na konci souboru (pokud provádíme načítání ze souboru) to bude méně. Počet skutečně načtených bajtů získáme snadno – z první návratové hodnoty.

Podívejme se nyní na typické použití tohoto rozhraní při načítání dat ze vstupního souboru. Soubor nejdříve otevřeme a zajistíme jeho uzavření na konci funkce:

fin, err := os.Open(filename)
if err != nil {
        log.Fatal(err)
}
defer fin.Close()

Dále vytvoříme buffer pro čtení dat:

const buffer_size = 16
buffer := make([]byte, buffer_size)

Další čtení (v našem případě po šestnácti bajtech) je již snadné:

for {
        read, err := fin.Read(buffer)
 
        if read > 0 {
                fmt.Printf("read %d bytes\n", read)
                fmt.Println(buffer[:read])
        }
 
        if err != nil {
                fmt.Printf("other error %v\n", err)
                break
        }
}

Úplná implementace čtení bajtů ze souboru po blocích pevné délky vypadá takto:

package main
 
import (
        "fmt"
        "io"
        "log"
        "os"
)
 
const filename = "test_input.txt"
const buffer_size = 16
 
func main() {
        fin, err := os.Open(filename)
        if err != nil {
                log.Fatal(err)
        }
        defer fin.Close()
 
        buffer := make([]byte, buffer_size)
 
        for {
                read, err := fin.Read(buffer)
 
                if read > 0 {
                        fmt.Printf("read %d bytes\n", read)
                        fmt.Println(buffer[:read])
                }
 
                if err == io.EOF {
                        fmt.Println("reached end of file")
                        break
                }
 
                if err != nil {
                        fmt.Printf("other error %v\n", err)
                        break
                }
        }
}

13. Čtení bloku bajtů ze standardního vstupu a z řetězce

Nepatrnou úpravou předchozího příkladu můžeme zajistit čtení bloku bajtů ze standardního vstupu. Ten je představován objektem os.Stdin, který pochopitelně nemusíme ani otevírat ani uzavírat. Podívejme se tedy jen na způsob implementace:

package main
 
import (
        "fmt"
        "io"
        "os"
)
 
const buffer_size = 16
 
func main() {
        buffer := make([]byte, buffer_size)
 
        for {
                read, err := os.Stdin.Read(buffer)
 
                if read > 0 {
                        fmt.Printf("read %d bytes\n", read)
                        fmt.Println(buffer[:read])
                }
 
                if err == io.EOF {
                        fmt.Println("reached end of file")
                        break
                }
 
                if err != nil {
                        fmt.Printf("other error %v\n", err)
                        break
                }
        }
}

Podobně můžeme využít objekt strings.Reader pro čtení bloku bajtů z řetězce, což je implementováno v dnešním třináctém demonstračním příkladu:

package main
 
import (
        "fmt"
        "io"
        "strings"
)
 
const input_string = "Hello world!"
const buffer_size = 4
 
func main() {
        r := strings.NewReader(input_string)
 
        buffer := make([]byte, buffer_size)
 
        for {
                read, err := r.Read(buffer)
 
                if read > 0 {
                        fmt.Printf("read %d bytes\n", read)
                        fmt.Println(buffer[:read])
                }
 
                if err == io.EOF {
                        fmt.Println("reached end of file")
                        break
                }
 
                if err != nil {
                        fmt.Printf("other error %v\n", err)
                        break
                }
        }
}

Načtené bloky bajtů se můžeme pokusit převést na řetězec konstruktorem string. Opět si ukažme celý zdrojový kód takto upraveného příkladu:

package main
 
import (
        "fmt"
        "io"
        "strings"
)
 
const input_string = "Hello world!"
const buffer_size = 4
 
func main() {
        r := strings.NewReader(input_string)
 
        buffer := make([]byte, buffer_size)
 
        for {
                read, err := r.Read(buffer)
 
                if read > 0 {
                        fmt.Printf("read %d bytes\n", read)
                        fmt.Println(string(buffer[:read]))
                }
 
                if err == io.EOF {
                        fmt.Println("reached end of file")
                        break
                }
 
                if err != nil {
                        fmt.Printf("other error %v\n", err)
                        break
                }
        }
}

14. Spojení více vstupů – multireader

Konečně se dostáváme k zajímavějším způsobům použití standardních vstupně-výstupních rozhraní. Dobrým příkladem může být funkce io.MultiReader, které se na vstup předá libovolné množství objektů implementujících rozhraní io.Reader a výsledkem bude opět objekt typu io.Reader, který ovšem bude vracet spojené vstupní proudy. To je potenciálně velmi užitečné a především – toto spojení dat se provede zcela automaticky a bez toho, abychom museli nějakým způsobem měnit logiku aplikace. V dalším příkladu je ukázáno, jak se spojí tři objekty typu Reader:

package main
 
import (
        "fmt"
        "io"
        "strings"
)
 
const input_string_1 = "Hello"
const input_string_2 = "world"
const input_string_3 = "!"
 
const buffer_size = 4
 
func main() {
        r1 := strings.NewReader(input_string_1)
        r2 := strings.NewReader(input_string_2)
        r3 := strings.NewReader(input_string_3)
        r := io.MultiReader(r1, r2, r3)
 
        buffer := make([]byte, buffer_size)
 
        for {
                read, err := r.Read(buffer)
 
                if read > 0 {
                        fmt.Printf("read %d bytes\n", read)
                        fmt.Println(string(buffer[:read]))
                }
 
                if err == io.EOF {
                        fmt.Println("reached end of file")
                        break
                }
 
                if err != nil {
                        fmt.Printf("other error %v\n", err)
                        break
                }
        }
}

Příklad výstupu:

read 4 bytes
Hell
read 1 bytes
o
read 4 bytes
worl
read 1 bytes
d
read 1 bytes
!
reached end of file

15. Rozhraní io.Writer

Opakem rozhraní io.Reader je pochopitelně rozhraní pojmenované io.Writer. Toto rozhraní předepisuje jedinou metodu určenou pro zápis bloku bajtů do libovolného výstupu. Metoda vrací počet skutečně zapsaných bajtů (ten se může lišit od kapacitu bufferu, například při chybě) a případnou hodnotu reprezentující chybu:

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

Typicky se tato metoda využívá při zápisu do souborů s binárním obsahem, ovšem můžeme ji pochopitelně použít i v případě, že potřebujeme pracovat s textovými soubory obsahujícími pouze ASCII znaky. A právě tento způsob použití je použit v dalším demonstračním příkladu:

package main
 
import (
        "fmt"
        "log"
        "os"
)
 
const filename = "test_output.txt"
const message = "Hello world!"
 
func main() {
        fout, err := os.Create(filename)
        if err != nil {
                log.Fatal(err)
        }
        defer fout.Close()
 
        buffer := []byte(message)
        written, err := fout.Write(buffer)
 
        if written > 0 {
                fmt.Printf("written %d bytes\n", written)
        }
 
        if err != nil {
                fmt.Printf("I/O error %v\n", err)
        }
}

16. Kopie souborů po blocích

Ukažme si ještě, jak zajistit kopii souborů po blocích nějaké pevně zadané délky. Samotná kopie po blocích samozřejmě není nic složitého, pouze musíme zajistit, aby se správně pracovalo s posledním blokem, který pochopitelně nemusí být zcela zaplněn. To je zajištěno zvýrazněným výrazem:

read, err := src.Read(buffer)
 
if read > 0 {
        fmt.Printf("read %d bytes\n", read)
        written, err := dst.Write(buffer[:read])
        if written > 0 {
                fmt.Printf("written %d bytes\n", written)
        }
        if err != nil {
                fmt.Printf("write error %v\n", err)
                return copied, err
        }
        copied += int64(written)
}

Následuje výpis celého zdrojového kódu tohoto příkladu:

package main
 
import (
        "fmt"
        "io"
        "os"
)
 
func closeFile(file *os.File) {
        fmt.Printf("Closing file '%s'\n", file.Name())
        file.Close()
}
 
func copyFile(srcName, dstName string) (written int64, err error) {
        src, err := os.Open(srcName)
        if err != nil {
                panic(err)
        }
        defer closeFile(src)
 
        dst, err := os.Create(dstName)
        if err != nil {
                panic(err)
        }
        defer closeFile(dst)
 
        buffer := make([]byte, 16)
        copied := int64(0)
 
        for {
                read, err := src.Read(buffer)
 
                if read > 0 {
                        fmt.Printf("read %d bytes\n", read)
                        written, err := dst.Write(buffer[:read])
                        if written > 0 {
                                fmt.Printf("written %d bytes\n", written)
                        }
                        if err != nil {
                                fmt.Printf("write error %v\n", err)
                                return copied, err
                        }
                        copied += int64(written)
                }
 
                if err == io.EOF {
                        fmt.Println("reached end of file")
                        break
                }
 
                if err != nil {
                        fmt.Printf("other error %v\n", err)
                        return copied, err
                }
        }
        return copied, nil
}
 
func testCopyFile(srcName, dstName string) {
        copied, err := copyFile(srcName, dstName)
        if err != nil {
                fmt.Printf("copyFile('%s', '%s') failed!!!\n", srcName, dstName)
        } else {
                fmt.Printf("Copied %d bytes\n", copied)
        }
        fmt.Println()
}
 
func main() {
        testCopyFile("test_input.txt", "output.txt")
}

17. Načtení celého řádku ze standardního vstupu

V dnešním posledním demonstračním příkladu je ukázáno jiné použití standardních vstupně-výstupních funkcí a metod. Využijeme zde balíček bufio, v němž se mj. nachází i objekt typu Reader obsahující metodu ReadString, které se předá znak, jenž reprezentuje konec dat. V případě, že metodě předáme znak konce řádku, načte tato metoda celý textový řádek a vrátí ho ve formě řetězce, tedy s využitím základních konverzních funkcí pro převod bajtů na Unicode:

package main
 
import (
        "bufio"
        "os"
)
 
func main() {
        reader := bufio.NewReader(os.Stdin)
 
        print("Login: ")
        login, err := reader.ReadString('\n')
        if err != nil {
                println("Error reading login")
        }
 
        print("Password: ")
        password, err := reader.ReadString('\n')
        if err != nil {
                println("Error reading password")
        }
 
        println(login)
        println(password)
}

18. Skládání rozhraní souvisejících se vstupně-výstupními operacemi

Ve standardní knihovně nalezneme i rozhraní, která jsou složena z některých rozhraní jednodušších. Příkladem může být především rozhraní se jménem io.ReadWriter s oběma metodami ReadWrite. Tyto metody ovšem nejsou předepsány přímo, ale nové rozhraní je skutečně složeno z rozhraní jednodušších, o čemž se ostatně můžeme snadno přesvědčit:

DT24

type ReadWriter interface {
        Reader
        Writer
}

Podobně nalezneme ve standardní knihovně i další podobná rozhraní vycházející ze dvou či tří rozhraní s původně jedinou metodou, zejména pak:

type ReadCloser interface {
        Reader
        Closer
}
 
type WriteCloser interface {
        Writer
        Closer
}
 
type ReadWriteCloser interface {
        Reader
        Writer
        Closer
}
 
type ReadSeeker interface {
        Reader
        Seeker
}
 
type WriteSeeker interface {
        Writer
        Seeker
}
 
type ReadWriteSeeker interface {
        Reader
        Writer
        Seeker
}

19. Repositář s demonstračními příklady

Zdrojové kódy všech dnes popsaných demonstračních příkladů byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/go-root (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně dva megabajty), můžete namísto toho použít odkazy na jednotlivé příklady, které naleznete v následující tabulce:

# Soubor Popis Cesta
1 01_read_byte.go čtení bajtů z objektu strings.Reader https://github.com/tisnik/go-root/blob/master/article22/01_re­ad_byte.go
2 02_better_read_byte.go explicitní test, zda se při čtení došlo na konec dat https://github.com/tisnik/go-root/blob/master/article22/02_bet­ter_read_byte.go
3 03_read_byte_from_unicode.go čtení jednotlivých bajtů (nikoli znaků) z řetězce https://github.com/tisnik/go-root/blob/master/article22/03_re­ad_byte_from_unicode.go
4 04_read_rune_from_unicode.go čtení znaků/run z řetězce https://github.com/tisnik/go-root/blob/master/article22/04_re­ad_rune_from_unicode.go
5 05_write_byte.go základní použití rozhraní ByteWriter https://github.com/tisnik/go-root/blob/master/article22/05_wri­te_byte.go
6 06_write_byte_read_rune.go kombinace zápisu bajtů a čtení znaků/run https://github.com/tisnik/go-root/blob/master/article22/06_wri­te_byte_read_rune.go
7 07_write_rune.go zápis jednotlivých znaků/run metodou WriteRune https://github.com/tisnik/go-root/blob/master/article22/07_wri­te_rune.go
8 08_unread_byte.go použití metody UnreadByte z rozhraní ByteScanner https://github.com/tisnik/go-root/blob/master/article22/08_un­read_byte.go
9 09_unread_more_bytes.go vícenásobné vrácení bajtu zpět do vstupu https://github.com/tisnik/go-root/blob/master/article22/09_un­read_more_bytes.go
10 10_unread_rune.go použití metody UnreadRune z rozhraní RuneScanner https://github.com/tisnik/go-root/blob/master/article22/10_un­read_rune.go
11 11_io_reader_read.go použití metody Read z rozhraní Reader https://github.com/tisnik/go-root/blob/master/article22/11_i­o_reader_read.go
12 12_stdin_read.go čtení bloku bajtů ze standardního vstupu https://github.com/tisnik/go-root/blob/master/article22/12_stdin_re­ad.go
13 13_string_reader_read.go čtení bloku bajtů z řetězce https://github.com/tisnik/go-root/blob/master/article22/13_strin­g_reader_read.go
14 14_string_reader_as_str.go čtení bloku bajtů a jejich převod na řetězec https://github.com/tisnik/go-root/blob/master/article22/14_strin­g_reader_as_str.go
15 15_multi_reader.go spojení více vstupů neboli multireader https://github.com/tisnik/go-root/blob/master/article22/15_mul­ti_reader.go
16 16_io_writer_write.go použití metody Write z rozhraní Writer https://github.com/tisnik/go-root/blob/master/article22/16_i­o_writer_write.go
17 17_input_via_reader.go načtení řetězce metodou ReadString https://github.com/tisnik/go-root/blob/master/article22/17_in­put_via_reader.go
18 18_file_block_copy.go načtení řetězce metodou ReadString https://github.com/tisnik/go-root/blob/master/article22/18_fi­le_block_copy.go

20. Odkazy na Internetu

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

Byl pro vás článek přínosný?