go語言學習筆記4(文件,管道,map)

go文件

輸入流:從文件到go程序內存,讀文件的過程
輸出流:從go程序內存到文件,寫文件的過程

  • 打開文件,返回文件指針和錯誤
    在這裏插入圖片描述
file, err := os.Open("文件路徑")
  • 關閉文件,返回錯誤信息,經常與defer連用

注意:退出函數時要及時關閉,否則會有內存泄漏
在這裏插入圖片描述

err = os.Close()
  • 讀取文件
  1. 帶緩存的讀取方式,需要打開和關閉文件
reader := bufio.NewReader(file)
str, err := reader.ReadString('\n')  //讀到換行就結束
  1. 一次性讀取到內存,適用於文件不大的情況
# 不需要打開和關閉文件,被封裝在ReadFile
content, err := ioutil.ReadFile(file)
  • 文件演示
import (
	"os"
	"fmt"
	"io"
	"bufio"
	"io/ioutil"
)

func main() {
	// open file
	file, err := os.Open("c:/test.txt")
	if err != nil{
		fmt.Println("open file error")
	}
	
	fmt.Println(file)   //輸出文件地址
	
	// close file
	defer err = file.Close()
	if err != nil{
		fmt.Println("close file error")
	}

	// read file method 1
	reader := bufio.NewReader(file)
	for {         // 直接循環
		str, err := reader.ReadString('\n')  //讀到換行就結束
		if err == io.EOF {  //讀到文件末尾
			break
		}
	}

	// read file method 2
	file = "d:/test.txt"
	content, err := ioutil.ReadFile(file)
	if err != nil{
		fmt.Println("read file error")
	}
	fmt.Println(content)  //默認是byte[],全是數字
	fmt.Println(string(content))
}
  • 寫文件
    在這裏插入圖片描述
import (
	"bufio"
)

func main() {
	fileName := "c:/test.txt"
	file, err := os.OpenFile(fileName , os.O_WRONLY | os.O_CREATE, 666)
	if err != nil{
		fmt.Println("open file error")
	}
	
	str := "Hello Amber!!!\n"  //換行,有的編輯器\r\n
	writer := bufio.NewWriter(file)
	writer.WriteString(str)  //此時僅把數據寫入緩存
	writer.Flush()  //將緩存內容寫入文件
	
	defer file.Close()
}

os.O_WRONLY | os.O_CREATE :寫文件,沒有就創建
os.O_WRONLY | os.O_APPEND :寫文件,不會覆蓋,追加
os.O_RDWR :可讀可寫

  • 文件拷貝
func main() {
	file1 = "c:/origin.txt"
	file2 = "c:/copy.txt"
	data, err := ioutil.ReadFile(file1)
	err := ioutil.WriteFile(file2, data, 666)
}
  • 判斷文件是否存在
func main() {
	_, err := os.Stat("c:/origin.txt")
	if err == nil{
		return true //文件/目錄存在
	}
	if os.IsNotExist(err){
	return false  //文件/目錄不存在
	}
}
  • 圖片/電影拷貝
import (
	"io/util"
	"bufio"
	"os"
)

func CopyFile(dstName string, srcName string) (written int64, err error){
	srcFile, err := os.Open(srcName)
	if err != nil{
		fmt.Println("open file error")
	}
	reader := bufio.NerReader(srcFile)
	
	defer srcFile.Close()

	// Open用於打開讀文件,OpenFile是打開寫文件
	dstFile, err := os.OpenFile(dstName, os.O_WRONLY | os.O_CREATE, 666)
	if err != nil{
		fmt.Println("open file error")
	}
	writer := bufio.NewWriter(dstFile)
	
	defer dstFile.Close()
	
	//copy
	return io.Copy(writer, reader)
}

func main() {
	srcFile := "c:/cat.jpg"
	dstFile := "d:/cat.jpg"
	_, err := CopyFile(dstFile, srcFile)
	if err != nil{
		fmt.Println("copy error")
	}elsd{
		fmt.Println("copy success")
	}
}

