GoLang读写数据---上

x33g5p2x  于2022-08-17 转载在 其他  
字(10.9k)|赞(0)|评价(0)|浏览(361)

读取用户的输入

我们如何读取用户的键盘(控制台)输入呢?

从键盘和标准输入 os.Stdin 读取输入,最简单的办法是使用 fmt 包提供的相关函数。

scanf

  • scanf:按照给定的格式依次读取数据(包括非法数据),不能换行输入(如果要换行需要在前面加一个scanln吸收掉回车符,就像c语言中的getchar)
package main

import "fmt"

//假如我们要输入一个人的年龄和名字
func main() {
	var name string
	var age int8
	fmt.Scanf("%s", &name)
	fmt.Scanf("%d", &age)
	//fmt.Scanf("%s %d", &name, &age)  对于scanf,这句话等价于上面两句话
	fmt.Println(name, "  ", age)
}

这两种写法都一样,如果我要把名字和年龄分两行输入是不行的,运行结果就像这样:

第一种输入方法(一次性输入):

第二种输入方法(把名字和年龄分两次输入):

scanf有两个返回值n和err,n是按指定格式成功输入数据的个数,err是读取过程中遇到的错误,如果没有错误,err的值就为nil

package main

import "fmt"

//假如我们要输入一个人的年龄和名字
func main()  {
	var name string
	var age int8
    n, err := fmt.Scanf("%s %d", &name, &age)  //用n,err分别接受scanf扫描成功的个数和错误返回值
    if err == nil{          //如果没有错误就输出name和age的值
        fmt.Println(name, "  ", age)
    }else{
        fmt.Println("读取成功",n, "个","错误:",err)
    }
	
}

我们用刚刚第二种没有成功的输入方法试试,看是什么错误

可以看到,我们只成功输入了bob这一个数据,有一个错误叫unexpected newline,这个错误其实就是我们输入的回车,因为scanf函数遇到换行符就结束,从缓冲区依次读取以空格分开的数据;对我们这个程序而言,首先按%s读入了bob,然后再按%d读取下一个数据(回车),但是回车键不是十进制整形数据,它按%d怎么可能读得进去呢,所以就出现了只成功读取一个数据,报错为 “没有意料到的新行”

scan和scanln

  • scan:比scanf高级,依次读取数据,遇到回车会忽略,可以换行输入(如果要先用了scan输入,再用scanf输入的话,需要在中间加一个scanln)
  • scanln:类似scan,但是遇到换行(回车)立马结束输入,如果要换行输入必须用多个scanln

跟scanf差不多,都是有两个返回值,一个是读取成功个数,另一个是错误值

package main

import "fmt"

//假如我们要输入一个人的年龄和名字
func main()  {
	var name string
	var age int8
	n, err := fmt.Scan(&name, &age)  //用n,err分别接受scanf扫描成功的个数和错误返回值
    
    /*n, err := fmt.Scan(&name)
	n, err = fmt.Scan(&age)*/    //对于scan这种写法也等价于上面那种写法
    
	if err == nil{          //如果没有错误就输出name和age的值
		fmt.Println("输出:", name, "  ", age)
	}else{
		fmt.Println("读取成功",n, "个","错误:",err)
	}
}

对于scan,我们是可以多个数据多行输入的

对于scanln,又有些不同了

package main

import "fmt"

//假如我们要输入一个人的年龄和名字
func main()  {
	var name string
	var age int8
	n, err := fmt.Scanln(&name, &age)  //这种写法的话必须把name和age一行输入,因为scanln是以回车为标志结束
    
    n, err := fmt.Scanln(&name) //这样写就可以分两行输入name和age
    n, err  = fmt.Scanln(&age)

	if err == nil{          //如果没有错误就输出name和age的值
		fmt.Println("输出:", name, "  ", age)
	}else{
		fmt.Println("读取成功",n, "个","错误:",err)
	}
}

其实scanln再换行的时候会把缓冲区的回车也收走,但是scan和scanf不会,所以就导致了scanf不能分多行输入数据。但是scan却可以,它虽然没有收走缓冲区的回车符,但是不会把回车符读进去,遇到回车它会继续读取下一个数据,而scanf会按照我们给的格式(如%d去读取数据),但是肯定读不进去的,所以就读取失败了

举例:

package main

import "fmt"

//假如我们要输入一个人的年龄和名字
func main()  {
	var name string
	var age int8
	fmt.Scan(&name)// 把Scan换成Scanln就可以了
    n, err  := fmt.Scanf("%d", &age)
    //原因:scan没有把第一行输入结束后的回车收走,导致scanf按%d的格式去读取回车符,那肯定读取失败啊
    //而scanln会把第一行输入结束的回车符读走,scanf会按%的格式去读取我们后面输入的数据
	if err == nil{         
		fmt.Println("输出:", name, "  ", age)
	}else{
		fmt.Println("读取成功",n, "个","错误:",err)
	}
}

正确写法:

package main

import "fmt"

//假如我们要输入一个人的年龄和名字
func main()  {
	var name string
	var age int8
	fmt.Scan(&name)
	fmt.Scanln()
	fmt.Scanf("%d", &age)
	
}

Fscan系列和Sscanf系列

  • Sscanf : 从字符串str扫描文本,根据format 参数指定的格式将成功读取的空白分隔的值保存进成功传递给本函数的参数。返回成功扫描的条目个数和遇到的任何错误。
package main

import "fmt"

var (
	input  = "大忽悠 / 18 / 大傻子"
	format = "%s / %d / %s"
	name   string
	age    int
	nick   string
)

func main() {
	fmt.Sscanf(input, format, &name, &age, &nick)
	fmt.Printf("姓名: %s ,年龄: %d, 绰号: %s", name, age, nick)
}

扩展:

  • Fscan系列
func Fscan(r io.Reader, a ...interface{}) (n int, err error)
func Fscanln(r io.Reader, a ...interface{}) (n int, err error)
func Fscanf(r io.Reader, format string, a ...interface{}) (n int, err error)

这几个函数功能分别类似于fmt.Scan、fmt.Scanf、fmt.Scanln三个函数,只不过它们不是从标准输入中读取数据而是从io.Reader中读取数据。

  • Sscan系列
func Sscan(str string, a ...interface{}) (n int, err error)
func Sscanln(str string, a ...interface{}) (n int, err error)
func Sscanf(str string, format string, a ...interface{}) (n int, err error)

这几个函数功能分别类似于fmt.Scan、fmt.Scanf、fmt.Scanln三个函数,只不过它们不是从标准输入中读取数据而是从指定字符串中读取数据。

bufio

您也可以使用 bufio 包提供的缓冲读取(buffered reader)来读取数据,正如以下例子所示:

package main

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

var inputReader *bufio.Reader
var input string
var err error

func main() {
	inputReader = bufio.NewReader(os.Stdin)
	fmt.Println("请输入数据,换行结束本次输入: ")
	input, err = inputReader.ReadString('\n')
	if err == nil {
		fmt.Printf("The input was: %s\n", input)
	}
}

inputReader 是一个指向 bufio.Reader 的指针。inputReader := bufio.NewReader(os.Stdin) 这行代码,将会创建一个读取器,并将其与标准输入绑定。

bufio.NewReader() 构造函数的签名为:func NewReader(rd io.Reader) *Reader

该函数的实参可以是满足 io.Reader 接口的任意对象,函数返回一个新的带缓冲的 io.Reader 对象,它将从指定读取器(例如 os.Stdin)读取内容。

返回的读取器对象提供一个方法 ReadString(delim byte),该方法从输入中读取内容,直到碰到 delim 指定的字符,然后将读取到的内容连同 delim 字符一起放到缓冲区。

ReadString 返回读取到的字符串,如果碰到错误则返回 nil。如果它一直读到文件结束,则返回读取到的字符串和 io.EOF。如果读取过程中没有碰到 delim 字符,将返回错误 err != nil。

在上面的例子中,我们会读取键盘输入,直到回车键(\n)被按下。

屏幕是标准输出 os.Stdout;os.Stderr 用于显示错误信息,大多数情况下等同于 os.Stdout。

一般情况下,我们会省略变量声明,而使用 :=,例如:

inputReader := bufio.NewReader(os.Stdin)
input, err := inputReader.ReadString('\n')

文件读写

读文件

在 Go 语言中,文件使用指向 os.File 类型的指针来表示的,也叫做文件句柄。我们在前面章节使用到过标准输入 os.Stdin 和标准输出 os.Stdout,他们的类型都是 *os.File。让我们来看看下面这个程序:

package main

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

func main() {
	inputFile, inputError := os.Open("dhy.test")

	if inputError != nil {
		fmt.Printf("An error occurred on opening the inputfile\n" +
			"Does the file exist?\n" +
			"Have you got acces to it?\n")
		return // exit the function on error
	}

	defer inputFile.Close()

	inputReader := bufio.NewReader(inputFile)

	for {
		inputString, readerError := inputReader.ReadString('\n')
		fmt.Printf("The input was: %s", inputString)
		if readerError == io.EOF {
			return
		}
	}
}

变量 inputFile 是 *os.File 类型的。该类型是一个结构,表示一个打开文件的描述符(文件句柄)。然后,使用 os 包里的 Open 函数来打开一个文件。该函数的参数是文件名,类型为 string。在上面的程序中,我们以只读模式打开 input.dat 文件。

