一、interface{}
golang中可以使用interface{}表示任何类型。
其实和java中的interface几乎一样,也是接口,需要实现类
二、strconv包
strconv包实现了基本数据类型与其字符串表示的转换,主要有以下常用函数:Atoi()
、Itia()
、parse系列、format系列、append系列。
案例:
netIf.InBytes, _ = strconv.ParseInt(fields[0], 10, 64)
netIf.InPackages, _ = strconv.ParseInt(fields[1], 10, 64)
netIf.InErrors, _ = strconv.ParseInt(fields[2], 10, 64)
netIf.InDropped, _ = strconv.ParseInt(fields[3], 10, 64)
netIf.InFifoErrs, _ = strconv.ParseInt(fields[4], 10, 64)
strconv.ParseInt(fields[0], 10, 64):将字符串转为数字,并且限制数字大小不能超过int64的最大值
golang strconv.ParseInt 是将字符串转换为数字的函数,功能非常强大.
参数1 数字的字符串形式
参数2 数字字符串的进制 比如二进制 八进制 十进制 十六进制
参数3 返回结果的bit大小 也就是int8 int16 int32 int64
三、io/ioutil包
读文件,比如linux的一些网卡的监控,需要读取文件:/proc/net/dev
golang读取:
contents, err := ioutil.ReadFile("/proc/net/dev")
ret := []*NetIf{}
reader := bufio.NewReader(bytes.NewBuffer(contents))
for {
lineBytes, err := file.ReadLine(reader)
line := string(lineBytes)
idx := strings.Index(line, ":")
if idx < 0 {
continue
}
netIf := NetIf{}
eth := strings.TrimSpace(line[0:idx])
if len(onlyPrefix) > 0 {
found := false
for _, prefix := range onlyPrefix {
if strings.HasPrefix(eth, prefix) {
found = true
break
}
}
if !found {
continue
}
}
netIf.Iface = eth
fields := strings.Fields(line[idx+1:])
netIf.InBytes, _ = strconv.ParseInt(fields[0], 10, 64)
netIf.InPackages, _ = strconv.ParseInt(fields[1], 10, 64)
ret = append(ret, &netIf)
}
四、golang中的strings.Index
golang字符串操作
strings.Index(s,str):返回子串str在字符串s中第一次出现的位置。如果找不到则返回-1;如果str为空,则返回0
func main(){
s := "hello world hello world"
str := "wo"
//返回子串str在字符串s中第一次出现的位置。
//如果找不到则返回-1;如果str为空,则返回0
index := strings.Index(s,str)
fmt.Println(index) //6
}
五、Golang之缓冲器bytes.Buffer
b := bytes.NewBuffer(s []byte) //从一个[]byte切片,构造一个Buffer
六、go中string与[]byte的互换
1、标准转换
标准转换方式:
// string to []byte
s1 := "hello"
b := []byte(s1)
// []byte to string
s2 := string(b)
2、强转换
通过unsafe和reflect包,可以实现另外一种转换方式,我们将之称为强转换(也常常被人称作黑魔法)。
func String2Bytes(s string) []byte {
sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
bh := reflect.SliceHeader{
Data: sh.Data,
Len: sh.Len,
Cap: sh.Len,
}
return *(*[]byte)(unsafe.Pointer(&bh))
}
func Bytes2String(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
强转换方式的性能会明显优于标准转换。
七、字符串截取
-
可以使用len(字符串变量)获取字符串的字节长度,其中英文占1个字节长度,中文占用3个字节长度
-
可以使用变量名[n]获取到字符串第n+1个字节,返回这个字节对应的Unicode码值(uint8类型).注意n的取值范围是[0,长度)
-
可以使用变量名[n:m]取出大于等于n小于m的字符序列
-
n和m都可以省略,省略时认为n为0,m为长度
-
因为中文占用三个字节,如果没有把中文完整取出,会出现乱码
-
func main() {
s := "smallming张"
fmt.Println(len(s)) //输出:12,字节长度,注意,中文三个字节
fmt.Println(s[1:4]) //输出:mal
fmt.Println(s[:2]) //输出:sm
fmt.Println(s[5:]) //输出:ming张
}
八、strings.Fields函数
以连续的空白字符为分隔符,将s切分成多个子串,结果中不包含空白字符本身。
返回str的子字符串切片
func main(){
s := "hello world hello world"
//str := "wo"
//以连续的空白字符为分隔符,将s切分成多个子串,结果中不包含空白字符本身。
//空白字符有:\t, \n, \v, \f, \r, ’ ‘, U+0085 (NEL), U+00A0 (NBSP) 。
//如果 s 中只包含空白字符,则返回一个空列表
index := strings.Fields(s)
fmt.Println(len(index)) //4
}
九、关于生成对象
我们一般用new或&去生成对象,他们生成的对象就是指针类型
netIf := &NetIf{}:netIf就是一个指针,指向对象的内存地址
netIf := NetIf{}:netIf则表示对象本身而不是指针
但是对netIf的使用上几乎没有区别,那为什么要有两种方式呢?
主要是指针的话方法传递的时候,直接传递的是指针,这个好处是空间小,尤其是分布式开发的时候,最好用指针
而调用方法直接传递对象的话就是传递对象本身了,对于分布式方法调用,会导致传递的流量变大
十、Golang 内置函数:make()
make() 是 Go 语言内存分配的内置函数,默认有三个参数。
make(Type, len, cap)
Type:数据类型,必要参数,Type 的值只能是 slice、 map、 channel 这三种数据类型。
len:数据类型实际占用的内存空间长度,map、 channel 是可选参数,slice 是必要参数。
cap:为数据类型提前预留的内存空间长度,可选参数。所谓的提前预留是当前为数据类型申请内存空间的时候,提前申请好额外的内存空间,这样可以避免二次分配内存带来的开销,大大提高程序的性能。
十一、golang调用函数
golang中函数可以不属于任何一个结构体,可以用包直接调用,如:
netIfs, err := nux.NetIfs(ifacePrefix)
nux是个包,包直接调用了函数,github.com/toolkits/nux
而Java中必须先创建对象,再通过对象调用函数
十二、函数名之前指定接收者
读Grafana mimir源码的时候,看到了如下的函数,func后面跟了一个括号,一开始以为是返回值,但是golang返回值定义在参数后面,后来咨询了下chatgpt。
原来,这是一个接收者,其实就是什么类型可以调用这个方法,比如下面的函数,其实就是 *Limits的 l ,可以直接调用下面的方法,如本例中:l.unmarshal(func(v any) error {
func (l *Limits) UnmarshalJSON(data []byte) error {
return l.unmarshal(func(v any) error {
xxx
})
}
func (l *Limits) unmarshal(decode func(any) error) error {
xxx
}
下面是chatgpt的回答:
在 Go 语言中,接收者是将方法(成员函数)与特定的数据类型关联起来的一种机制。接收者就是方法执行时所作用于的数据类型的实例,可以将其理解为其他面向对象编程语言(如 Java)中的 `this` 或 `self` 关键字。
接收者为函数添加了一个隐式的第一个参数,而这个参数代表了调用此方法的数据类型的实例。当你在这个实例上调用方法时,接收者参数会自动传递给该方法。
以下是一个详细解释接收者用法的例子:
```go
package main
import (
"fmt"
)
// 定义一个简单的结构体,用于表示人的姓名和年龄。
type Person struct {
Name string
Age int
}
// 接收者是 Person 结构的 方法。它在 Person 类型的实例上打印 "Hello" 消息。
func (p Person) SayHello() {
fmt.Println("Hello, my name is", p.Name)
}
// 带有接收者的方法,它在 Person 类型的实例上增加年龄。
func (p *Person) AddAge(years int) {
p.Age += years
}
func main() {
// 创建一个 Person 结构体实例
person := Person{Name: "Alice", Age: 30}
// 使用接收者在 person 实例上调用 SayHello 方法
person.SayHello() // 输出 "Hello, my name is Alice"
// 使用接收者在 person 实例上调用 AddAge 方法
person.AddAge(5) // person.Age 变为 35
fmt.Println("Person after adding 5 years:", person)
}
```
在示例中,我们定义了一个名为 `Person` 的结构体,并为其实现了两个方法:`SayHello` 和 `AddAge`。这两个方法都具有接收者参数,它们指定了这些方法应作用于哪种数据类型。
`SayHello` 方法的接收者是 `Person` 类型,其方法签名为 `func (p Person) SayHello()`。这表示只要有一个 `Person` 类型的实例,我们就可以在这个实例上调用 `SayHello` 方法。
`AddAge` 方法的接收者是 `*Person` 指针类型,其方法签名为 `func (p *Person) AddAge(years int)`。由于接收者类型是指针,当在 `Person` 实例上调用 `AddAge` 方法时,我们可以直接修改这个实例的状态(即修改其 `Age` 属性)。
在 `main` 函数中,我们创建了一个名为 `person` 的 `Person` 类型实例,并在该实例上调用了这两个方法。
总结一下,接收者的用法主要是为了将方法与特定的数据类型关联。在方法内部可以通过接收者参数访问和操作数据类型的实例。带有接收者的方法可以在其数据类型的实例上调用,接收者参数会自动传入方法内。
十三、结构体定义:标签
如下结构体第三列,这是golang对结构体的支持,即标签,指从某种格式的文件中获取key对应的value值。
结构体标签被放置在反引号 (``) 中,紧跟字段类型之后。标签的格式通常是 `key:"value"`
key如JSON、YAML、XML 等。
以下定义了yaml和json两个,即从yaml中取或从json中取request_rate的值。
type Limits struct {
// Distributor enforced limits.
RequestRate float64 `yaml:"request_rate" json:"request_rate" category:"experimental"`
RequestBurstSize int `yaml:"request_burst_size" json:"request_burst_size" category:"experimental"`
IngestionRate float64 `yaml:"ingestion_rate" json:"ingestion_rate"`
}