We already saw that the bytes package offers Buffer, which has both reading and writing capabilities. This implements all the methods of the ByteReader interface, plus more than one Writer interface:
- io.Writer: This can act as a regular writer
- io.WriterAt: This makes it possible to write from a certain position onward
- io.ByteWriter: This makes it possible to write single bytes
bytes.Buffer is a very flexible structure considering that it works for both, Writer and ByteWriter and works best if reused, thanks to the Reset and Truncate methods. Instead of leaving a used buffer to be recycled by the GC and make a new buffer, it is better to reset the existing one, keeping the underlying array for the buffer and setting the slice length to 0.
In the previous chapter, we saw a good example of buffer usage:
bookList := []book{
{Author: grr, Title: "A Game of Thrones", Year: 1996},
{Author: grr, Title: "A Clash of Kings", Year: 1998},
{Author: grr, Title: "A Storm of Swords", Year: 2000},
{Author: grr, Title: "A Feast for Crows", Year: 2005},
{Author: grr, Title: "A Dance with Dragons", Year: 2011},
{Author: grr, Title: "The Winds of Winter"},
{Author: grr, Title: "A Dream of Spring"},
}
b := bytes.NewBuffer(make([]byte, 0, 16))
for _, v := range bookList {
// prints a msg formatted with arguments to writer
fmt.Fprintf(b, "%s - %s", v.Title, v.Author)
if v.Year > 0 { // we do not print the year if it's not there
fmt.Fprintf(b, " (%d)", v.Year)
}
b.WriteRune('\n')
if _, err := b.WriteTo(dst); true { // copies bytes, drains buffer
fmt.Println("Error:", err)
return
}
}
A buffer is not made for composing string values. For this reason, when the String method is called, bytes get converted into strings, which are immutable, unlike slices. The new string created this way is made with a copy of the current slice, and changes to the slice do not touch the string. It's neither a limit nor a feature; it is an attribute that can lead to errors if used incorrectly. Here's an example of the effect of resetting a buffer and using the String method:
package main
import (
"bytes"
"fmt"
)
func main() {
b := bytes.NewBuffer(nil)
b.WriteString("One")
s1 := b.String()
b.WriteString("Two")
s2 := b.String()
b.Reset()
b.WriteString("Hey!") // does not change s1 or s2
s3 := b.String()
fmt.Println(s1, s2, s3) // prints "One OneTwo Hey!"
}
The full example is available at https://play.golang.org/p/zBjGPMC4sfF