博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
go 读取文件
阅读量:6422 次
发布时间:2019-06-23

本文共 8522 字,大约阅读时间需要 28 分钟。

  hot3.png

按字节读取

将整个文件读入内存

标准库提供了多种函数和实用程序来读取文件数据。

这意味着两个先决条件:

  1. 该文件必须适合内存
  2. 我们需要知道文件的大小,以便实例化一个足够大的缓冲区来保存它。
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 = 100file, 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]))}

与完全读取文件相比,主要区别在于:

  1. 我们读取,直到我们得到一个EOF标记,所以我们添加一个特定的检查 err == io.EOF。
  2. 我们定义缓冲区大小,所以我们可以控制我们想要的“块”大小。这可以提高正确使用时的性能,因为操作系统使用高速缓存正在读取的文件。
  3. 如果文件大小不是缓冲区大小的整数倍,则最后一次迭代将只将剩余的字节数添加到缓冲区,从而调用buffer[:bytesread]。在正常情况下, bytesread将与缓冲区大小相同。

对于循环的每一次迭代,内部文件指针被更新。当下一次读取发生时,从文件指针偏移开始的数据返回到缓冲区的大小。所有读取/读取调用在内部翻译成系统调用并发送到内核,内核管理这个指针。

同时读取文件块

如果我们想要加快上面提到的块的处理呢?一种方法是使用多个go routines!

使用ReadAt与read是有一些区别的。

注意:不限制goroutine的数量,它只是由缓冲区大小来定义的。事实上,这个数字可能有一个上限。

const BufferSize = 100file, 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. //如果没除尽,就要加1if remainder := filesize % BufferSize; remainder != 0 {  concurrency++}var wg sync.WaitGroupwg.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 nowvar lines []stringfor scanner.Scan() {  lines = append(lines, scanner.Text())}fmt.Println("read lines:")for _, line := range lines {  fmt.Println(line)}

逐个单词扫描

该bufio软件包包含基本的预定义分割功能

  1. ScanLines(默认)
  2. ScanWords
  3. ScanRunes(对于遍历UTF-8码点非常有用,而不是字节)
  4. ScanBytes

所以要读取文件,并在文件中创建一个单词列表,可以使用类似这样的内容:

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 []stringfor 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 wordlistbufferSize := 50words := make([]string, bufferSize)pos := 0for 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 []stringscanner := 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)
  1. data 是输入字节的字符串
  2. atEOF 是传递给函数的标志,表示令牌的结尾
  3. advance使用它我们可以指定要处理的位置数作为当前的读取长度。扫描循环完成后,此值用于更新光标位置
  4. token 是扫描操作的实际数据
  5. err 如果你想表示一个问题。

为了简单起见,我展示了一个读取字符串的例子,而不是一个文件。

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)。

  1. ioutil.ReadAll() - >采取一个类似于io的对象,并将整个数据作为字节数组返回
  2. io.ReadFull()
  3. io.ReadAtLeast()
  4. io.MultiReader - >一个非常有用的基元来组合多个类似io的对象。所以你可以有一个要读取的文件列表,并将它们视为一个连续的数据块,而不是管理在每个以前的对象末尾切换文件对象的复杂性。

错误修复

为了突出显示“读取”功能,我选择了使用打印出来并关闭文件的错误函数的路径:

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()。

参考

PS: 觉得不错的请点个赞吧!! (ง •̀_•́)ง

转载于:https://my.oschina.net/solate/blog/1604122

你可能感兴趣的文章
微信小程序教学第三章(含视频):小程序中级实战教程:列表篇-页面逻辑处理...
查看>>
页面间通信与数据共享解决方案简析
查看>>
Swift 中 Substrings 与 String
查看>>
作为一个开源软件的作者是一种什么样的感受?
查看>>
移动端适配知识你到底知多少
查看>>
TiDB 在 G7 的实践和未来
查看>>
重新认识javascript对象(三)——原型及原型链
查看>>
小学生学“数学”
查看>>
【Vue】组件使用之参数校验
查看>>
FastDFS蛋疼的集群和负载均衡(十七)之解决LVS+Keepalived遇到的问题
查看>>
深入剖析Redis系列(二) - Redis哨兵模式与高可用集群
查看>>
Android 用于校验集合参数的小封装
查看>>
iOS混合开发库(GICXMLLayout)七、JavaScript篇
查看>>
instrument 调试 无法指出问题代码 解决
查看>>
理解缓存
查看>>
im也去中心化?Startalk(星语)的去中心化设计之路
查看>>
BAT 经典算法笔试题 —— 磁盘多路归并排序
查看>>
一次完整的HTTP请求
查看>>
Nginx限制带宽
查看>>
All Web Application Attack Techniques
查看>>