Working with IO Operations in Go
Go's IO operations are built around two main interfaces: io.Reader
and io.Writer
. Understanding these interfaces and their implementations is crucial for effective data handling in Go.
Core IO Interfaces
The Reader Interface
type Reader interface {
Read(p []byte) (n int, err error)
}
The Reader
interface represents an entity from which you can read data. Common implementations include:
- Files (
os.File
) - Network connections (
net.Conn
) - Strings (
strings.Reader
) - Bytes (
bytes.Reader
)
Example usage:
func readData(r io.Reader) error {
buf := make([]byte, 1024)
for {
n, err := r.Read(buf)
if err == io.EOF {
break
}
if err != nil {
return err
}
// Process buf[:n]
fmt.Printf("Read %d bytes\n", n)
}
return nil
}
The Writer Interface
type Writer interface {
Write(p []byte) (n int, err error)
}
The Writer
interface represents an entity to which you can write data. Common implementations include:
- Files (
os.File
) - Network connections (
net.Conn
) - Strings (
strings.Builder
) - Bytes (
bytes.Buffer
)
Example usage:
func writeData(w io.Writer, data []byte) error {
n, err := w.Write(data)
if err != nil {
return err
}
fmt.Printf("Wrote %d bytes\n", n)
return nil
}
Buffered IO
Using bufio
Package
The bufio
package implements buffered IO, which can improve performance by reducing the number of system calls:
func readLines(r io.Reader) error {
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := scanner.Text()
// Process line
fmt.Println(line)
}
return scanner.Err()
}
func writeBuffered(w io.Writer) error {
bw := bufio.NewWriter(w)
_, err := bw.WriteString("Hello, World!\n")
if err != nil {
return err
}
return bw.Flush()
}
IO Utilities
Copy Operations
The io
package provides utility functions for copying data:
func copyExample() error {
src := strings.NewReader("Hello, World!")
dst := &bytes.Buffer{}
written, err := io.Copy(dst, src)
if err != nil {
return err
}
fmt.Printf("Copied %d bytes\n", written)
return nil
}
MultiReader and MultiWriter
Combine multiple readers or writers:
func multiReaderExample() error {
r1 := strings.NewReader("Hello")
r2 := strings.NewReader("World")
mr := io.MultiReader(r1, r2)
// Read from mr will read from r1 then r2
data, err := io.ReadAll(mr)
if err != nil {
return err
}
fmt.Printf("Read: %s\n", data)
return nil
}
Best Practices
Always Close Resources
file, err := os.Open("file.txt") if err != nil { return err } defer file.Close()
Use Buffered IO for Performance
file, err := os.Create("large-file.txt") if err != nil { return err } defer file.Close() writer := bufio.NewWriter(file) defer writer.Flush()
Handle EOF Correctly
func readUntilEOF(r io.Reader) error { buf := make([]byte, 1024) for { n, err := r.Read(buf) if err == io.EOF { break // Expected EOF, not an error } if err != nil { return err // Unexpected error } // Process buf[:n] } return nil }
Use io.ReadAll for Small Files
func readSmallFile(filename string) ([]byte, error) { file, err := os.Open(filename) if err != nil { return nil, err } defer file.Close() return io.ReadAll(file) }
Common Patterns
Implementing Custom Readers
type limitReader struct {
r io.Reader
remaining int64
}
func (l *limitReader) Read(p []byte) (n int, err error) {
if l.remaining <= 0 {
return 0, io.EOF
}
if int64(len(p)) > l.remaining {
p = p[:l.remaining]
}
n, err = l.r.Read(p)
l.remaining -= int64(n)
return
}
Implementing Custom Writers
type countingWriter struct {
w io.Writer
count int64
}
func (cw *countingWriter) Write(p []byte) (n int, err error) {
n, err = cw.w.Write(p)
cw.count += int64(n)
return
}
Error Handling
Common IO errors and how to handle them:
func handleIOErrors(err error) {
switch {
case errors.Is(err, io.EOF):
fmt.Println("Reached end of file")
case errors.Is(err, io.ErrShortWrite):
fmt.Println("Short write occurred")
case errors.Is(err, io.ErrUnexpectedEOF):
fmt.Println("Unexpected EOF")
default:
fmt.Printf("Unknown error: %v\n", err)
}
}
Next Steps
- Learn about File Handling
- Explore HTTP Client operations
- Study JSON Processing