bufio

Guided tour · I/O & Files · pkg.go.dev →

Buffered I/O on top of io.Reader/Writer. Adds line-reading, large single reads, and a fluent Scanner.

Buffer a Reader/Writer for cheap line-at-a-time I/O. Scanner is the right tool for "read a file/stdin line by line".

Read stdin line by line
sc := bufio.NewScanner(os.Stdin)
for sc.Scan() {
    line := sc.Text()
}
if err := sc.Err(); err != nil { ... }
Read a file line by line
f, err := os.Open("data.txt")
if err != nil { return err }
defer f.Close()
sc := bufio.NewScanner(f)
for sc.Scan() { handle(sc.Text()) }
Lift Scanner line limit (default 64KB)
sc.Buffer(make([]byte, 0, 64*1024), 1<<20)
Buffered writer (don't forget Flush)
w := bufio.NewWriter(f)
defer w.Flush()
w.WriteString("hi\n")

Why buffer?

Each os.File.Read is a syscall. bufio reads a big chunk ahead and hands you slices out of memory — cheap iteration, same Reader/Writer interface.

Scanner — the line-reading workhorse

Scan lines (the default)

sc := bufio.NewScanner(strings.NewReader("one\ntwo\nthree\n"))
for sc.Scan() {
    fmt.Println(sc.Text())
}
if err := sc.Err(); err != nil {
    log.Fatal(err)
}
Output
one
two
three

Scan words or runes

sc := bufio.NewScanner(strings.NewReader("the  quick brown"))
sc.Split(bufio.ScanWords)
for sc.Scan() {
    fmt.Println(sc.Text())
}
Output
the
quick
brown

Increase the buffer for long lines

Default max line is 64 KiB. Feed it your own buffer for longer lines.

sc := bufio.NewScanner(r)
buf := make([]byte, 0, 1024*1024)
sc.Buffer(buf, 10*1024*1024)  // up to 10 MiB per line

Reader — more flexible than Scanner

Scanner is line-at-a-time. Reader lets you peek, unread, ReadString with any delimiter, and handle binary.

ReadString — delimiter-based

Returns the match including the delimiter. Returns err != nil at EOF even with data, so handle both.

r := bufio.NewReader(strings.NewReader("abc,def,"))
for {
    s, err := r.ReadString(',')
    if s != "" {
        fmt.Printf("%q\n", s)
    }
    if err != nil {
        break
    }
}
Output
"abc,"
"def,"

Peek — look without consuming

r := bufio.NewReader(strings.NewReader("hello"))
b, _ := r.Peek(2)
fmt.Println(string(b))
fmt.Println(string(mustRead(r, 5)))
Output
he
hello

ReadByte / UnreadByte

r := bufio.NewReader(strings.NewReader("abc"))
c, _ := r.ReadByte()
r.UnreadByte()
d, _ := r.ReadByte()
fmt.Printf("%c %c\n", c, d)  // 'a' 'a'

Writer — always Flush

bufio.Writer buffers writes until the buffer is full or you Flush. Forgetting Flush loses data.

Flush before close

f, _ := os.Create("out.txt")
defer f.Close()
w := bufio.NewWriter(f)
defer w.Flush()      // MUST Flush before f.Close runs
fmt.Fprintln(w, "hello")