Golang 有料才能 够浪

^_^ math

// 上下取整
math.Ceil(3.14) // 4
math.Floor(3.14) // 3


// 四舍五入到小数点 后 n 位
func RoundFloat(f float64, n int) float64 {
	n10 := math.Pow10(n)
	return math.Trunc((f+0.5/n10)*n10) / n10
}

// 分解小数
a,b := math.Modf(3.14)   // a=3 b=0.1400000...0001

// 获取整数位
res := math.Trunc(3.5415) // 3
// 自己试出来的语法糖:
var a float64 = 3.5415
int(a) // ==> 3

// 绝对值
math.Abs(-8) // 8

// 幂运算
math.Pow(5,3) // 125
math.Pow10(4) // 10的4次方  ==> 10000
math.Sqrt(125) // 5
math.Cqrt(25) // 5

// 三角函数
math.Sin(math.PI / 2) // 1

// 20以内的随机数
rand.Seed(time.Now().UnixNano())
res := rand.Intn(20)

自定义方法 实现go中没有的 数据处理方法:

// golang没有类似python的 round()函数 四舍五入 为整数,脑洞:先+0.5,然后向下取整!
func round(x float64) int {
	return int(math.Floor(x + 0.5))
}

// 四舍五入到小数点 后 n 位, 如果n=0,效果同上 round方法,区别在于 返回的数据类型
func RoundFloat(f float64, n int) float64 {
	j := math.Pow10(n)
	return math.Trunc((f+0.5/j)*j) / j
}

// 获取 浮点数 的小数点位数, 最多15位小数, 方式一
func GetFloatLen(a float64) int {
	res := strconv.FormatFloat(a, 'f', -1, 64)
	resArr := strings.Split(res, ".")
	return len(resArr[1])
}

// 获取 浮点数 的小数点位数, 最多15位小数,方式二
func GetFloatLen(a float64) int {
	i := 0
	for {
		j := math.Pow10(i)
		tmp := int(a * j)
		// fmt.Println(j, "==", int(b))

		if a*j == float64(tmp) {
			// 原理: 3.14 
			// 31.4  比较   30
			// 314   比较 	310
			// 3140  比较	3140

			break
		}
		i++
	}
	return i
}

说明:

  • 为了保证方法的通用性,大部分方法的 参数 和 返回值 都是 float64 类型的。
  • 小数点位数 超过 6位 会自动采用 科学计数法 来表示,如下:
type Users struct {
	Backs float64
}

func main() {
	obj := Users{Backs: 0.000000002}
	res, _ := json.Marshal(&obj)
	fmt.Println(obj, string(res)) // {2e-09} {"Backs":2e-9}
}
  • 如场景要求 展示的 小数长度 即使大于 6位 也不能用 科学计数法 展示,可以 将 小数 转化成 指定位数字符串类型

^_^ string

var s string = "abcd-abcd,你好"

strings.Repeat(s, 2) 	// "abcd-abcd,你好abcd-abcd,你好"
var s string = "+abcd+"

strings.Trim("+") 		// "abcd"
strings.TrimRight("+") 		// "+abcd"
strings.TrimLeft("+") 		// "abcd+"
var s string = "abcd-abcd,你好"

strings.Count(s, "ab") 		// 2

strings.HasPrefix(s, "a")		 // true
strings.HasSuffix(s, "你好") 		// true
strings.Contains(s,"d-a")		 // true
strings.ContainsAny(s,"老铁你好")		 // true

strings.Index(s, "cd")		 // 2
strings.LastIndex(s, "cd") 		// 7

strings.EqualFold("aBc", "abc") 		// 忽略大小写:true
"aBc" == "abc"		 // 不忽略大小写:false

var s string = "abcd-abcd,你好"

strings.Split(s, "c")		 // []string {"ab", "d-ab", "d,你好"}
strings.SplitAfter(s, "c")		 // []string {"abc", "d-abc", "d,你好"}
// Join 反向操作
var q = []string{"a", "b", "c"}
strings.Join(q, "")		 // abc
strings.Join(q, "-")		 // a-b-c

strings.ToUpper(s) 		// "ABCD-ABCD,你好"
strings.ToLower(s)
strings.Title("aaa bbb") 		// Aaa Bbb

strings.Replace("a-b-c", "-", "+", 1)		 // "a+b-c"
strings.Replace("a-b-c", "-", "+", -1)		 // "a+b+c"

^_^ strconv

  • bool
// 除了字符串: "1", "t", "true" (字母不区分大小写),其他均为false
strconv.ParseBool("yse") // false

strconv.FormatBool(true) // "true"
  • int64
// 参2:进制;  参3:随便
strconv.ParseInt("11", 10, 64) 		// int64: 11

strconv.FormatInt(int64(11), 10) 		// "11"
  • int
// 字符串 转 整数 数字 的快捷方式
var str string = "100" // str 必须是整数
strconv.Atoi(str)  // int: 100
  • float64
strconv.ParseFloat("3.14", 64) 		// float64: 3.14

// 参2:格式标记; 参3:小数点后保留位数  参4:格式化的float类型 32 or 64
strconv.FormatFloat(3.14, 'f', 4, 64)
strconv.FormatFloat(3.14, 'f', -1, 64) // -1 保留小数点原先的位数(最多15位数)

说明:
Parse => 字符串—转化—>别的类型, 返回 两个值
Format => 别的类型—转化—>字符串, 返回 一个值


^_^ regexp

  • 元字符

. : 匹配所有字符

\w : 数字和字母
\W : 非 数字和字母 (±=)

\d : 数字
\D : 非 数字

\s : 空白字符
\S : 非 空白字符

[ ]: 字符集合
[a-zA-Z0-9] ===》 所有 大小写字母 和 数字
[\u4e00-\u9fa5] ===》 所有汉字 (从 “一”到 ”龥”)
[^0-9] ===》 集合中的 ^ 范围取反: 除数字以外的所有字符

  • 限定符

^ : 开头位置限定符
$ : 结束位置限定符

? + * : 重复限定符
x* ===》 匹配零个或多个 x,优先匹配更多(贪婪模式: 倾向于多的)
x+ ===》 匹配一个或多个 x,优先匹配更多(贪婪)
x? ===》 匹配零个或一个 x,优先匹配一个(贪婪)
x{n,m} ===》 匹配 n 到 m 个 x,优先匹配更多(贪婪)
x{n,} ===》 匹配 n 个或多个 x,优先匹配更多(贪婪)
x{n} ===》 只匹配 n 个 x
后加?改变贪婪模式 为 非贪婪模式:
x*? ===》 匹配零个或多个 x,优先匹配更少(非贪婪模式: 倾向于少的)
x+? ===》 匹配一个或多个 x,优先匹配更少(非贪婪)
x?? ===》 匹配零个或一个 x,优先匹配零个(非贪婪)
x{n,m}? ===》 匹配 n 到 m 个 x,优先匹配更少(非贪婪)
x{n,}? ===》 匹配 n 个或多个 x,优先匹配更少(非贪婪)
x{n}? ===》 只匹配 n 个 x

{} : 次数限定符
x{3} => 3个x; x{3,8} 至少3个 ,最多8个

  • 正则应用
var s string = "111ww222ww333"

// 定义一个 正则变量
reg := regexp.MustCompile(`[\d]+`)

reg.FindString(s) //111
reg.FindAllString(s, 2) // [111 222]
reg.FindAllString(s, -1) //[111 222 333]
reg.MatchString(s) //true

^_^ 自定义 切片 相关方法

func push(arr []int, item int) []int {
	return append(arr, item)
}

func pop(arr []int) []int {
	l := len(arr)
	if l > 0 {
		return arr[0 : l-1]
	} else {
		return arr
	}
}

func addByIndex(arr []int, item int, index int) []int {
	l := len(arr)
	if index <= l && index >= 0 {
		before := arr[:index]
		after := arr[index:]
		tmp := append([]int{}, before...)
		tmp = append(tmp, item)
		return append(tmp, after...)
	}
	return arr
}

func delByIndex(arr []int, index int) []int {
	l := len(arr)
	if index <= l-1 && index >= 0 {
		return append(arr[:index], arr[index+1:]...)
	}
	return arr
}

func delByValue(arr []int, value int) []int {
	for i, v := range arr {
		if v == value {
			return append(arr[:i], arr[i+1:]...)
		}
	}
	return arr
}

^_^ function

默认值参数

go中没有 默认值参数, 但是我可以实现 默认值参数的 功能:

package main

import (
	"fmt"
	"strconv"
	"strings"
)

//  自定义 默认参数 的 结构体
type Arr struct {
	// 这里用 字符串的好处是 可以 方便 转换为其他的类型,
	// 这就是的 默认值参数 的 类型 范围更广
	ctx string
}

func (that *Arr) Change(ctx string) {
	that.ctx = ctx
}

