语言须能精致准确表达出想要表达的对象。
举例来说,好小说家不该滥用“不可思议”“难以捉摸”等词,因为他有责任把这种“不可思议”与“难以捉摸”写清楚,传达给读者。 —— F.R.利维斯 《伟大的传统》
19年上半年Go语言使用笔记
公司里新项目基本都换成了Go语言,,老项目有条件的也在慢慢用Go重构。
所以这大半年除了偶尔维护Python2老代码的时候还写下Python, 其他大部分时间基本都在写JS和Go。:stuck_out_tongue:
下面是 半年来使用中记的一些记录,整理了下发出来。
1. 发送Get以外的额请求
以 PATCH方法为例:
package main
import (
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)
func main() {
region := "js"
gwIp := "1.1.1.1"
stateChangeBody := `{"region":"` + region+`","gw_ip":"`+ gwIp+`"}`
var stateChangeBytes = []byte(stateChangeBody)
//stateChangeBody 若使用结构体 , stateChangeBytes就用 json.Marshal(stateChangeBody)
fmt.Println("body:",bytes.NewBuffer(stateChangeBytes))
stateChangeReq, _:= http.NewRequest("PATCH", "http://localhost:8000/api/gateway/state", bytes.NewBuffer(stateChangeBytes))
//请求的body是 io.reader interface,需要实现
//strings.NewReader byte.NewReader bytes.NewBuffer 实现了read 方法
client := &http.Client{}
stateChangeResp, _ := client.Do(stateChangeReq)
// 若响应使用gzip 编码 ,需要使用gzip.NewReader 解压,
//reader, err := gzip.NewReader(stateChangeResp.Body)
//if err != nil {
// return nil, fmt.Errorf("GZIP Read ERROR %s", err.Error())
//}
//Response, err := ioutil.ReadAll(reader)
//if err != nil {
// return nil, fmt.Errorf("BODY READ ERR %s", err.Error())
//}
stateChangeResponse, _ := ioutil.ReadAll(stateChangeResp.Body)
//fmt.Println("response Body:", string(stateChangeRespData))
var stateChangeRespData map[string]interface{}
if err := json.Unmarshal(stateChangeResponse, &stateChangeRespData); err != nil {
fmt.Println("response err:", err)
}
if _, ok := stateChangeRespData["res"]; !ok {
fmt.Println("response err:","无数据")
}
if stateChangeRespData["res"]=="faild" {
fmt.Println("result:","更新失败")
}
}
2. invalid character ‘<’ looking for beginning of value
使用json.Unmarshal()
反序列化时出现。
func main() {
timeStamp := time.Now().Format("2006/01/02 15:04")
metric := "probe_duration_seconds"
ip := "1.1.1.1"
probeIpApi := "http://api/ip?start_time=" + timeStamp + "&end_time="+ timeStamp + "&metric="+ metric + "&ip=" + ip
probeIpResp, err := http.Get(probeIpApi)
if err != nil {
fmt.Println("请求API失败", err)
return
}
defer probeIpResp.Body.Close()
probeIpRespBody,err := ioutil.ReadAll(probeIpResp.Body)
if err != nil {
fmt.Println("请求API io读取失败", err)
return
}
fmt.Println(string(probeIpRespBody))
var probeIpRespDat =make(map[string]interface{},0)
if err := json.Unmarshal(probeIpRespBody, &probeIpRespDat); err != nil{
fmt.Println("请求API 反序列化失败", err)
return
}
ipConnectedness := probeIpRespDat
fmt.Println(ipConnectedness)
}
原因
打印出来返回的结果是
<html>
<head><title>502 Bad Gateway</title></head>
</html>
返回的为html时会报这个错误。
而未返回结果的原因是,url中的time含有空格导致。
解决
确保返回的数据正确就可解决。
还有确保请求时的url正确编码有两种方法。
- 将url拼完后进行一次编码:
probeIpApi := "http://api/ip?start_time=" + timeStamp + "&end_time="+ timeStamp + "&metric="+ metric + "&ip=" + ip u,_ := url.Parse(probeIpApi) q := u.Query() u.RawQuery = q.Encode() //urlencode fmt.Println(u.String()) probeIpResp, err := http.Get(u.String())
- 将ulr中参数通过
URL.Query()
添加:req, err := http.NewRequest("GET", "http://api/ip", nil) if err != nil { log.Print(err) os.Exit(1) } q := req.URL.Query() q.Add("start_time", start_time) q.Add("end_time", end_time) q.Add("metric", metric) q.Add("ip", ip) req.URL.RawQuery = q.Encode() client := &http.Client{} resp, err = client.Do(req)
注意:此种方法会将 空格 编码为“+”而不是“%20”。
W3C标准规定,当Content-Type为application/x-www-form-urlencoded
时,URL中查询参数名和参数值中空格要用加号 “+” 替代,所以几乎所有使用该规范的浏览器在表单提交后,URL查询参数中空格都会被编成加号”+”。
而在另一份规范(RFC 2396,定义URI)里, URI里的保留字符都需转义成%HH
格式(Section 3.4 Query Component),因此空格会被编码成”%20”,加号”+”本身也作为保留字而被编成”%2B”。
对于某些遵循RFC 2396标准的应用来说,它可能不接受查询字符串中出现加号”+”,认为它是非法字符。所以一个安全的举措是URL中统一使用”%20”来编码空格字符。
上面的例子中可以在最后,再调用replaceAll("\\+", "%20")
,将所有加号+替换为%20。
3.Get Resource List Err: CLIENT DO ERROR Post : unsupported protocol scheme
request 发送请求时, 目标url不合法.
两次遇到这个问题,由于配置中 本地测试时 高危的url注释未打开.
4.结构体的坑之大小写
结构体名字没有首字母大写别的包无法调用
struct的属性是否被导出,遵循大小写的原则:首字母大写的被导出,首字母小写的不被导出。
所以:
- 如果struct名称首字母是小写的,这个struct不会被导出。连同它里面的字段也不会导出,即使有首字母大写的字段名。
- 如果struct名称首字母大写,则struct会被导出,但只会导出它内部首字母大写的字段,那些小写首字母的字段不会被导出。
但是这里有一个特殊情况(这个限制不仅仅是结构体),当你有另一个首字母大写的结构体Test,包含了一个首字母小写的结构体test类型的参数,那么就够在包外通过调用这个首字母大写的结构体,间接的调用这个首字母小写的结构体的内容(前提必须是参数首字母大写)
var hi test = test{"hello"}
type test struct {
Test string
}
type Test struct {
Test1 test
}
func (m * Test) InitHi(){
m.Test1=hi
}
结构体中参数首字母没有大写时,别的包虽然可以调用这个结构体,但是找不到这个结构体中没有首字母大写的参数。
5.float类型精确度(保留小数位数)
Go没有python那样现成的内置函数, 比如round(number , n)
或者Decimal
包, 只能自己封装啦:
-
转换成字符串,截取,再转为float
func FloatRound(f float64, n int) float64 {
format := "%." + strconv.Itoa(n) + "f"
res, _ := strconv.ParseFloat(fmt.Sprintf(format, f), 64)
return res
}
-
用math包
func Round(f float64, n int) float64 {
n10 := math.Pow10(n)
return math.Trunc((f+0.5/n10)*n10) / n10
//Trunc保留整数部分
//比如1.5334要保留一位的话, (1.5334*10)保留整数位,再除以 10 就可以了,加0.5是为了四舍五入.
}
6. Go中时间戳相关
import (
"strconv"
"time"
)
func main() {
timwStamp := time.Now()
//timwStamp: 2019-05-13 17:19:37.3712671 +0800 CST m=+0.008498901 ,
//type: time.Time
timeStampUnix := time.Now().Unix()
//timwStamp--Unix: 1557739177
//type : int64
timeString := strconv.FormatInt(timeStamp,10)
//timeString: 1557739177
//type: string
timeNow := time.Unix(timeStamp, 0)
//timwNow--Unix : 2019-05-13 17:19:37 +0800 CST
//type: time.Time
lastTime := timeNow.Format("2006-01-02 15:04:05")
//lastTime: 2019-05-13 17:19:37
//type: time.Time
timeParse,_ := time.Parse("2006-01-02 15:04:05",timeString)
//timeParse: 0001-01-01 00:00:00 +0000 UTC
// type: time.Time
}
7.Go语音中单引号和双引号区别,及rune类型
Go语言的字符串类型string
在本质上就与其他语言的字符串类型不同:
- Java的String、C++的std::string以及Python3的str类型都只是定宽字符序列
- Go语言的字符串是一个用UTF-8编码的变宽字符序列,它的每一个字符都用一个或多个字节表示
即:一个Go语言字符串是一个任意字节的常量序列。
Golang的双引号和反引号都可用于表示一个常量字符串,不同在于:
- 双引号用来创建可解析的字符串字面量(支持转义,但不能用来引用多行)
- 反引号用来创建原生的字符串字面量,这些字符串可能由多行组成(不支持任何转义序列),原生的字符串字面量多用于书写多行消息、HTML以及正则表达式
- 单引号则用于表示Golang的一个特殊类型:
rune
,类似其他语言的byte
但又不完全一样,是指:码点字面量(Unicode code point),不做任何转义的原始内容。
大部分能查到的资料就到此为止,没有解释码点字面量到底是什么.
官方文档解释:
rune is an alias for int32 and is equivalent to int32 in all ways.
//int32的别名,几乎在所有方面等同于int32
It is used, by convention, to distinguish character values from integer values.
//它用来区分字符值和整数值
在Go中 ,byte 是 ` uint8 `的别名,常用来处理ascii字符;
rune
的命名应该来自于北欧的『如尼文字』,是int32
的别名,表示一个Unicode码点.
被大部分人转载的所谓”码点字面量”其实是翻译的锅.
有关rune
类型,相信看完下面的例子就会明白:
var a byte = 'a'
// value: 97 即ASCII 编码为 97
// type: uint8
// length: binary.Size(a) == 1
// unsafe.Sizeof(a) == 1
var c rune = '你' // 注意: 此处只能用 『单引号』
// value: 20320 即Unicode 码为 20320
// type: int32/rune
// length: binary.Size(c) == 4
// unsafe.Sizeof(c) == 4
b := 'b'
// value: 98
// type: int32/rune
// length: binary.Size(b) == 11
d := '我'
// value: 25105
// type: int32/rune
// length: binary.Size(d) == 4
e := "e"
// value: e
// type: string
// length: len(e) == 1
f := "他"
// value: 他
// type: string
// length: len(f) == 3
// len([]rune(f)) == 1
// utf8.RuneCountInString(f) == 1
可以看到,rune
类型就像文档中说的那样, 主要用于『字符值』而不是『整数值』.
在这个例子中, len(f) == 3
是因为Golang中string
底层是通过[]byte
实现的。
中文字符在unicode
下占2个字节,在utf-8
编码下占3个字节 (编码问题可参见之前文章 字符编码浅谈及Python中的编码问题 ),而Golang默认编码正好是utf-8
。
ps:
int
, uint
和 uintptr
在 32 位系统上通常为 32 位宽( 4字节 ),在 64 位系统上则为 64 位宽( 8字节)。
var z int = 8
// unsafe.Sizeof(z) == 8 我的是64位系统
// unsafe.Sizeof总是在编译期就进行求值,而不是在运行时
8 . slice [],被jsoniter.Marshal()转换为null
,期望保留为[]
空slice
type A stuct{
Value []string
}
func main() {
data := &A{}
data2 := &A{Value: []string{}}
buf, _ := json.Marshal(&data)
fmt.Println(string(buf))
buf2, _ := json.Marshal(&data2)
fmt.Println(string(buf2))
}
输出:
{"Value":null}
{"Value":[]}
9. 利用Map去重
Go没有python中set
这种数据结构.
不过当有用到类似set
的需求时, 可以直接make 一个map, 将item 设置为key.
value 可以设置为布尔值或者其他 , 这样就可以不用每次都去遍历数组,就可以新添加的item是否与之前的重复.
10. 组合和继承
刚开始用Go的时候,由于它不支持完全的面向对象,是怎么用怎么 难受…
Go中没有继承的概念, 它是通过接口来实现类似功能。
Go中还有一种叫做组合的概念,看如下代码
package main
import "fmt"
type Base struct {
// nothing
}
func (b *Base) ShowA() {
fmt.Println("showA")
b.ShowB()
}
func (b *Base) ShowB() {
fmt.Println("showB")
}
type Derived struct {
Base
}
func (d *Derived) ShowB() {
fmt.Println("Derived showB")
}
func main() {
d := Derived{}
d.ShowA()
}
输出结果为:
showA
showB
可以看到,结果并没有输出 "Derived showB"
。
上面的Derived包含了Base,自动的含有了Base的方法,因为其不是继承,所以不会根据具体传入的对象而执行对应的方法。
Python中的类继承表示Base是Derived的一个父类,具有一种层次关系。
但是Go的组合则是一种 包含关系,是Derived 包含了Base,所以Derived的实例可以使用Base的所有方法。