如果文件不存在或者程序没有足够的权限打开这个文件,Open函数会返回一个错误:inputFile, inputError = os.Open(“input.dat”)。如果文件打开正常,我们就使用 defer inputFile.Close() 语句确保在程序退出前关闭该文件。然后,我们使用 bufio.NewReader 来获得一个读取器变量。

通过使用 bufio 包提供的读取器(写入器也类似),如上面程序所示,我们可以很方便的操作相对高层的 string 对象,而避免了去操作比较底层的字节。

接着,我们在一个无限循环中使用 ReadString(‘\n’) 或 ReadBytes(‘\n’) 将文件的内容逐行(行结束符 ‘\n’)读取出来。

注意: 在之前的例子中,我们看到,Unix和Linux的行结束符是 \n,而Windows的行结束符是 \r\n。在使用 ReadString 和 ReadBytes 方法的时候,我们不需要关心操作系统的类型,直接使用 \n 就可以了。另外,我们也可以使用 ReadLine() 方法来实现相同的功能。

一旦读取到文件末尾,变量 readerError 的值将变成非空(事实上,其值为常量 io.EOF),我们就会执行 return 语句从而退出循环。

其他类似函数:

1) 将整个文件的内容读到一个字符串里:

如果您想这么做,可以使用 io/ioutil 包里的 ioutil.ReadFile() 方法,该方法第一个返回值的类型是 []byte,里面存放读取到的内容,第二个返回值是错误,如果没有错误发生,第二个返回值为 nil。

类似的,函数 WriteFile() 可以将 []byte 的值写入文件。

package main

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

func main() {
	inputFile := "main.go"
	outputFile := "products_copy.txt"
	buf, err := ioutil.ReadFile(inputFile)
	if err != nil {
		fmt.Fprintf(os.Stderr, "File Error: %s\n", err)
	}
	fmt.Printf("%s\n", string(buf))
	err = ioutil.WriteFile(outputFile, buf, 0644)
	if err != nil {
		panic(err.Error())
	}
}

2) 带缓冲的读取

在很多情况下,文件的内容是不按行划分的,或者干脆就是一个二进制文件。在这种情况下,ReadString()就无法使用了,我们可以使用 bufio.Reader 的 Read(),它只接收一个参数:

package main

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

func main() {
	file, err := os.Open("main.go")
	if err != nil {
		fmt.Printf("文件打开出错: %v", err)
		return
	}

	reader := bufio.NewReader(file)

	bytes := make([]byte, 1024)

	reader.Read(bytes)

	fmt.Println(string(bytes))
}

变量 n 的值表示读取到的字节数.

3) 按列读取文件中的数据

如果数据是按列排列并用空格分隔的,你可以使用 fmt 包提供的以 FScan 开头的一系列函数来读取他们。请看以下程序,我们将 3 列的数据分别读入变量 v1、v2 和 v3 内,然后分别把他们添加到切片的尾部。

package main

import (
	"fmt"
	"os"
)

func main() {
	file, err := os.Open("dhy.test")

	if err != nil {
		panic(err)
	}

	defer file.Close()

	var col1, col2, col3 []string

	for {
		var v1, v2, v3 string
		_, err := fmt.Fscanln(file, &v1, &v2, &v3)
		// scans until newline
		if err != nil {
			break
		}
		col1 = append(col1, v1)
		col2 = append(col2, v2)
		col3 = append(col3, v3)
	}

	fmt.Println(col1)
	fmt.Println(col2)
	fmt.Println(col3)
}

注意: path 包里包含一个子包叫 filepath,这个子包提供了跨平台的函数,用于处理文件名和路径。例如 Base() 函数用于获得路径中的最后一个元素(不包含后面的分隔符):

import "path/filepath"
filename := filepath.Base(path)

compress包:读取压缩文件

compress包提供了读取压缩文件的功能,支持的压缩文件格式为:bzip2、flate、gzip、lzw 和 zlib。

下面的程序展示了如何读取一个 gzip 文件。

package main
import (
    "fmt"
    "bufio"
    "os"
    "compress/gzip"
)
func main() {
    fName := "MyFile.gz"
    var r *bufio.Reader
    fi, err := os.Open(fName)
    if err != nil {
        fmt.Fprintf(os.Stderr, "%v, Can't open %s: error: %s\n", os.Args[0], fName,
            err)
        os.Exit(1)
    }
    defer fi.Close()
    fz, err := gzip.NewReader(fi)
    if err != nil {
        r = bufio.NewReader(fi)
    } else {
        r = bufio.NewReader(fz)
    }
    for {
        line, err := r.ReadString('\n')
        if err != nil {
            fmt.Println("Done reading file")
            os.Exit(0)
        }
        fmt.Println(line)
    }
}

Golang zip 压缩与解压

写文件

