[string] Reading a file line by line in Go

Use:

  • reader.ReadString('\n')
    • If you don't mind that the line could be very long (i.e. use a lot of RAM). It keeps the \n at the end of the string returned.
  • reader.ReadLine()
    • If you care about limiting RAM consumption and don't mind the extra work of handling the case where the line is greater than the reader's buffer size.

I tested the various solutions suggested by writing a program to test the scenarios which are identified as problems in other answers:

  • A file with a 4MB line.
  • A file which doesn't end with a line break.

I found that:

  • The Scanner solution does not handle long lines.
  • The ReadLine solution is complex to implement.
  • The ReadString solution is the simplest and works for long lines.

Here is code which demonstrates each solution, it can be run via go run main.go, or at https://play.golang.org/p/RAW3sGblbas

package main

import (
    "bufio"
    "bytes"
    "fmt"
    "io"
    "os"
)

func readFileWithReadString(fn string) (err error) {
    fmt.Println("readFileWithReadString")

    file, err := os.Open(fn)
    if err != nil {
        return err
    }
    defer file.Close()

    // Start reading from the file with a reader.
    reader := bufio.NewReader(file)
    var line string
    for {
        line, err = reader.ReadString('\n')
        if err != nil && err != io.EOF {
            break
        }

        // Process the line here.
        fmt.Printf(" > Read %d characters\n", len(line))
        fmt.Printf(" > > %s\n", limitLength(line, 50))

        if err != nil {
            break
        }
    }
    if err != io.EOF {
        fmt.Printf(" > Failed with error: %v\n", err)
        return err
    }
    return
}

func readFileWithScanner(fn string) (err error) {
    fmt.Println("readFileWithScanner (scanner fails with long lines)")

    // Don't use this, it doesn't work with long lines...

    file, err := os.Open(fn)
    if err != nil {
        return err
    }
    defer file.Close()

    // Start reading from the file using a scanner.
    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        line := scanner.Text()

        // Process the line here.
        fmt.Printf(" > Read %d characters\n", len(line))
        fmt.Printf(" > > %s\n", limitLength(line, 50))
    }
    if scanner.Err() != nil {
        fmt.Printf(" > Failed with error %v\n", scanner.Err())
        return scanner.Err()
    }
    return
}

func readFileWithReadLine(fn string) (err error) {
    fmt.Println("readFileWithReadLine")

    file, err := os.Open(fn)
    if err != nil {
        return err
    }
    defer file.Close()

    // Start reading from the file with a reader.
    reader := bufio.NewReader(file)
    for {
        var buffer bytes.Buffer

        var l []byte
        var isPrefix bool
        for {
            l, isPrefix, err = reader.ReadLine()
            buffer.Write(l)
            // If we've reached the end of the line, stop reading.
            if !isPrefix {
                break
            }
            // If we're at the EOF, break.
            if err != nil {
                if err != io.EOF {
                    return err
                }
                break
            }
        }
        line := buffer.String()

        // Process the line here.
        fmt.Printf(" > Read %d characters\n", len(line))
        fmt.Printf(" > > %s\n", limitLength(line, 50))

        if err == io.EOF {
            break
        }
    }
    if err != io.EOF {
        fmt.Printf(" > Failed with error: %v\n", err)
        return err
    }
    return
}

func main() {
    testLongLines()
    testLinesThatDoNotFinishWithALinebreak()
}

func testLongLines() {
    fmt.Println("Long lines")
    fmt.Println()

    createFileWithLongLine("longline.txt")
    readFileWithReadString("longline.txt")
    fmt.Println()
    readFileWithScanner("longline.txt")
    fmt.Println()
    readFileWithReadLine("longline.txt")
    fmt.Println()
}

func testLinesThatDoNotFinishWithALinebreak() {
    fmt.Println("No linebreak")
    fmt.Println()

    createFileThatDoesNotEndWithALineBreak("nolinebreak.txt")
    readFileWithReadString("nolinebreak.txt")
    fmt.Println()
    readFileWithScanner("nolinebreak.txt")
    fmt.Println()
    readFileWithReadLine("nolinebreak.txt")
    fmt.Println()
}

