第八节 Go 常见的内置包和IO

 

go的常用的内置函数、IO操作、小综合案例

与其说是教程,更像是总结

一起学习:smiley:


1. 字符串

1.1 遍历中文字符串 []rune(str) len([]rune(str))

func main(){
	s := "go语言"

	fmt.Println(s)
	fmt.Println(len(s))
	for _, ch := range s {
		fmt.Printf("%c", ch)
	}

	fmt.Println()

	fmt.Println([]byte(s))
	fmt.Println(len([]byte(s)))
	for _, ch := range []byte(s) {
		fmt.Printf("%c", ch)
	}

	fmt.Println()

	fmt.Println([]rune(s))
	fmt.Println(len([]rune(s)))
	for _, ch := range []rune(s) {
		fmt.Printf("%c", ch)
	}
}
go语言
14
go语言
[230 136 145 231 136 177 103 111 232 175 173 232 168 128]
14
我爱go语言
[25105 29233 103 111 35821 35328]
6
我爱go语言

1.2 常用函数 基本都用,用到的时候再记录下来

检索字符串

分割字符串

大小写转换

修剪字符串

比较字符串

1.3 相同的两个类型之间互转

// IT类型的底层是int类型
type IT int

// a的类型为IT,底层是int
var a IT = 5

// 将a(IT)转换为int,b现在是int类型
b := int(5)

// 将b(int)转换为IT,c现在是IT类型
c := IT(b)

1.4 字符串格式处理

1.int转换为字符串:Itoa()

// Itoa(): int -> string
println("a" + strconv.Itoa(32))  // a32

2.string转换为int:Atoi()

由于string可能无法转换为int,所以这个函数有两个返回值:第一个返回值是转换成int的值,第二个返回值判断是否转换成功。

// Atoi(): string -> int
i,_ := strconv.Atoi("3")
println(3 + i)   // 6

// Atoi()转换失败
i,err := strconv.Atoi("a")
if err != nil {
    println("converted failed")
}

3.Parse类函数用于转换字符串为给定类型的值:ParseBool()、ParseFloat()、ParseInt()、ParseUint()。

func ParseInt(s string, base int, bitSize int) (i int64, err error)
func ParseUint(s string, base int, bitSize int) (uint64, error)
b, err := strconv.ParseBool("true")
f, err := strconv.ParseFloat("3.1415", 64)
i, err := strconv.ParseInt("-42", 10, 64)
u, err := strconv.ParseUint("42", 10, 64)

4.将给定类型格式化为string类型:FormatBool()、FormatFloat()、FormatInt()、FormatUint()。

func FormatInt(i int64, base int) string
func FormatUint(i uint64, base int) string
s := strconv.FormatBool(true)
s := strconv.FormatFloat(3.1415, 'E', -1, 64)
s := strconv.FormatInt(-42, 16)
s := strconv.FormatUint(42, 16)

5.Append类函数

AppendTP类函数用于将TP转换成字符串后append到一个slice中:AppendBool()、AppendFloat()、AppendInt()、AppendUint()。

Append类的函数和Format类的函数工作方式类似,只不过是将转换后的结果追加到一个slice中。

package main

import (
	"fmt"
	"strconv"
)

func main() {
    // 声明一个slice
	b10 := []byte("int (base 10):")
    
    // 将转换为10进制的string,追加到slice中
	b10 = strconv.AppendInt(b10, -42, 10)
	fmt.Println(string(b10))

	b16 := []byte("int (base 16):")
	b16 = strconv.AppendInt(b16, -42, 16)
	fmt.Println(string(b16))
}
int (base 10):-42
int (base 16):-2a

2. regexp正则表达式

  • regexp.Compile一般处理用户输入
  • regexp.MustCompile 一般自己输入(保证正确)
const text = "My email is haorongzhu@gmail.com"
const text2 = `
My email is haorongzhu@gmail.com
email1 is bac@def.org
email2 is   kkk@qq.com
emial3 is sans@kn.com.cn
`

2. 1 写死判断

re, _ := regexp.Compile("haorongzhu@gmail.com")
match := re.FindString(text)
fmt.Println(match)

2.2 使用正则表达式

  • ”.” 一个字符
  • ”.+” 一个或多个字符
  • “*+” 零个或多个字符
re2, _ := regexp.Compile(`.+@.+\..+`) //``里不会有转义字符,直接给
match2 := re2.FindString(text)
fmt.Println(match2)
  • [a-zA-Z0-9] 字母和数字
re3, _ := regexp.Compile(`[a-zA-Z0-9]+@[a-zA-Z0-9]+\.[a-zA-Z0-9]+`) 
match3 := re3.FindString(text)
fmt.Println(match3)
  • [a-zA-Z0-9.] 方括号中不要加\来转义!
re4, _ := regexp.Compile(`[a-zA-Z0-9]+@[a-zA-Z0-9.]+\.[a-zA-Z0-9]+`) 
match4 := re4.FindAllString(text2, -1)                               //-1 全部
fmt.Println(match4)
  • FindAllStringSubmatch 全部匹配输出
re5, _ := regexp.Compile(`([a-zA-Z0-9]+)@([a-zA-Z0-9]+)(\.[a-zA-Z0-9.]+)`)
match5 := re5.FindAllStringSubmatch(text2, -1)                            //-1 全部
for _, m := range match5 {
   fmt.Println(m)
}

3. 随机数

3.1 生成固定的伪随机数

package main

import (
	"fmt"
	"math/rand"
)

var p = fmt.Println

func main() {
	p(rand.Int()) //生成一个非负的伪随机数int值
	p(rand.Float64()) //生成一个非负的伪随机数float64值
	p(rand.Intn(n)) //生成一个非负的伪随机数[0,n]值
}
5577006791947779410
0.9405090880450124
47

3.2 生成动态的伪随机数

func main() {
	//使用种子创建伪随机资源
	s1 := rand.NewSource(time.Now().UnixNano())
	//利用资源生成*Rand
	r1 := rand.New(s1)
	//调用伪随机.Rand中需要的方法,生成
	p(r1.Intn(100))
}

可以简写

rand.Seed(time.Now().UnixNano())
rand.Intn()/.Float64()/......

3.3 不重复随机数

//生成count个[start,end)结束的不重复的随机数
func generateRandomNumber(start int, end int, count int) []uint64 {
	//范围检查
	if end < start || (end-start) < count {
		return nil
	}
	//存放结果的slice
	nums := make([]uint64, 0)
	for len(nums) < count {
		//生成真随机数
		num, _ := rand.Int(rand.Reader, big.NewInt(9))
		a := num.Uint64()
		//查重
		exist := false
		for _, v := range nums {
			if v == a {
				exist = true
				break
			}
		}
		if !exist {
			nums = append(nums, a)
		}
	}
	return nums
}

4. 键盘输入

fmt.Scanln(&userName, &password)

var p = fmt.Println

func main() {
	var userName string
	var password string
	fmt.Scanln(&userName, &password)
	p(userName, " ", password)
}

I/O操作

1. 文件信息 FileInfo fileStat

在根目录下新建abc.txt文件

type fileStat struct{
	name string
	size int64
	mode FileMode
	modTime time,time
	sys syscall.Stat_t
}
func main() {
   printMessage("abc.txt")
}

func printMessage(filePath string) {
   fileInfo, err := os.Stat(filePath)
   if err != nil {
      fmt.Println(err.Error())
   } else {
      fmt.Printf("数据类型是 %T\n", fileInfo)
      fmt.Println("文件名: ", fileInfo.Name())
      fmt.Println("是否为目录: ", fileInfo.IsDir())
      fmt.Println("文件权限: ", fileInfo.Mode())
      fmt.Println("文件最后修改的时间: ", fileInfo.ModTime())
   }
}
数据类型是 *os.fileStat
文件名:  abc.txt
是否为目录:  false
文件权限:  -rw-rw-rw-
文件最后修改的时间:  2021-07-21 16:19:50.4050098 +0800 CST

Linux的文件权限

文件权限:  -rw-rw-rw-
-=0  r=4 w=2 x=1
第一位 - -> 普通文件 d -> 目录
第2-4位  所有者权限
第5-7位  所属组权限
第8-10位 其他用户权限

2. 文件路径

  • filepath.IsAbs() 判断是否是绝对路径
  • filepath.Rel() 获取相对路径
  • filepath.Abs() 获取绝对路径
  • path.Join() 拼接字符串

3. 文件常规操作

3.1 创建目录

  • os.MKdir()
  • os.MKdirAll()
func main() {
   fileName1 := "./test1"
   //创建单级目录
   err := os.Mkdir(fileName1, 0777)
   if err != nil {
      fmt.Println(err)
   } else {
      fmt.Println("Yes", fileName1)
   }

   //创建多级目录
   fileName2 := "./test2/abc/xyz"
   err = os.MkdirAll(fileName2, 0421)
   if err != nil {
      fmt.Println(err)
   } else {
      fmt.Println("yes", fileName2)
   }

   fileInfo, err := os.Stat(fileName1)
   fmt.Println(fileInfo.Mode())
}
mkdir ./test1: Cannot create a file when that file already exists.
yes ./test2/abc/xyz
drwxrwxrwx

3.2 创建文件 os.Create(fileName1)

func main() {
   fileName1 := "./test1/abc.txt"
   //创建文件
   file1, err := os.Create(fileName1)
   if err != nil {
      fmt.Println(err)
   } else {
      fmt.Println("创建成功", file1)
   }
}

3.3 打开和关闭文件

  • os.OpneFile(filename,mode,perm) 打开文件
  • **mode:打开方式 “ ” 分隔**
  • perm:权限
  • file.Close() 关闭文件

3.4删除文件

  • os.Remove(fileName)
  • os.RemoveAll(fileName)

3.5 读取文件

func main() {
   //读取文件
   fileName := "./abc.txt"
   file, err := os.Open(fileName)
   defer file.Close()
   if err != nil {
      fmt.Println("打开文件错误", err)
   } else {
      bs := make([]byte, 1024*8, 1024*8) //length capacity
      n := -1
      for {
         n, err = file.Read(bs)
         if n == 0 || err == io.EOF {
            fmt.Println("读取文件结束")
            break
         }
         fmt.Println(string(bs[:n]))
      }
   }
}

3.6 写入文件

  • file.Write([]byte(“asbcijs nk”))
  • file.WriteString(“中国字中国字中国字中国字”)
func main() {
   //写入文件
   file, err := os.OpenFile("./abc.txt", os.O_APPEND, os.ModePerm)
   defer file.Close()
   if err != nil {
      fmt.Println("打开文件异常", err.Error())
   } else {
      n, err := file.Write([]byte("asbcijs nk"))
      if err != nil {
         fmt.Println(err)
      } else {
         fmt.Println(n)
      }

      n, err = file.WriteString("中国字中国字中国字中国字")
      if err != nil {
         fmt.Println(err.Error())
      } else {
         fmt.Println("写入ok: ", n)
      }
   }
}

3.7 复制文件

io.Copy()

func main() {
   //复制文件
   //源文件
   srcFile := "./abc.txt"
   //准备生成的文件
   destFile := "./test2/abc/xyz/abc2.txt"

   total, err := copyFile(srcFile, destFile)
   if err != nil {
      fmt.Println(err)
   } else {
      fmt.Println("复制完成!", total)
   }
}

func copyFile(srcFile, destFile string) (int64, error) {
   file1, err := os.Open(srcFile)
   if err != nil {
      return 0, err
   }

   file3, err := os.OpenFile(destFile, os.O_RDWR|os.O_CREATE, os.ModePerm)
   if err != nil {
      return 0, err
   }

   defer file3.Close()
   defer file1.Close()
   return io.Copy(file3, file1)
}

综合案例

广度优先搜索算法

**配置文件 maze.in **

6 5
0 1 0 0 0
0 0 0 1 0
0 1 0 1 0
1 1 1 0 0
0 1 0 0 1
0 1 0 0 0

0 1 0 0 0
0 0 0 1 0
0 1 0 1 0
1 1 1 0 0
0 1 0 0 1
0 1 0 0 0

读取文件并测试

package main

import (
	"fmt"
	"os"
)

/**
读取迷宫文件
*/
func readMaze(fileName string) [][]int {
	//读取文件
	file, err := os.Open(fileName)
	if err != nil {
		panic(err)
	}
	//读取迷宫的大小
	var row, col int
	fmt.Fscanf(file, "%d %d", &row, &col)

	//开辟内存
	maze := make([][]int, row) //开辟一块有row个([]int)的内存
	for i := range maze {
		maze[i] = make([]int, col) //再开辟内部的col个int内存
		//读取数据
		for j := range maze[i] {
			fmt.Fscan(file, &maze[i][j]) //记得取地址,不然没法修改值,输出全是0
		}
	}

	return maze
}

func main() {
	//读取文件
	maze := readMaze("maze/maze.in")

	for _, row := range maze {
		for _, val := range row {
			fmt.Printf("%d ", val)
		}
		fmt.Println()
	}
}

实现向四周走

package main

import (
   "fmt"
   "os"
)

/**
读取迷宫文件
*/
func readMaze(fileName string) [][]int {
   //读取文件
   file, err := os.Open(fileName)
   if err != nil {
      panic(err)
   }
   //读取迷宫的大小
   var row, col int
   fmt.Fscanf(file, "%d %d", &row, &col)

   //开辟内存
   maze := make([][]int, row) //开辟一块有row个([]int)的内存
   for i := range maze {
      maze[i] = make([]int, col) //再开辟内部的col个int内存
      //读取数据
      for j := range maze[i] {
         fmt.Fscan(file, &maze[i][j]) //记得取地址,不然没法修改值,输出全是0
      }
   }

   return maze
}

/**
创建一个点
*/
type point struct {
   i, j int
}

/**
定义走的四个方向
*/
var dirs = [4]point{
   {-1, 0}, //往前
   {0, -1}, //往左
   {1, 0},  //往后
   {0, -1}, //往右
}

/**
实现point结构相加(go语言没有运算符重载)
func (p *point) add(r point) 可以直接传指针,直接修改,但出错不好查找
*/
func (p point) add(r point) point { //返回值类型,不容易出错
   return point{p.i + r.i, p.j + r.j}
}

/**
走迷宫
迷宫、起点、终点
*/
func walk(maze [][]int, start, end point) {
   //创建记录步骤的二维数组并初始化
   steps := make([][]int, len(maze))
   for i := range steps {
      steps[i] = make([]int, len(maze[i]))
   }

   //创建一个队列并初始化为起点
   queue := []point{start}

   //退出条件,队列中有可探索的路
   for len(queue) > 0 {
      cur := queue[0]   //获取当前位置
      queue = queue[1:] //队头出列

      for _, dir := range dirs {
         next := cur.add(dir) //获取到四个能走的坐标
      }
   }
}

func main() {
   //读取文件
   maze := readMaze("maze/maze.in")

   //for _, row := range maze {
   // for _, val := range row {
   //    fmt.Printf("%d ", val)
   // }
   // fmt.Println()
   //}

   //走迷宫
   walk(maze, point{0, 0}, point{len(maze), len(maze[0]) - 1})
}

最终代码

package main

import (
   "fmt"
   "os"
)

/**
读取迷宫文件
*/
func readMaze(fileName string) [][]int {
   //读取文件
   file, err := os.Open(fileName)
   if err != nil {
      panic(err)
   }
   //读取迷宫的大小
   var row, col int
   fmt.Fscanf(file, "%d %d", &row, &col)

   //开辟内存
   maze := make([][]int, row) //开辟一块有row个([]int)的内存
   for i := range maze {
      maze[i] = make([]int, col) //再开辟内部的col个int内存
      //读取数据
      for j := range maze[i] {
         //不指定格式就不会自动补0
         //注意IDE默认的空格字符,调为Linux下的
         fmt.Fscan(file, &maze[i][j]) //记得取地址,不然没法修改值,输出全是0
      }
   }

   return maze
}

/**
创建一个点
*/
type point struct {
   i, j int
}

/**
定义走的四个方向
*/
var dirs = [4]point{
   {-1, 0}, //往前
   {0, -1}, //往左
   {1, 0},  //往后
   {0, 1},  //往右
}

/**
实现point结构相加(go语言没有运算符重载)
func (p *point) add(r point) 可以直接传指针,直接修改,但出错不好查找
*/
func (p point) add(r point) point { //返回值类型,不容易出错
   return point{p.i + r.i, p.j + r.j}
}

/**
打印二维数组
*/
func printArr(arr [][]int) {
   for _, row := range arr {
      for _, val := range row {
         fmt.Printf("%3d ", val) //3位对齐
      }
      fmt.Println()
   }
}

/**
判断位置是否越界
*/
func (p point) at(grid [][]int) (int, bool) {
   //上界和右界
   if p.i < 0 || p.i >= len(grid) {
      return 0, false
   }

   //左界和下界
   if p.j < 0 || p.j >= len(grid[0]) {
      return 0, false
   }

   return grid[p.i][p.j], true
}

/**
//走迷宫
//迷宫、起点、终点
//*/
func walk(maze [][]int, start, end point) [][]int {
   //创建记录步骤的二维数组并初始化
   steps := make([][]int, len(maze))
   for i := range steps {
      steps[i] = make([]int, len(maze[i]))
   }

   //创建一个队列并初始化为起点
   queue := []point{start}

   //退出条件,队列中有可探索的路
   for len(queue) > 0 {
      cur := queue[0]   //获取当前位置
      queue = queue[1:] //队头出列

      //发现终点
      if cur == end {
         break
      }

      for _, dir := range dirs {
         next := cur.add(dir) //获取到四个能走的坐标

         val, ok := next.at(maze)
         if !ok || val == 1 { //越界或撞墙了
            continue
         }

         val, ok = next.at(steps)
         if !ok || val != 0 { //越界或走了重复的路
            continue
         }

         if next == start { //越界或走到了起点
            continue
         }

         /**
         开始探索
         */
         //填充步骤数
         curStep, _ := cur.at(steps)
         steps[next.i][next.j] = curStep + 1
         //加入队列
         queue = append(queue, next)
      }
   }

   return steps
}

func main() {
   //读取文件
   maze := readMaze("maze/maze.in")

   //走迷宫
   steps := walk(maze, point{0, 0}, point{len(maze), len(maze[0]) - 1})
   printArr(steps)
}

实现将迷宫答案追加写回文件

/**
将答案写入文件
*/
func printToFile(fileName string, arr [][]int) {
   //读取文件
   file, err := os.OpenFile(fileName, os.O_APPEND, 0666)
   if err != nil {
      panic(err)
   }

   var str []string
   str = append(str, "\n迷宫答案\n")

   for _, row := range arr {
      for _, val := range row {
         str = append(str, strconv.Itoa(val))
      }
      str = append(str, "\n")
   }

   var d1 = []byte(fmt.Sprintf(strings.Join(str, " ")))
   n, err2 := file.Write(d1) //写入文件(字节数组)
   if err2 != nil || n == 0 {
      panic(err2)
   }

   defer file.Close()
}

查看系统信息

package main

import (
    "fmt"
    "runtime"
	"os"
)

func main() {
    //操作系统
    fmt.Println("GOOS:", runtime.GOOS)
    //架构
    fmt.Println("GOARCH:", runtime.GOARCH)
    //GOROOT
    fmt.Println("GOROOT:", runtime.GOROOT())
    //go版本
    fmt.Println("Version:", runtime.Version())
    //cpu数
    fmt.Println("NumCPU:", runtime.NumCPU())
    if name, err := os.Hostname();err == nil {
        fmt.Println(`电脑名称:`, name)
    }
}

参考资料

Go基础系列:数据类型转换(strconv包)