标准库提供了多种函数和实用程序来读取文件数据。
这意味着两个先决条件:
file, err := os.Open("filetoread.txt")
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
fileinfo, err := file.Stat()
if err != nil {
fmt.Println(err)
return
}
filesize := fileinfo.Size()
buffer := make([]byte, filesize)
bytesread, err := file.Read(buffer)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("bytes read: ", bytesread)
fmt.Println("bytestream to string: ", string(buffer))
在大多数情况下,一次读取文件是有效的,但有时候我们会希望使用多块内存来读取文件。
读一个大小的文件,处理,并重复,直到结束
const BufferSize = 100
file, err := os.Open("filetoread.txt")
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
buffer := make([]byte, BufferSize)
for {
bytesread, err := file.Read(buffer)
if err != nil {
if err != io.EOF {
fmt.Println(err)
}
break
}
fmt.Println("bytes read: ", bytesread)
fmt.Println("bytestream to string: ", string(buffer[:bytesread]))
}
与完全读取文件相比,主要区别在于:
对于循环的每一次迭代,内部文件指针被更新。当下一次读取发生时,从文件指针偏移开始的数据返回到缓冲区的大小。所有读取/读取调用在内部翻译成系统调用并发送到内核,内核管理这个指针。
如果我们想要加快上面提到的块的处理呢?一种方法是使用多个go routines!
使用ReadAt与read是有一些区别的。
注意:不限制goroutine的数量,它只是由缓冲区大小来定义的。事实上,这个数字可能有一个上限。
const BufferSize = 100
file, err := os.Open("filetoread.txt")
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
fileinfo, err := file.Stat()
if err != nil {
fmt.Println(err)
return
}
filesize := int(fileinfo.Size())
// Number of go routines we need to spawn.
concurrency := filesize / BufferSize
// check for any left over bytes. Add one more go routine if required. //如果没除尽,就要加1
if remainder := filesize % BufferSize; remainder != 0 {
concurrency++
}
var wg sync.WaitGroup
wg.Add(concurrency)
for i := 0; i < concurrency; i++ {
go func(chunksizes []chunk, i int) {
defer wg.Done()
chunk := chunksizes[i]
buffer := make([]byte, chunk.bufsize)
bytesread, err := file.ReadAt(buffer, chunk.offset)
// As noted above, ReadAt differs slighly compared to Read when the
// output buffer provided is larger than the data that's available
// for reading. So, let's return early only if the error is
// something other than an EOF. Returning early will run the
// deferred function above
if err != nil && err != io.EOF {
fmt.Println(err)
return
}
fmt.Println("bytes read, string(bytestream): ", bytesread)
fmt.Println("bytestream to string: ", string(buffer[:bytesread]))
}(chunksizes, i)
}
wg.Wait()
注意:始终检查返回的字节数,并重新输出缓冲区。
可以实现通过使用类似 Scanner类型,和相关的功能bufio包。
这个bufio.Scanner类型实现了一个“分割”功能的函数,并根据这个函数前进一个指针。
如bufio.ScanLines,对于每一次迭代,内置的分割函数将指针推进到下一个换行符
file, err := os.Open("filetoread.txt")
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
// Returns a boolean based on whether there's a next instance of `\n`
// character in the IO stream. This step also advances the internal pointer
// to the next position (after '\n') if it did find that token.
read := scanner.Scan()
if read {
fmt.Println("read byte array: ", scanner.Bytes())
fmt.Println("read string: ", scanner.Text())
}
// goto Scan() line, and repeat
要以行的方式读取整个文件,可以使用类似这样的内容:
file, err := os.Open("filetoread.txt")
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
// This is our buffer now
var lines []string
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
fmt.Println("read lines:")
for _, line := range lines {
fmt.Println(line)
}
该bufio软件包包含基本的预定义分割功能
所以要读取文件,并在文件中创建一个单词列表,可以使用类似这样的内容:
file, err := os.Open("filetoread.txt")
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanWords)
var words []string
for scanner.Scan() {
words = append(words, scanner.Text())
}
fmt.Println("word list:")
for _, word := range words {
fmt.Println(word)
}
该ScanBytes分离功能将使输出作为我们在前期已经看到相同的Read()例子
主要区别:每次我们需要在扫描的情况下追加 字节/字符串 数组时,动态分配的问题
file, err := os.Open("filetoread.txt")
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanWords)
// initial size of our wordlist
bufferSize := 50
words := make([]string, bufferSize)
pos := 0
for scanner.Scan() {
if err := scanner.Err(); err != nil {
// This error is a non-EOF error. End the iteration if we encounter
// an error
fmt.Println(err)
break
}
words[pos] = scanner.Text()
pos++
if pos >= len(words) {
// expand the buffer by 100 again
newbuf := make([]string, bufferSize)
words = append(words, newbuf...)
}
}
fmt.Println("word list:")
// we are iterating only until the value of "pos" because our buffer size
// might be more than the number of words because we increase the length by
// a constant value. Or the scanner loop might've terminated due to an
// error prematurely. In this case the "pos" contains the index of the last
// successful update.
for _, word := range words[:pos] {
fmt.Println(word)
}
所以我们最终只做了很少的片段“增长”操作,但是根据bufferSize 文件中字数的不同,我们最终可能会得到一些空的插槽
bufio.NewScanner作为一个参数,需要一个满足一个io.Reader接口的类型 ,这意味着它可以处理任何具有Read定义的方法的类型 。标准库中返回“reader”类型的字符串实用程序方法之一是strings.NewReader 函数。从字符串中读出单词时,我们可以将它们结合起来:
file, err := os.Open("_config.yml")
longstring := "This is a very long string. Not."
handle(err)
var words []string
scanner := bufio.NewScanner(strings.NewReader(longstring))
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
words = append(words, scanner.Text())
}
fmt.Println("word list:")
for _, word := range words {
fmt.Println(word)
}
用基本file.Read()或 Scanner类型手动解析CSV文件/字符串是非常麻烦的,因为根据分割函数的“word” bufio.ScanWords被定义为由Unicode空格分隔的一堆符文。
读取个别符文,并跟踪缓冲区的大小和位置(如lexing / parsing中所做的)
我们可以定义一个新的分割功能,直到遇到读者一个逗号读取字符,然后返回块时,Text()或者Bytes()被调用。
type SplitFunc func(data []byte, atEOF bool) (advance int, token []byte, err error)
为了简单起见,我展示了一个读取字符串的例子,而不是一个文件。
csvstring := "name, age, occupation"
// An anonymous function declaration to avoid repeating main()
ScanCSV := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
commaidx := bytes.IndexByte(data, ',')
if commaidx > 0 {
// we need to return the next position
buffer := data[:commaidx]
return commaidx + 1, bytes.TrimSpace(buffer), nil
}
// if we are at the end of the string, just return the entire buffer
if atEOF {
// but only do that when there is some data. If not, this might mean
// that we've reached the end of our input CSV string
if len(data) > 0 {
return len(data), bytes.TrimSpace(data), nil
}
}
// when 0, nil, nil is returned, this is a signal to the interface to read
// more data in from the input reader. In this case, this input is our
// string reader and this pretty much will never occur.
return 0, nil, nil
}
scanner := bufio.NewScanner(strings.NewReader(csvstring))
scanner.Split(ScanCSV)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
如果你只是想读一个文件到缓冲区呢, 使用 ioutil
bytes, err := ioutil.ReadFile("_config.yml")
if err != nil {
log.Fatal(err)
}
fmt.Println("Bytes read: ", len(bytes))
fmt.Println("String read: ", string(bytes))
如果你有大文件,不要运行这个脚本
filelist, err := ioutil.ReadDir(".")
if err != nil {
log.Fatal(err)
}
for _, fileinfo := range filelist {
if fileinfo.Mode().IsRegular() {
bytes, err := ioutil.ReadFile(fileinfo.Name())
if err != nil {
log.Fatal(err)
}
fmt.Println("Bytes read: ", len(bytes))
fmt.Println("String read: ", string(bytes))
}
}
在标准库中有更多的功能来读取文件(或者更准确地说,是一个Reader)。
为了突出显示“读取”功能,我选择了使用打印出来并关闭文件的错误函数的路径:
func handleFn(file *os.File) func(error) {
return func(err error) {
if err != nil {
file.Close()
log.Fatal(err)
}
}
}
// inside the main function:
file, err := os.Open("filetoread.txt")
handle := handleFn(file)
handle(err)
这样做,错过了一个关键的细节:当没有错误,程序运行完成时,我没有关闭文件句柄。如果程序运行多次而不会引发任何错误,则会导致文件描述符泄漏。
我的初衷是避免defer因为内部log.Fatal调用 os.Exit不运行递延函数,所以我选择了明确的关闭文件,但是后来错过了另一个成功运行的情况。
我已经更新了要使用的示例defer,而return不是依靠os.Exit()。