package main
import (
	"os"
	"bufio"
	"fmt"
)
func main () {
	// var outputWriter *bufio.Writer
	// var outputFile *os.File
	// var outputError os.Error
	// var outputString string
	outputFile, outputError := os.OpenFile("output.dat", os.O_WRONLY|os.O_CREATE, 0666)
	if outputError != nil {
		fmt.Printf("An error occurred with file opening or creation\n")
		return
	}
	defer outputFile.Close()
	outputWriter := bufio.NewWriter(outputFile)
	outputString := "hello world!\n"
	for i:=0; i<10; i++ {
		outputWriter.WriteString(outputString)
	}
	outputWriter.Flush()
}

除了文件句柄,我们还需要 bufio 的 Writer。我们以只写模式打开文件 output.dat,如果文件不存在则自动创建:

outputFile, outputError := os.OpenFile("output.dat", os.O_WRONLY|os.O_CREATE, 0666)

可以看到,OpenFile 函数有三个参数:文件名、一个或多个标志(使用逻辑运算符“|”连接),使用的文件权限。

我们通常会用到以下标志:

  • os.O_RDONLY:只读
  • os.O_WRONLY:只写
  • os.O_CREATE:创建:如果指定文件不存在,就创建该文件。
  • os.O_TRUNC:截断:如果指定文件已存在,就将该文件的长度截为0。

在读文件的时候,文件的权限是被忽略的,所以在使用 OpenFile 时传入的第三个参数可以用0。而在写文件时,不管是 Unix 还是 Windows,都需要使用 0666。

然后,我们创建一个写入器(缓冲区)对象:

outputWriter := bufio.NewWriter(outputFile)

接着,使用一个 for 循环,将字符串写入缓冲区,写 10 次:outputWriter.WriteString(outputString)

缓冲区的内容紧接着被完全写入文件:outputWriter.Flush()

如果写入的东西很简单,我们可以使用 fmt.Fprintf(outputFile, “Some test data.\n”) 直接将内容写入文件。fmt 包里的 F 开头的 Print 函数可以直接写入任何 io.Writer,包括文件。

下面展示了不使用 fmt.FPrintf 函数,使用其他函数如何写文件:

package main
import "os"
func main() {
    os.Stdout.WriteString("hello, world\n")
    f, _ := os.OpenFile("test", os.O_CREATE|os.O_WRONLY, 0666)
    defer f.Close()
    f.WriteString("hello, world in a file\n")
}

使用 os.Stdout.WriteString(“hello, world\n”),我们可以输出到屏幕。

我们以只写模式创建或打开文件”test”,并且忽略了可能发生的错误:f, _ := os.OpenFile(“test”, os.O_CREATE|os.O_WRONLY, 0666)

我们不使用缓冲区,直接将内容写入文件:f.WriteString( )

实例演示

将学生数据以JSON字符串的格式保存到文件中,然后再从文件中读取某个学生的数据:

type StuInterface interface {
	Save()
	Get(id int) Stu
}
package service

import (
	"bufio"
	"encoding/json"
	"fmt"
	"os"
)

type Stu struct {
	Name string
	Age  int
	Id   int
}

func NewStu(name string, age int, id int) *Stu {
	return &Stu{Name: name, Age: age, Id: id}
}

//Save 将学生数据序列化后保存到文件中
func (s *Stu) Save() {

	file, err := os.OpenFile("stu.dbl", os.O_CREATE|os.O_APPEND, 0666)

	if err != nil {
		panic(err)
		return
	}

	defer file.Close()

	//只会导出结构体中被导出的字段---属性名大写
	marshal, err := json.Marshal(s)
	if err != nil {
		panic(err)
		return
	}
	fmt.Println("序列化结果: ", string(marshal))

	file.Write(marshal)

	file.WriteString("\n")
}

//Get 从文件中读取学生数据
func (s *Stu) Get(id int) Stu {
	file, err := os.Open("stu.dbl")

	if err != nil {
		panic(err)
		return Stu{}
	}

	defer file.Close()

	reader := bufio.NewReader(file)

	var jsonData [][]byte

	i := 0

	for {
		line, _, err := reader.ReadLine()
		if err != nil {
			break
		}
		i++
		jsonData = append(jsonData, line)
	}

	var stu Stu

	for _, stuJson := range jsonData {
		err := json.Unmarshal(stuJson, &stu)
		if err != nil {
			panic(err)
			return Stu{}
		}
		if stu.Id == id {
			return stu
		}
	}
	
	return Stu{}
}
package main

import (
	. "GoStudy/service"
	"fmt"
)

func main() {
	var s StuInterface = NewStu("狗蛋儿", 18, 2)
	//s.Save()
	stu := s.Get(3)

	//判断是否为空结构体
	if stu != (Stu{}) {
		fmt.Println("查找的结果为: ", stu)
	}
}

相关文章