獲取命令行參數

  • DOS >>> copy a.txt b.txt
import(
	"os"
)

func main() {
	for i, val := range os.Args {
		fmt.Println("args[%v]=%v", i, val)
	}
}
  • 通過flag包獲取命令行>>> mysql -u root -p root -h localhost
import (
	"flag"
)

func main() {
	var user string
	var pwd string
	var host string
	var port int

	// &user:獲取-u後面的輸入的參數
	//"u":-u指定參數
	// "":默認值
	//"用戶名,默認爲空":註釋
	flag.StringVar(&user, "u", "", "用戶名,默認爲空")
	flag.StringVar(&pwd, "p", "", ",密碼默認爲空")
	flag.StringVar(&host, "h", "localhost", "主機")
	flag.StringVar(&port, "port", 3306, "端口")

	flag.Parse()	 //必須進行轉換
	fmt.Println("user=%v,pwd=%v,host=%v,port=%v", user, pwd, host, port)
}

json

應用場景:網絡傳輸前後臺都是以json格式

驗證jason格式化網址:www.json.cn

  • 序列化:把key-vlue類型的數據如數組,切片序列化成json字符串
import (
	"encoding/json"
)

type Student struct {
	Name string
	Age int
}

func main() {
	stu := Student{"amber", 17}
	
	// 序列化,傳遞的是地址&stu否則無法改變stu的值,默認值拷貝,返回值是byte[]
	data, err := json.Marshal(&stu)
	if err != nil{
		fmt.Println("序列化fail")
	}
	fmt.Println(string(data))
}
  • json反序列化成struct
import (
	"encoding/json"
)

type Student struct {
	Name string
	Age int
}

func main() {
	str := "{Name: "amber", "Age": 16}"
	var stu Student 
	
	// 第一個參數是轉換前的[]byte類型,第二個參數是被轉換的數據
	err := ejson.Unmarshal([]byte(str), &stu)
	fmt.Println(stu)
}

go單元測試

控制檯:go test -v 帶-v代表正確錯誤都有日誌,沒有-v正確不會有日誌顯示在終端
測試單個文件:go test one_test.go
測試單個方法:go test -v -test.run TestUpper

文件名必須以_test.go結尾

函數名必須以Test開頭

testing框架:將以test.go文件引入,main函數調用所有以Test開頭函數

PASS 代表成功,FAILURE代表失敗

import (
	"testing"  //引入go testing的框架
)

// 函數名必須以Test開頭,其後的第一個字母不能是a-z
func TestUpper(t *testing.T) {
	res := Upper(10)
	if res != 55{
		t.Fatalf("error")   //停止程序,記錄log
	}
	t.Logf("success")
}

goroutine協程

併發:多線程程序在單核上運行
並行:多線程程序在多核上運行
在這裏插入圖片描述

  • 特點
  1. 有獨立的棧空間
  2. 共享程序堆空間
  3. 調度有用戶控制
  4. 協程是輕量級的線程
func test() {
	for i :=0; i < 10; i++{
		fmt.Println("test hello world")
	}
}

func main() {
	go test()   //開啓協程,主線程和test交叉執行
	for i :=0; i < 10; i++{
		fmt.Println("main hello world")
	}
}

遇到go關鍵字時,主線程會再開出一個分支執行go後面的方法,主線程仍繼續執行,若主線程執行完畢,分支線程無論是否結束都要結束

主線程比較耗費資源,分支線程是輕量級的
在這裏插入圖片描述

  • MPG模式
    M:操作系統的主線程
    P:協程上下文環境(程序運行所需的資源,比如cpu分配)
    G:協程

舉個🐾:現有ABCD四個客人點菜,A點了10個炒菜,廚師做菜的時候BCD就一直等着,但當廚師在做燉湯的時候,可以把B的菜先炒了,計算機可以來回在A和B之間切換

import "runtime"

func main() {
	cpuNum := runtime.NumCPU()
	fmt.Printf("電腦上CPU個數=%v", cpuNum)

	//設置使用的CPU個數
	runtime.GOMAXPROCS(cpuNum - 1)
}

Question🐣
當多個協程同時對數據庫寫入時,會報錯

解決方案:1,管道,2,互斥鎖

import "sync"

//lock時全局互斥鎖,Mutex是結構體,裏面有lock和unlock2個方法
var lock sync.Mutex

//在執行寫入操作之前
lock.lock()
map[0] = res
//在執行完寫入操作之後
lock.Unlock()

go管道channel

特點:

  1. 先進先出【FIFO】
  2. 線程安全,無需加鎖

聲明:var 變量名 chan 數據類型

初始化:make
在這裏插入圖片描述

func main() {
	var intChan chan int
	
	// make創建存放3個int類型的管道
	intChan = make(chan int, 3)
	fmt.println(intChan)  //輸出的是地址,&intChan也是地址

	//向管道寫入數據
	intChan<- 100
	num := 200
	intChan<- num
	fmt.println(len(intChan), cap(intChan)) 
	// 注意,make時指定的容量就是3,如果寫入超過3個會報錯

	// 取出數據,若數據已全部被取出還在取會報錯
	var num2 int
	num2 = <-intChan

	// 僅把數據扔出,不給任何變量
	 <-intChan
}

在這裏插入圖片描述
取出的數據類型也是interface,需要斷言
在這裏插入圖片描述

  • 管道的關閉
var intChan chan int
intChan = make(chan int, 3)
intChan<- 100
close(intChan) //關閉管道
intChan<- 200  //報錯,管道關閉不允許寫入
n:= <-intChan  //可以正常讀取
  • 遍歷管道
    ①:遍歷時,如果channel沒有關閉,會出現deadlock錯誤
    ②:遍歷時,如果channel關閉,正常遍歷

注意:遍歷管道時不能使用len(),因爲每取出一個數據,管道的長度是會變的

intchan2 := make(chan int, 100)
for i:=0;i<100;i++ {
	intchan2<- i
}
close(intchan2)   //關閉
for i range intchan2 {
	fmt.Println(i)   //直接取出
}
  • 協程和管道結合

問題描述:定義2個協程,一個寫入50個數據,一個讀出50個數據,並且保證在主進程結束之前完成讀寫操作

思路:爲防止主線程在協程之前結束,需要在定義一個管道專門用來放讀完的標誌

在這裏插入圖片描述

func write(intChan chan int) {
	for i :=1;i <=50;i++{
		intChan <- i
	}
	close(intChan)
}

func read(intChan chan int, boolChan chan bool) {
	for {
		val, ok := <- intChan
		if !ok {
			fmt.Println("read data=%v", val)
		}
	}
	boolChan <- true
	close(boolChan)
}

func main() {
	intChan := make(chan int, 50)
	exitChan := make(chan bool, 1)

	go write(intChan)
	go read(intChan, exitChan)
	
	for {
		_, ok := <- exitChan 
		if !ok {
			break
		}
	}
}
  • 管道阻塞
    不管讀的速度快還是寫的速度塊都不會發生死鎖,因爲編譯器會分析
for {
	select {  //如果管道沒有關閉,也可以遍歷數據,不會被阻塞,若管道里沒有數據自動匹配下一個case
		case v := <-intChan:
			fmt.Println(v)
		case v := <-stringChan:
			fmt.Println(v)
		default :
			break  //只能跳到select,無法跳出for循環
			return  // 可以
	}
}

①:管道聲明爲雙向 var 管道名 chan int
②:管道聲明爲只寫 var 管道名 chan <- int
③:管道聲明爲只讀 var 管道名 <- chan int

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章