// 实现 默认值参数 的函数
func aaa(generalArr int, ctx ...string) {
	// 定义 3 个 默认值参数
	name := Arr{
		ctx: "tom", // 设置默认值
	}
	age := Arr{
		ctx: "10",
	}
	sex := Arr{
		ctx: "man",
	}

    // 这是核心:
	// 按照 默认值参数 的 名称 , 给 默认值参数 对应赋值
	for _, v := range ctx {
		key := strings.Trim(strings.Split(v, "=")[0], " ")
		val := strings.Trim(strings.Split(v, "=")[1], " ")
		switch key {
		case "name":
			name.Change(val)
		case "age":
			age.Change(val)
		case "sex":
			sex.Change(val)
		default:
			return
		}
	}
	// 普通参数
	fmt.Println("普通参数:", generalArr)
	ageInt, _ := strconv.Atoi(age.ctx)
	// 默认值参数
	fmt.Println("默认值参数: name:", name.ctx, "-----age:", ageInt, "-----sex:", sex.ctx)
}

func main() {
	aaa(123)                                       //name: tom -----age: 10 -----sex: man
	aaa(123, "name = cat", "sex=110", "age = 789") // name: cat -----age: 110 -----sex: man
}

^_^ time

  • 时间段
// 1.官方时间段
const (
    Nanosecond  Duration = 1 								 //纳秒(参考最底层)
    Microsecond          = 1000 * Nanosecond 	 // 微秒
    Millisecond          = 1000 * Microsecond 		// 毫秒
    Second               = 1000 * Millisecond 			// 秒
    Minute               = 60 * Second							// 分
    Hour                 = 60 * Minute 								// 时
)
time.Sleep(time.Second * 5)

// 2.生成时间段
duration := time.ParseDuration("3600s") 	// 单位: h、m、s、ms、us、ns
time.Sleep(duration)

//换算:
duration.Hours()		 // 1
duration.Minutes() 		// 60
duration.Seconds()		 // 3600
duration.Milliseconds() 		// 3600000
  • 时间点
// 获取 时间 结构体
Tm := time.Now() 		//struct: 2020-02-22 22:22:22.169010574 +0800 CST m=+0.000046516

Tm.Format("2006/01/02 15:04:05") 		// string: 2020/02/22 22:22:22

Tm.Unix() 		// 单位为 秒 的 时间戳
Tm.UnixNano()		 // 单位为 纳秒 的 时间戳

Tm.Year() 		// 2020
Tm.Month()		 // February
int(Tm.Month())		 // 2
Tm.Day() 		// 22
Tm.Hour() 		// 22
Tm.Minute()		 // 22
Tm.Second() 		// 22
  • 特定 时间点
// 两个小时之前的时间
time.Now().Add(-2 * time.Hour).Format("2006/01/02 15:04:05")

// 两个小时之后的时间
time.Now().Add(2 * time.Hour).Format("2006/01/02 15:04:05")

// 指定时间点
timeStand := "2006/01/02 15:04:05"
timeStr := "2020/08/08 10:10:10"
area,_ := time.LoadLocation("Asia/Shanghai") // Asia/Shanghai 不能写错,区分大小写
Tm,_ := time.ParseInLocation(timeStand, timeStr, area)

// 指定时间点(2)
time.Date(2020,08,08,10,10,10,0, time.Local)
  • 时戳互转
// 时 --转--> 戳
timeStamp := time.Now().Unix()

// 戳 --转--> 时
Tm := time.Unix(timeStamp, 0)		 // 参数1:时间戳; 参数2:0-9亿内任意数
  • 间隔计算
// 计算 时间间隔
start := time.Now()
    time.Sleep(time.Second * 2)
duration := time.Since(start)
    fmt.Println(duration) // 2.000336177s

// 计算 日期间隔
wtt := time.Date(1993, 7, 28, 12, 30, 6, 100, time.Local)
zqq := time.Date(1994, 10, 22, 10, 00, 00, 100, time.Local)
  // 时间早的 作为参数,时间晚的 调用方法
res := zqq.Sub(wtt) // 10821h29m54s

// 如果计算 现在时间  到目标时间 的 数据距离,可以用Sub方法,如下:
	aimTime := time.Now().Add(30 * time.Minute)
	res := aimTime.Sub(time.Now()).Seconds() // 警告: should use time.Until instead of t.Sub(time.Now())
	fmt.Println(res)

// golang的 包中的 Until方法,是Sub计算现在时间  到目标时间 的 数据距离 的快捷方法,
// 所以上面报了那样的警告,为了移除警告,我们这样做:
	aimTime := time.Now().Add(30 * time.Minute)
	RES := time.Until(aimTime).Seconds() //得到秒数,建议用 int() 取整
	fmt.Println(RES)
	
说明: 如果计算 现在的时间 距离 过去的时间,那么得到的是负数
  • 世界统一时间UTC

世界的每个地区都有自己的本地时间,在Internet及无线电通信时,有一个统一的时间是非常重要的!
整个地球分为二十四时区,每个时区都有自己的本地时间。
在国际无线电通信中,为统一而普遍使用一个标准时间,称为通用协调时(UTC, Universal Time Coordinated)。
格林尼治 的当地时间 称之为GMT(Greenwich Mean Time), UTC 在时间值上 采用 了 GMT时间。
但要区分, UTC是属于世界的,而GMT是属于格林尼治 当地的
CST 是中国的当地时间(China Standard Time UT+8:00)
CST 是古巴的当地时间(Cuba Standard Time UT-4:00)
CST 是美国中部的当地时间(Central Standard Time (USA) UT-6:00)
CST 是澳大利亚中部的当地时间(Central Standard Time (Australia) UT+9:30)
LON 是伦敦的当地时间
JRS 耶路撒冷(Jerusalem)以色列和巴勒斯坦的当地时间,等等

	// 世界所有电脑的统一时间 :UTC 时间
	time.Now().UTC().Format("2006-01-02 15:04:05") // 2021-08-03 08:12:07
	// 当地时间
	time.Now().Local().Format("2006-01-02 15:04:05") // 2021-08-03 16:12:07
	// 默认 获取的就是 当地时间
	time.Now().Format("2006-01-02 15:04:05") // 2021-08-03 16:12:07

	// 重点:  go 的 --时间体-- 的表现时间的形式:   时间值   +   时区   +   字母缩写   +   [ 其他 ]
	北京 := time.Now().Local()          // 2021-08-03 16:56:42.944422723    +0800    CST
	世界标准 := time.Now().UTC()   // 2021-08-03 08:56:42.944422915    +0000    UTC
  • 世界各地时间
// 纽约时间
	北京时间 := time.Now()
	fmt.Println(北京时间) // 2021-08-03 17:26:35  +0800  CST 

	local, _ := time.LoadLocation("America/New_York") // 参数 时区标志
	// 和 当地现在 时间值 一样的 纽约时间 (不一定是 纽约人 正在经历的时间,除非纽约 和 中国 一个时区)
	t, _ := time.ParseInLocation("2006-01-02 15:04:05", time.Now().Format("2006-01-02 15:04:05"), local)
	fmt.Println(t)  //   2021-08-03 17:26:35 -0400 EDT
	fmt.Println(t.Zone())  // EDT
	 // 和 当地现在 时间值 对应的 纽约时间 (此时此刻北京本地时间 对应的 纽约的当地时间, 纽约人正在经历的时间)
	NY := t.Local()
	fmt.Println(NY) //  2021-08-04 05:26:35 +0800 CST

//耶路撒冷时间
	北京时间 := time.Now()
	fmt.Println(北京时间) // 2021-08-03 17:32:37 CST m=+0.000051113

	local, _ := time.LoadLocation("Asia/Jerusalem")
	t, _ := time.ParseInLocation("2006-01-02 15:04:05", time.Now().Format("2006-01-02 15:04:05"), local)
	fmt.Println(t) 		 // 2021-08-03 17:32:37 +0300 IDT
	fmt.Println(t.Zone())  // IDT
	YLSL := t.Local()
	fmt.Println(YLSL) 	// 2021-08-03 22:32:37 +0800 CST
  • 通过 UTC 和 时区 计算 世界各地时间

公式: UTC + 时区差=本地时间
时区差东为正,西为负;东八区 比UTC 快个小时,故而记为: +0800,
如果快12个小时零45 分钟 则记为: +1245。

1. 假设 UTC 时间为:  2021-08-03 08:56:42, 则记为: 0856
北京时间 则为: 0856 ++0800=1656  ==> 2021-08-03 16:56:42

纽约 是 西五区,记为: -0500
纽约时间则为: 0856+-0500=0356   ==> 2021-08-03 03:56:42



2.  以上运算的结果都是 正数, 如果出现负数,则如下操作:
若结果是负数就意味着是UTC前一天(即昨天),
把这个负数加上2400就是UTC在前一天的时间。
例如,本地(北京)时间是 0325 (凌晨325分),那么,UTC就是 0325 - 0800 = -0475,
负号意味着是前一天, -0475 + 2400 = 1925,既前一天的晚上725分。

^_^ runtime

golang 的 runtime 在 golang 中的地位类似于 Java 的虚拟机,不过 go runtime 不是虚拟机.
golang 程序生成可执行文件在指定平台上即可运行,效率很高, 它和 c/c++ 一样编译出来的是二进制可执行文件.

