# Golang读写数据


<!--more-->

# 第十二章：读写数据

## 前言

继续开始读写数据的学习，其实就是I/O流了估计是，这方面还是挺重要的。



## 读取用户的输入

`Scanln` 扫描来自标准输入的文本，将空格分隔的值依次存放到后续的参数内，直到碰到换行。`Scanf` 与其类似，除了 `Scanf` 的第一个参数用作格式字符串，用来决定如何读取。（感觉Go这方面类似C的scanf了）

```go
package main

import "fmt"

func main() {
	fmt.Println("please input：")
	var firstName,lastName string
	var n1,n2 string
	fmt.Scanln(&firstName,&lastName)
	fmt.Printf("Hello %s %s\n",firstName,lastName)
	fmt.Scanf("%s,%s",&n1,&n2)
	fmt.Printf("%s,%s\n",n1,n2)
}

/*
please input：
a1 a2
Hello a1 a2
a1,a2
a1,a2,
 */
```

也可以用bufio包来读取：

```go
package main

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

func main() {
	inputReader := bufio.NewReader(os.Stdin)
	fmt.Println("please input：")
	input, err := inputReader.ReadString('\n')
	if err != nil {
		fmt.Printf("input error!")
		return
	}
	fmt.Println(input)
	data := []byte(input)
	fmt.Println(data)//包括\n
}


```



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





## 文件读写

### 读文件

逐行读取文件：

```go
package main

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

func main() {
	inputFile,inputError := os.Open("1.txt")
	if inputError!=nil {
		fmt.Println("file error!")
		return
	}
	fmt.Println(reflect.TypeOf(inputFile))
	defer inputFile.Close()

	inputReader := bufio.NewReader(inputFile)
	for {
		inputString,readerError := inputReader.ReadString('\n')
		fmt.Println(inputString)
		if readerError == io.EOF{
			return
		}

	}

}


```



也可以利用`io/ioutil`的`ReadFile()`函数讲整个文件的内容读取到一个`[]byte`中：

```go
package main

import (
	"fmt"
	"io/ioutil"
)

func main() {
	inputFile := "1.txt"
	buf,err := ioutil.ReadFile(inputFile)
	if err!=nil{
		fmt.Println("error")
		panic(err.Error())
	}
	fmt.Print(string(buf))

}


```



带缓冲的读取：

```go
package main

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

func main() {
	inputFile,inputError := os.Open("1.txt")
	if inputError != nil {
		panic(inputError.Error())
	}
	defer inputFile.Close()
	inputReader := bufio.NewReader(inputFile)
	var buf []byte = make([]byte,1024)
	for {
		n,err := inputReader.Read(buf)
		if err==io.EOF{
			break
		}
		fmt.Print(string(buf[:n]))
	}


}


```

### 写文件

```go
package main

import (
	"bufio"
	"os"
)

func main() {
	outputFile,_ := os.OpenFile("1.txt",os.O_WRONLY|os.O_CREATE, 0666)
	defer outputFile.Close()
	outputWriter := bufio.NewWriter(outputFile)
	outString := "hello,world\nwwwwww"
	outputWriter.WriteString(outString)
	outputWriter.Flush()
}


```



`os.OpenFile`的第二个参数是flag，有如下常用标志：

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



也可以不使用缓冲区，直接写入：

```go
package main

import (
	"os"
)

func main() {
	outputFile,_ := os.OpenFile("1.txt",os.O_WRONLY|os.O_CREATE, 0666)
	defer outputFile.Close()
	outputFile.WriteString("hello,world")
}


```





练习：

```go
package main

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

type Page struct {
	Title string
	Body  []byte
}

func (page *Page) save(){
	outputFile,_ := os.OpenFile(page.Title,os.O_WRONLY|os.O_CREATE, 0666)
	defer outputFile.Close()
	outputFile.Write(page.Body)
}
func (page *Page) load(){
	page.Body,_ =ioutil.ReadFile(page.Title)
	fmt.Print(string(page.Body))
}

func main() {
	page := new(Page)
	page.Title = "2.txt"
	page.Body  = []byte("hello!!!!")
	page.save()
	page.load()
}


```

## 文件拷贝

利用`io.Copy()`

```go
package main

import (
	"io"
	"os"
)

func main() {
	CopyFile("3.txt","1.txt")
}

func CopyFile(dstName,srcName string)(written int64,err error){
	src,err := os.Open(srcName)
	defer src.Close()
	dst,err := os.OpenFile(dstName,os.O_WRONLY|os.O_CREATE,0666)
	defer dst.Close()
	return io.Copy(dst,src)
}
```

`defer` 的使用：当打开目标文件时发生了错误，那么 `defer` 仍然能够确保 `src.Close()` 执行。如果不这么做，文件会一直保持打开状态并占用资源。





## 从命令行读取参数

### os包

`os.Args`

这个命令行参数会放置在切片 `os.Args[]` 中（以空格分隔），从索引 1 开始（`os.Args[0]` 放的是程序本身的名字）。



### flag包

感觉比os包解析命令行参数更好，而且用法更偏向于那些命令行参数的解析。

每个Flag结构体：