func createFileThatDoesNotEndWithALineBreak(fn string) (err error) {
    file, err := os.Create(fn)
    if err != nil {
        return err
    }
    defer file.Close()

    w := bufio.NewWriter(file)
    w.WriteString("Does not end with linebreak.")
    w.Flush()
    return
}

func createFileWithLongLine(fn string) (err error) {
    file, err := os.Create(fn)
    if err != nil {
        return err
    }
    defer file.Close()

    w := bufio.NewWriter(file)
    fs := 1024 * 1024 * 4 // 4MB
    // Create a 4MB long line consisting of the letter a.
    for i := 0; i < fs; i++ {
        w.WriteRune('a')
    }
    // Terminate the line with a break.
    w.WriteRune('\n')

    // Put in a second line, which doesn't have a linebreak.
    w.WriteString("Second line.")
    w.Flush()
    return
}

func limitLength(s string, length int) string {
    if len(s) < length {
        return s
    }
    return s[:length]
}

I tested on:

  • go version go1.15 darwin/amd64
  • go version go1.7 windows/amd64
  • go version go1.6.3 linux/amd64
  • go version go1.7.4 darwin/amd64

The test program outputs:

Long lines

readFileWithReadString
 > Read 4194305 characters
 > > aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
 > Read 12 characters
 > > Second line.

readFileWithScanner (scanner fails with long lines)
 > Failed with error bufio.Scanner: token too long

readFileWithReadLine
 > Read 4194304 characters
 > > aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
 > Read 12 characters
 > > Second line.
 > Read 0 characters
 > > 

No linebreak

readFileWithReadString
 > Read 28 characters
 > > Does not end with linebreak.

readFileWithScanner (scanner fails with long lines)
 > Read 28 characters
 > > Does not end with linebreak.

readFileWithReadLine
 > Read 28 characters
 > > Does not end with linebreak.
 > Read 0 characters
 > > 

Examples related to string

How to split a string in two and store it in a field String method cannot be found in a main class method Kotlin - How to correctly concatenate a String Replacing a character from a certain index Remove quotes from String in Python Detect whether a Python string is a number or a letter How does String substring work in Swift How does String.Index work in Swift swift 3.0 Data to String? How to parse JSON string in Typescript

Examples related to file

Gradle - Move a folder from ABC to XYZ Difference between opening a file in binary vs text Angular: How to download a file from HttpClient? Python error message io.UnsupportedOperation: not readable java.io.FileNotFoundException: class path resource cannot be opened because it does not exist Writing JSON object to a JSON file with fs.writeFileSync How to read/write files in .Net Core? How to write to a CSV line by line? Writing a dictionary to a text file? What are the pros and cons of parquet format compared to other formats?

Examples related to parsing

Got a NumberFormatException while trying to parse a text file for objects Uncaught SyntaxError: Unexpected end of JSON input at JSON.parse (<anonymous>) Python/Json:Expecting property name enclosed in double quotes Correctly Parsing JSON in Swift 3 How to get response as String using retrofit without using GSON or any other library in android UIButton action in table view cell "Expected BEGIN_OBJECT but was STRING at line 1 column 1" How to convert an XML file to nice pandas dataframe? How to extract multiple JSON objects from one file? How to sum digits of an integer in java?

Examples related to go

Has been blocked by CORS policy: Response to preflight request doesn’t pass access control check Go test string contains substring Golang read request body How to uninstall Golang? Decode JSON with unknown structure Access HTTP response as string in Go How to search for an element in a golang slice How to delete an element from a Slice in Golang How to set default values in Go structs MINGW64 "make build" error: "bash: make: command not found"

Examples related to line

How do I compute the intersection point of two lines? Java Replace Line In Text File draw diagonal lines in div background with CSS How to make a new line or tab in <string> XML (eclipse/android)? How do you run a command for each line of a file? How can I format a list to print each element on a separate line in python? Remove lines that contain certain string How to paste text to end of every line? Sublime 2 Making PHP var_dump() values display one line per value How to do multiple line editing?