runtime.GOOS 		// 操作系统
runtime.GC() 		// 强行进行一次GC

runtime.NumCPU() 		//cup 核数
runtime.GOMAXPROCS(4) 		// 应用使用核数最大值

runtime.NumGoroutine()		 // 此时还在工作的协程的个数(包括主协程)
runtime.Gosched() 		// 协程让出时间片
runtime.Goexit() 		// 协程退出
  • 函数调用位置 信息
  • runtime.Caller()
    查看 指定 调用层级 信息
  • runtime.Callers()
    查看 全部 调用层级 信息

runtime.Caller()

package main

import (
	"fmt"
	"runtime"
)

func main() {
	for i := 0; i < 6; i++ {
		aaa(i)
	}
}

func aaa(n int) {
	bbb(n)
}

func bbb(n int) {
	/*
		pc,file,line, ok := runtime.Caller(n)返回4个值:
		// 参数:
		n: 是要提升的堆栈帧数,0-当前函数,1-上一层函数: 就是说为0时,是runtime.Caller执行的位置,line会显示runtime的行号,为1时,会显示执行runtime这个函数上外包的函数的行号,以此类推。


		// 返回值:
		pc:是uintptr这个返回的是函数指针,
		    通过runtime.FuncForPC(pc).Name() 可以获取该函数的函数名

		file:是函数所在文件名目录
		line:所在行号
		ok:是否可以获取到信息
	*/
	pc, file, line, ok := runtime.Caller(n)
	funcName := runtime.FuncForPC(pc).Name()

	fmt.Printf("调用的函数名:%s 所在文件:%s 行:%d 该调用层级是否存在 %t \n",
		funcName, file, line, ok,
	)
}

=========== 输出:==============
调用的函数名:main.bbb 所在文件:/home/hero/Desktop/Golang/GOtest/main.go 行:33 该调用层级是否存在 true 
调用的函数名:main.aaa 所在文件:/home/hero/Desktop/Golang/GOtest/main.go 行:15 该调用层级是否存在 true 
调用的函数名:main.main 所在文件:/home/hero/Desktop/Golang/GOtest/main.go 行:10 该调用层级是否存在 true 
调用的函数名:runtime.main 所在文件:/usr/local/go/src/runtime/proc.go 行:250 该调用层级是否存在 true 
调用的函数名:runtime.goexit 所在文件:/usr/local/go/src/runtime/asm_amd64.s 行:1571 该调用层级是否存在 true 
调用的函数名: 所在文件: 行:0 该调用层级是否存在 false 

runtime.Callers()

// 文件目录
.
├── aaa_bbb
│   └── a.go
├── go.mod
├── go.sum
└── main.go

// ============== a.go 中的代码 ==============
package aaa_bbb

import (
	"fmt"
	"runtime"
)

func Aaa() {
	// 存储
	pcs := make([]uintptr, 100) // 函数 最多可查看的 调用 层数

	// 获取 所有的 调用者:
	// 0 从第一个 开始
	// 1 从 函数本身 开始
	// 2 从 上一级函数的调用 开始
	depth := runtime.Callers(0, pcs) // 返回 调用的 总层级数

	// 查看 所有的 调用者 的 具体信息
	frames := runtime.CallersFrames(pcs[:depth])

	for i := 0; i < depth; i++ {
		frame, more := frames.Next()
		fmt.Printf("所在文件%s:  行:%d  函数名:%s  是否还有上级:%t \n",
			frame.File, frame.Line, frame.Function, more)
	}
}



// ============== main.go 中的代码 ==============
package main

import "gotest/aaa_bbb"

func main() {
	aaa_bbb.Aaa()
}

=========== 输出:==============
所在文件/usr/local/go/src/runtime/extern.go:  行:235  函数名:runtime.Callers  是否还有上级:true 
所在文件/home/hero/Desktop/Golang/GOtest/aaa_bbb/a.go:  行:16  函数名:gotest/aaa_bbb.Aaa  是否还有上级:true 
所在文件/home/hero/Desktop/Golang/GOtest/main.go:  行:6  函数名:main.main  是否还有上级:true 
所在文件/usr/local/go/src/runtime/proc.go:  行:250  函数名:runtime.main  是否还有上级:true 
所在文件/usr/local/go/src/runtime/asm_amd64.s:  行:1571  函数名:runtime.goexit  是否还有上级:false 



^_^ os

程序操作

os.Exit(255) 		// 强制性 立刻!全部!退出Go程序(参数范围: 0--255)

系统操作

  • 环境变量
// 获取指定的环境变量
os.Getenv("GOROOT")
// 获取全部的环境变量
os.Environ()
// 清空所有环境变量(慎)
os.Clearenv()

目录操作

// 获取Go程序当前的工作目录
os.Getwd() 		

// 确定目录分割符号
os.IsPathSeparator('/')  // true  (Linux下: gap == "/")

// 设定工作目录,如果不手动设定,则默认为main.go文件所在的目录即为工作目录
os.Chdir("./aaa") // 这样设定以后,后面代码中的路径 ./ 就代表 原先 ./aaa/

// 创建 单级目录(参1:目录路径,参2:目录权限), 如果创建的目录存在,则报错
os.Mkdir("./abc", os.ModePerm) //  os.ModePerm == 0777
/*
	os.Mkdir("./config1", 0100) // d--x------
	os.Mkdir("./config2", 0200) // d-w-------
	os.Mkdir("./config3", 0400) // dr--------
	os.Mkdir("./config4", 0000) // d---------
*/

// 创建 多级目录,如果创建的目录存在,不报错
os.MkdirAll("./a/b/c", 0700)

// 修改文件权限
os.Chmod("./test", 0777)


// 删除 指定的文件  or  空的文件夹目录
os.Remove("./abc")

// 删除目录 (以及目录中的全部内容)
os.RemoveAll("./abc")

// 修改 目录 or 文件 名称
os.Rename("./aaa", "./bbb")

// 移动 单文件,类比mv命令
// 移动 单目录,则该目录下的所有 子文件 也会 以递归的方式 一同 移动到目标目录下
os.Rename("./aa.tx", "./bbb/aa.txt")

// 获取文件信息
fileInfo, err := os.Stat("./a.txt")
if err != nil {
	fmt.Println(err)
}
fmt.Println("文件名", fileInfo.Name())       // 文件名 a.txt
fmt.Println("文件大小", fileInfo.Size())      // 文件大小 99
fmt.Println("文件权限", fileInfo.Mode())      // 文件权限 -rw-rw-r--
fmt.Println("文件是否是目录", fileInfo.IsDir())  // 文件是否是目录 false
fmt.Println("文件修改时间", fileInfo.ModTime()) // 文件修改时间 2021-10-10 14:26:18.176703279 +0800 CST+0800 CST

操作文件

我们都知道在Linux中万物都被称为文件,文件处理是一个非常常见的问题,
操作一个文件离不开这几个动作:

  • 创建文件
  • 打开文件
  • 读取文件
  • 写入文件
  • 关闭文件
  • 打包/解包
  • 压缩/解压缩
  • 改变文件权限
  • 删除文件
  • 移动文件
  • 重命名文件
  • 清空文件

Go语言官方库:os、io/ioutil、bufio涵盖了文件操作的所有场景。

  • os
    os提供了对文件IO直接调用的方法
  • io/ioutil
    io/ioutil也提供对文件IO直接调用的方法,不过Go语言在Go1.16版本已经弃用了io/ioutil库, 因为io/ioutil包是一个定义不明确且难以理解的东西集合。该软件包提供的所有功能都已移至其他软件包,所以io/ioutil中操作文件的方法都在io库有相同含义的方法,大家以后在使用到ioutil中的方法是可以通过注释在其他包找到对应的方法
  • bufio
    bufio提供缓冲区操作文件的方法。

1.基础操作

这里我把以下操作 归为对文件的基本操作,对文件的基本操作直接使用os库中的方法即可

  • 创建文件
  • 关闭文件
  • 改变文件权限
import (
 "log"
 "os"
)
func main() {
 // 创建文件
 f, err := os.Create("asong.txt")

 // 获取文件信息
 fileInfo, err := f.Stat()

 log.Printf("File Name is %s\n", fileInfo.Name())
 log.Printf("File Permissions is %s\n", fileInfo.Mode())
 log.Printf("File ModTime is %s\n", fileInfo.ModTime())

 // 改变文件权限
 err = f.Chmod(0777)

 // 改变拥有者
 err = f.Chown(os.Getuid(), os.Getgid())

 // 再次获取文件信息 验证改变是否正确
 fileInfo, err = f.Stat()

 log.Printf("File change Permissions is %s\n", fileInfo.Mode())

 // 关闭文件
 err = f.Close()
 
 // 删除文件
 err = os.Remove("asong.txt")

}

2.打开文件

打开文件流 OpenFile(arr1, arr2, arr3)

参1: 文件路径

参2: 文件模式

O_RDONLY : 只读
O_WRONLY:只写
O_RDWR: 读写双操作

