Standard Library I/O
The io Package
Section titled “The io Package”The io package defines the fundamental I/O interfaces that permeate Go’s standard library:
type Reader interface { Read(p []byte) (n int, err error)}
type Writer interface { Write(p []byte) (n int, err error)}
type Closer interface { Close() error}
type ReaderAt interface { ReadAt(p []byte, off int64) (n int, err error)}
type WriterAt interface { WriteAt(p []byte, off int64) (n int, err error)}
type Seeker interface { Seek(offset int64, whence int) (int64, error)}
type ReadWriter interface { Reader Writer}
type ReadCloser interface { Reader Closer}
type WriteCloser interface { Writer Closer}io.Reader Contract
Section titled “io.Reader Contract”Read reads up to len(p) bytes into p. It returns the number of bytes read (n) and any error Encountered. Key semantics:
Readreturnsio.EOFwhen the stream ends.io.EOFis not an error in the conventional sense — it indicates that no more bytes are available.n > 0anderr != nilcan occur simultaneously. This means some bytes were read before the error. The caller should process thenbytes before handling the error.n == 0anderr == nilis valid and means “try again later” (e.g., for non-blocking reads).n == 0anderr == io.EOFmeans the stream is exhausted.
io.Copy
Section titled “io.Copy”io.Copy copies from a Reader to a Writer:
n, err := io.Copy(dst, src)It uses a 32 KB internal buffer and handles io.ReaderFrom/io.WriterTo optimizations Automatically.
io.TeeReader
Section titled “io.TeeReader”io.TeeReader returns a Reader that writes to a Writer as it reads:
tee := io.TeeReader(reader, os.Stdout) // prints everything readdata, _ := io.ReadAll(tee)io.LimitReader
Section titled “io.LimitReader”io.LimitReader returns a Reader that reads at most N bytes:
limited := io.LimitReader(file, 1024) // read at most 1024 bytesio.MultiReader and io.MultiWriter
Section titled “io.MultiReader and io.MultiWriter”Combine multiple readers or writers:
r := io.MultiReader(reader1, reader2, reader3)w := io.MultiWriter(os.Stdout, logFile)The bufio Package
Section titled “The bufio Package”bufio provides buffered I/O wrappers around io.Reader and io.Writer. Buffering reduces system Call overhead by batching small reads and writes.
bufio.Reader
Section titled “bufio.Reader”reader := bufio.NewReader(file)
line, err := reader.ReadString('\n') // read until delimiterline, isPrefix, err := reader.ReadLine() // read a line (no trailing \n)
rune, size, err := reader.ReadRune() // read a single Unicode runebufio.Scanner
Section titled “bufio.Scanner”bufio.Scanner provides a convenient interface for reading data line by line or token by token:
scanner := bufio.NewScanner(os.Stdin)for scanner.Scan() { fmt.Println(scanner.Text())}if err := scanner.Err(); err != nil { log.Fatal(err)}Default scanner splits by lines. Custom split functions:
scanner := bufio.NewScanner(file)scanner.Split(bufio.ScanWords) // split by whitespace
for scanner.Scan() { fmt.Println(scanner.Text())}The scanner has a default max token size of 64 KB. Increase it if needed:
scanner := bufio.NewScanner(file)buf := make([]byte, 0, 1024*1024) // 1 MB bufferscanner.Buffer(buf, 10*1024*1024) // allow up to 10 MB tokensbufio.Writer
Section titled “bufio.Writer”writer := bufio.NewWriter(os.Stdout)writer.WriteString("hello\n")writer.WriteString("world\n")writer.Flush() // must flush to ensure all data is writtenUse defer writer.Flush() to ensure buffered data is written on function exit.
The os Package
Section titled “The os Package”File Operations
Section titled “File Operations”// Read entire filedata, err := os.ReadFile("config.json")
// Write entire fileerr := os.WriteFile("output.txt", data, 0644)
// Open file for readingf, err := os.Open("input.txt")defer f.Close()
// Open file for writing (creates or truncates)f, err := os.Create("output.txt")defer f.Close()
// Open file with flagsf, err := os.OpenFile("log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)defer f.Close()File Flags
Section titled “File Flags”| Flag | Meaning |
|---|---|
os.O_RDONLY | Read-only |
os.O_WRONLY | Write-only |
os.O_RDWR | Read-write |
os.O_APPEND | Append to file |
os.O_CREATE | Create if not exists |
os.O_TRUNC | Truncate when opening |
os.O_EXCL | Used with O_CREATE, fail if exists |
File Permissions
Section titled “File Permissions”File permissions use Unix-style octal notation:
0644 // owner: rw, group: r, others: r0755 // owner: rwx, group: rx, others: rx0600 // owner: rw, group: -, others: -Directory Operations
Section titled “Directory Operations”entries, err := os.ReadDir("./dir") // read directory entrieserr := os.Mkdir("newdir", 0755) // create directoryerr := os.MkdirAll("a/b/c", 0755) // create directory and parentserr := os.Remove("file.txt") // remove file or empty directoryerr := os.RemoveAll("dir") // remove directory and contentserr := os.Rename("old", "new") // rename or moveos.Stdin, os.Stdout, os.Stderr
Section titled “os.Stdin, os.Stdout, os.Stderr”os.Stdin // *os.File, standard inputos.Stdout // *os.File, standard outputos.Stderr // *os.File, standard errorThe fmt Package
Section titled “The fmt Package”Print Functions
Section titled “Print Functions”fmt.Print("hello") // write to stdoutfmt.Println("hello") // write with newlinefmt.Printf("value: %d\n", 42) // formatted writefmt.Fprintf(w, "value: %d\n", 42) // write to any io.Writerfmt.Sprintf("value: %d", 42) // format to stringfmt.Errorf("failed: %w", err) // format to errorFormat Verbs
Section titled “Format Verbs”| Verb | Meaning |
|---|---|
%v | Default format |
%+v | Struct with field names |
%#v | Go syntax representation |
%T | Type of the value |
%d | Decimal integer |
%x | Hexadecimal integer |
%o | Octal integer |
%b | Binary integer |
%f | Decimal float |
%e | Scientific notation |
%s | String |
%q | Quoted string |
%p | Pointer address |
%t | Boolean (true/false) |
%w | Error (wraps for errors.Is/As) |
Width and Precision
Section titled “Width and Precision”fmt.Printf("|%10s|\n", "hello") // | hello| (right-aligned, width 10)fmt.Printf("|%-10s|\n", "hello") // |hello | (left-aligned, width 10)fmt.Printf("%.2f\n", 3.14159) // 3.14fmt.Printf("%10.2f\n", 3.14) // 3.14encoding/json
Section titled “encoding/json”Marshaling
Section titled “Marshaling”type User struct { Name string `json:"name"` Age int `json:"age,omitempty"` Email string `json:"email,omitempty"`}
u := User{Name: "Alice", Age: 30}data, err := json.Marshal(u)// {"name":"Alice","age":30}
data, err := json.MarshalIndent(u, "", " ")// {// "name": "Alice",// "age": 30// }Unmarshaling
Section titled “Unmarshaling”var u Usererr := json.Unmarshal(data, &u)JSON Streaming
Section titled “JSON Streaming”decoder := json.NewDecoder(reader)for decoder.More() { var item Item if err := decoder.Decode(&item); err != nil { break } process(item)}
encoder := json.NewEncoder(writer)encoder.Encode(item) // writes JSON followed by newlineRaw JSON
Section titled “Raw JSON”Use json.RawMessage to defer parsing of a portion of JSON:
type Envelope struct { Type string `json:"type"` Data json.RawMessage `json:"data"`}
func handle(e Envelope) { switch e.Type { case "user": var u User json.Unmarshal(e.Data, &u) case "event": var ev Event json.Unmarshal(e.Data, &ev) }}Custom Marshaling
Section titled “Custom Marshaling”Implement json.Marshaler and json.Unmarshaler for custom serialization:
func (t Time) MarshalJSON() ([]byte, error) { return json.Marshal(t.Format(time.RFC3339))}
func (t *Time) UnmarshalJSON(data []byte) error { var s string if err := json.Unmarshal(data, &s); err != nil { return err } parsed, err := time.Parse(time.RFC3339, s) *t = Time(parsed) return err}Intuition
Section titled “Intuition”I/O is a postal service with standardized envelopes: io.Reader and io.Writer are the universal interfaces — every data source (files, network, memory buffers) and every data sink speaks the same language. It’s like having one standard envelope size that fits letters, photos, and packages alike. bufio is the mailroom that batches small letters into bundles to reduce trips to the post office (system calls). json.Encoder writing to an http.ResponseWriter is just another reader/writer pair — no special case needed.
Why it matters: The io.Reader/io.Writer interface pair is the most reused abstraction in Go’s standard library. Once you understand that everything is just “read bytes from here” and “write bytes to there,” you can compose any I/O operation by connecting readers to writers like building blocks.
The key insight: io.EOF is not an error — it’s the normal way a stream says “I’m done.” Treat it as completion, not failure.
Common Pitfalls
Section titled “Common Pitfalls”Not closing files. Always use
defer f.Close()after opening a file. Even whenClosefails, the file descriptor is released.Ignoring io.EOF correctly.
io.EOFis not a fatal error.io.ReadAllreturnsio.EOFonly if zero bytes were read (an empty stream). For normal reads,io.EOFaccompanies the last batch of data.Forgetting to flush bufio.Writer. Buffered data is not written until
Flush()is called or the buffer is full. Usedefer writer.Flush().Scanner token too long.
bufio.Scannerhas a default 64 KB max token size. If you are reading long lines, increase the buffer withscanner.Buffer().JSON nil vs empty. A
nilslice marshals tonull; an empty slice[]T{}marshals to[]. Anilmap marshals tonull; an empty map marshals to{}. This difference matters for API consumers.JSON unmarshal target must be a pointer.
json.Unmarshal(data, u)(non-pointer) silently succeeds without populatingu. Always pass a pointer:json.Unmarshal(data, &u).Using
%vinstead of%wfor error formatting.%vloses the error chain. Use%wto preserve it forerrors.Isanderrors.As.Not checking scanner errors. After a
scanner.Scan()loop, always checkscanner.Err(). The loop may exit due to an I/O error, not justio.EOF.
Summary
Section titled “Summary”This topic covers the core concepts of standard library i/o, including underlying theory, practical implementation, and key applications.
Key concepts include:
- core concepts and terminology
- algorithms and computational thinking
- practical implementation
- security and ethical considerations
- applications in the real world
Understanding these concepts thoroughly is essential for both examinations and practical programming, and requires both theoretical knowledge and hands-on practice.
Worked Examples
Section titled “Worked Examples”Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.
Cross-References
Section titled “Cross-References”- net/http: HTTP handlers and clients built on io.Reader/Writer interfaces.
- Strings and Time: String manipulation and time parsing used with I/O streams.
- Channels: Concurrent pipeline patterns using io.Reader/Writer with goroutines.