快捷搜索:  汽车  科技

go读取大文件(GO编程读取文件)

go读取大文件(GO编程读取文件)$ go run byLine.go /tmp/swtag.log /tmp/adobegc.log | wc 4761 88521 568402如下的命令会校验前述输出的精确性:func main() { flag.Parse() if len(flag.Args()) == 0 { fmt.Printf("usage: byLine <file1> [<file2> ...]\n") return } for _ file := range flag.Args() { err := lineByLine(file) if err != nil { fmt.Println(err) } }

go读取大文件(GO编程读取文件)(1)

文本文件是Unix系统中最常见的一种文件。在本节中,你将学习如何以三种方式读取文本文件:逐行读取、逐词读取、逐字符读取。如你所见,逐行读取是访问文本文件的最简单方法,而逐词读取是最困难的方法。

如果你仔细观察byLine.go、byWord.go、byCharacter.go程序,你会发现它们的Go代码有许多相似之处。首先,它们都是逐行读取输入文件。其次,除了main()函数的for循环中调用的函数不同之外,这三个实用程序都具有相同的main()函数。最后,除了函数的实现部分不同之外,处理输入文本文件的三个函数几乎是相同的。

逐行读取文本文件

逐行读取文本文件是最常用的方式。这也是我们首先介绍它的原因。byLine.go程序分为三部分,将帮助你理解这个技巧。

byLine.go第一部分代码如下:

package main import ( "bufio" "flag" "fmt" "io" "os" )

引入bufio包表示我们将使用缓冲区输入。

func lineByLine(file string) error { var err error f err := os.Open(file) if err != nil { return err } defer f.Close() r := bufio.NewReader(f) for { line err := r.ReadString('\n') if err == io.EOF { break } else if err != nil { fmt.Printf("error reading file %s" err) break } fmt.Printf(line) } return nil }

所有的实现都在lineByLine()函数中。在确保可以打开指定的文件名进行读取之后,你调用bufio.NewReader()创建一个新的读实例,然后你可以调用bufio.ReadString()逐行读取文件。行分隔符通过bufio.ReadString()参数指定,它指示bufio.ReadString()一直读取,直到碰到行分隔符为止。当参数是换行符时,不断调用bufio.ReadString()会逐行读取输入文件!注意,使用fmt.Print()而不是fmt.Println()输出读取行,说明每个输入行中都包含了换行符。

byLine.go第三部分代码如下:

func main() { flag.Parse() if len(flag.Args()) == 0 { fmt.Printf("usage: byLine <file1> [<file2> ...]\n") return } for _ file := range flag.Args() { err := lineByLine(file) if err != nil { fmt.Println(err) } } }

执行byLine.go,并使用wc(1)处理输出会产生如下的输出内容:

$ go run byLine.go /tmp/swtag.log /tmp/adobegc.log | wc 4761 88521 568402

如下的命令会校验前述输出的精确性:

$ wc /tmp/swtag.log /tmp/adobegc.log 131 693 8440 /tmp/swtag.log 4630 87828 559962 /tmp/adobegc.log 4761 88521 568402 total逐词读取文本文件

技术将通过byWord.go文件演示,它由四部分组成。正如你在Go代码中看到的,分隔一行中的单词可能比较棘手。程序的第一部分如下:

package main import ( "bufio" "flag" "fmt" "io" "os" "regexp" )

byWord.go的第二部分代码如下:

func wordByWord(file string) error { var err error f err := os.Open(file) if err != nil { return err } defer f.Close() r := bufio.NewReader(f) for { line err := r.ReadString('\n') if err == io.EOF { break } else if err != nil { fmt.Printf("error reading file %s" err) return err }

wordByWord()函数的这部分代码和byLine.go程序的lineByLine()函数一样。

byWord.go第三部分代码如下:

r := regexp.MustCompile("[^\\s] ") words := r.FindAllString(line -1) for i := 0; i < len(words); i { fmt.Printf(words[i]) } } return nil }

wordByWord()函数的剩余代码是全新的,并使用正则表达式对输入的每行进行单词分割。正则表达式regexp.MustCompile("[^\\s] ")使用空格分割单词。

byWord.go的最后一部分代码如下:

func main() { flag.Parse() if len(flag.Args()) == 0 { fmt.Printf("usage: byWord <file1> [<file2> ...]\n") return } for _ file := range flag.Args() { err := wordByWord(file) if err != nil { fmt.Println(err) } } }

执行byWord.go会产生如下的输出:

$ go run byWord.go /tmp/adobegc.log 01/08/18 20:25:09:669 | [INFO]

可以使用wc(1)验证byWord.go的正确性:

$ go run byWord.go /tmp/adobegc.log | wc 91591 91591 559005 $ wc /tmp/adobegc.log 4831 91591 583454 /tmp/adobegc.log

如你所见,wc(1)计算所得的单词数和byWord.go一致。

逐字符读取文本文件

学会如何逐字符读取文本文件,这是一个非常少见的需求,除非你想创建文本编辑器。相关的Go代码参考byCharacter.go,将分为四部分介绍。

byCharacter.go第一部分代码如下:

package main import ( "bufio" "flag" "fmt" "io" "os" )

如你所见,本程序不需要使用正则表达式。

byCharacter.go第二部分代码如下:

func charByChar(file string) error { var err error f err := os.Open(file) if err != nil { return err } defer f.Close() r := bufio.NewReader(f) for { line err := r.ReadString('\n') if err == io.EOF { break } else if err != nil { fmt.Printf("error reading file %s" err) return err }

byCharacter.go第三部分代码如下:

for _ x := range line { fmt.Println(string(x)) } } return nil }

此处,你读取每一行,并使用range分割。range返回两个值:丢弃第一个值,它是line变量中当前字符的位置,并使用第二个值。但是,该值不是字符-这就是为什么必须使用string()函数将其转换为字符的原因。

注意,由于fmt.Println(string(x))语句,每个字符都按行打印,这意味着程序的输出将很大。如果想要压缩输出,应该使用fmt.Print()函数。

byCharacter.go的最后一部分代码如下:

func main() { flag.Parse() if len(flag.Args()) == 0 { fmt.Printf("usage: byChar <file1> [<file2> ...]\n") return } for _ file := range flag.Args() { err := charByChar(file) if err != nil { fmt.Println(err) } } }

执行byCharacter.go会产生如下输出:

$ go run byCharacter.go /tmp/adobegc.log 0 1 / 0 8 / 1 8

注意,上述Go代码也能用来统计输入文件的字符个数,即是Go版本的wc(1)命令行实现。

从/dev/random中读取

学习如何从/dev/random系统设备读取数据。/dev/random系统设备的目的是生成随机数据,你可以使用这些数据来测试程序,或者,在本例中,你将为随机数生成器生成随机数种子。从/dev/random获取数据可能有些棘手,这也是要在这里特别讨论它的主要原因。

在macOS High Sierra机器上,/dev/random文件具有如下的权限:

$ ls -l /dev/random crw-rw-rw- 1 root wheel 14 0 Jan 8 20:24 /dev/random

同样,在Debian Linux机器上,/dev/random系统设备具有如下的Unix文件权限:

$ ls -l /dev/random crw-rw-rw- 1 root root 1 8 Jan 13 12:19 /dev/random

这意味着/dev/random文件在这两种Unix变体系统上具有类似的文件权限。惟一区别是它们的文件组权限不同,macOS是wheel,Debian Linux上是root。

本节主题的程序是devRandom.go,分为三部分。第一部分代码如下:

package main import ( "encoding/binary" "fmt" "os" )

为了从dev/random中读取数据,需要引入encoding/binary标准包,因为/dev/random返回二进制数据,需要解码。

devRandom.go第二部分代码如下:

func main() { f err := os.Open("/dev/random") defer f.Close() if err != nil { fmt.Println(err) return }

和以往一样,打开/dev/random,因为Unix中一切皆是文件。

devRandom.go最后一部分代码如下:

var seed int64 binary.Read(f binary.LittleEndian &seed) fmt.Println("Seed:" seed) }

调用binary.Read()函数从/dev/random系统设备中读取数据, binary.Read()函数需要三个参数:第二个参数(binary.LittleEndian)指定了小端字节序,另一个选项是binary.BigEndian,在计算机使用大端字节序时使用。

执行devRandom.go得到如下输出:

$ go run devRandom.go Seed: -2044736418491485077 $ go run devRandom.go Seed: -517485437251490328 $ go run devRandom.go Seed: 7702177874251412774从文件中读取所需的数据量

学习如何准确读取所需的数据量。这种技术在读取二进制文件时特别有用,在二进制文件中,必须以特定的方式解码读取的数据。不过,这种技术仍然适用于文本文件。

这种技术背后的逻辑并不难:创建一个所需大小的字节切片,并使用该字节切片进行读取。为了更有趣一点,我们将使用一个函数实现这个功能,这个函数具有两个参数。一个参数用于指定需要读取的数据量,另一个参数将使用*os.File文件类型,用于访问所需的文件。该函数的返回值将是所读取的数据。

本节的实现文件是readSize.go,分为四部分。程序接受一个简单的参数,为字节切片的大小。

当使用当前技术时,这个特定的程序可以帮助你使用所需的缓冲区大小复制任何文件。

readSize.go的第一部分代码如下:

package main import ( "fmt" "io" "os" "strconv" )

readSize.go的第二部分代码如下:

func readSize(f *os.File size int) []byte { buffer := make([]byte size) n err := f.Read(buffer) if err == io.EOF { return nil } if err != nil { fmt.Println(err) return nil } return buffer[0:n] }

这即是前面讨论的函数。尽管代码相当直接,但仍有一点需要解释。io.Reader.Read()方法返回两个参数:读取的字节数以及error变量。

readSize()函数的作用是:使用io.Read()的第一个返回值返回字节切片大小。虽然这是一个很小的细节,而且只有在到达文件末尾时才重要,但是它确保实用程序的输出与输入相同,并且不包含任何额外的字符。最后,还要检查io.EOF 表示已经到达文件的末尾。当发生错误时,函数返回。

代码的第三部分如下:

func main() { arguments := os.Args if len(arguments) != 3 { fmt.Println("<buffer size> <filename>") return } bufferSize err := strconv.Atoi(os.Args[1]) if err != nil { fmt.Println(err) return } file := os.Args[2] f err := os.Open(file) if err != nil { fmt.Println(err) return } defer f.Close()

readSize.go的最后一部分代码如下:

for { readData := readSize(f bufferSize) if readData != nil { fmt.Println(string(readData)) } else { break } } }

所以,你需要一直读取输入文件,直到返回错误或者nil。

执行readSize.go,传入处理的二进制文件,并使用wc(1)处理它的输出,来验证程序的正确性。

复制代码$ go run readSize.go 1000 /bin/ls | wc 80 1032 38688 $ wc /bin/ls 80 1032 38688 /bin/ls读取CSV文件

CSV文件是纯文本文件。在本节中,你将学习如何读取包含平面点的文本文件,这意味着每一行将包含一对坐标。此外,你还将使用一个名为Glot的外部Go库,它将帮助你创建从CSV文件中读取的点的图表。注意,Glot使用Gnuplot,这意味着你需要在Unix机器上安装Gnuplot才能使用Glot。

程序是CSVplot.go,分为五部分。第一部分代码如下:

package main import ( "encoding/csv" "fmt" "github.com/Arafatk/glot" "os" "strconv" )

CSVplot.go第二部分代码如下:

func main() { if len(os.Args) != 2 { fmt.Println("Need a data file!") return } file := os.Args[1] _ err := os.Stat(file) if err != nil { fmt.Println("Cannot stat" file) return }

在本部分中,你将看到一种使用强大的os.Stat()函数检查文件是否已经存在的技术。

CSVplot.go第三部分代码如下:

f err := os.Open(file) if err != nil { fmt.Println("Cannot open" file) fmt.Println(err) return } defer f.Close() reader := csv.NewReader(f) reader.FieldsPerRecord = -1 allRecords err := reader.ReadAll() if err != nil { fmt.Println(err) return }

CSVplot.go第四部分代码如下:

xP := []float64{} yP := []float64{} for _ rec := range allRecords { x _ := strconv.ParseFloat(rec[0] 64) y _ := strconv.ParseFloat(rec[1] 64) xP = append(xP x) yP = append(yP y) } points := [][]float64{} points = append(points xP) points = append(points yP) fmt.Println(points)

此处,你将字符串转为数字,并追加到二维切片points中。

CSVplot.go最后一部分代码如下:

dimensions := 2 persist := true debug := false plot _ := glot.NewPlot(dimensions persist debug) plot.SetTitle("Using Glot with CSV data") plot.SetXLabel("X-Axis") plot.SetYLabel("Y-Axis") style := "circle" plot.AddPointGroup("Circle:" style points) plot.SavePlot("output.png") }

在前面的go代码中,你了解了如何使用Glot库及其Glot.SavePlot()函数创建PNG文件。

可以猜到,在编译和执行CSVplot.go之前,需要下载Glot Go库,它需要从Unix shell执行以下命令:

$ go get github.com/Arafatk/glot

你可以通过查看~/go目录查看当前命令的执行结果:

$ ls -l ~/go/pkg/darwin_amd64/github.com/Arafatk/ total 240 -rw-r--r-- 1 mtsouk staff 119750 Jan 22 22:12 glot.a $ ls -l ~/go/src/github.com/Arafatk/glot/ total 120 -rw-r--r-- 1 mtsouk staff 1818 Jan 22 22:12 README.md -rw-r--r-- 1 mtsouk staff 6092 Jan 22 22:12 common.go -rw-r--r-- 1 mtsouk staff 552 Jan 22 22:12 common_test.go -rw-r--r-- 1 mtsouk staff 3162 Jan 22 22:12 core.go -rw-r--r-- 1 mtsouk staff 138 Jan 22 22:12 core_test.go -rw-r--r-- 1 mtsouk staff 3049 Jan 22 22:12 function.go -rw-r--r-- 1 mtsouk staff 511 Jan 22 22:12 function_test.go -rw-r--r-- 1 mtsouk staff 4955 Jan 22 22:12 glot.go -rw-r--r-- 1 mtsouk staff 220 Jan 22 22:12 glot_test.go -rw-r--r-- 1 mtsouk staff 10536 Jan 22 22:12 pointgroup.go -rw-r--r-- 1 mtsouk staff 378 Jan 22 22:12 pointgroup_test.go

包含要绘制的点的CSV数据文件具有以下格式:

$ cat /tmp/dataFile 1 2 2 3 3 3 4 4 5 8 6 5 -1 12 -2 10 -3 10 -4 10

执行CSVplot.go会产生如下的输出:

$ go run CSVplot.go /tmp/doesNoExits Cannot stat /tmp/doesNoExits $ go run CSVplot.gp /tmp/dataFile [[1 2 3 4 5 6 -1 -2 -3 -4] [2 3 3 4 8 5 12 10 10 10]]

猜您喜欢: