fmt, io, os, bufio, encoding/json, net/http, time, slog, regexp, flag, embed.
bufio — Buffered I/O & Scanners
Raw io.Reader/io.Writer are unbuffered: every call may translate to a syscall.
bufio wraps them with an in-memory buffer so you can read line-by-line, token
by token, or just batch tiny writes into one big one.
bufio.Scanner — line-at-a-time input
Scanner is the easiest way to walk text. Default split function is by line.
go playground
Loading...
Scan()returnsfalsewhen input is done or when something errored.Text()is the current token without the trailing newline.- Always check
scanner.Err()after the loop —Scanreturningfalsedoesn't tell you whether it was EOF or a real failure.
Tokenizing words
Swap the split function to walk words, runes, or your own custom delimiter:
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
Built-ins: ScanLines (default), ScanWords, ScanRunes, ScanBytes.
bufio.Reader — when you need more control
Reader is the heavier-duty cousin. It can peek, read exact byte counts, and
read up to a delimiter of your choice:
r := bufio.NewReader(conn)
line, err := r.ReadString('\n') // includes the '\n'
b, err := r.ReadByte()
peek, err := r.Peek(4) // look without consuming
bufio.Writer — batch small writes
Every os.Stdout.Write is a syscall. Wrap with bufio.Writer and many small
writes become one:
w := bufio.NewWriter(os.Stdout)
defer w.Flush() // never forget Flush — buffered bytes are lost otherwise
for i := 0; i < 1000; i++ {
fmt.Fprintln(w, "line", i)
}
When to reach for bufio
- Reading user input or stdin line-by-line? →
bufio.Scanner. - Parsing a protocol with mixed reads (peek a header, then read N bytes)? →
bufio.Reader. - Writing many small chunks to a file or network? →
bufio.Writer. - One-shot read of a whole small file? →
os.ReadFileis simpler.
You scan a file with `bufio.Scanner` and the loop ends after only two lines, but you expected ten. The first thing to check is: