蹒跚学Go第六天-文本文件处理

文本文件的处理

字符串在开发中经常用到,包括用户的输入,数据库读取的数据等,我们经常需要对字符串进行分割、连接、转换等操作,我们可以通过Go标准库中的strings和strconv两个包中的函数进行相应的操作。
Go 语言标准库中有几个包专门用于处理文本。

  • strings:字符串操作 -> 这个包提供了很多操作字符串的简单函数,通常一般的字符串操作需求都可以在这个包中找到。

  • vbytes:byte slice便利操作 -> 在Go语言中string是内置类型,同时它与普通的slice类型有着相似的性质,均可以进行切片(slice)操作。

  • strconv:字符串和基本数据类型之间转换 -> 因为在Go语言中,没有隐式类型转换,字符串类型和int、float、bool等类型之间不能直接使用基本数据类型那套方法转换,需要使用这个包的方法实现转换。

  • regexp:正则表达式 -> 谈到字符串必然离不开正则表达式,这个包提供了正则表达式的功能。

  • unicode:Unicode编码、UTF-8/16编码。

创建文件

将数据存储到文件之前需要做的是创建文件,Go语言中提供了Create()函数专用来创建文件,当改函数创建文件时候首先会判断是否存在则覆盖原文件,不存在则创建,同时创建成功后,该文件会默认打开直接可以先其中写入数据

创建的步骤:
(1)导入“OS”包 -> 创建文件读写文件的函数都在包中
(2)指定创建的文件存放的路径以及文件名字
(3)执行create()函数,进行文件创建
(4)关闭文件
  • 例子
func createfile(path string) {
    //创建文件 -> 返回值有两个一个是创建的一个是错误信息
    f,err := os.Create(path)
    if err!=nil {   //如果有错则报错退出
        fmt.Println("errer :",err)
        return
    }
    defer f.Close() //异常时候保证能退出整个函数,关闭文件
}
func main() {
    var filePath = "a.txt"
    createfile(filePath)
}

写入数据

  • 以下程序Seek()函数返回值存储到变量N中,值为文件末尾的位置,WriteAt()的返回是写写入的数据长度
func createfile(path string) {
    //创建文件 -> 返回值有两个一个是创建的一个是错误信息
    f,err := os.Create(path)
    if err!=nil {   //如果有错则报错退出
        fmt.Println("errer :",err)
        return
    }
    var str string
    var a int
    for i := 1;i <=10;i++{
        str = fmt.Sprintf("i = %d\n",i)
        buf := []byte(str)
        //f.WriteString()不接受类字符串所以要换Write
        //第三种写入的方式使用WriteAt( )函数,在指定的位置写入数据
        n,_:=f.Seek(0,os.SEEK_END)  //查找文件末尾的偏移量
        //从文件末尾的偏移量开始写入内容
        a,err = f.WriteAt([]byte(buf),n)
        fmt.Println(a)
        //n,err := f.Write(buf)     //检查是否成功写入文件
        if err != nil{  //判断异常
            fmt.Println(err,n)
        }
    }
    defer f.Close() //异常时候保证能退出整个函数,关闭文件
}
func main() {
    var filePath = "a.txt"
    createfile(filePath)
}

追加数据

open()是打开文件、openfile()的区别是,open只有只读权限
在使用read函数读取文件内容时候,需要一个切片的类型,而定义切片类型时候为字符数组将文件中的内容保存在切片中,同时除了对其判断是否出错以外还需要判断是否到文件末尾。
read()返回的是从文件中读取的数据的长度,最后输出切片中存储的文件数据。注意点:读取是从开始到整个数据长度,有可能存储到切片中的数据达不到切片的总长度

func writefile(path string){
    //打开文件
    f,err := os.OpenFile(path,os.O_APPEND,6)
    //openfile()有三个参数项(打开文件路径,打开的模式,权限)
    //常见的打开模式为:O_RDONLY(只读)、O_WRONLY(只写)、O_RDWR(读写)、O_APPEND(追加)
    if err != nil {
        fmt.Println("err=",err)
        return
    }
    n,err1 := f.WriteString("先说你好!")
    if err1 != nil {
        fmt.Println("err=",err1)
        return
    }
    fmt.Println(n)
    //关闭文件
    defer f.Close()
}
func main(){
    writefile("a.txt")
}
权限 解读
0 没有任何权限
1 执行权限(如果是可执行文件,是可以运行的)
2 写权限
3 写权限与执行权限
4 读权限
5 读权限与执行权限
6 读权限与写权限
7 读权限,写权限,执行权限

