golang技术分享

1、python2和python3的bytes

if __name__ == '__main__':
	ss = 'terry0609_08\x04\x04\x04\x04'
		print(type(ss))
	 	print(ss[-1])
	 	print(ss[-2]) 	

python3中bytes可以是任何二进制数据,文本/图片/视频/音频等等。str就是文本数据。

python2中把b'abcd'视为str,对str的切片是把\x04当作1位,数字8被视为unicode即char,输出的就是'8'。
python3中对bytes的切片是把\x04当作1位,数字8被视为utf-8的int,输出的都是数值56。

出错的代码

def  unpadding(self, s):
	return s[0:-ord(s[-1])]

出错代码原因分析:

  没有注明类型,不能进行类型的判读,代码的写法比较trick。

python3中bytes和str的转换

b = b"example" # bytes object  
s = "example"  # str object  
s2b = bytes(s, encoding = "utf8")    # str to bytes 
s2b = str.encode(s) # str to bytes 
s2b = s.encode("utf8") # str to bytes 
b2s = str(b, encoding = "utf8")      # bytes to str  
b2s = bytes.decode(b) #  bytes to str  
b2s = b.decode("utf8") #  bytes to str  
  • bytes不一定能变成str,当bytes是图片,视频等非文本的二进制内容时,bytes就不能decode成str了。

2、typing标注类型和类型别名

python3.5 以后的新特性

​typing​模块的作用:

  • 类型检查,防止运行时传入参数不符合。

  • 作为开发文档附加说明,方便使用者调用传入和返回类型。

  • 如果传入参数类型不匹配会报错【IDE层面的报错】。

def  func(name: str) -> str:
	return  "Hello"  + name
if  __name__ == '__main__':
	print(func(123))

使用标注前首先需要导入typing标注库, 常用的标注

from typing import List, Dict, Tuple
	List[str]
	Dict[str, str]
	Dict[List[str], str]
	Tuple[str, str]

注意:解释器不会对返回类型做检查,比如实际返回是一个dict,但是标注的是str,程序可以正常运行。

3、 golang的string,byte和rune

中文字符在unicode下占2个字节,在utf-8编码下占3个字节,而golang默认编码正好是utf-8。

  • string不可变,当使用range遍历时,自动转换为[]rune
s := "hello 中国"
for c, v  := range s {
     fmt.Printf("%d %c\n", c, v)
}

输出结果
可以发现输出的序号跟预期不太符合

0 h
1 e
2 l
3 l
4 o
5  
69
  • byte可变,按照utf-8输出
package main

import "fmt"

func main() {
	s := "hello 中国"
	a := []byte(s)
	for c, v  := range a {
		fmt.Printf("%d %d\n", c, v)
	}
}

byte中的汉字被解码为3个字节

0 104
1 101
2 108
3 108
4 111
5 32
6 228
7 184
8 173
9 229
10 155
11 189

如果想要输出可以识别的字符,通常使用string转换一下

rune可变,按照unicode输出

  • rune=int32 四个字节

  • byte=int8 1个字节

package main

import "fmt"

func main() {
	s := "hello 中国"
	a := []rune(s)
	for c, v  := range a {
		fmt.Printf("%d %s\n", c, string(v))
	}
}

输出结果

0 h
1 e
2 l
3 l
4 o
5  
67

4、 json与map之间的相互转换

  • map转json
package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	//创建一个map
	m := make(map[string]interface{}, 4)
	m["company"] = "byte"
	m["subjects"] = []string{"GO", "C++", "Java"}
	m["ok"] = true
	m["price"] = 666.666
	//编码成json
	//result, err := json.Marshal(m)
	result, err := json.MarshalIndent(m, "", "  ")
	if err != nil {
		fmt.Printf("error: %s", err)
		return
	}
	fmt.Printf("result = %v", string(result))

}

因为map是无序的,所以不能保证每次输出的顺序相同。

json转map

package main

import (
	"encoding/json"
	"fmt"
)

func main() {
	//创建一个json
	result := `{
		 "company": "bytedance",
		 "isok": true,
		 "price": 666.666,
		 "subjects": ["GO","C++","Java"]
   	}`
	//创建一个用于接收的map
	mp := make(map[string]interface{}, 99)
	json.Unmarshal([]byte(result), &mp)
	fmt.Println(mp)
}

输出的结果

map[company:bytedance isok:true price:666.666 subjects:[GO C++ Java]]

5、 json与结构体之间的转换

json转结构体

package main

import (
	"encoding/json"
	"fmt"
)

//变量名必须大写
type IT struct {
	Company string   `json:"company"`
	Subject []string `json:"subjects"`
	Isok    bool     `json:"-"` //不输出
	Price   float64  `json:",string"`
}

func main() {
	//定义一个结构体变量 同时初始化
	s := IT{"bytedance", []string{"GO", "C++", "Python", "Java"}, true, 666.66}
	//编码 根据内容生成Json文本
	//buf, err := json.Marshal(s)
	buf, err := json.MarshalIndent(s, "", "        ")
	if err != nil {
		fmt.Printf("error %s", err)
	}
	fmt.Printf("%+v", string(buf))
}

结构体转json

package main

import (
	"encoding/json"
	"fmt"
)

//变量名必须大写
type IT struct {
	Company string
	Subject []string
	Price   float64
}

func main() {
	result := `{
		 "Company": "bytedance",
		 "Subject": ["GO","C++","Python","Java"],
		 "Price": 666.66
   }`
	/*创建一个结构体变量*/
	var it IT
	err := json.Unmarshal([]byte(result), &it)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Printf("%#v\n", it)

}

main.IT{Company:“bytedance”, Subject:[]string{“GO”, “C++”, “Python”, “Java”}, Price:666.66}

  1. golang中的自定义排序

golang内置了一些基础数据类型的排序,如int,string,float等,其他类型的结构体需要自己实现less, map,len三个函数。

内置排序

package main

import (
	"fmt"
	"sort"
)

func main() {
	// int排序
	ints := []int{1, 8, 4, 5}
	sort.Ints(ints)
	fmt.Println(ints)
	// string排序
	names := []string{"hello", "world", "yao", "jun"}
	sort.Strings(names)
	fmt.Println(names)
}

自定义排序

package main

import (
	"fmt"
	"sort"
)

type Student struct {
	name string
	age  int
}

type stuSlice []Student
func (s stuSlice) Len() int {
	return len(s)
}

func (s stuSlice) Less(i, j int) bool {
	return s[i].age < s[j].age
}
func (s stuSlice) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

func main() {
	students := []Student{{"mike", 16}, {"jane", 22}, {"Ben", 19}}
	sort.Sort(stuSlice(students))
	for k, v := range students {
		fmt.Println(k, v)
	}
}

输出结果

0 {mike 16}
1 {Ben 19}
2 {jane 22}

7、golang中的单元测试

  • t *testing.T 普通测试函数

  • b *testing.B benchMark测试

普通测试函数

通常test文件的命名规范是xxx_test.go

test case的函数命名为TestXxx形式 否则go test会跳过该test不执行


func  F(a,b int)int{
	return a+b
}

测试代码

package main

import "testing"

func TestF(t *testing.T) {
	res := F(3, 4)
	if res != 7 {
		t.Errorf("add wrong!")
	}
}

Go test不保证多个test按照顺序执行,但是我们通常会要求按照顺序执行。

使用t.Run来执行subtests可以做到控制test执行的顺序

package main

import (
	"fmt"
	"testing"
)

func TestF(t *testing.T) {
	t.Run("a1", func(t *testing.T) { fmt.Println("a1") })
	t.Run("a2", func(t *testing.T) { fmt.Println("a2") })
	t.Run("a3", func(t *testing.T) { fmt.Println("a3") })
}

使用TestMain作为初始化test,并且使用m.Run()来调用其他tests可以完成一些需要初始化操作的testing,比如数据库连接,文件打开,rest服务登录等。如果没有在TestMain中调用m.Run()则除了TestMain以外的其他tests都不会被执行。

func  TestMain(m *testing.M){
	fmt.Println("test main first")
	m.Run()
}

benchmark测试

benchmark函数一般以BenchMark开头

benchmark函数的case一般会跑b.N次,而且每次执行都会如此

在执行过程中会根据实际case的执行时间是否稳定会增加b.N的次数以达到稳态

go test -bench =. //命令行
func BenchmarkF(b *testing.B) {
for i := 0; i < b.N; i++{
	F(100, 10)
	}
}
func F(a, b int)int{
	return a+b
}

如果benchmark测试的函数执行时间始终无法趋于稳态 则永远无法执行完

func  unstable(n int){
	for n > 0{
		n--
	}
}
func BenchmarkF(b *testing.B)
	for i := 0; i < b.N; i++{
		unstable(i) //如果benchmark测试的函数执行时间始终无法趋于稳态 则永远无法执行完
	}
}

8、golang中切片的坑

slice传参数时新增失败

package main
import "fmt"
func modifySlice(s []string) { //切片新增无效
	s[0] = "modify 123"
	s = append(s, "456")
}

func main() {
	s := []string{"123"}
	modifySlice(s)
	fmt.Println(s) //返回结果 [modify 123] 新增的数据没有生效
	s = modifySliceNew(s)
	fmt.Printf("%v", s) //必须重新接收返回值 此时生效 因为修改的指针返回了

}

正确使用方式

原因是传入的参数是指针的拷贝 但是新增数据是修改的指针

func  modifySliceNew(s []string)[]string{  //切片新增生效
    s[0] = "modify 123"
	s = append(s, "456")
	return s
}

9、golang中删除切片中某个元素

  • 方法1: 删除某个元素后,剩余的元素往前移动
  • 方法2: 重新开一个切片,把满足条件的往新切片中放入
  • 方法3: 索引累计,如果是删除的元素索引就不加1,最后根据累计的索引进行切片
package main

import (
	"fmt"
)

func main() {
	//创建一个slice
	sliceU := []int{1, 0, 1, 1, 0, 1, 0, 1, 1}
	// 要求:把切片中所有的0删除掉
	//slice的删除方式1
	//删除剩余的元素往前移动
	for i, v := range sliceU {
		if v == 0 {
			sliceU = append(sliceU[:i], sliceU[i+1:]...)
		}
	}
	fmt.Println(sliceU)
	//slice的删除方式2
	//重新开一个数组,把满足条件的往新数组中写入
	var newSliceU []int
	for _, v := range sliceU {
		if v != 0 {
			newSliceU = append(newSliceU, v)
		}
	}
	fmt.Println(newSliceU)
	// slice的删除方式3
	// 索引累计 最后切片
	index := 0
	for _, v := range sliceU {
		if v != 0 {
			sliceU[index] = v
			index++
		}
	}
	fmt.Println(newSliceU[:index])
}

从上面的结果可以看出,数据范围较小时,方法2和方法3性能差不多,数据量大以后,方法1总体上要差于其他两个策略。

方法1

时间复杂度:O(n^2)

空间复杂度:O(n)

方法2

时间复杂度:O(n)

空间复杂度:O(n)

方法3

时间复杂度:O(n)

空间复杂度:O(1)

10、数组传参

package main

import "fmt"
func f(arr [10]int){  //不仅是数组还必须长度一致
	arr[1] = 100
	fmt.Println(arr)
}
func main(){
	/*
	数组:类型和长度确定。
	值类型:传递参数时,进行值拷贝。
	*/
	var arr [10]int
	arr[1] = 2
	arr[2] = 3
	f(arr)
	fmt.Println(arr)
	arr2 := arr
	arr2[1] = 100  //拷贝以后改变 没影响
	fmt.Println(arr2)
	fmt.Println(arr)

}

11、深浅拷贝

package main

import "fmt"
func f(arr [10]int){  //不仅是数组还必须长度一致
	arr[1] = 100
	fmt.Println(arr)
}
func main(){
	/*
	切片:slice是对底层数组一段内容的引用,slice是一个fat pointer存储的是实际数组的地址,切片的长度,最大容量
	注意:应该避免在两个变量中修改同一个底层数组
	data[low:high:max]high和max都是不包含当前下标的。
	len()
	cap()
	append()
	copy() 可以实现对切片的深拷贝 切片间的直接赋值是浅拷贝,只是赋值了共同的底层数组地址
	对同一个数组进行的不同切片,对某个切片进行操作,对其他切片是没有感知的,但是如果是对底层数组修改,两者都有感知。

	引用类型都是浅拷贝
	操作切片也是操作底层数组,slice采用两倍扩容机制,一旦扩容就重新指向一个新的底层数组。
	cap是切片开始位置到数组末尾的长度

	*/
	var arr [10]int
	arr[0] = 100
	arr[1] = 200
	arr[2] = 300
	slice1 := arr[0:2]
	slice2 := arr[1:5]
	fmt.Println(cap(slice1))
	fmt.Println(cap(slice2))

	slice3 := slice2
	arr[1] = 20000
	fmt.Println("_______")
	fmt.Println(slice1)
	fmt.Println(slice2)
	fmt.Println(slice3)

}

12、golang常用操作

  1. strings.Contains(str, “hello”) 是否包含某个字符串
  2. strings.Count(str, “hello”) 字符串数量统计
  3. 字符串拆分为数组strings.Split(s, sep),sep表示以什么什么来拆分s字符串。
  4. 字符串大小写转换,strings.ToUpper(s)转大写,strings.ToLower(s)转小写
  5. strings.Trim(s, cutset),cutset代表要去除的两边字符。strings.TrimLeft(s, cutset)。
  6. strings.HasPrefix(s, prefix)。判断字符串是否以什么开头。

13、字符串转整形

val, typeErr := strconv.Atoi(myStr2)
b2, errs:= strconv.ParseInt(s2, 2, 64) //把字符串二进制转换为十进制整数

14、整型转字符串

value := strconv.Itoa(myInt)
ss2 := strconv.FormatInt(b2, 2) //把整数以二进制转换为字符串

15、字符串转[]byte

myByte := []byte(myStr3)

16、编码

一个英文字符对应一个byte,一个中文字符对应三个byte。

一个rune对应一个UTF-8字符,所以一个中文字符对应一个rune。%c输出对应的字符和汉字。

rune相当于go的char。

byte=uint8

rune=uint32

17、时间

t:= time.Now() //当前时间

18、读一行

bf := bufio.NewReader(os.Stdin)
ss, errs := bf.ReadString('\n')
lst := string.Field(ss) //按照空格切分字符串

19、数组初始化

cities := [...]string{"北京", "上海","深圳"}
cities := []string{"上海", "成都"}
	for index,value := range cities{
		fmt.Println(index, value)
	}

你可能感兴趣的:(Go)