O_APPEND:写入的内容追加到原文件内容
O_TRUNC :写入的内容覆盖掉原文件内容

O_CREATE若文件不存在则创建,然后再获取这个文件; 若文件存在直接获取这个文件
O_EXCL: 文件必须存在, 只能 获取 已有的 文件##

O_SYNC同步的方式打开这个文件,意味着 不能 多个 协程同时 向这个文件 写入内容,只能一个一个的排队进行写入操作。同步方式打开的目的:防止多个协程写入的内容发生覆盖

参3: 文件权限

在linux系统下的 文件权限控制: chmod 666 a.txt, 在golang中是四位数字0666的前面的0表示文件,后面的3个数字才表示 文件控制权限。
在window下此参数无效可随便写个值 占位。

// 例1:
file,_ := os.OpenFile("./a.txt", os.O_RDWR | os.O_TRUNC | os.O_CREATE, 0666)

// 语法糖:
file,_ := os.Create("./a.txt")
// 例2:
file,_ := os.OpenFile("./a.txt", os.O_RDONLY, 0666)

// 语法糖:
file,_ := os.Open("./a.txt")

3.写操作

  • 按行写文件
    os、buffo写数据都没有提供按行写入的方法,所以我们可以在数据中 加入换行符 来实现,来看示例:
import (
 "bufio"
 "log"
 "os"
)
// ================== 直接操作IO ====================
func writeLine(filename string) error {
 data := []string{
  "asong",
  "test",
  "123",
 }

 f, err := os.OpenFile(filename, os.O_WRONLY, 0666)


 for _, line := range data{
  _,err := f.WriteString(line + "\n")

 }
 f.Close()

 return nil
}


// =================== 使用缓存区写入 ===================
func writeLine(filename string) error {
 file, err := os.OpenFile(filename, os.O_WRONLY, 0666)

 // 为这个文件创建 buffered writer
 bufferedWriter := bufio.NewWriter(file)
 
 for i:=0; i < 2; i++{
  // 写字符串到buffer
  bytesWritten, err := bufferedWriter.WriteString(
   "asong真帅\n",
  )

  log.Printf("Bytes written: %d\n", bytesWritten)
 }

 // 查看 缓存中 的 字节数
 unflushedBufferSize := bufferedWriterc.Bufferd()  
 log.Printf("Bytes buffered: %d\n", unflushedBufferSize)

 // 还有多少字节可用(未使用的缓存大小)
 bytesAvailable := bufferedWriter.Available()
 log.Printf("Available buffer: %d\n", bytesAvailable)

 // 将 内存buffer 冲刷到 硬盘中
 err = bufferedWriter.Flush()

 // 关闭文件
 file.Close()

 return nil
}
  • 其他写入api
// 创建 写入流缓存
bufferedWriter := bufio.NewWriter(file)

// 读取流 常用api
bufferedWriter.WriteByte('a')
bufferedWriter.WriteRune('你')
bufferedWriter.WriteString("写入的内容。\n")

4.读操作

  • 读取全文件

有两种方式我们可以读取全文件:
os 中提供了ReadFile方法可以快速读取全文
os/ioutil 中提供了ReadAll方法可以快速读取全文

import (
 "io/ioutil"
 "log"
 "os"
)
// ================= ReadFile ==================
func readAll(filename string) error {
 data, err := os.ReadFile(filename)

 log.Printf("read %s content is %s", filename, data)
 return nil
}


// ================= ReadAll ==================
func ReadAll2(filename string) error {
 file, err := os.Open("asong.txt")

 content, err := ioutil.ReadAll(file)
 log.Printf("read %s content is %s\n", filename, content)

 file.Close()
 return nil
}
  • 逐行读取
    bufio中提供了三种方法ReadLine、ReadBytes(“\n”)、ReadString(“\n”)可以按行读取数据,下面我使用 ReadBytes(“\n”)来写个例子:
func readLine(filename string) error {
	file, _ := os.OpenFile(filename, os.O_RDONLY, 0666)

	bufferedReader := bufio.NewReader(file)
	for {
		lineBytes, err := bufferedReader.ReadBytes('\n')

		line := strings.TrimSpace(string(lineBytes))
		if err != nil && err != io.EOF {
			return err
		}

		if err == io.EOF {
			log.Printf("readline %s every line data is %s\n", filename, line)
			break
		}

		log.Printf("readline %s every line data is %s\n", filename, line)
	}

	file.Close()
	return nil
}
  • 按块读取文件
    按照 字节长度 读取文件,可以用如下方法:
  • os库的Read方法
  • os库配合bufio.NewReader调用Read方法
  • os库配合io库的ReadFull、ReadAtLeast方法
// use bufio.NewReader(推荐) ===============================================
func readByte(filename string) error {
	file, err := os.OpenFile(filename, os.O_RDONLY, 0666)
	if err != nil {
		return err
	}
	// 创建 Reader
	r := bufio.NewReader(file)

	// 每次读取 2 个字节
	buf := make([]byte, 2)
	for {
		n, err := r.Read(buf)
		if err != nil && err != io.EOF {
			return err
		}

		if n == 0 {
			break
		}
		log.Printf("writeByte %s every read 2 byte is %s\n", filename, string(buf[:n]))
	}
	file.Close()
	return nil
}

// use os ===============================================
func readByte2(filename string) error {
	file, err := os.OpenFile(filename, os.O_RDONLY, 0666)
	if err != nil {
		return err
	}

	// 每次读取 2 个字节
	buf := make([]byte, 2)
	for {
		n, err := file.Read(buf)
		if err != nil && err != io.EOF {
			return err
		}

		if n == 0 {
			break
		}
		log.Printf("writeByte %s every read 2 byte is %s\n", filename, string(buf[:n]))
	}
	file.Close()
	return nil
}

// use os and io.ReadAtLeast ===============================================
func readByte3(filename string) error {
	file, err := os.OpenFile(filename, os.O_RDONLY, 0666)
	if err != nil {
		return err
	}

	// 每次读取 2 个字节
	buf := make([]byte, 2)
	for {
		n, err := io.ReadAtLeast(file, buf, 0)
		if err != nil && err != io.EOF {
			return err
		}

		if n == 0 {
			break
		}
		log.Printf("writeByte %s every read 2 byte is %s\n", filename, string(buf[:n]))
	}
	file.Close()
	return nil
}
  • 按分隔符读取
    bufio包中提供了Scanner扫描器模块,它的主要作用是把数据流分割成一个个标记并除去它们之间的空格,他支持我们定制Split函数做为分隔函数,分隔符可以不是一个简单的字节或者字符,我们可以自定义分隔函数,在分隔函数实现分隔规则以及指针移动多少,返回什么数据,如果没有定制Split函数,那么就会使用默认ScanLines作为分隔函数,也就是使用换行作为分隔符,bufio中还提供了默认方法ScanRunes、ScanWrods,下面我们用SacnWrods方法写个例子,获取用空格分隔的文本:
func readScanner(filename string) error {
	file, err := os.OpenFile(filename, os.O_RDONLY, 0666)
	if err != nil {
		return err
	}

	scanner := bufio.NewScanner(file)
	// 可以定制Split函数做分隔函数
	// ScanWords 是scanner自带的分隔函数用来找空格分隔的文本字
	scanner.Split(bufio.ScanWords)
	for {
		success := scanner.Scan()
		if success == false {
			// 出现错误或者EOF是返回Error
			err = scanner.Err()
			if err == nil {
				log.Println("Scan completed and reached EOF")
				break
			} else {
				return err
			}
		}
		// 得到数据,Bytes() 或者 Text()
		log.Printf("readScanner get data is %s", scanner.Text())
	}
	file.Close()
	return nil
}
  • 其他读取api
// 创建 读取流
reader := bufio.NewReader()

// 读取流 常用api
 res, err := reader.ReadByte()
 res, length, err := reader.ReadRune()
 str, err := reader.ReadString('\n')

// 例:一个字一个字读,直到结束
for {
	res, _, err := reader.ReadRune()
	fmt.Println(string(res))
	if err == io.EOF {
        fmt.Println(“文件读取完毕”)
		break
    }
}

// 例: 一句一句读,直到结束
for {
	res,err := reader.ReadString('\n')
	fmt.Println(res)
	if err == io.EOF {
        fmt.Println(“文件读取完毕”)
		break
    }
}

5.判断文件是否存在

go中判断 文件or文件夹 是否存在是通过 方法os.Stat() 返> 回的 错误值 判断的:

/1/、os.Stat() 返回的 错误值 为 nil,说明 文件 存在

/2/、os.Stat() 返回的 错误值 不为nil, 则使用 os.IsNotExist()方法 将这个错误 作为参数,判断这错误是不是 文件不存在 导致的错误,
若是返回为 true, 说明 是因为 不存在 而导致的。

/3/、若是返回为 false, 则说明是其他原因 导致 的 错误。

func isExist(filePath string) (bool, error,string) {
    _, err := os.Stat(path)
    if err == nil {
        return true, nil, “文件存在”
    } else {
        if os.IsNotExist(err) {
            return false, nil, “文件不存在”
        } else {
            return false, err, “包错原因不是因为文件不存在导致的”
        }
    }
}

6.文件复制

io.Copy(写入, 读出)

	resp, err := http.Get("https://www.baidu.com/")

	f, err := os.Create("body.txt")

	io.Copy(f, resp.Body)

	resp.Body.Close()
	f.Close()

7.打包/解包

Go语言的archive包中提供了tar、zip两种打包/解包方法,这里以zip的打包/解包为例子:

  • 解压
import (
 "archive/zip"
 "fmt"
 "io"
 "log"
 "os"
)

func main()  {
 // Open a zip archive for reading.
 r, err := zip.OpenReader("asong.zip")


 defer r.Close()
 // Iterate through the files in the archive,
 // printing some of their contents.
 for _, f := range r.File {
  fmt.Printf("Contents of %s:\n", f.Name)
  rc, err := f.Open()
  if err != nil {
   log.Fatal(err)
  }
  _, err = io.CopyN(os.Stdout, rc, 68)
  if err != nil {
   log.Fatal(err)
  }

  rc.Close()
 }
}
  • 压缩
func writerZip()  {
 // Create archive
 zipPath := "out.zip"
 zipFile, err := os.Create(zipPath)
 if err != nil {
  log.Fatal(err)
 }

 // Create a new zip archive.
 w := zip.NewWriter(zipFile)
 // Add some files to the archive.
 var files = []struct {
  Name, Body string
 }{
  {"asong.txt", "This archive contains some text files."},
  {"todo.txt", "Get animal handling licence.\nWrite more examples."},
 }
 for _, file := range files {
  f, err := w.Create(file.Name)
  if err != nil {
   log.Fatal(err)
  }
  _, err = f.Write([]byte(file.Body))
  if err != nil {
   log.Fatal(err)
  }
 }
 // Make sure to check the error on Close.
 err = w.Close()
 if err != nil {
  log.Fatal(err)
 }
}

os/exec 终端操作

Go语言调用Shell与可执行文件

常用api:
  • func Command(name string, arg ...string) *Cmd

返回一个*Cmd, 用于执行name指定的程序(携带arg参数)

  • func (c *Cmd) Run() error

执行Cmd中包含的命令,阻塞直到命令执行完成

  • func (c *Cmd) Start() error

执行Cmd中包含的命令,该方法立即返回,并不等待命令执行完成

  • func (c *Cmd) Wait() error

该方法会阻塞直到Cmd中的命令执行完成,但该命令必须是被Start方法开始执行的

  • func (c *Cmd) Output() ([]byte, error)

执行Cmd中包含的命令,并返回标准输出的切片

  • func (c *Cmd) CombinedOutput() ([]byte, error)

执行Cmd中包含的命令,并返回标准输出与标准错误合并后的切片

  • func (c *Cmd) StdinPipe() (io.WriteCloser, error)

返回一个管道,该管道会在Cmd中的命令被启动后连接到其标准输入

  • func (c *Cmd) StdoutPipe() (io.ReadCloser, error)

返回一个管道,该管道会在Cmd中的命令被启动后连接到其标准输出

  • func (c *Cmd) StderrPipe() (io.ReadCloser, error)

返回一个管道,该管道会在Cmd中的命令被启动后连接到其标准错误

栗子
  • 调用 命令
	// 执行 没有 返回数据到命令行 的命令 (操作类的)
	cmd := exec.Command("touch", "./aaa.txt")
	cmd.Run()

	// 执行 有 返回数据到命令行 的命令 (查询类的)
	cmd2 := exec.Command("ls", "-al")
	res, _ := cmd2.Output()
	fmt.Println(string(res)) 
  • 调用 可执行文件 a.sh
	command := `./a.sh` //前提:test.sh是可执行文件
	cmd := exec.Command("/bin/bash", "-c", command)
	output, _ := cmd.Output() //输出到标准流
	fmt.Println(string(output))

a.sh文件:

#!/bin/bash
rm aaa.txt
./abc  #还可以在这里 再调用别的可执行文件
ls

^_^ net

Go 标准库中 提供的 net包, 支持 基于IP层、TCP/UDP层 以及 更高层(如HTTP、FTP、SMTP)的 网络操作,
其中 用户 IP层的 称之为 Raw Socket。 该包 中 最重要的 两个函数:

  • net.Listen()
  • net.Dail()

HTTP

get请求
res, _ := http.Get("http://httpbin.org/get")

defer res.Body.Close()

content, _ := ioutil.ReadAll(res.Body)
fmt.Println(string(content))

// 发送 http://httpbin.org/get?name=tom;age=15 的带参数的get请求
req, err := http.NewRequest(http.MethodGet, "http://httpbin.org/put", nil)
	if err != nil {
		fmt.Println(err)
	}

	// get 的请求参数
	arr := make(url.Values)
	arr.Add("name", "tom")
	arr.Add("age", "15")

	req.URL.RawQuery = arr.Encode()

	// 发起请求
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		fmt.Println(err)
	}
	defer res.Body.Close()
	content, _ := ioutil.ReadAll(res.Body)
	fmt.Println(string(content))
post请求
res, _ := http.Post("http://localhost:8090/post", " ", nil)

defer res.Body.Close()

content, _ := ioutil.ReadAll(res.Body)
fmt.Println(string(content))
发送 json格式 的请求体数据
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
)

type man struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func main() {
	obj := man{"tom", 11}

	jsonObj, _ := json.Marshal(obj)
	fmt.Println(string(jsonObj))

	arr1 := "http://localhost:8090/post"
	arr2 := "application/json"
	arr3 := bytes.NewReader(jsonObj)

	res, _ := http.Post(arr1, arr2, arr3)

	defer res.Body.Close()
	content, _ := ioutil.ReadAll(res.Body)

	fmt.Printf("%s", content)
}
发送 表单格式 的请求体数据
package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
)

func main() {
	// 创建一个 盛放 表单数据 的map
	data := make(url.Values)
	// 加入 表单数据
	data.Add("name", "tom")
	data.Add("age", "18")
	formData := data.Encode()

	arr1 := "http://localhost:8090/post"
	arr2 := "application/x-www-form-urlencoded"
	arr3 := strings.NewReader(formData)
	res, _ := http.Post(arr1, arr2, arr3)

	defer res.Body.Close()
	content, _ := ioutil.ReadAll(res.Body)
	fmt.Printf("%s", content)
}
put请求

http包没有提供可以直接使用的 put和delete 请求方法,通过读取get请求的原码,仿照实现

// 创建一个新的请求
req, err := http.NewRequest(http.MethodPut, "http://localhost:8090/put", nil)
if err != nil {
	fmt.Println(err)
}
// 发起请求
res, err := http.DefaultClient.Do(req)
if err != nil {
	fmt.Println(err)
}
defer res.Body.Close()
content, _ := ioutil.ReadAll(res.Body)
fmt.Println(string(content))
设置请求头
	reqData := bytes.NewReader([]byte(`{"age":10}`))
	req, err := http.NewRequest(http.MethodPost, "http://****", reqData)
	if err != nil {
		return err
	}

	req.Header.Add("name", "wtt")
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}

	defer res.Body.Close()
	content, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return err
	}
	fmt.Println(string(content))
设置cookie
req, _ := http.NewRequest("GET", "http://localhost:8090/get", nil)

//添加cookie,key为 caller,value为 tom,可以添加多个cookie
cookie1 := &http.Cookie{Name: "caller", Value: "tom", HttpOnly: true}
req.AddCookie(cookie1)

req.Header.Add("name", "tom") // 顺便 再 设置一下 请求头

// resp, err := http.DefaultClient.Do(req)
// 若不使用 上面 默认的 请求客户端,也可以 自己生成一个
clientByWtt := &http.Client{}

res, err := clientByWtt.Do(req)
防止 发出的请求 被多次 重定向
package main

import (
	"errors"
	"fmt"
	"net/http"
)

func countRedirect(req *http.Request, via []*http.Request) error {
	if len(via) > 5 {
		return errors.New("重定向次数太多,老子不请求了")
	}
	return nil
}

func main() {

	request, err := http.NewRequest(http.MethodGet, "http://httpbin.org/absolute-redirect/10", nil)
	if err != nil {
		fmt.Println(err)
	}

	clientBySet := &http.Client{CheckRedirect: countRedirect}

	_, err = clientBySet.Do(request)

	if err != nil {
		panic(err)
	}
}
跳过 https证书验证
// 默认是开启 https证书验证的,所以不合法的是无法进行访问的
http.DefaultClient.Transport = &http.Transport{
	TLSClientConfig: &tls.Config{
		InsecureSkipVerify: true,
	},
}

res, _ := http.Get("http://httpbin.org/get")
defer res.Body.Close()
content, _ := ioutil.ReadAll(res.Body)
fmt.Println(string(content))
响应报文

res , _ := http.Get(“http://httpbin.org/get”)

响应体 (res.Body)
content, _ := ioutil.ReadAll(res.Body) //返回 字节切片
状态 (res.Status)
状态码 (res.StatusCode)
响应头 (res.Header)
fmt.Println(res.Header["Content-Type"])
获取 cookies
package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/cookiejar"
)

func main() {
	jar, _ := cookiejar.New(nil) //创建cookie容器
	client := &http.Client{
		Jar: jar, // 将容器 配置到 客户请求实例 中
	}
	// 客户请求实例 发起请求
	r, _ := client.Get("http://httpbin.org/cookies/set?name=tom&password=123")
	defer r.Body.Close()

	cookies, _ := ioutil.ReadAll(r.Body)
	fmt.Println(string(cookies))
}

练手:下载一张图片,要求看到实时下载的进度

type Reader struct {
	io.Reader
	Total   int64
	Current int64
}

func (r *Reader) Read(p []byte) (n int, err error) {
	n, err = r.Reader.Read(p)
	r.Current += int64(n)
	fmt.Printf("当前进度:%d%% \n", r.Current*100/r.Total)
	return
}

func main() {
	url := "https://user-gold-cdn.xitu.io/2019/6/30/16ba8cb6465a6418?w=826&h=782&f=png&s=279620"
	r, _ := http.Get(url)
	defer r.Body.Close()

	file, _ := os.Create("./bbb/bbb1.png")
	defer file.Close()

	reader := &Reader{
		Reader: r.Body,
		Total:  r.ContentLength,
	}
//reader 的 Read方法 在此内置调用了,所以 结构体的  Read的方法名是 固定的。
	n, err := io.Copy(file, reader) 	
fmt.Println(n, err) //279620(文件大小) <nil>
}

开启http服务

package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
)

func main() {
	http.HandleFunc("/", aaa)
	http.HandleFunc("/who", bbb)

	url := "localhost:9753"
	fmt.Println("监听:", url)
	// 服务监听
	err := http.ListenAndServe(url, nil)
	if err != nil {
		panic(err)
	}
}

func aaa(w http.ResponseWriter, r *http.Request) {
	io.WriteString(w, "hi")
}

// 路由处理函数
func bbb(w http.ResponseWriter, r *http.Request) {

	fmt.Println(r.Method) // POST
	fmt.Println(r.URL)    // who?aaa=bbbb
	fmt.Println(r.Header)

	defer r.Body.Close()
	res, _ := ioutil.ReadAll(r.Body) // 获取 post的json格式的请求体
	fmt.Println(string(res))

	resp := `{
		"name": "tom",
		"age": 11 
	}`
	io.WriteString(w, resp)
}

^_^ reflect

  • 测试变量用例
type man struct {
	caller string
	Age    int `json:"old"`
}

func (that man) ShowName() {
	fmt.Println(that.caller)
}

func (that man) showAge() {
	fmt.Println(that.Age)
}

func (that man) HowOld(inY int, test string) int {
	fmt.Println(test)
	fmt.Println(that.Age)
	return that.Age + inY
}

// 实例变量
tom := man{
	caller: "TOM",
	Age:    10,
}
  • 类型系统
T := reflect.TypeOf(tom)

T.Name() //string >>---> man

T.Kind() //reflect.Kind >>---> struct
T.Kind().String() //string >>---> struct

T.PkgPath() //string >>---> main
// 不论字段首字母是否大写,都会被统计
T.NumField() //int >>---> 2

// 字段的 索引 由 内部字段顺序 决定
T.Field(0) //reflect.StructField >>---> {Name:caller PkgPath:main Type:string Tag: Offset:0 Index:[0] Anonymous:false}

/* 
  嵌套字段 的 深层索引 查找方式:
  T.FieldByIndex([]int{1,0}) 
*/

T.FieldByName("caller") // 同上

T.Field(1).Tag.Get("json") // string >>---> old
// 只有首字母大写的方法,才会被统计
T.NumMethod() // int >>---> 2

// 方法索引,由 方法名的ASCII决定
T.Method(0) // reflect.Method >>---> {Name:HowOld PkgPath: Type:func(main.man, int, string) int Func:<func(main.man, int, string) int Value> Index:0}

T.MethodByName("HowOld") // 同上

T.Method(0).NumIn() //int >>---> 3 (that也算一个参数)
  • 值系统
V := reflect.ValueOf(&tom).Elem() //reflect.Value >>---> {caller:TOM Age:10}

V.Field(0) //reflect.Value >>---> TOM

V.FieldByName("Age") //reflect.Value >>---> 10

//反射赋值 只能改变 首字母大写的字段
V.FieldByName("Age").SetInt(int64(20))

V.FieldByName("Age").Int() //转为正射变量
// 无参调用
arr := []reflect.Value{}
V.Method(1).Call(arr)

// 有参调用
arr := []reflect.Value{
	reflect.ValueOf(10),
	reflect.ValueOf("aaa---test"),
}
V.Method(0).Call(arr)
  • struct —转—>map
func main() {
	obj := struct {
		name string 
		sex  string 
		age  int   
	}{
		"tom",
		"man",
		11,
	}

	m := make(map[string]interface{}, 10)

	v := reflect.ValueOf(&obj).Elem()
	t := reflect.TypeOf(obj)

	for i := 0; i < t.NumField(); i++ {
		switch t.Field(i).Type.String() {
		case "string":
			m[t.Field(i).Name] = v.Field(i).String()
		case "int":
			m[t.Field(i).Name] = v.Field(i).Int()

		default:
			continue
		}
	}
	fmt.Println(m)                      // map[age:11 name:tom sex:man]
	fmt.Println(m["age"].(int64) == 11)  //  true
}
  • map数据 通过tag 对应到 结构体
type aaa struct {
	Tag1 float64 `mp:"tag1"`
	Tag2 float64 `mp:"tag2"`
	Tag3 float64 `mp:"tag3"`
}

func main() {
	inData := map[string]float64{
		"tag1": 1,
		"tag2": 11,
		"tag3": 111,
	}
	obj := aaa{}
	V := reflect.ValueOf(&obj).Elem() //obj址类型
	T := reflect.TypeOf(obj) // obj值类型

	for k, v := range inData {
		for i := 0; i < T.NumField(); i++ {
			if T.Field(i).Tag.Get("mp") == k {
				V.Field(i).SetFloat(v)
			}
		}
	}
	fmt.Println(obj)
}

^_^ sort

切片元素排序

	s := []string{"name", "tom", "abc"}
	fmt.Println(s)  // [name tom abc]
	sort.Strings(s) // 按照字母进行排序
	fmt.Println(s)  // [abc name tom]

	i := []int{5, 1, 3}
	fmt.Println(i) //[5 1 3]
	sort.Ints(i)   // 按照数字进行排序
	fmt.Println(i) // [1 3 5]

	// sort.Float64s
  • 结构体切片 排序
	type User struct {
		index int // 排序字段
		// 其它需要字段 ...
		name string
	}
	users := []User{
		{1, "tom"},
		{0, "cat"},
		{5, "wtt"},
		{5, "wtt1"},
	}

	// 结构体切片 排序
	sort.SliceStable(users, func(i int, j int) bool {
		// return users[i].index < users[j].index // 由小到大
		return users[i].index > users[j].index // 由大到小
	})

	// 查看结果
	for _, val := range users {
		fmt.Println(val)
	}

^_^ flag

这个包的用途是接收命令行参数的。
其实os模块的os.Args,也带有这个功能,但是这个功能有点欠缺。
不能接收像-u root -p 3306这样指定key的值,只能接收像root 3306这样的方式。

flag可以接收以下几种类型:

  • bool
  • int系列(int,int64,uint,uint64)
  • float系列(float32,float64)
  • string
	// 第一个参数是命令行 -key,第二个参数是 默认值,第三个参数是 -h 提示
	var user = flag.String("user", "root", "用户名")
	var port = flag.Int("port", 3306, "端口")
	var ip = flag.String("ip", "localhost", "mysql ip")

	// 使用flag包,最后要使用flag.Parse()转换一下才能获取命令行参数。
	flag.Parse()

	// flag.Type返回的是一个 指针,必须通过 *变量取值
	fmt.Println(*user, *port, *ip)

/*
==================================================================
1.执行: go run main.go -h
输出:
Usage of /tmp/go-build3491633386/b001/exe/main:
  -ip string
        mysql ip (default "localhost")
  -port int
        端口 (default 3306)
  -user string
        用户名 (default "root")



2.执行:go run main.go -user tom -ip 100 -port 8080
输出:tom 8080 100
*/

^_^ 进程信号

func main() {

	sig := make(chan os.Signal)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGHUP)
	go func() {
		for s := range sig {
			switch s {
			case syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGHUP:
				fmt.Println("接收到 信号", s)
				fmt.Println("但是等3秒再退出")
				time.Sleep(time.Second * 3)
				if i, ok := s.(syscall.Signal); ok {
					os.Exit(int(i))
				} else {
					os.Exit(0)
				}
			}
		}
	}()

	time.Sleep(time.Hour)

}

^_^ rsa

package rsawtt

import (
	"bytes"
	"crypto"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/sha512"
	"crypto/x509"
	"encoding/base64"
	"encoding/pem"
	"fmt"
	"os"
	"strings"
)

type RsaClient struct {
	private *rsa.PrivateKey
	public  *rsa.PublicKey

	// 签名、验签的 算法, 默认采用sha1, 也可以是 sha256等
	AlgorithmType string
	// 注意说明:
	Help string
}

// privatePEMPath 私钥文件路径; publicPEMPath 公钥文件路径
func NewRsaClient(privatePEMPath, publicPEMPath string) (RsaClient, error) {
	// 初始化公钥 ==============================
	//打开文件
	file, err := os.Open(publicPEMPath)
	if err != nil {
		return RsaClient{}, err
	}
	defer file.Close()
	//读取文件的内容
	info, err := file.Stat()
	if err != nil {
		return RsaClient{}, err
	}

	buf := make([]byte, info.Size())
	file.Read(buf)

	publicKey, err := DecodePublicKey(buf)
	if err != nil {
		return RsaClient{}, err
	}

	// 初始化私钥 ==============================
	//打开文件
	file2, err := os.Open(privatePEMPath)
	if err != nil {
		return RsaClient{}, err
	}
	defer file2.Close()
	//获取文件内容
	info2, err := file2.Stat()
	if err != nil {
		return RsaClient{}, err
	}

	buf2 := make([]byte, info2.Size())
	file2.Read(buf2)

	privateKey, err := DecodePrivateKey(buf2)
	if err != nil {
		return RsaClient{}, err
	}

	return RsaClient{
		public:        publicKey,
		private:       privateKey,
		AlgorithmType: "sha1",
		Help: `
			1、同一个 rsa私钥 对 同一明文 进行 签名加密 得到的结果是一样的
			2、同一个 rsa公钥 对 同一明文 进行 加密处理 得到的结果是不一样的, 这是因为:对数据的padding即填充有关
			3、不论使用pkcs1 或 pkcs8 的密钥对,只要能正常解析出来, 加密 和 签名 得到的 密文都是一样的
		`,
	}, nil

}

// 自检函数, 测试 公私钥是否为一对。
func (r RsaClient) TestAllFuncs() {
	var text = "hello world"
	fmt.Println("待加密、签名的 明文:", text)

	res, _ := r.RSA_Encrypt([]byte(text))
	fmt.Println("加密之后的密文:", res)

	res3, _ := r.RSA_Decrypt(res)
	fmt.Println("解密之后的明文:", res3)

	res4, _ := r.SignatureRSA(text)
	fmt.Println("签名之后的 电子签名:", res4)
	res5, _ := r.VerifySignatureRSA(text, res4)
	if res5 {
		fmt.Println("签名验证 通过:", res5)

	} else {
		fmt.Println("签名验证不通过:", res5)
	}
}

//RSA加密
/*
	在 RSA 加密中,使用公钥加密时,常用的填充方式有以下几种:
	1、PKCS#1 v1.5 填充方式:
		这是 RSA 加密中最常用的填充方式之一,也是 Go 语言中 rsa.EncryptPKCS1v15 方法使用的填充方式。
		它的具体实现方式可以参考 RSA 标准文档中的描述。

	2、OAEP 填充方式:
		这是一种更加安全的填充方式,它可以提供更好的安全性和抗攻击性。
		在 Go 语言中,可以使用 rsa.EncryptOAEP 方法进行加密,它支持 OAEP 填充方式。

	3、NoPadding 填充方式:
		这是一种不进行填充的方式,它要求明文数据的长度必须和 RSA 密钥的长度相同。
		在 Go 语言中,可以使用 rsa.EncryptPKCS1v15 方法进行加密,但需要注意填充的长度不能超过 RSA 密钥的长度减去 11 个字节。
*/
func (r RsaClient) RSA_Encrypt(plainText []byte) (string, error) {
	//对明文进行加密返回密文, 使用 PKCS#1 v1.5 的填充方式
	cipherText, err := rsa.EncryptPKCS1v15(rand.Reader, r.public, plainText)
	if err != nil {
		return "", err
	}
	// base64 处理 密文
	cipherTextBase64 := base64.StdEncoding.EncodeToString(cipherText)
	return cipherTextBase64, nil
}

//RSA解密
func (r RsaClient) RSA_Decrypt(cipherTextBase64 string) (string, error) {

	// 解析 base64 处理后的 密文
	cipherText, err := base64.StdEncoding.DecodeString(cipherTextBase64)
	if err != nil {
		return "", err
	}

	//对密文进行解密
	plainText, err := rsa.DecryptPKCS1v15(rand.Reader, r.private, cipherText)
	if err != nil {
		return "", err
	}

	//返回明文
	return string(plainText), nil
}

// rsa签名
func (r RsaClient) SignatureRSA(plainTextString string) (string, error) {
	plainText := []byte(plainTextString)

	switch r.AlgorithmType {
	case "sha512":
		/*
			使用 sha512算法对数据进行签名
		*/
		// 创建一个hash对象
		myhash := sha512.New()
		// 给hash对象添加数据
		myhash.Write(plainText)
		// 计算hash值
		hashText := myhash.Sum(nil)
		signText, err := rsa.SignPKCS1v15(rand.Reader, r.private, crypto.SHA512, hashText)
		if err != nil {
			return "", err
		}

		signTextBase64 := base64.StdEncoding.EncodeToString(signText)
		return signTextBase64, nil

	case "sha256":
		/*
			使用 sha256 算法对数据进行签名
		*/
		myhash := sha256.New()
		myhash.Write(plainText)
		hashText := myhash.Sum(nil)

		// 使用rsa函数对散列值签名
		signText, err := rsa.SignPKCS1v15(rand.Reader, r.private, crypto.SHA256, hashText)
		if err != nil {
			return "", err
		}

		signTextBase64 := base64.StdEncoding.EncodeToString(signText)
		return signTextBase64, nil

	case "sha1":
		/*
			通过使用 sha1 算法对数据进行签名
		*/
		// 使用SHA-1算法对数据进行签名
		h := sha1.New()
		h.Write(plainText)
		hashed := h.Sum(nil)

		signText, err := rsa.SignPKCS1v15(rand.Reader, r.private, crypto.SHA1, hashed)
		if err != nil {
			return "", err
		}

		signTextBase64 := base64.StdEncoding.EncodeToString(signText)
		base64 := strings.ReplaceAll(signTextBase64, "\n", "")
		base64 = strings.ReplaceAll(base64, "\r", "")
		return base64, nil
	}

	return "", nil
}

// rsa验证签名
func (r RsaClient) VerifySignatureRSA(plainText, signTextBase64 string) (bool, error) {
	signText, err := base64.StdEncoding.DecodeString(signTextBase64)
	if err != nil {
		return false, err
	}

	switch r.AlgorithmType {
	case "sha512":
		//  对原始明文进行hash运算得到散列值
		hashText := sha512.Sum512([]byte(plainText))
		//  签名认证
		err = rsa.VerifyPKCS1v15(r.public, crypto.SHA512, hashText[:], signText)
		return err == nil, err

	case "sha256":
		// 计算消息的 SHA256 哈希值
		h := sha256.New()
		h.Write([]byte(plainText))
		hashText := h.Sum(nil)

		//  签名认证
		err = rsa.VerifyPKCS1v15(r.public, crypto.SHA256, hashText[:], signText)
		return err == nil, err

	case "sha1":
		// 计算消息的 SHA1 哈希值
		h := sha1.New()
		h.Write([]byte(plainText))
		hashed := h.Sum(nil)

		// 验证签名
		err = rsa.VerifyPKCS1v15(r.public, crypto.SHA1, hashed, signText)
		if err != nil {
			return false, err
		}

		return true, nil
	}
	return false, nil

}

// 生成RSA私钥和公钥,保存到文件中,调用参数一般用2048:  GenerateRSAKey(2048), 也可以是 1024、3096
func GenerateRSAKey(bits int) error {
	//GenerateKey函数使用随机数据生成器random生成一对具有指定字位数的RSA密钥
	//Reader是一个全局、共享的密码用强随机数生成器
	privateKey, err := rsa.GenerateKey(rand.Reader, bits)
	if err != nil {
		return err
	}
	//保存私钥
	//通过x509标准将得到的ras私钥序列化为ASN.1 的 DER编码字符串
	X509PrivateKey := x509.MarshalPKCS1PrivateKey(privateKey)
	//使用pem格式对x509输出的内容进行编码
	//创建文件保存私钥
	privateFile, err := os.Create("private.pem")
	if err != nil {
		return err
	}
	defer privateFile.Close()
	//构建一个pem.Block结构体对象
	privateBlock := pem.Block{Type: "RSA Private Key", Bytes: X509PrivateKey}
	//将数据保存到文件
	pem.Encode(privateFile, &privateBlock)

	//保存公钥
	//获取公钥的数据
	publicKey := privateKey.PublicKey
	//X509对公钥编码
	X509PublicKey, err := x509.MarshalPKIXPublicKey(&publicKey)
	if err != nil {
		return err
	}
	//pem格式编码
	//创建用于保存公钥的文件
	publicFile, err := os.Create("public.pem")
	if err != nil {
		return err
	}
	defer publicFile.Close()
	//创建一个pem.Block结构体对象
	publicBlock := pem.Block{Type: "RSA Public Key", Bytes: X509PublicKey}
	//保存到文件
	pem.Encode(publicFile, &publicBlock)

	return nil
}

