go基础
取地址符#
取地址符:获取某个对象实例的内存地址。假定变量a的类型是T,那么,&a返回的结果类型为 *T(T类型的指针)。
var p *T = &a
变量p保存了 a 的内存地址。
&符号获取变量的内存地址。
取地址运算符常用于函数的参数传递。
代码块#
代码块:Go语言中的代码块是包裹在一对大括号内部的声明和语句序列。空代码块是一对大括号内部没有任何声
明或其他语句。每个源码文件是一个代码块,每个函数也是一个代码块,每个if语句、for语句、switch语句和
select语句都是一个代码块。甚至switch或select语句中的case子句也都是独立的代码块。go代码块支持嵌套。
// 显示代码块
func Foo() {
// 这里是显示代码块,包裹在函数的函数体内
}
// 隐式代码块
所有Go源码都在该隐式代码块中,就相当于所有Go代码的最外层都存在一对大括号。
在函数内部声明的类型标识符的作用域范围始于类型定义中的标识符,止于其最里面的那个包含块的末尾。
func Foo() { // 显示代码块开始
{ // 代码块1
type bar struct {} // 类型标识符bar作用域始于此
{ // 代码块2
a := 7 // a的作用域始于此
{
//...
}
// a的作用域止于此
}
// 类型标识符bar的作用域止于此
}
}// 显示代码块结束
Duration类型#
type Duration int64
Duration表示的是时间段---两个时间点之间的差。
Duration类型以纳秒为单位,实际应用中通过以下常量来简单换算
const (
Nanosecond Duration = 1
Microsecond = 1000 * Nanosecond
Millisecond = 1000 * Microsecond
Second = 1000 * Millisecond
Minute = 1000 * Second
Hour = 1000 * Minute
)
eg:
a := 25 * time.Second
b := 7 * time.Hour
Duration类型还有用于获取时间段内各种计数方式
1、Hours方法:获取总小时数
2、Minutes方法:获取总分钟数
3、Seconds方法:获取总秒数
4、MilliSecond方法、Microsecond方法、Nanoseconds方法
eg:
var dr = 24 * time.Hour
fmt.Printf("共有 %d 分钟",int64(dr.Minutes()))
Time结构体用来表示时间实例,精度为纳秒。
Now:获取当前的系统时间,返回Time实例。
Date:通过向函数传入参数来初始化Time实例。
// 获取系统的当前时间
var ct = time.Now()
// 获取时间实例各部分的值 年月日时分秒
var (
year = ct.Year()
month = ct.Month()
day = ct.Day()
hour = ct.Hour()
minute = ct.Minute()
second = ct.Second()
)
调用Date可以同时获取年月日的值,调用Clock方法可以同时获取时分秒的值
var now = time.Now()
year,month,day := now.Date()
hour,minute,second := now.Clock()
Time类型支持时间差运算(例如加、减)
// 初始化Time对象
var theTime = time.Date(2015,12,3,0,0,0,0,time.Local)
// 30小时之后的时间点
var newTime1 = theTime.Add(30 * time.Hour)
// 4天之前
var newTime2 = theTime.Add(-4 * 24 * time.Hour)
// Add方法接收Duration类型的参数。
Timer类型#
Timer是一种特殊的计时器,当指定的时间到期后,会将当前时间发送到其C字段中。
C字段是只读的通道类型(<-chan Time),其他协程将通过C字段接收Time实例(计时器过期时所设置的时间)。
Time类型的典型用户是在异步编程中处理操作超时行为。
C字段可视为单个事件的信号灯,所有等待信号的协程会被阻止,直到从C字段中督导Time实例为止。
func main() {
// 创建新的 Timer实例
var timer = time.NewTimer(5 * time.Second)
// 创建一个通道实例,用于标识任务已经完成
var completed = make(chan bool)
// 退出main函数时关闭通道
defer close(completed)
// 在新的协程上执行任务
go func(){
// 随机生成执行任务所需的时间
// 作用是模拟任务所消耗的时间
rand.Seed(time.Now().Unix())
var thelong = rand.Intn(10)
// 暂停当前协程
time.Sleep(time.Duration(thelong) * time.Second)
// 发送信号,表示完成任务
completed <- true
}()
//判断任务是完成还是超时
select {
case <- completed:
fmt.Println("任务完成")
case <- timer.C:
fmt.Println("任务超时")
}
}
调用 NewTimer 函数可以创建新的 Timer实例,其参数接收Duration 类型的值,表示Timer的过期时间。
select语句中的每个case子句被执行的概率相等。
如果completed通道线接收到信息,说明另一个协程中的任务已完成。
如果timer.C先收到信息,说明timer对象所设定的时间已过期,此时completed通道没有收到信息,表明另一个协程上的任务未完成,即任务超时。
new 函数为指定的类型分配内存空间,并使用类型的默认值进行初始化,最后返回新分配内存空间的地址。
匿名函数定义且立刻调用的方案还有一个典型的应用场景
func main() {
// 创建新的通道对象
var ch = make(chan byte)
go func(){
fmt.Println("新协程")
// 向通道对象发送数据
ch <- 1
}()
// 从通道对象接收数据
<- ch
fmt.Println("主协程")
}
在启动新协程后,主协程代码执行到<-ch 这一行,此时通道对象中没有数据,代码会一直处于等待状态。当新协程向通道对象发送数据后,主协程的代码顺利几首到数据后会恢复执行。这样主协程会等待新协程执行完后再继续。
基于类型构建的swtich语句#
switch语句还可以用变量的类型作为参考表达式,只要某个case子句所指定的类型与表达式所返回的类型相同,该case子句所对应的分支代码就会执行。
表达式使用了类型断言(type assertion)的格式,例如:
var x interface{} = "hello"
actvalue := x.(string)
x.(string)表达式完成类型断言,并把变量x所引用的值转换为string类型,赋值给 actvalue变量,虽然在运行阶段变量 x 和actvalue的值相同,但它们的数据类型不同。
var x interface{} = ...
var actvalue string = ...
而在基于类型的switch语句中,要求使用关键字 type 来替换具体的类型,并且作为参考表达式的变量
必须声明为接口类型。例如:
var t interface{} = 0.0000012
switch t.(type) {
case int:
fmt.Println("变量 t 是 int 类型")
case string:
fmt.Println("变量 t 是 string 类型")
...
default:
fmt.Println("变量 t 是 未知类型")
}
switch fallthrough语句:如果在某个case子句代码的最后出现fallthrough语句,那么紧跟在case子句后的代码就会被执行。
结构体初始化#
结构体初始化分为两大类---默认初始化和手动初始化。
当使用var关键字声明结构体类型的变量后,程序会为该结构体的各个字段分配默认值。
//为字段分配默认值
var x filestruct
var x = filestruct{}
x := filestruct{}
注意如果声明的变量是指针类型,那么变量的默认值是nil。此时若直接访问结构体的字段会引发错误,因为指针未引用任何对象,即空指针。
var px *filestruct // nil
fmt.Printf("文件名:%s\n",px.name) // 错误
如果变量的类型声明为指针类型,那么可以先创建结构体实例并完成初始化,然后用取地址运算符(&)获取其内存地址,再赋值给指针变量。
常用变量的区别#
*[n]T 与 [n]*T的区别:
*[n]T 指针类型,存放类型为[n]T的实例内存。
[n]*T 数组类型,其元素类型为指向数值的指针类型。
Print、Println、Printf
将文本信息输出到标准输出流,一般是输出到屏幕上
Fprint、Fprintln、Fprintf
将文本输出到文件,或者实现了io.Writer接口的对象
Sprint、Sprintln、Sprintf
生成文本信息,仅以字符形式返回给调用者,无输出
%t 和 %T的区别
%t表示布尔值的字符串形式 %T表示对象所属的类型名称。
控制浮点数的精度
%.nf n表示要设置的精度,精度前的小数点不能省略。%.3f表示设置精度为3
%<宽度>.<精度>f 输出的字符宽度和浮点数精度可以同时使用
参数索引 从1开始
fmt.Printf("%[2]s:%[1]d",120,"item") %[2]s使用第二个参数的值,%[1]d使用120.
Scan、Scanln、Scanf
从标准输入流中读取信息,通常是键盘输入
Fscan、Fscanln、Fscanf
从文件或者实现 io.Reader接口的流中读取信息
strings包:#
连接字符串:
"+" 或者 strings.Join([]string{...},"#")
替换字符串:
Replace(s string ,old string, new string, n int) string
n设置为0 表示替换次数不受限制
拆分字符串:
Split:依据分隔符拆分字符串,并去掉分隔符
SplitAfter:依据分隔符拆分字符串,拆分点在分隔符之后,并且保留分隔符
SplitN:依据分隔符拆分字符串,并去掉分隔符。此函数可以指定拆分后字符串实例的数量(即子串的个数)。
如果已拆分的字符串实例个数已达到指定的数量,那么余下的部分将不再拆分。
SplitAfterN:依据分隔符拆分字符串,保留分隔符,并且可以指定拆分结果的数量,情况与SplitN函数类似。
查找字符串:
查找字符串有多中方式,大致有:
1、包含。指定的子字符串是否存在于原字符串中。Contains、ContainsRune函数。或者子字符串是否出现在
原字符串的首部或尾部,即前缀/后缀,如HasPrefix、HasSuffix函数。
2、索引。查找子字符串在原字符串中的位置。Index,LastIndex函数。
3、统计。计算子字符串在原字符串中出现的次数,Count函数。
HasPrefix(s,prefix string)bool HasSuffix(s,suffix string)bool
Index函数返回子字符串第一次出现的索引,LastIndex返回子字符串最后一次出现的索引。如果原字符串中
找不到子字符串,就返回-1。
Index/LastIndex函数返回的索引是字符串中字节位置,而不是字符的位置。需要进行处理:
func runeIndexOf(src string,byteIndex int) int {
if byteIndex <= 0 {
return byteIndex
}
theBuffer := []byte(src)[:byteIndex]
// 再转为字符串
str := string(theBuffer)
// 转为 rune 切片,把字符串打散成由单个字符组成的切片
rs := []rune(str)
// 返回字符个数
return len(rs)
}
修剪字符串:
去除前缀和后缀:TrimPrefix、TrimSuffix
去除字符串首尾的空格:TrimeSpace
修剪指定的字符:
Trim:在字符串的首部和尾部同时修剪。
TrimLeft:只在原字符串的首部修剪指定的字符。
TrimRight:在原字符串的尾部修剪指定的字符。
重复字符串:
Repeat函数的功能将输入字符串重复指定的次数,组成新的字符串实例并返回。
字符串转为整数值:ParseInt ParseUnit
字符串转为浮点数:ParseFloat
切换大小写:ToUpper ToLower
数学函数:#
Abs函数接收一个float64类型参数并返回它的绝对值。
Max和Min返回两个浮点数的最大值和最小值。
GO语言的类型转换格式:<目标类型>(x) int8(x)
开平方:使用Sqrt函数 开立方:使用Cbrt函数
大型数值:使用math/big包,用于大型整数值和浮点数值。
res := new(big.Int)
res.Add(bigInt1,bigInt2)
生成随机浮点数:math.rand包 产生0到1的浮点数
rand.Float32() rand.Float64()
生成随机整数
Int:返回值为int类型,范围0到int类型的最大正数值 rand.Int()
Int31:返回值int32类型。
Int63:返回值int64类型。
Intn:返回值为int类型。参数n限制产生随机数的最大值(不包含)。若n<=0,则会发生错误。rand.Intn(100)
在相同的平台/操作系统上,已相同的算法、相同的种子所产生的随机数序列是完全相同的。
设置随机种子:
// 获取时间戳
timestamp := time.Now().Unix()
// 设置随机数种子
rand.Seed(timestamp)
//产生随机数
for x := 0; x < 5; x++{
num := rand.int()
fmt.Println(num)
}
生成随机全排列:
rand.Perm(n) 可以生成一个包含n个元素的整数切片实例,其中每个元素范围[0,n)
rand.Perm(3) 生成3数,0,1,2 随机排列
洗牌程序:
Shuffle函数功能是随机产生两个索引值,可以用于交换集合(如切片)中的两个元素,即洗牌程序。
Shuffle函数的签名如下:
func Shuffle(n int,swap func(i, j int))
参数n指定原集合中的元素个数。参数swap是函数类型,该函数要求有两个int类型的输入参数,表示被随机交换顺序的两个元素索引。
eg:
var list = []int{1,2,3,4,5,6,7}
fmt.Printf("原顺序:\n%v\n\n",list)
// 用于交换元素顺序的函数
var swap = func(i,j int) {
list[i],list[j] = list[j],list[i]
}
// 开始洗牌
var ln = len(list)
// 第一轮
rand.Shuffle(ln, swap)
fmt.Printf("第一轮:\n%v\n\n",list)
// 第二轮
rand.Shuffle(ln, swap)
fmt.Printf("第二轮:\n%v\n\n",list)
生成随机字节序列:
使用Rand函数可以生成随机字节序列,其长度取决于传递给函数的切片实例的长度。字节byte代表的是一个ASCII码的一个字符(打印出的是数字)
rune类型可以写中文
var key []byte = make([]byte,64)
rand.Read(key)
// 十六进制输出
fmt.Printf("%x",key)
排序:#
在sort包中,封装了若干函数,可以直接用于对切片对象进行排序。支持的切片类型有:
[]int、[]float64和[]string
这些函数分别是:
Ints:对整数类型的切片对象进行排序。可以使用IntsAreSorted函数检验是否已进行过排序。
Float64s:对浮点数类型的切片对象进行排序。可以使用Float64sAreSorted函数检验是否已进行过排序。
Strings:对字符串类型的切片对象进行排序。可以使用StringsAreSorted函数验证是否已进行过排序。
以上各函数都是递增排序---从小到大进行排序。
eg: var intNms = []int{780,-5,20,375}
sort.Ints(intNums) // [-5 20 375 780]
var str = []string{"zero","bus","as"}
sort.Strings(str) // [as bus zero]
实现递减排序:
sort.Slice函数可以通过less参数所引用的自定义函数来实现排序。
此排序方案具有一定灵活性--不仅可以实现默认的递增排序,也可以实现递减排序。
Slice函数的签名:
func Slice(slice interface{}, less func(i,j int) bool)
slice参数虽然定义为空接口类型,但它要求必须是切片类型(或基于切片类型定义的新类型),否则在运行阶段会引发错误。
less参数代表将要进行比较的两个元素的索引。
eg:
var nums = []int{5,2,4,6}
//用于递增排序的函数
var increaseFun = func(i,j int) bool {
return nums[i] < nums[j]
}
// 用于递减排序的函数
var decreaseFun = func(i,j int) bool {
return !(nums[i] < nums[j])
}
sort.Slice(nums,increaseFun)
sort.Slice(nums,decreaseFun)
//按字符串的长度排序
sort.Slice(strs, func(i,j int) bool {
return len(str[i]) < len(strs[j])
})
Interface接口:#
sort包中的Interface适用于自定义类型,如结构体。实现该接口可以由开发人员根据实际需要完成排序行为。
Interface接口定义如下:
type Interface interface{
// 列表中元素的个数
Len() int
// 实现排序规则,看看索引为 i 的元素是否小于索引为 j 的元素
Less(i, j int) bool
// 交换元素位置
Swap(i, j int)
}
Interface接口进行排序时调用 sort 包中的Sort函数,并把实现了 Interface 接口的对象传递给函数。
eg:将int32整数列表中的元素按照被8整除后的余数来排序。
// 注意:由于不能对内置类型和现有类型定义方法,所以必须先定义一个新的类型,之后才能实现Interface接口。
type Int32Nums []int32
func (x Int32Nums) Len() int {
return len(x)
}
func (x Int32Nums) Less(i, j int) bool {
m1 := x[i] % 8
m2 := x[j] % 8
return m1 < m2
}
func (x Int32Nums) Swap(i, j int) {
x[i],x[j] = x[j],x[i]
}
var dx = Int32Nums{27,102,58,47,85}
sort.Sort(dx)
eg: 实现按商品的入库时间排序
// 1、定义结构体
type goods struct {
id unit32 // 商品编号
name string // 商品名称
qty unit16 // 商品数量
time int64 // 入库时间
}
//2、基于[]goods类型定义新类型--goodsCollection
type goodsCollection []goods
//3、实现 Interface 接口的方法
func (c goodsCollection) Len() int{
return len(c)
}
func (c goodsCollection) Less(i,j int) bool {
return c[i].time < c[j].time
}
func (c goodsCollection) Swap(i,j int) {
c[i],c[j] = c[j],c[i]
}
// 注意:在实现Less方法时,进行比较的是goods对象的 time字段。
//4、初始化一组商品信息
var mygoods = goodsCollection {
goods{
id : 7201,
name : "商品B",
qty : 18,
time: 1683777852
},
goods{
id : 7101,
name : "商品A",
qty : 28,
time: 1583777852
},
goods{
id : 7201,
name : "商品C",
qty : 20,
time: 1783777852
}
}
//5、调用Sort函数执行排序
sort.Sort(mygoods)
输入与输出:#
简单的内存缓冲区:
内存缓冲区可以将数据临时存放在内存中,以供程序读写。当应用程序退出后,数据就会丢失。相对于磁盘
文件,直接在内存中读写数据具有更高的效率,适合在程序需要时存放少量数据,不宜使用过于庞大的数据,
毕竟内存空间有限。
最简单的内存缓冲区就是使用byte类型的数组/切片。
例如:
// 初始化[]byte实例,长度为0
var buffer = make([]byte,0)
// 向缓存区添加数据
buffer = append(buffer, []byte("一二三四五"))
buffer = append(buffer, []byte("六七八九十"))
//输出
fmt.Printf("缓冲区数据:%v\n",buffer)
fmt.Printf("转字符串后:%v\n",buffer)
注意:调用make函数创建[]byte实例时设置其长度为0,这是为了防止出现空字节。因为长度不为0的[]byte
实例会使用数值0来初始化元素列表,当调用append函数后会把新的数据追加到原数据的末尾,这会导致最终
的缓冲区出现一段空白内容。假设初始化[]byte实例时设置长度为3,那么它会产生3个字节---0、0、0,随后
追加1个字节---1,最后整个缓冲区变为0、0、0、1,即出现了3个空字节。
文件操作#
copy函数:#
copy是标准库中的内置函数,其签名如下:
func copy(dst, src []Type) int
该函数的功能是复制切片实例的元素。
dst参数为目标对象(接收元素的切片实例),src参数为源对象(被复制元素的切片实例)。
copy函数的返回值为成功复制的元素个数。
io.Copy函数#
签名:
func Copy(dst Writer, src Reader)(written int64, err error)
只要赋值给src的类型实现了 Reader 或 ReaderFrom接口,dst实现了Writer或WriterTo接口即可。
注意:io.Copy与内置的copy函数不同,copy函数用于复制切片中的元素,而io.Copy函数则用于复制字节流。
文件操作:#
常见的文件操作有创建和删除文件、重命名文件、读取与写入内容、获取文件信息等。
GO语言标准库中,与文件操作相关的API主要集中在两个包中---io/ioutil和os。
从 Go 语言 1.16 开始,ioutil.ReadAll、ioutil.ReadFile 和 ioutil.ReadDir 被弃用,因为 io/ioutil 包被弃用。
解决方法如下,使用 io 或 os 包中相同的方法替换,即修改自己按如下参照修改包名即可
ioutil.ReadAll -> io.ReadAll
ioutil.ReadFile -> os.ReadFile
ioutil.ReadDir -> os.ReadDir
// others
ioutil.NopCloser -> io.NopCloser
ioutil.ReadDir -> os.ReadDir
ioutil.TempDir -> os.MkdirTemp
ioutil.TempFile -> os.CreateTemp
ioutil.WriteFile -> os.WriteFile
文件操作中的两个类型:#
1、os.File
2、FileInfo
FileInfo对象可以通过调用File对象的Stat方法或调用os.Stat函数来获取。
Create函数和Open函数:os.Create os.Open
Create函数以可读可写的权限打开目标文件。如果目标文件不存在,则创建,并赋予读写权限;
如果文件已经存在,则清空其内容。若顺利调用,将返回File对象,否则返回指向PathError(描述错误信息的类型)对象的指针。
Open函数仅以只读方式打开文件,返回的File对象只支持读取文件内容。如果发生错误,返回值列表中将包含指向 PathError对象的指针。
使用完File对象后,调用Close方法,以便及时释放其所占用的资源。
File类型实现了StringWriter接口,如果写入的内容是文本,可直接调用WriteString方法。
重命名文件 os.Rename#
Rename函数的签名如下:
func Rename(oldpath, newpath string) error
该函数实际执行的操作是移动(Move) --- 将文件从oldpath 移动到 newpath。
所以Rename 函数不仅可以重命名文件,也可以移动文件。
获取文件信息:os.Stat#
调用Stat函数后会得到一个实现FileInfo接口的对象实例,通过此对象能获取一些常规的文件信息--例如文件名、文件大小、修改时间等。
eg:
//创建文件
var file, err = os.Create("myfile")
...
//获取文件信息
info, err := os.Stat("myfile")
...
info.Name() info.Size()
info, err := file.Stat()
//注意:调用File对象的Stat方法与调用Stat函数,结果上是一样的。
OpenFile函数: os.OpenFile#
func OpenFile(name string, flag int, perm FileMode) (*File, error)
name参数指定要打开的文件名。flag参数是一个整数值,指定以何种模式打开文件,常用值有:
O_APPEND、O_TRUNC、O_RDONLY、O_WRONLY、O_RDWR、O_CREATE
perm参数用linux权限标志位来为新文件设置权限。
eg:os.OpenFile("demo.txt",os.O_APPEND|os.O_CREATE,0777)
创建目录: os.MkdirAll#
func Mkdir(name string, perm FileMode) error
func MkdirAll(path string, perm FileMode) error
这个两个函数的区别在于:MkdirAll函数可以一次性创建多级目录。
删除目录:os.RemoveAll#
func Remove(name string) error
func RemoveAll(path string) error
创建硬链接:#
os.Link(oldname string, newname string) error
oldname表示旧的链接(文件名),newname 表示新的链接(文件名)
为原始文件创建硬链接,实际上没有产生新的文件。硬链接执行原始文件。
一个文件可以有多个硬链接,当所有的硬链接都删除后,原始文件也会被删除。
WriteFile函数 与 ReadFile函数:#
这两个函数位于 io/ioutil 包中
ReadFile函数签名: func ReadFile(filename string) ([]byte, error)
此函数会读取文件中的所有内容并以 []byte 类型返回。
WriteFile函数签名:func WriteFile(filename string, data []byte, perm os.FileMode) error
此函数把data参数指定的数据一次性写入文件。
注意:同一个文件如果要分多次写入,就不能调用 WriteFile函数。因为该函数每次调用都会将现有
的文件内容清空。
更改程序的工作目录:#
程序在启动时,会把程序可执行文件所在的目录设置为工作目录。在程序代码中,可以调用 os.Chdir 函数改变工作目录。
path/filepath包:#
用于以与目标操作系统定义的文件路径兼容的方式来处理文件名路径。
相关函数:
Abs(path string)(string,error) 返回所给目录的绝对路径
IsAbs(path string)bool 判断路径是否为绝对路径
Base(path string) string获取路径path中最后一个分隔符之后的部分(不包含分隔符)
Split(path string)(dir,file string) 获取path中最后一个分隔符前后的两部分,之前包含分隔符,之后不包含分隔符
Ext(path string)string 获取路径字符串中的文件扩展名
Dir(path string)string 获取path中最后一个分隔符之前的部分(不包含分隔符)
FromSlash(path string)string 将path中的'/'转为系统相关的路径分隔符
Base64编码:#
StdEncoding:标准编码方案
URLEncoding:URL特殊编码方案
eg:
// 原数据
var testData = "Some Data"
// 编码
var encodeStr = base64.StdEncoding.EncodingToString([]byte(testData))
// 解码
var decodeData, err = base64.StedEncoding.DecodeString(encodestr)
EncodingToString 方法是对原数据进行编码后,已字符串形式返回。如果希望返回字节序列,可以调用Encode方法。
DecodeString方法的输入参数是已编码数据的字符串形式,若希望用字节序列作为输入参数,可以调用Decode方法。
eg:
var data = []byte("Some data")
// 编码
// 确定编码后会产生多少个子句
n := base64.StdEncoding.EncodedLen(len(data))
encodeData := make([]byte, n)
base64.StdEncoding.Encode(encodeData, data)
// 解码
// 先确定解码后的字节个数
n = base64.StdEncoding.DecodedLen(len(encodeData))
decodeData := make([]byte, n)
base64.StdEncoding.Decode(decodeData, encodeData)
基于流的编码与解码
如果原数据比较大(基于流的形式),例如文件,这时候应当使用实现了Writer接口的对象来进行编码,然后使用实现了Reader接口的对象来解码。
encoding/base64包提供了这样的功能。继续编码操作时,可以调用 NewEncoder函数,会返回一个专用于
Base64编码的Writer对象,在进行解码操作时,调用NewDecoder 函数,会返回一个专门解码Base64数据
的Reader对象,通过该对象能读取原始数据。
NewEncoder 函数返回的对象实现了Writer接口,即公开 Writer方法,可多次调用 Write方法写入内容。
eg:
// 创建文件
file, err := os.Create("encoded.bin")
...
// 准备编码
var b64Writer = base64.NewEncoder(base64.StdEncoding, file)
// 写入要编码的内容
// 可以多次写入
b64Writer.Write([]byte("春江潮水连海平\n"))
b64Writer.Write([]byte("海上明月共潮生\n"))
...
// 释放资源
b64Writer.Close()
file.Close()
// 打开文件
file, err = os.Open("encoded.bin")
...
//准备解码
var decoder = base64.NewDecoder(base64.StdEncoding, file)
io.Copy(os.Stdout, decoder)
file.Close()
自定义字符映射表:
调用NewEncoding 函数可通过参数来传递字符映射表,base64包内部定义了两个标注的字符映射表。
const encodeStd = "..."
const encodeURL = "..."
开发者也可以向 NewEncoding 函数传递自定义的字符映射表。
字符映射表是一个字符串表达式,并且需要满足一下条件:
1、字符串的总大小必须是64字节。
2、字符串中不能出现换行符--- "\n","\r"。
eg:
//自定义字符串映射表
var encodeStr = "xxxxxx..."
// 创建encoding
custEncoding := base64.NewEncoding(encodeStr)
// 待编码的数据
var testData := "xxxxx"
// 编码
var ecStr = custEncoding.EncodeToString([]byte(testData))
// 解码
var decodeData, _ := custEncoding.DecodeString(ecStr)
RSA算法:#
RSA算法是一种公开秘钥的加密体制---加密与解密使用不同的秘钥。
公钥可以对外公开,通过网络传输,只用于加密;界面需要使用私钥,私钥不能对外公开。
在 cypto/rsa包中,有两个结构体与RSA秘钥有关
一个是 PublicKey结构体,表示公钥
一个是PrivateKey结构体表示私钥
调用 rsa 的 GenerateKey函数可以随机生成RSA私钥
func GenerateKey(random io.Reader, bits int) (*PrivateKey, error)
random 参数一般是引用 crypto/rand 包中的Reader变量。
bits参数指定秘钥的位数,一般来说,位数大,安全性相对高一些。因此,推荐使用 1024或2048位的秘钥。
私钥中包含了公钥的数据,可以从生成的私钥总获取公钥。
RSA加密有两种方案:
第一种:PKCS#1 v1.5标准
// 加密
func EncryptPKCS1v15(rand io.Reader, pub *PublicKey, msg []byte)([]byte, error)
// 解密
func DecryptPKCS2v15(rand io.Reader, priv *PrivateKey, ciphertext []byte) ([]byte, error)
rand 参数一般引用 crypto/rand包中 Reader变量,使用随机数是位了防止 "时序"攻击。
msg 要加密的消息,ciphertext是要解密的消息。
eg:
// 准备秘钥,本例中随机生成秘钥
// 生成秘钥
var prvKey, _ = rsa.GenerateKey(rand.Reader, 512)
// 从私钥中获取公钥
var pubKey = &prvKey.PublicKey
// 待加密的消息
var mgs = "测试内容"
// 加密
var cipherText, _ = rsa.EncryptPKCS1v15(rand.Reader, pubKey, []byte(msg))
// 解密
var decText, _ = rsa.DecryptPKCS1v15(rand.Reader, prvKey, cipherText)
另一种RSA方案是OAEP,对应的两个函数:
// 加密
func EncryptOAEP(hash hash.Hash, random io.Reader, pub *PublicKey, msg []byte, label []byte) ([]byte, error)
// 解密
func DecryptOAEP(hash hash.Hash, random io.Reader, priv *PrivateKey, ciphertext []byte, label []byte) ([]byte, error)
OAEP是一种优化方案,它增加了两个参数:hash参数指定要使用的哈希算法,label参数可以指定任意内容,它不会被加密,仅作为密文的附加内容被传递,可以为空。
eg:
// 生成秘钥
var prvKey, _ = rsa.GenerateKey(rand.Reader, 1024)
// 获取公钥
var pubKey = &prvKey.PublicKey
// 待加密的消息
var msg = "测试内容"
// 加密
var cipherText, _ = rsa.EncryptOAEP(sha256.New(), rand.Reader, pubKey, []byte(msg), nil)
// 解密
var decText, _ = rsa.DecryptOAEP(sha256.New(), rand.Reader, prvKey, cipherText, nil)
RSA 算法能加密的内容有限,通常用于加密简短的文本信息。
命令行参数:
在Go程序代码中,可以通过os.Args变量来获取其他调用者传递进来的命令行参数列表,其类型为[]string。该变量会在 os 包被初始化时赋值。其中,参数列表的第一个元素总是当前程序的名称(可执行文件路径,可以是绝对路径,也可以是相对路径),从第二个元素起才是命令行参数。
命令行参数分析包API--flag包#
flag包认可的命令参数格式是 参数名以'-'或 '--'开头,参数值位于参数名之后。
eg:
-d 100 或者 --d 100
参数名 d 参数值 100
参数名与参数值之间除了使用空格,也可以使用 "="分隔
eg: -a=100
命令行参数与变量的绑定:
fla包的工作方式是将代码中定义的变量与对应的命令行参数进行绑定,程序代码中只需要访问特定的变量就能得到命令行参数的值。
通过两种方式将变量绑定到命令行参数上:
第一种方式是调用以下函数,返回值是指针类型。
// 绑定 string 类型的变量
func String(name string, value string, usage string) *string
// 绑定 int 类型的变量
func Int(name string, value int, usage string) *int
// 绑定 int64 类型的变量
func Int64(name string, value int64, usage string) *int64
// 绑定 bool 类型的变量
func Bool(name string, value bool, usage string) *bool
// 绑定 uint 类型的变量
func Uint(name string, value unit, usage string) *uint
// 绑定 uint64类型的变量
func Unit64(name string, vaue uint64, usage string) *uint64
// 绑定 float64类型的变量
func Float64(name string, value float64, usage string) *float64
// 绑定 time.Duration 类型的变量
// 内部调用了 time.ParseDuration 函数来分析参数值
// 参数值使用了字符串方式传递,如 "10h"表示10个小时,"10s"表示10秒钟等
func Duration(name string, value time.Duration, usage string) *time.Duration
name为命令行参数的名称,value是该参数的默认值(当命令行参数被忽略时使用此默认值),
usage 指定与命令行参数相关的帮助信息。
eg:
//注册命令行参数
pWid := flag.Int("width", 0, "矩形的宽度")
pHei := flag.Int("height", 0, "矩形的高度")
// 重要:一定要调用此函数
flag.Parse()
// 计算面积
ar := (*pWid) * (*pHei)
在变量与命令行参数绑定后,必须调用一次flag.Parse函数。此函数将对传递到当前应用程序的命令行参数进行分析,并把参数值赋值给绑定的变量。在应用程序运行后,只需调用一次即可。
另外注意:pWid和pHei变量是指针类型(*int),直接访问只是获取到int值的内存地址,而不是真实的值。
计算面积需要使用真实的值,所以通过*pWid、*pHei的方式获取指针指向的实际值。
& 取地址 * 取值
第二种方式是先定义变量,然后把变量的内存地址传递相关函数,其原理和第一种方式相同,只是调用
方式不同。这些函数包括:
func StringVar(p *string, name string, value string, usage string)
func BoolVar(p *bool, name string, value bool, usage string)
func IntVar(p *int, name string, value int, usage string)
func Int64Var(p *int64, name string, value int64, usage string)
func Float64Var(p *float64, name string,value float64, usage string)
func DurationVar(p *time.Duration, name string, value time.Duration, usage string)
func UintVar(p *uint, name string, value uint, usage string)
func Uint64Var(p *uint64, name string, value uint64, usage string)
参数p 为应用变量的地址,其他参数与前面的String、Int、Bool等函数相同。
eg:
//变量列表
var (
num1 float64
num2 float64
opt string
)
// 绑定命令行参数
flag.Float64Var(&num1,"x",0.00,"第一个操作数")
flag.Float64Var(&num2,"y",0.00,"第二个操作数")
flag.StringVar(&opt,"o","+","运算方式")
// 分析命令行参数
flag.Parse()
通道:#
在Go的异步编程中,通道类型(channel,类型名称为chan)既可以用于协程之间的数据通信,也可以用于协程之间的同步。
通道类型:
chan T // 双向通道,既可以发送数据,也可以接收数据
chan<-T // 只能向通道发送数据
<-chan T // 只能从通道接收数据
T表示通道中可存放的数据类型。eg: chan int
通道对象的实例通过make创建。
数据缓存:
无缓冲的通道要求发送与接收操作同时进行---向通道发送数据的同时必须有另一个协程在接收(否则阻塞)。
eg:
func main() {
// 创建通道实例
var mych = make(chan uint)
// 开启新的协程
go func() {
fmt.Println("开始执行新协程")
// 向通道发送数据
mych<- 350
fmt.Println("新协程执行完毕")
}()
// 暂停一下
time.Sleep(time.Second)
fmt.Println("主协程退出")
}
main协程等待1秒钟后提出,从执行结果看,新启动的协程并没有完全被执行。程序在向通道mych发送数据后就被阻塞了,无法继续执行,这是因为整数350发送到通道后没有被及时读取所致。
解决方法是在main函数中接收通道中的数据。 <-mych
这样新创建的协程与主协程是异步执行的,使得通道mych 的发送与接收行为可以同时完成,程序不会被阻塞,最终两个协程都顺利执行。
带缓冲的通道的读与写可以不同时进行。
这是因为向通道发送数据后,数据会缓存在通道中,不要求立即被取出,代码也不会被阻塞---哪怕main函数中未接收通道的数据也不会阻塞。
但是,若是通道中缓存的数据量已满,再次向通道发送数据就会被阻塞,直到数据被接收为止。
单向通道:
var ch1 = make(<-chan bool)
var ch2 = make(chan<- bool)
// ch1单向通道实例,只能从通道接收数据,不能向通道发送数据。
// ch2单向通道实例,只能向通道发送数据,不能接收数据。
互斥锁(sync包公开的Mutex类型):#
var locker = new(sync.Mutex)
// 上锁
locker.Lock()
...
// 解锁
locker.Unlock()
Lock 方法 与 Unlock 方法的调用必须成对出现,即锁定资源后,要记得将其解锁,否则其他协程将永远无法访问资源。
WaitGroup类型:#
sync.WaitGroup 类型内部维护一个计数器,某个Go协程调用Wait方法后会被阻塞,直到WaitGroup对象的计数器变为0。
调用Add方法可以增加计数器的值,调用Done方法会使计数器的值减1。实际上,Done方法内部也调用了Add方法,传递的参数值为 -1。
var wg sync.WaitGroup
//增加计数器
wg.Add(3)
...
// 开启3个协程
// 执行完成时将计数器减1
defer wg.Done()
...
// 等待各协程执行完成
wg.Wait()
HTTP客户端:#
net/http包定义了一个Client类型,客户端可以使用该类型来实现向HTTP服务器发送请求消息,并获得服务器的回复消息。
为了方便,http包定义了DefaultClient变量
var DefaultClient = &Client{}
func Get(url string) (resp *Response, err error)
func Head(url string) (resp *Response, err error)
func Post(url string) (resp *Response, err error)
func PostForm(url string) (resp *Response, err error)
eg:下载
var response,err = http.Get("http://localhost/download")
if err != nil{
fmt.Println(err)
return
}
// 读取服务器回应的内容
// 并保存到本地文件中
outfile, err := os.Create("123.jpg")
if err != nil{
fmt.Println(err)
return
}
defer outfile.Close()
// 直接复制流中数据
io.Copy(outfile, response.Body)
eg:post方式
var strReader = strings.NewReader("test data")
var response, err = http.Post("http://localhost/submit","text/plain;charset=utf8",strReader)
if err != nil {
fmt.Println(err)
return
}
// 接收服务器响应
io.Copy(os.Stdout, response.Body)
eg:表单形式
// 待提交的数据
var formdata = map[string][]string{
"subject":{"some message"},
"body":{"the content of a message"},
"order":{"1"},
"tags":{"tag01","tag02","tag03"},
}
response, err := http.PostFrom("http://localhost/upforms",formdata)
if err != nil {
fmt.Println(err)
return
}
// 输出服务器响应信息
io.Copy(os.Stdout, response.Body)
PostForm函数与Post函数的用法接近,不过PostForm函数是通过data参数来存储数据正文的。
该参数的类型为url.Values,本质上是一个映射(map)类型--map[string][]string。
其中key为字符串类型,value为一个字符串列表。
Get、Post、PostForm函数都返回一个Response对象,此对象中封装了与服务器返回的消息有关的数据。其中Body字段表示HTTP消息的正文部分。
发送自定义HTTP头:#
若需要修改或添加自定义的HTTP头,就得调用http.Client对象的Do方法。
在调用Do方法前,需要初始化http.Request实例。该实例可通过http.NewRequest函数创建,之后通过Request.Header字段设置HTTP头。
Header以映射类型为基础,表示HTTP头的集合。
type Header map[string][]string
设置好相关的字段值后,将Request实例传递给Do方法,就可以向服务器发送HTTP请求了。
eg:
// 创建请求
var req, err = http.NewRequest(http.MethodGet, "http://localhost", nil)
if err != nil {
fmt.Println(err)
return
}
// 设置自定义的HTTP头
req.Header.Add("client-name","da")
req.Header.Add("client-ver","1.0")
//发起请求
response, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Println(err)
return
}
io.Copy(os.Stdout, response.Body)
调用http.NewRequest函数时需要提供三个参数:
1、HTTP请求方法,如GET、POST。类型为字符串也可以使用常量。
2、服务器的URL
3、请求消息的正文。
调用Do方法时不必创建新的http.Client实例,http包通过DefaultClient变量已经公开了一个http.Client实例,可以直接使用。
取余:#
任意数对7取模【%】相当于把任意数固定在0~6的范围
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
2021-01-19 sublime 光标选中多行
2021-01-19 mysql删除重复记录并且只保留一条