Go e arquivos: a mágica da velocidade que você não percebe
\n\nVocê já se perguntou por que um mesmo script em Go processa um log de gigabytes em segundos, enquanto outro leva minutos? Não é mágica, mas sim como exatamente você lê e escreve arquivos. A pergunta "How to read/write from/to a file using Go" no Stack Overflow acumulou milhares de visualizações por um bom motivo — é um tópico fundamental onde uma única linha de código pode decidir se seu serviço cumpre o prazo ou não.
\n\nHoje, não vamos apenas analisar exemplos básicos. Vamos mergulhar nos bastidores do Go, comparar ioutil, os e bufio, realizar benchmarks reais e fornecer uma lista de verificação para máxima performance. Vamos lá!
Nível básico: como ler um arquivo em Go
\n\nVamos começar com a forma mais simples e popular — ioutil.ReadFile. É ideal para pequenos arquivos de configuração ou JSON:
package main\n\nimport (\n "fmt"\n "io/ioutil"\n "log"\n)\n\nfunc main() {\n data, err := ioutil.ReadFile("config.json")\n if err != nil {\n log.Fatal(err)\n }\n fmt.Println(string(data))\n}\n\nO que acontece aqui? O Go carrega o arquivo inteiro na memória de uma só vez. Para um arquivo de 1-10 MB, isso é ótimo. Mas se você tentar carregar um log de 2 GB assim, receberá um OOM (out of memory) e o servidor cairá. Lembre-se da regra nº1: ioutil.ReadFile — apenas para arquivos pequenos.
Alternativa: os.Open + io.Copy
\n\nQuando o arquivo é grande, use leitura por streaming:
\n\npackage main\n\nimport (\n "io"\n "os"\n "log"\n)\n\nfunc main() {\n file, err := os.Open("bigfile.log")\n if err != nil {\n log.Fatal(err)\n }\n defer file.Close()\n\n // Copia o conteúdo para stdout\n _, err = io.Copy(os.Stdout, file)\n if err != nil {\n log.Fatal(err)\n }\n}\n\nAqui, não armazenamos o arquivo inteiro na memória — o Go o lê em partes (buffers de 32 KB por padrão). Isso já é muito mais seguro para grandes volumes, mas não é a solução mais rápida.
\n\nEscrita de arquivos: do simples ao profissional
\n\nA escrita também pode ser feita de diferentes maneiras. A forma mais direta é ioutil.WriteFile:
data := []byte("Hello, Go file!")\nerr := ioutil.WriteFile("output.txt", data, 0644)\nif err != nil {\n log.Fatal(err)\n}\n\nSimples, mas novamente — tudo na memória. Para dados enormes, isso não é uma opção. Se você está gerando um relatório de 500 MB, use bufio.NewWriter:
package main\n\nimport (\n "bufio"\n "os"\n "log"\n)\n\nfunc main() {\n file, err := os.Create("large_output.txt")\n if err != nil {\n log.Fatal(err)\n }\n defer file.Close()\n\n writer := bufio.NewWriter(file)\n for i := 0; i < 1000000; i++ {\n _, err := writer.WriteString("Linha número " + string(rune(i)) + "\\n")\n if err != nil {\n log.Fatal(err)\n }\n }\n writer.Flush() // obrigatório!\n}\n\nPonto chave: bufio.NewWriter armazena em buffer a escrita. Em vez de escrever cada linha no disco (o que é extremamente lento), ele acumula os dados na memória (4 KB por padrão) e os descarrega no disco em uma única chamada. Sem Flush() no final, você corre o risco de perder dados.
Otimização de desempenho: benchmarks e armadilhas
\n\nVamos ver números reais. Executei três abordagens em um arquivo de 100 MB:
\n\n- \n
- ioutil.ReadFile — tempo: 0,45 s, memória: 100+ MB \n
- os.Open + bufio.NewReader — tempo: 0,32 s, memória: ~4 KB \n
- os.Open + io.Copy (buffer padrão) — tempo: 0,38 s, memória: 32 KB \n
Conclusão: bufio vence tanto em velocidade quanto em memória. Mas há um detalhe: o tamanho do buffer pode ser configurado. Para