// 加载公钥
func DecodePublicKey(pemContent []byte) (publicKey *rsa.PublicKey, err error) {
	block, _ := pem.Decode(pemContent)
	if block == nil {
		return nil, fmt.Errorf("pem.Decode(%s):pemContent decode error", pemContent)
	}

	pubKey, err := x509.ParsePKCS1PublicKey(block.Bytes)
	if err != nil {
		// 在go中有 x509.ParsePKCS1PublicKey ,但是没有 x509.ParsePKCS8PublicKey 这种直接解析 pkcs8格式rsa公钥的函数,
		// 如果要解析pkcs8格式的公钥,可以使用 x509.ParsePKIXPublicKey 方法。
		pub, err := x509.ParsePKIXPublicKey(block.Bytes)
		if err != nil {
			return nil, fmt.Errorf("x509.ParsePKIXPublicKey(%s),err:%w", pemContent, err)
		}
		pubKey, ok := pub.(*rsa.PublicKey)
		if !ok {
			return nil, fmt.Errorf("公钥解析出错 [%s]", pemContent)
		}
		fmt.Println(" ===> pkcs8 格式的公钥 <=== ")
		publicKey = pubKey

	} else {
		fmt.Println(" ===> pkcs1 格式的公钥 <=== ")
		publicKey = pubKey
	}
	return publicKey, nil
}

// 加载私钥
func DecodePrivateKey(pemContent []byte) (privateKey *rsa.PrivateKey, err error) {
	block, _ := pem.Decode(pemContent)
	if block == nil {
		return nil, fmt.Errorf("pem.Decode(%s):pemContent decode error", pemContent)
	}
	privateKey, err = x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		pk8, err := x509.ParsePKCS8PrivateKey(block.Bytes)
		if err != nil {
			return nil, fmt.Errorf("私钥解析出错 [%s]", pemContent)
		}
		var ok bool
		privateKey, ok = pk8.(*rsa.PrivateKey)
		if !ok {
			return nil, fmt.Errorf("私钥解析出错 [%s]", pemContent)
		}
		fmt.Println(" ===> pkcs8 格式的私钥 <=== ")
	} else {
		fmt.Println(" ===> pkcs1 格式的私钥 <=== ")
	}
	return privateKey, nil
}

// RSA分段加密
/*
	RSA 公钥加密的字符串长度最大取决于密钥的长度。
	假设使用的是 2048 位的 RSA 公钥,那么 ==> 最大 <== 加密字符串长度为 2048 / 8 - 11 = 245 个字节,
	所以 分段加密 是很有必要的。
*/
func (r RsaClient) RSA_EncryptSection(plainText []byte) (bytesEncrypt []byte, err error) {

	keySize, srcSize := r.public.Size(), len(plainText)
	fmt.Println("密钥长度", keySize, "明文长度", srcSize)

	offSet, once := 0, keySize-11
	buffer := bytes.Buffer{}
	for offSet < srcSize {
		endIndex := offSet + once
		if endIndex > srcSize {
			endIndex = srcSize
		}
		// 加密一部分
		bytesOnce, err := rsa.EncryptPKCS1v15(rand.Reader, r.public, plainText[offSet:endIndex])
		if err != nil {
			return nil, err
		}
		buffer.Write(bytesOnce)
		offSet = endIndex
	}
	bytesEncrypt = buffer.Bytes()
	return
}

// RSA分段解密
func (r RsaClient) RSA_DecryptSection(cipherText []byte) ([]byte, error) {

	keySize, srcSize := r.private.Size(), len(cipherText)
	fmt.Println("密钥长度", keySize, "密文长度", srcSize)

	var offSet = 0
	var buffer = bytes.Buffer{}
	for offSet < srcSize {
		endIndex := offSet + keySize
		if endIndex > srcSize {
			endIndex = srcSize
		}
		bytesOnce, err := rsa.DecryptPKCS1v15(rand.Reader, r.private, cipherText[offSet:endIndex])
		if err != nil {
			return nil, err
		}
		buffer.Write(bytesOnce)
		offSet = endIndex
	}
	bytesDecrypt := buffer.Bytes()
	return bytesDecrypt, nil

}

// 分段加密测试
func (r RsaClient) TestFuncsSection() {
	var text = "hello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello worldhello world"
	fmt.Println("待加密、签名的 明文:", text)

	res, err := r.RSA_EncryptSection([]byte(text))
	if err != nil {
		fmt.Println(err)
		return
	}
	resBase64 := base64.StdEncoding.EncodeToString(res)
	fmt.Println("分段加密的密文:", resBase64)

	res2, err := r.RSA_DecryptSection(res)
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("分段解密得到的明文:", string(res2))
}

^_^ base64

Go中的系统库中提供了encoding/base64编码/解码的内置支持.
encoding/base64提供了四种模式的编码/解码

  • StdEncoding:常规编码 (常用)
  • URLEncoding:URL safe 编码
  • RawStdEncoding:常规编码,末尾不补 =
  • RawURLEncoding:URL safe 编码,末尾不补 =

其中,URL safe 编码,相当于是替换掉字符串中的特殊字符,+ 和 /。

package main

import (
    "encoding/base64"
    "fmt"
)

func main() {
    msg := []byte("Hello world. 你好,世界!")

    encoded := base64.StdEncoding.EncodeToString(msg)
    fmt.Println(encoded)
    // SGVsbG8gd29ybGQuIOS9oOWlve+8jOS4lueVjO+8gQ==

    decoded, _ := base64.StdEncoding.DecodeString(encoded)
    fmt.Println(string(decoded))
    // Hello world. 你好,世界!



    encoded = base64.RawStdEncoding.EncodeToString(msg)
    fmt.Println(encoded)
    // SGVsbG8gd29ybGQuIOS9oOWlve+8jOS4lueVjO+8gQ

    decoded, _ = base64.RawStdEncoding.DecodeString(encoded)
    fmt.Println(string(decoded))
    // Hello world. 你好,世界!



    encoded = base64.URLEncoding.EncodeToString(msg)
    fmt.Println(encoded)
    // SGVsbG8gd29ybGQuIOS9oOWlve-8jOS4lueVjO-8gQ==

    decoded, _ = base64.URLEncoding.DecodeString(encoded)
    fmt.Println(string(decoded))
    // Hello world. 你好,世界!



    encoded = base64.RawURLEncoding.EncodeToString(msg)
    fmt.Println(encoded)
    // SGVsbG8gd29ybGQuIOS9oOWlve-8jOS4lueVjO-8gQ

    decoded, _ = base64.RawURLEncoding.DecodeString(encoded)
    fmt.Println(string(decoded))
    // Hello world. 你好,世界!
}

^_^ 其他

进度条

package main

import (
	"fmt"
	"time"
)

func main() {
	var bar Bar
	totalItems := 120
	bar.NewOption(0, int64(totalItems))
	// bar.NewOptionWithGraph(0, 100, "★")
	// bar.NewOptionWithGraph(0, 100, "☂")
	for i := 0; i <= totalItems; i++ {
		time.Sleep(100 * time.Millisecond)
		bar.Play(int64(i))
	}
	bar.Finish()
}

type Bar struct {
	// 属性首字母小写,说明以下属性 是方法的 记录状态, 只有方法可以操作
	percent int64  //百分比
	cur     int64  //当前进度位置
	total   int64  //总进度
	rate    string //进度条
	graph   string //显示符号
}

// 初始化 进度条
func (bar *Bar) NewOption(start, total int64) {
	bar.cur = start
	bar.total = total
	if bar.graph == "" {
		bar.graph = "█" // 默认进度符号
	}
	bar.percent = bar.getPercent()
	for i := 0; i < int(bar.percent); i += 2 {
		bar.rate += bar.graph //初始化进度条位置
	}
}

func (bar *Bar) getPercent() int64 {
	return int64(float32(bar.cur) / float32(bar.total) * 100)
}

// 自定义 进度符号 的 初始化进度条
func (bar *Bar) NewOptionWithGraph(start, total int64, graph string) {
	bar.graph = graph
	bar.NewOption(start, total)
}

// 展示 进度条
func (bar *Bar) Play(cur int64) {
	bar.cur = cur
	last := bar.percent
	bar.percent = bar.getPercent()
	if bar.percent != last && bar.percent%2 == 0 {
		bar.rate += bar.graph
	}
	// \r 是 进度符号 叠加显示 的 核心实现
	fmt.Printf("\r[%-50s]%3d%%  %8d/%d", bar.rate, bar.percent, bar.cur, bar.total)
}

// 进度条完成后 给一个 换行
func (bar *Bar) Finish() {
	fmt.Println()
}

输出效果

[██████████████████████████████████████████████████]100%       110/110
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值