按行读取

  • 需要使用到bufio包中的ReadBytes函数

当使用ReadBytes函数,传递参数为\n,表示遇见\n就结束,所以使用了死循环(每一次循环读取一次)当到达文件末尾退出循环,最后读取的数据打印出来,ReadBytes返回的是字节切片所以打印时候要转换成字符串

func readline(path string){
    //打开文件
    f,err := os.Open(path)
    if err != nil {
        fmt.Println("err=",err)
        return
    }
    //新建一个缓冲区,将内容暂时放缓冲区
    r := bufio.NewReader(f)
    //循环读取文件中的内容,直到文件末尾位置
    for {
        //遇到“\n”结束读取,但是“\n”也读取进入
        buf,err := r.ReadBytes('\n')
        if err != nil{
            //当文件已经结束
            if err == io.EOF{
                break
            }
            fmt.Println("err=",err)
        }
        fmt.Printf("BUF= #%s#\n",string(buf))
    }
    defer f.Close()
}
func main(){
    readline("a.txt")
}
  • 案例练习
var Object struct{
    SourFileName string
    DesFileName string
}
//让用户输入要拷贝的文件名称(源文件->目标文件)
func Iputinfo() {
    fmt.Println("请输入源文件名称:")
    fmt.Scanf("%s",&Object.SourFileName)
    fmt.Println("请输入目标件名称:")
    fmt.Scanf("%s",&Object.DesFileName)
}
//创建目标文件,并读取源文件的内容
func creafile(path string,sespath string) {
    a,err1:=os.Open(path)
    if err1 != nil{
        fmt.Println("err1=",err1)
        return
    }
    //新建目的文件
    d,err2 := os.Create(sespath)
    if err2 != nil {
        fmt.Println("err2=",err2)
        return
    }
    defer a.Close()
    defer d.Close()
    //核心处理从源文件读取内容 - > 目标文件,读多少写多少
    buf := make([]byte,4*1024)  //4k的临时缓冲区
    for {
        n,err3 := a.Read(buf)
        if err3 != nil {
            fmt.Println("err2=",err3)
            if err3 == io.EOF{ //判断读到结束时候跳出整个循环
                break
            }
        }
        d.Write(buf[:n])
    }
}
func main() {
    creafile("a.txt","b.txt")
}

字符串

字符串的处理

从文件中将数据读取出来以后,很多情况下并不是直接将数据打印出来,而是要做相应的处理。例如:去掉空格等一些特殊的符号,对一些内容进行替换等。这里就涉及到对一些字符串的处理。在对字符串进行处理时,需要借助于包“strings”

Contains

  • 功能:字符串s中是否包含substr返回为bool
func Contains(s,substr string) bool
  • 例子
func main(){
    var str string = "nihaonihao"
    //str中判断是否包含字符串
    fmt.Println(strings.Contains(str,"ni")) //返回true
    fmt.Println(strings.Contains(str,"a 1"))    //返回flase
}
输出值:
true
flase

Join

  • 功能字符串连接,将slice a通过sep连接起来
func Join(a []string,sep string) string
  • 例子
func main(){
    s := []string{"aaa","bbb","ccc"}
    buf := strings.Join(s,"\n")
    fmt.Println(buf)
}
输出值:
aaa
bbb
ccc

Index

  • 功能:在字符串S中查找sep所在的位置,返回位置找不到返回-1
func index(s,sep string) int
  • 例子
func main(){
    s := []string{"aaa","bbb","ccc"}
    for i := 0; i < len(s);i++ {
        fmt.Println(strings.Index(s[i],"a"))
    }
}

Replace

  • 功能在s字符串中,把old字符串替换为new字符串,n表示替换的次数,小于0表示全部替换
func Replace(s,old string,new string,n int) string
  • 例子
func main(){
    s := []string{"aaa","bab","caac","abcada"}
    for i := 0; i < len(s);i++ {
        fmt.Println("当前的循环次数:",i,"->",strings.Replace(s[i],"a","rr",i))
    }
}
输出值:
当前的循环次数: 0 -> aaa
当前的循环次数: 1 -> brrb
当前的循环次数: 2 -> crrrrc
当前的循环次数: 3 -> rrbcrrdrr

Split

  • 功能把s字符串按照sep分割,返回slice
func Split(s,sep string) []string
  • 例子
func main(){
    s := []string{"a|aa","ba|b","ca|ac","ab|cada"}
    for i := 0; i < len(s);i++ {
        fmt.Println("当前的循环次数:",i,"->",strings.Split(s[i],"|"))
    }
}
输出值:
当前的循环次数: 0 -> [a aa]
当前的循环次数: 1 -> [ba b]
当前的循环次数: 2 -> [ca ac]
当前的循环次数: 3 -> [ab cada]

Fields

  • 功能:去除s字符串的空格符,并且按照空格分割返回slice
func Fields(s string) [string]
  • 例子
    fmt.Printf("Fields are: %q", strings.Fields("  foo bar  baz   "))
    //运行结果:Fields are: ["foo" "bar" "baz"]

Trim

  • 功能:在s字符串的头部和尾部去除cutset指定的字符串
func Trim(s string, cutset string) string
例子:
str := "    \"123\"    "
fmt.Println(strings.Trim(str," "))
输出结果:
"123"

对字符串进行遍历修改其中一项

func main() {
    a := "test"
    b := []byte(a)
    b[2] = 'x'  // 'x'为rune类型
    a = string(b)
    fmt.Println(a)
}
//上边的示例并不是更新字符串的正确姿势,因为一个 UTF8 编码的字符可能会占多个字节,比如汉字就需要 3~4 个字节来存储,此时更新其中的一个字节是错误的
//更新字串的正确姿势:将 string 转为 rune slice(此时 1 个 rune 可能占多个 byte),直接更新 rune 中的字符
func main() {
    x := "text"
    xRunes := []rune(x)
    xRunes[0] = '我'
    x = string(xRunes)
    fmt.Println(x)  // 我ext
}

字符串转换

(1)Append 系列函数将整数等转换为字符串后,添加到现有的字节数组中

func main() {
    str := make([]byte,0,100)
    str = strconv.AppendInt(str,12341,10)   //以10进制的方式进行追加
    str = strconv.AppendBool(str,false) //追加bool值
    str = strconv.AppendQuote(str,"傻逼") //追加字符串
    str = strconv.AppendQuoteRune(str,'一')  //只能使用追加一个字符
    fmt.Println(string(str))
}
输出的值:
12341false"傻逼"'一'

(2)Format 将其他类型的转换为字符串类型

func main() {
    var str string
    //bool转字符串
    str = strconv.FormatBool(false)
    fmt.Println(str)
    //整形转字符串
    str = strconv.Itoa(1231231231)
    fmt.Println(str)
    //浮点转字符串 -> 'f'指打印格式使用小数方式 -> 3为小数位 -> 64以float64处理
    str = strconv.FormatFloat(3.14,'f',3,64)
}
输出结果:
false
1231231231

Parse 系列函数把字符串转换为其他类型

func main(){
    //字符串转换其他类型
    var flag bool
    var err error
    flag,err = strconv.ParseBool("true")
    if err == nil{
        fmt.Println("err =",flag)
    }else {
        fmt.Println("err =",err)
    }
    //把字符串转换成整数
    a,_ := strconv.Atoi("12341")
    fmt.Println("a=",a)
    //字符串 -> 布尔
    b,_ := strconv.ParseFloat("234.123",64)
    if err == nil{
        fmt.Println("flag = ",b)
    }else {
        fmt.Println("err = ",err)
    }
}
输出结果:
err = true
a= 12341
flag =  234.123

正则表达式

正则表达式是一种进行模式匹配和文本操纵的复杂而又强大的工具。虽然正则表达式比纯粹的文本匹配效率低,但是它却更灵活。按照它的语法规则,随需构造出的匹配模式就能够从原始文本中筛选出几乎任何你想要得到的字符组合。
Go语言通过regexp标准包为正则表达式提供了官方支持,如果你已经使用过其他编程语言提供的正则相关功能,那么你应该对Go语言版本的不会太陌生,但是它们之间也有一些小的差异,因为Go实现的是RE2标准,除了\C,详细的语法描述参考:http://code.google.com/p/re2/wiki/Syntax
其实字符串处理我们可以使用strings包来进行搜索(Contains、Index)、替换(Replace)和解析(Split、Join)等操作,但是这些都是简单的字符串操作,他们的搜索都是大小写敏感,而且固定的字符串,如果我们需要匹配可变的那种就没办法实现了,当然如果strings包能解决你的问题,那么就尽量使用它来解决。因为他们足够简单、而且性能和可读性都会比正则好。

func main(){
    contest := "3.14 12342321 .5612 ddy 2.18 qqw 6.23 666."
    //MustCompile解析并且返回一个正则表达式成功返回,失败产生panic
    //Regexp就可以匹配文本 -> \d匹配数字[0-9],d+ 重复>1次匹配d,贪婪匹配(优先重复匹配d)
    exp := regexp.MustCompile(`\d+\.\d+`)
    //返回正则表达式所有不重叠的匹配结果的[]string切片如果没有匹配返回nil
    result := exp.FindAllString(contest,-1)
    fmt.Printf("%v\n",result)
    fmt.Printf("\n----------------------\n\n")
    contest1 := `
        <title>标题</title>
        <div>揍你</div>
        <div>好的</div>
        <div>你
            好</div>
        <body>GOGO</body>`
    //(.*?)被括起来的表达式作为分组
    //匹配<div>xxx</div>模式的所有字符串
    exp1 := regexp.MustCompile(`<div>(.*?)</div>`)
    result1 := exp1.FindAllStringSubmatch(contest1,-1)
    fmt.Printf("%v\n",result1)
}
输出结果:
[3.14 2.18 6.23]
----------------------
[[<div>揍你</div> 揍你] [<div>好的</div> 好的]]

输入与输出

Go语言将I/O操作封装在以下几个包中

  • io:为I/O原语(I/O primitives)提供基本的接口,在io包中最重要的是两个接口——Reader和Writer接口。只要程序实现了这两个接口,它就有了I/O的功能。

  • io/ioutil:封装一些实用的I/O函数,这个包主要提供了一些常用、方便的I/O操作函数。

package main

import (
    "fmt"
    "io/ioutil"
    "strings"
)

func main(){
    //1.读取文件中的所有数据
    filename1 := "/golang/qq.txt"
    data,err := ioutil.ReadFile(filename1)
    if err == nil{
        fmt.Println("err=",err)
    }else {
        fmt.Println(string(data))
    }
    //2.写入数据
    filename2 :="/golang/a.txt"
    s := "面朝大海春暖花开"
    err1 := ioutil.WriteFile(filename2,[]byte(s),0777)
    fmt.Println(err1)

    //3.读线程文本
    s1 := "qwe jk12h12kl3j1kaskldjnak"
    r := strings.NewReader(s1)
    data1,_ := ioutil.ReadAll(r)
    fmt.Println(data1)

    //4.读一个目录
    //ReadDir()->读取一个目录下的子内容、子文件和子目录(目录只读一层)
    dirName := "/golang/nihao"
    fileinfo,_ := ioutil.ReadDir(dirName)
    fmt.Println(len(fileinfo))
    //遍历打印文件夹下的文件信息
    for i:=0;i<len(fileinfo);i++{
        fmt.Println(i,fileinfo[i].Name(),fileinfo[i].IsDir())
    }
}
输出的值:
err= <nil>
<nil>
[113 119 101 32 106 107 49 50 104 49 50 107 108 51 106 49 107 97 115 107 108 100 106 110 97 107]
3
0 a.qq false
1 asda true
2 asdq.atx false
  • fmt:实现格式化I/O,类似C语言中的printf和scanf。

  • bufio:实现带缓冲的I/O。它封装于io.Reader和io.Writer对象,创建了另一个对象(Reader和Writer),在提供缓冲的同时实现了一些文本I/O的功能。

package main

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

func main(){
    /*
    bufio包:高级i/o读写
             buffer缓存
             io:input/output
    将I/O包下的Reader/Write对象进行封装,带缓存的封装,提高读写的效率
    */
    fileName := "/golang/a.txt"
    //作为是IO包下的Reader、Write的实现
    file,_ := os.Open(fileName)
    //构建带缓存的Reader对象:bufio.Reader
    r := bufio.NewReader(file)
    fmt.Printf("%T\n",r)
    // ReadLine()不建议使用
//    data,flag,err := r.ReadLine()
//    fmt.Println(string(data))
//    fmt.Println(flag)
//    fmt.Println(err)

    for {
        s,err := r.ReadString('\n')
        if err == io.EOF{
            fmt.Println("读取完成")
            break
        }
        fmt.Println(s)
    }
   //读取字节
   //data,_ := r.ReadBytes('\n')
   //fmt.Println(string(data))

   //读取键盘
    d := bufio.NewReader(os.Stdin)
    data2,_ := d.ReadString('\n')
    fmt.Println(data2)
}