```go
type Flag struct {
    Name     string // name as it appears on command line
    Usage    string // help message
    Value    Value  // value as set
    DefValue string // default value (as text); for usage message
}
```



指定参数：

```go
var do = flag.String("w","no out","print")
//func String(name string, value string, usage string) *string
```

注意返回的是指针。



flag.Parse() 扫描参数列表（或者常量列表）并设置 flag, flag.Arg(i) 表示第 i 个参数。Parse() 之后 flag.Arg(i) 全部可用，flag.Arg(0) 就是第一个真实的 flag，而不是像 os.Args(0) 放置程序的名字。

flag.Narg() 返回参数的数量。解析后 flag 或常量就可用了。



`flag.PrintDefaults()` 打印 flag 的使用帮助信息。



需要注意的是，从第一个不能解析的参数开始，后面的所有参数都是无法解析的。即使后面的参数中含有预定义的参数。



例子：

```go
package main

import (
	"flag"
	"fmt"
)

var do = flag.String("w","no out","print")

func main() {
	//flag.PrintDefaults()
	flag.Parse() // Scans the arg list and sets up flags
	fmt.Println(*do)
}


```



```shell
C:\Users\15997\Desktop>go run main.go -w 123
123

C:\Users\15997\Desktop>go run main.go
no out

C:\Users\15997\Desktop>go run main.go -w=123
123

C:\Users\15997\Desktop>
```



`-help`可以起到`flag.PrintDefaults()`的作用，不过它就只能打印帮助信息了，联想到之前使用工具中的-help，这部分还是挺重要的，接下来写工具就需要解析这些参数。





## 用切片读写文件

```go
package main

import (
	"fmt"
	"os"
)

func cat(f *os.File){
	const NBUF = 1024
	var buf [NBUF]byte
	for {
		switch nr, _ := f.Read(buf[:]);true {
		case nr<0:
			fmt.Println("can not read")
			os.Exit(1)
		case nr==0://EOF
			return
		case nr>0:
			fmt.Print(string(buf[:nr]))
		}
	}
}

func main() {
	f,_ := os.Open("1.txt")
	defer f.Close()
	cat(f)
}


```



## JSON数据格式

将对象转换为JSON：

```go
package main

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

type Person struct {
	Age int
	Name string
	Mate School
}
type School struct{
	Name string
	Position string
}

func main(){
	person := Person{18,"feng",School{"a","b"}}
	//得到字符串
	js,_ := json.Marshal(person)
	fmt.Printf("%s",js)

	//写入文件
	f,_ := os.OpenFile("1.json",os.O_WRONLY|os.O_CREATE,0666)
	defer f.Close()
	enc := json.NewEncoder(f)
	enc.Encode(person)
}
```

`json.Marshal`

注意结构体成员的首字母要大写才能成功转换。



出于安全考虑，在 web 应用中最好使用 `json.MarshalforHTML()` 函数，其对数据执行 HTML 转码，所以文本可以被安全地嵌在 HTML `<script>` 标签中。





不是所有的数据都可以编码为 JSON 类型：只有验证通过的数据结构才能被编码：

- JSON 对象只支持字符串类型的 key；要编码一个 Go map 类型，map 必须是 map [string] T（T 是 json 包中支持的任何类型）

- Channel，复杂类型和函数类型不能被编码
- 不支持循环数据结构；它将引起序列化进入一个无限循环
- 指针可以被编码，实际上是对指针指向的值进行编码（或者指针是 nil）







将JSON格式转换为对象：

```go
package main

import (
	"encoding/json"
	"fmt"
)

type Person struct {
	Age int
	Name string
	Mate School
}
type School struct{
	Name string
	Position string
}

func main(){
	person := Person{18,"feng",School{"a","b"}}
	//得到字符串
	js,_ := json.Marshal(person)
	fmt.Printf("%s\n",js)


	var p Person
	err := json.Unmarshal(js,&p)
	if err!=nil{
		panic(err.Error())
	}
	fmt.Println(p)


}
```

利用`json.Unmarshal`：`func Unmarshal(data []byte, v interface{}) error`

第二个参数需要是指针。



## Go中的密码学

Go也内置了许多的加密解密包。

- hash 包：实现了 adler32、crc32、crc64 和 fnv 校验；

- crypto 包：实现了其它的 hash 算法，比如 md4、md5、sha1 等。以及完整地实现了 aes、blowfish、rc4、rsa、xtea 等加密算法。



```go
package main

import (
	"crypto/md5"
	"fmt"
	"io"
)

func main(){
	hasher := md5.New()
	io.WriteString(hasher,"feng")
	fmt.Printf("%x\n",hasher.Sum(nil))
	//

	hasher.Reset()
	data := []byte("hello")
	hasher.Write(data)
	fmt.Printf("%x\n",hasher.Sum(nil))

	fmt.Printf("%x",md5.Sum(data))
}
```



---

> 作者: [Scan](https://www.scan.work/)  
> URL: https://5canx.github.io/posts/%E7%AC%AC%E5%8D%81%E4%BA%8C%E7%AB%A0%E8%AF%BB%E5%86%99%E6%95%B0%E6%8D%AE/  
> 转载 URL: https://github.com/bfengj/CTF/tree/main/Web/go/golang_learn_notes