输出值:
*bufio.Reader
1.面朝大海春暖花开

2.思念边成海在窗外进不来

3.真的被打败爱已存在

读取完成

总结

io读写

  • A -> 文件信息
    • os.Stat() -> fileinfo

    • Name(),isDir(),size(),mode(),modetime()

  • B -> 文件常用操作

    • 路径:绝对/相对(相对指当前的工程)

    • 创建文件夹:os.mkdir(),os.mkdirAll()

    • 创建文件:os.Create()

    • 打开文件:os.Open(),os.OpenFile()

    • 文件关闭:file.Close()

    • 文件的删除:remove(),removeAll()

  • C -> 文件的读和写

    • 打开文件的模式:文件不存在需要(0777->rwx)
Read([]byte) --> info,error
Write([]byte) --> info,error
WrityeString(string) --> info,error
seek(offset,whence) //设置指针光标位置:offset偏移量,whence->0代表距离文件开头,1当前位置,2末尾
  • D -> 包
    • IO包:Reader接口,Write接口(效率低)

    • bufio包:将IO包下的Reader和Write进一步的封装,带有缓存的Reader和Write,实现高效的读写。本质:将一个字节数组作为一个缓冲区 -> 内存

    • 例子:ReadBytes()->[]byte、ReadString()->string、ReadLine()读写量超过缓存则无法完全读取数据

    • 例子:Write()、WriteString() -> 发生了写入都要使用flush()刷新

    • ioutil:只需要传递给它文件名则返回所有的数据[]byte数组类型

    • 例子ReadFile()、WriteFile(filename,data,mode)、ReadDir()获取文件夹下的子文件子目录、ReadAll()

综合练习

模拟投票系统
设定参与选举的人数,依次投票按0退出否则一直循环投票相同的票数重新投票依次排序出投票的结果

var students[]student
var flag = true

type student struct {
    no int
    name string
    count int
}
//初始化接受参数
func initinfo(){
    sum := 0
    fmt.Println("请输入要参加的人数:")
    fmt.Scanln(&sum)
    students = make([] student,0,sum)
    for i:=1;i<=sum;i++{
        name := ""
        fmt.Printf("请输出第%d候选人的名字:\n",i)
        fmt.Scanln(&name)
        s := student{i,name,0}
        students = append(students,s)
    }
}
//排序
func printinfo(){
    for i := 1;i < len(students);i++{
        for j := 0;j < len(students)-i;j++{
            if students[j].count < students[j+1].count{
                students[j],students[j+1]=students[j+1],students[j]
            }
        }
    }
    for i:= 0;i<len(students);i++{
        s:=students[i]
        fmt.Printf("【%d】%s同学,当前票数为:%d\n",s.no,s.name,s.count)
    }
}
func getResult(){
    sum:=0
    highcount:= students[0].count
    for i := 0;i<len(students);i++{
        if students[i].count == highcount{
            sum ++
        }
    }
    if sum == 1 {
        fmt.Printf("%s同学,获取的票数最高为:%d,撒花留恋!",students[0].name,students[0].count)
        flag = false
    }else{
        for i :=0;i<sum;i++{
            fmt.Printf("%s同学,\t",students[i].name)
        }
        fmt.Printf("均获取最多的票数:%d,需要重新投票\n",highcount)
        students = students[:sum]
        for i:=0;i<len(students);i++{
            students[i].no = i+1
            students[i].count = 0
        }
    }
}

func vote(){
    for {
        fmt.Println("请输入要投的编号,当你输入0表示退出投票!")
        n := -1
        fmt.Scan(&n)
        if n == 0{
            fmt.Println("投票结束!")
            break
        }else if n>0 && n<= len(students){
            students[n-1].count = students[n-1].count + 1
        }else {
            fmt.Println("本次投票无效请重新投票!重新输入:")
        }
    }
}
func main(){
    //初始化Info
    initinfo()
    for flag{
        //显示信息
        printinfo()
        //投票
        vote()
        //计算票数
        printinfo()
        //处理结果
        getResult()
    }
}

蹒跚学Go第六天-文本文件处理》有1个想法

发表评论

电子邮件地址不会被公开。 必填项已用*标注