Golang学习笔记二

[TOC]

类型

type

关键字type,用于定义结构体,接口,还可以自定义类型,定义类型别名等。

Go 语言不存在隐式类型转换,所有的转换都必须显式说明。类型转换:valueOfTypeB = typeB(valueOfTypeA)

type A int
type B int
var a A = 1
var b B = B(a)

通过type定义的新类型不会拥有原基础类型所附带的方法。

函数也是一中的类型,以函数签名作为类型:type typeFunc func (int, int) int

自定义类型不会继承原有类型的方法,但接口方法或组合类型的内嵌元素则保留原有的方法。

其他

new() 和 make()的区别 new(T) 为每个新的类型T分配一片内存,初始化为 0 并且返回类型*T,适用于值类型如数组和结构体;它相当于 &T{}。 make(T) 返回一个类型为 T 的初始值,它只适用于3种内建的引用类型:slice、map 和 channel。

func new(Type) *Type

func make(t Type, size ...IntegerType) Type

基础数据类型

复合数据类型

结构体

结构体是将不同类型命名字段序列打包为复合类型。字段名必须唯一,可用”_“补位,支持使用自身指针类型成员。字段名和排列顺序也是结构体类型的组成部分。

空结构struct{}。

匿名字段,没有名字,只有类型。也叫嵌入字段(类型),由于没有名字所以每种数据类型只能有一个匿名字段。

Go语言结构体中这种含匿名(内嵌)字段和内嵌结构体的结构,可近似地理解为面向对象语言中的继承概念。但Go中的“继承”是通过内嵌或者说组合来实现的。Go更崇尚用组合。

字段标签,用于对字段描述的元数据。也是类型的组成部分。在运行期间,可以通过反射获取标签信息。通常用作 数据格式校验,数据库关系映射。

type attr struct {}

type file struct {
	name string
	attr //匿名
}

//通过反射获取标签信息
func (sd *StructDemo) demo2() {
	type user struct {
		name string `昵称`
		sex byte `性别`
	}

	u := user{"Tony", 1}
	v := reflect.ValueOf(u)
	t := v.Type()

	for i, n := 0, t.NumField(); i < n; i++ {
		fmt.Printf("%s : %v\n", t.Field(i).Tag, v.Field(i))
	}
	//昵称 : Tony
	//性别 : 1
}

内存布局,结构体内存总是一次性分配。各字段在相邻的地址空间安定义的顺序排列。对于引用类型,字符串,指针,在结构体内存中只包含基本的头数据。

结构体和它所包含的数据在内存中是以连续内存空间的形式存在的,即使结构体中嵌套有其他的结构体,复合空间局部性,对CPU友好,这在性能上带来了很大的优势。在分配内存时,做字段对齐处理,通常以字段中最长的基础类型宽度为标准。

unsafe.Sizeof函数返回操作数在内存中的字节大小,参数可以是任意类型的表达式.

unsafe.Alignof 函数返回对应参数的类型需要对齐的倍数.

内存地址对齐,计算机在加载和保存数据时,如果内存地址合理地对齐的将会更有效率。由于地址对齐这个因素,一个聚合类型(结构体或数组)的大小至少是所有字段或元素大小的总和,或者更大因为可能存在内存空洞。内存空洞是编译器自动添加的没有被使用的内存空间,用于保证后面每个字段或元素的地址相对于结构或数组的开始地址能够合理地对齐。

使用 new 函数给一个新的结构体变量分配内存,返回指针,表达式 new(Type) 和 &Type{} 是等价的:

//new(S)为S类型的变量分配内存,并初始化(a = 0,b = 0.0),返回包含该位置地址的类型 *S的值。
//字面量的方式初始化,返回的对象本身
func demo4() {
	type S struct {
		a int
		b float64
	}
	s1 := new(S)
	s2 := S{}
	fmt.Printf("%T, %v\n", s1, s1) //*S, &{0 0}
	fmt.Printf("%T, %v\n", s2, s2) //S, {0 0}
}

用法:可将结构体设为不可导出,通过定义New函数来强制使用工厂方法。

标准库

fmt 包

Go 语言标准库中的 fmt 包提供了有关格式化输入输出的方法。 可以输出到控制台、文件、其他满足 io.Writer 接口的类型。

print

输出类的,以print为关键字,根据是否有前缀f,s,是否有后缀ln,f共9个函数:

后缀,用于控制输出格式:

  • 没有结尾修饰的,直接输出原字符串:Print、Fprint、Sprint
  • ln结尾的自动换行,如:Println、Fprintln、Sprintln
  • 以f结尾的按照指定格式化文本输出内容,如:Printf、Fprintf、Sprintf

前缀,用于区分输出到什么类型:

  • 缀是”F”, 则指定了输出到实现io.Writer接口的类型,如:Fprint、Fprintf、Fprintln
  • 前缀是”S”, 则是输出到字符串变量中,如:Sprint、Sprintf、Sprintln
  • 没有前缀的,输出内容到标准输出os.Stdout,如 Print、Printf、Println.

scan

用于输入,读取内容。规则与print类似。

后缀,用于控制输入格式:

  • 没有结尾修饰的, 读取内容时不关注换行,如:Scan、Fscan、Sscan
  • ln结尾的,读取到换行时停止,一次读取一行,如:Scanln、Fscanln、Sscanln
  • 以f结尾的,根据格式化文本读取,如:Scanf、Fscanf、Sscanf

前缀,控制从什么类型读取:

  • 缀是”F”, 读取到指定的指定了io.Reader,如:Fscan、Fscanf、Fscanln
  • 前缀是”S”, 从字符串读取,如:Sscan、Sscanf、Sscanln
  • 没有前缀的,从标准输入os.Stdin读取文本,如:Scan、Scanf、Scanln

格式化


//TODO 通用
//%v	默认格式输出类型的值,或者是使用其类型的String()方式输出的自定义值,若该方法存在
//%+v   添加字段名(如结构体)
//%T	使用Go语法输出的值的类型
//%%    字面上的百分号,并非值的占位符
//%#v	相应值的Go语法表示

//+     总打印数值的正负号;对于%q(%+q)保证只输出ASCII编码的字符。
//-     左对齐
//#     备用格式:为八进制添加前导 0(%#o),为十六进制添加前导 0x(%#x)或0X(%#X),为 %p(%#p)去掉前导 0x;对于 %q,若 strconv.CanBackquote 返回 true,就会打印原始(即反引号围绕的)字符串;如果是可打印字符,%U(%#U)会写出该字符的Unicode编码形式(如字符 x 会被打印成 U+0078 'x')。
//' '  (空格)为数值中省略的正负号留出空白(% d);以十六进制(% x, % X)打印字符串或切片时,在字节之间用空格隔开
//0     填充前导的0而非空格;对于数字,这会将填充移到正负号之后

//TODO 整数:
//%b	表示为二进制
//%c	该值对应的unicode码值
//%d	表示为十进制
//%o	表示为八进制
//%q	该值对应的单引号括起来的go语法字符字面值,必要时会采用安全的转义表示
//%x	表示为十六进制,使用a-f
//%X	表示为十六进制,使用A-F
//%U	表示为Unicode格式:U+1234,等价于"U+%04X"

//TODO 字符串和[]byte:
//%s	直接输出字符串或者[]byte
//%q	该值对应的双引号括起来的Go语法字符串字面值,必要时会采用安全的转义表示
//%x	每个字节用两字符十六进制数表示(使用a-f)
//%X	每个字节用两字符十六进制数表示(使用A-F)

//TODO 指针:
//%p	表示为十六进制,并加上前导的0x

//TODO 浮点数、复数:
//%b	无小数部分、二进制指数的科学计数法,如-123456p-78;参见strconv.FormatFloat
//%e	科学计数法,如-1234.456e+78
//%E	科学计数法,如-1234.456E+78
//%f	有小数部分但无指数部分,如123.456
//%F	等价于%f
//%g	根据实际情况采用%e或%f格式(以获得更简洁、准确的输出)
//%G	根据实际情况采用%E或%F格式(以获得更简洁、准确的输出)

宽度控制:宽度通过一个紧跟在百分号后面的十进制数指定,如果未指定宽度,则表示值时除必需之外不作填充。如%6s表示输出字符串宽度6 精度控制:精度通过宽度值后跟点号后跟的十进制数指定。如果未指定精度,会使用默认精度;如果点号后没有跟数字,表示精度为0。

func format1() {
	x := struct {
		id int
		name string
		isTrue bool
	}{
		3344, "Sword", true,
	}
	m := []byte("hello")

	//输出变量值
	fmt.Printf("%v\n", x)	//{3344 Sword true}
	//输出结构字段名
	fmt.Printf("%+v\n", x)	//{id:3344 name:Sword isTrue:true}
	fmt.Printf("%v\n", x.id)	//3344
	fmt.Printf("%%v : %v\n",  x)	//%v : {3344 Sword true}

	//输出变量的go语法表示
	fmt.Printf("%%#v : %#v\n",  x.name)	//%#v : "Sword"
	//%#v : struct { id int; name string; isTrue bool }{id:3344, name:"Sword", isTrue:true}
	fmt.Printf("%%#v : %#v\n",  x)
	fmt.Printf("%%#v : %#v\n",  m)	//%#v : []byte{0x68, 0x65, 0x6c, 0x6c, 0x6f}

	//指针
	fmt.Printf("%p\n", &x)	//0xc000062150

	fmt.Println("12345678901234567890")

	fmt.Printf("|%6d|%6d|\n", 12, 345)	//|    12|   345|
	//二进制输出
	fmt.Printf("%%b 二进制输出 : %b\n", x.id)		//111

	//%t 布尔值
	fmt.Printf("*****%t\n", x.isTrue)	//false

	//字符
	fmt.Printf("*****%c\n", "Sword"[1])	//S

	//字符串,宽度控制,输出占用长度10
	fmt.Printf("*****%10s***\n", "Jacky")	//*****     Jacky***
	fmt.Printf("*****%2s***\n", "Jacky")		//*****Jacky***

	//浮点进度,默认精度
	fmt.Printf("*****%f\n", math.Pi)		//*****3.141593
	//保留5位小数
	fmt.Printf("*****%.5f\n", math.Pi)	//*****3.14159
	//保留6位小数,不足补0
	fmt.Printf("*****%.6f\n", 122.3344)	//*****122.334400

	//要最对齐,使用 - 标志
	//左对齐
	fmt.Printf("|%-6.2f|%-6.2f|\n", 1.2, 3.45)	//|1.20  |3.45  |
	//右对齐,默认
	fmt.Printf("|%6.2f|%6.2f|\n", 1.2, 3.45)		//|  1.20|  3.45|
}

输入,读取:

func demo3() {
	var a int
	var b string
	//在os.Stdin 按照一定格式输入才能读到变量a,b中
	fmt.Scan(&a, &b)	//输入 1 2
	fmt.Scanf("a=%d, b=%d\n", &a, &b) //输入 a=1, b=12
	fmt.Scanf("%d %d", &a, &b)//输入 1 2
	fmt.Println(a, b)
}


func demo4() {
	var a string
	var b int
	//从字符串中读取
	fmt.Sscan("11 helloworld 1 hehe", &a, &b)
	fmt.Println(a, b)	//11 helloworld
	fmt.Sscanf("11 helloworld 1 hehe", "11 helloworld %d %s", &a, &b)
	fmt.Println(a, b) // 1 hehe
}

实现接口自定义格式:

type Xx struct {
	Name string
}

//法打印对象时,会调用这个方法
//实现fmt.Stringer
func (x Xx) String() string {
	return fmt.Sprintf("**%s**", x.Name)
}

//%#v 触发
//实现fmt.GoStringer接口
func (x Xx) GoString() string {
	return x.Name + " GoString"
}

func demo5() {
	x := Xx{"Sword"}
	fmt.Print(x)		//**Sword**
	fmt.Printf("%#v", x)	//Sword GoString
}

log 包

Go语言标准日志包。 源代码:

type Logger struct {
	mu     sync.Mutex // ensures atomic writes; protects the following fields
	prefix string     // prefix to write at beginning of each line
	flag   int        // properties
	out    io.Writer  // destination for output
	buf    []byte     // for accumulating text to write
}

// New creates a new Logger
func New(out io.Writer, prefix string, flag int) *Logger {
    return &Logger{out: out, prefix: prefix, flag: flag}
}

// Println calls Output to print to the standard logger.
// Arguments are handled in the manner of fmt.Println.
func Println(v ...interface{}) {
	std.Output(2, fmt.Sprintln(v...))
}

使用:

//直接打印日志
func demo1() {
	log.Print("日志")	//2018/02/11 00:13:02 日志
	log.Println("日志")	//2018/02/11 00:13:02 日志
	log.Printf("%#v\n", demo1)	//2018/02/11 00:16:12 (func())(0x4a62b0)
}

//判断目录是否存在
func isDirExist() bool {
	path := "./Runtime"
	fs, err:= os.Stat(path)
	if err != nil {
		return os.IsExist(err)
	}
	return fs.IsDir()
}

//写入日志文件
func demo2() {
	if !isDirExist() {
		os.Mkdir("./Runtime", 0666)
	}

	y, m, d := time.Now().Date()
	logfile := fmt.Sprintf("%d-%d-%d.log", y, m, d)
	logPath := "./Runtime/" + logfile
	fd ,err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
	defer fd.Close()
	if err != nil {
		log.Fatalln(err)
	}

	logger := log.New(fd, "[日志前缀]", log.LstdFlags)
	logger.Println("日志日志-Brave Soul")
}

//[日志前缀]2018/02/11 19:46:15 日志日志-Brave Soul
//[日志前缀]2018/02/11 19:46:16 日志日志-Brave Soul
//[日志前缀]2018/02/11 19:46:16 日志日志-Brave Soul

OS包

在os包下,有 exec,signal,user三个子包。

文件与IO

IO的操作在Go 语言很多标准库。主要的如 io, ioutil, bufio这三个标准库。 在 io 包中最重要的是两个接口:Reader 和 Writer 接口。只要实现了Reader 和 Writer 接口这两个接口,就有了 IO 的功能

package io

type Reader interface {
	Read(p []byte) (n int, err error)
}

type Writer interface {
	Write(p []byte) (n int, err error)
}

type Closer interface {
	Close() error
}

IO 操作分类 io 包提供基本的接口,如Reader 、Writer、Closer 、Seeker等,其他如os.File、ioutil, bufio包都实现了这些接口。常见:

  1. os.File 对象,提供了基础的文件操作。
  2. io/ioutil 封装一些实用的 I/O 函数。
  3. bufio包实现带缓冲I/O。
  4. fmt 包现格式化 I/O。

os包文件操作

os中不同函数打开(创建)文件的操作,如os.Open,os.Create最终还是通过OpenFile来实现,而OpenFile由编译器根据系统的情况来选择不同的底层功能来实现。

//这三个函数都会返回一个文件对象。
os.Open(name string) //使用只读模式打开文件;
os.Create(name string) //创建新文件,如文件存在则原文件内容会丢失;
os.OpenFile(name string, flag int, perm FileMode) //可以指定flag和FileMode 

//flag
const (
	// Exactly one of O_RDONLY, O_WRONLY, or O_RDWR must be specified.
	//必须指定O_RDONLY、O_WRONLY或O_RDWR中的一个。
	O_RDONLY int = syscall.O_RDONLY // open the file read-only.
	O_WRONLY int = syscall.O_WRONLY // open the file write-only.
	O_RDWR   int = syscall.O_RDWR   // open the file read-write.
	// The remaining values may be or'ed in to control behavior.
	
	O_APPEND int = syscall.O_APPEND // append data to the file when writing.
	O_CREATE int = syscall.O_CREAT  // create a new file if none exists.
	O_EXCL   int = syscall.O_EXCL   // used with O_CREATE, file must not exist.
	O_SYNC   int = syscall.O_SYNC   // open for synchronous I/O.
	O_TRUNC  int = syscall.O_TRUNC  // truncate regular writable file when opened.
)

os.Open,os.Create, os.OpenFile 打开(创建)返回文件对象os.File,后续读写,关闭等一些操作基于os.File对象。

在打开文件时,必须指定O_RDONLY、O_WRONLY或O_RDWR其中的一个。其他如O_APPEND ,O_CREATE ,O_TRUNC 等配合一起使用。

func osFileDemo() {
	path := "./data.txt"
	f, _ := os.OpenFile(path, os.O_RDWR, 0666)
	defer f.Close()
	b := make([]byte, 1024)
	_, err := f.Read(b)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(string(b))
}

ioutil包文件操作

ioutil包基于os.Open、os.File、bytes.Buffer做了封装,使用较为方便。

//ReadAll从r读取数据,直到出现错误或EOF
func ReadAll(r io.Reader) ([]byte, error) {}

//ReadFile读取整个文件,它不把EOF从Read作为一个错误
func ReadFile(filename string) ([]byte, error){}

//WriteFile将数据写入指定的文件,文件不存在则使用perm权限创建,存在则先清空。
func WriteFile(filename string, data []byte, perm os.FileMode) error{}
func ReadDir(dirname string) ([]os.FileInfo, error){}

ioutil.ReadFile的读取效率比ioutil.ReadAll更高,ReadFile会先计算出file文件的size,再传入readAll(f, n),在初始化对应capacity大小的buff,来读取字节流。

//ioutil.ReadFile(path)
//`ioutil.ReadFile的读取效率比ioutil.ReadAll更高`,ReadFile会先计算出file文件的size,再传入readAll(f, n)
func ioDemo1() {
	path := "./data.txt"
	cxt, err := ioutil.ReadFile(path)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", cxt)
}

//ioutil.ReadAll
func ioDemo2() {
	path := "./data.txt"
	f, err := os.OpenFile(path, os.O_RDONLY, 0777)
	defer f.Close()
	if err != nil {
		log.Fatal(err)
	}
	cxt, err := ioutil.ReadAll(f)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", cxt)
}

//ioutil.WriteFile
func ioDemo3() {
	cxt := []byte("Space Traveller")
	path := "./io.txt"
	if err := ioutil.WriteFile(path, cxt, 0666); err != nil {
		log.Fatal(err)
	}
	fmt.Println("success")
}

ioutil.ReadAll 可以读取所有io.Reader 流。在网络连接中,也经常使用ioutil.ReadAll 来读取流。

bufio包

bufio包实现了IO的缓冲操作,通过内嵌io.Reader、io.Writer接口,新建了Reader ,Writer 结构体。同时也实现了Reader 和 Writer 接口。 bufio.Reader :

type Reader struct {
	buf          []byte
	rd           io.Reader // reader provided by the client
	r, w         int       // buf read and write positions
	err          error
	lastByte     int // last byte read for UnreadByte; -1 means invalid
	lastRuneSize int // size of last rune read for UnreadRune; -1 means invalid
}

使用:

func bufioDemo1() {
	path := "./data.txt"
	f, err := os.OpenFile(path, os.O_RDONLY, 0777)
	defer f.Close()
	if err != nil {
		panic(err)
	}
	//bufioRead(f)
	//bufioReadLine(f)
	bufioReadstring(f)
}

//bufio Read
func bufioRead(f io.Reader) {
	buf := make([]byte, 1024)
	r := bufio.NewReader(f)
	for {
		n, err := r.Read(buf)
		if err != nil && err != io.EOF{
			log.Fatal(err)
		}
		if n == 0 {
			break
		}
		fmt.Print(string(buf[:n])) //注意这里切片的范围
	}
	fmt.Println()
}

//bufio ReadLine 按行读取
func bufioReadLine(f io.Reader) {
	r := bufio.NewReader(f)
	for {
		data, _, err := r.ReadLine()
		if err == io.EOF {
			break
		}
		fmt.Println(string(data))
	}
}

//bufio ReadString
func bufioReadstring(f io.Reader) {
	r := bufio.NewReader(f)
	for {
		data, err := r.ReadString('\n')
		if err == io.EOF {
			break
		}
		fmt.Print(data)
	}
}

bufio.Writer源代码:

type Writer struct {
	err error
	buf []byte
	n   int
	wr  io.Writer
}

// NewWriter returns a new Writer whose buffer has the default size.
func NewWriter(w io.Writer) *Writer {
	return NewWriterSize(w, defaultBufSize)
}

// NewWriterSize returns a new Writer whose buffer has at least the specified
// size. If the argument io.Writer is already a Writer with large enough
// size, it returns the underlying Writer.
func NewWriterSize(w io.Writer, size int) *Writer {
	// Is it already a Writer?
	b, ok := w.(*Writer)
	if ok && len(b.buf) >= size {
		return b
	}
	if size <= 0 {
		size = defaultBufSize
	}
	return &Writer{
		buf: make([]byte, size),
		wr:  w,
	}
}

使用示例:

func bufioWrite() {
	cxt := "Space Traveller\n"
	path := "./io.txt"

	//没有就创建,有则追加写入
	//Exactly one of O_RDONLY, O_WRONLY, or O_RDWR must be specified.
	f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	defer f.Close()
	if err != nil {
		log.Fatal(err)
	}
	w := bufio.NewWriter(f)
	n, err := w.WriteString(cxt)
	if err != nil {
		log.Fatal(err)
	}
	w.Flush()
	fmt.Println(n)
}

unsafe 包

在C语言中通过指针,可以通过指针加偏移量的操作,直接在地址中,修改,访问变量的值。在Go 语言中不支持指针运算.其实通过unsafe包,我们可以完成类似的操作。

在unsafe包中,只提供了3个函数,两个类型。

type ArbitraryType int
type Pointer *ArbitraryType

func Alignof(x ArbitraryType) uintptr  返回变量对齐字节数量
func Offsetof(x ArbitraryType) uintptr 
func Sizeof(x ArbitraryType) uintptr	  返回操作数在内存中的字节大小

unsafe.Sizeof函数, 返回操作数在内存中的字节大小,参数可以是任意类型的表达式,但是它并不会对表达式进行求值。

unsafe.ArbitraryType

ArbitraryType是int的一个别名,在Go中对ArbitraryType赋予特殊的意义。代表一个任意Go表达式类型。Go 语言的指针类型长度与int类型长度,在内存中占用的字节数是一样的。

unsafe.Pointer

大多数指针类型会写成*T,表示是“一个指向T类型变量的指针”。

Pointer 是ArbitraryType指针类型为基础的新类型。

unsafe.Pointer 是int指针类型的一个别名,在Go中可以把Pointer类型,理解成任何指针的父类型。它可以包含任意类型变量的地址,一个普通的T类型指针可以被转化为unsafe.Pointer类型指针,并且一个unsafe.Pointer类型指针也可以被转回普通的指针,被转回普通的指针类型并不需要和原始的T类型相同。

unsafe.Pointer是特别定义的一种指针类型(译注:类似C语言中的void*类型的指针)。

通常Pointer不能参与指针运算,比如在某个指针地址上加上一个偏移量。需要将Pointer类型先转换成uintptr类型,做完地址加减法运算后,再转换成Pointer类型。通过*操作达到取值、修改值的目的。

unsafe.Pointer在Go 语言中是用于各种指针相互转换的桥梁,即将其他指针转为Pointer,再由Pointer转为其他指针,或者由Pointer转换为 uintptr 进行指针运算。

uintptr是Go 语言的内置类型,是一个无符号的整型数,足以保存任何类型的地址。

uintptr和unsafe.Pointer的区别

  • unsafe.Pointer只是单纯的通用指针类型,用于转换不同类型指针,它不可以参与指针运算;
  • uintptr是用于指针运算的,GC 不把 uintptr 当指针,也就是说 uintptr 无法持有对象, uintptr 类型的目标会被回收;
  • unsafe.Pointer 可以和 普通指针 进行相互转换;
  • unsafe.Pointer 可以和 uintptr 进行相互转换。但是将uintptr转为unsafe.Pointer指针可能会破坏类型系统。

通过unsafe.Pointer修改struct字段的值。需要提前知道结构体V的成员布局,然后根据字段计算偏移量,以及考虑对齐值,最后通过指针运算得到成员指针,利用指针达到修改成员值得目的。结构体的成员在内存中的分配是一段连续的内存,结构体中第一个成员的地址就是这个结构体的地址,我们也可以认为是相对于这个结构体偏移了0。

func demo2() {
	var x struct {
		a bool
		b int16
	}

	//普通指针&x 先转为Pointer,再转为uintptr,再加上b的地址偏移量,再获取b对象指针
	//只有uintptr才能进行指针运算
	pb := (*int16)(unsafe.Pointer(uintptr(unsafe.Pointer(&x)) + unsafe.Offsetof(x.b)))
	*pb = 55
	println(x.b)
}

示例:

type D struct {
	A int32
	b int64
	c int32
}

//实现fmt.Stringer接口
func (d D) String() string {
	fmt.Printf("A=%d\n", d.A)
	fmt.Printf("b=%d\n", d.b)
	fmt.Printf("c=%d\n", d.c)
	return ""
}

func demo3() {
	var ptr *D = &D{100, 200, 99}
	fmt.Printf("%T, %v\n", ptr, ptr)  //*D, &{100 200}

	//通过Pointer转为其他类型指针
	p1 := (*int32)(unsafe.Pointer(ptr))
	fmt.Printf("%T, %v\n", p1, p1)  //*int32, 0xc000016160

	fmt.Println("指针uintptr值:", uintptr(unsafe.Pointer(ptr))) //指针uintptr值: 824633811296

	*p1 = int32(99)	//改变了A的值

	//通过指针运算给c赋值
	pc := (*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) + unsafe.Offsetof(ptr.c)))
	*pc = 999

	fmt.Println(ptr)
	//A=99
	//b=200
	//c=999
}

地址对齐

为了更高效,计算机在加载和保存数据时,会对内存地址合理地对齐处理。由于地址对齐这个因素,一个聚合类型(结构体或数组)的大小至少是所有字段或元素大小的总和,或者更大因为可能存在内存空洞。内存空洞是编译器自动添加的没有被使用的内存空间,用于保证后面每个字段或元素的地址相对于结构或数组的开始地址能够合理地对齐(译注:内存空洞可能会存在一些随机数据,可能会对用unsafe包直接操作内存的处理产生影响)。

unsafe.Alignof 函数返回对应参数的类型需要对齐的倍数.

unsafe.Offsetof 函数的参数必须是一个字段 x.f, 然后返回 f 字段相对于 x 起始地址的偏移量, 包括可能的空洞.

unsafe.Sizeof函数返回操作数在内存中的字节大小,参数可以是任意类型的表达式,但是它并不会对表达式进行求值。Sizeof函数返回的大小只包括数据结构中固定的部分,例如字符串对应结构体中的指针和字符串长度部分,但是并不包含指针指向的字符串的内容。

Go语言的规范并没有要求一个字段的声明顺序和内存中的顺序是一致的,所以理论上一个编译器可以随意地重新排列每个字段的内存位置,且不同的顺序,由于内存对齐,占用的内存空间也不同。

func align() {
	//结构体的字段顺序是类型定义的一部分X1, X2 是不同类型
	type X1 struct {
		a bool
		b int16
		c int64
	}

	type X2 struct {
		a bool
		c int64
		b int16
	}

	x1 := X1{}
	x2 := X2{}

	println("unsafe.Sizeof(x1) = ", unsafe.Sizeof(x1))	//unsafe.Sizeof(x1) =  16
	println("unsafe.Sizeof(x2) = ", unsafe.Sizeof(x2))	//unsafe.Sizeof(x2) =  24
}

net/http

net/http包较为主要的文件: server.go,client.go, request.go, response.go。

server

server.go中的接口:

//Handler接口用于响应HTTP请求
type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

type ResponseWriter interface {
	Header() Header
	Write([]byte) (int, error)
	WriteHeader(statusCode int)
}

type Flusher interface {
	// Flush sends any buffered data to the client.
	Flush()
}

type CloseNotifier interface {
	CloseNotify() <-chan bool
}

type Hijacker interface {
	Hijack() (net.Conn, *bufio.ReadWriter, error)
}

最终http服务是通过实现ServeHTTP(ResponseWriter, *Request)来达到服务端接收客户端请求并响应的目的。

HTTP 构建的网络应用只要关注两个端—客户端(Clinet)和服务端(Server)。来自 Clinet 的 Request与来自Server端的Response的交互。

结构体:

//ServeMux为Go中的http请求多路复用器,它将每一个请求的URL和一个注册模式的列表进行匹配,
//然后调用和URL最匹配的模式的处理请求
//ServeMux主要作用就是完成路由功能
type ServeMux struct {
	mu    sync.RWMutex
	m     map[string]muxEntry
	es    []muxEntry // slice of entries sorted from longest to shortest.
	hosts bool       // whether any patterns contain hostnames
}

//muxEntry用于存放handler和路由规则
type muxEntry struct {
	h       Handler
	pattern string
}

//根据给定的请求,返回处理该请求的handler
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {}

// 将handler注册为指定的模式,如果该模式已经有了handler,则会出错panic。
func (mux *ServeMux) Handle(pattern string, handler Handler) {}

// HandleFunc为给定的模式注册处理函数。
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)){}

// 将最接近请求url模式的handler分配给指定的请求。 
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request){}

Handle和HandleFunc就是用来往DefaultServeMux的map[string]muxEntry中增加对应的handler和路由规则。

//创建了一个server对象,并调用server.ListenAndServe启动监听端口
http.ListenAndServe(":8080", nil)

//客户端请求过来,Go就开启一个goroutine 处理请求
func (srv *Server) Serve(l net.Listener) error {
	...
	for {
	    rw, e := l.Accept()
	    ...
	    c := srv.newConn(rw)
		c.setState(c.rwc, StateNew) 
		//为每个请求开启一个goroutine 处理请求
		go c.serve(ctx)
	}
}

服务端实例:

func webServer() {
	//注册处理函数
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "乱世孤旅 url=%q\n", r.URL.Path)
	})
	http.HandleFunc("/t", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "斜风细雨不须归")
	})

	fmt.Println("listen on port :88")
	//创建了一个server对象
	// The handler is typically nil, in which case the DefaultServeMux is used.
	log.Fatal(http.ListenAndServe(":88", nil))
}


//curl http://127.0.0.1:88/HandleFunc
//curl http://127.0.0.1:88/Handle
func webServerMux() {
	mux := http.NewServeMux()

	//mux.HandleFunc也是基于mux.Handle实现
	mux.HandleFunc("/HandleFunc", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "HandleFunc注册")
	})

	//http.HandlerFunc函数类型 实现了handler接口
	mux.Handle("/Handle", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Handle注册")
	}))

	//mux 实现了handler接口 才能作为参数传入
	log.Fatal(http.ListenAndServe(":88", mux))
}

自定义处理器

//自定义处理器,只要实现handler即可
type customHandler struct {}

func (c *customHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("自定义handler"))
}

func serverCustomHandler() {
	mux := http.NewServeMux()
	//或者传入函数转为http.HandlerFunc函数类型,该类型实现了handler接口
	mux.Handle("/", new(customHandler))
	log.Fatal(http.ListenAndServe(":88", mux))
}

client

Client具有Do,Get,Head,Post以及PostForm等方法。 其中Do方法可以对Request进行一系列的设定,而其他的对request设定较少。如:

http.Get() //发送简单的get请求
http.Post() //发送post请求
http.PostForm() //表单post请求
http.Client.Do() //http.NewRequest和http.Client来灵活配置任何http请求

http.Get,http.Post,等最终也是基于http.Client.Do()方法发送请求。 示例:


//通过http.NewRequest和http.Client来配置请求
//http.Client与http.NewRequest结合可以模拟任何http Request请求,方法是Do。
func demo1() {
	url := "https://golang.google.cn/"
	req,err := http.NewRequest("GET", url, nil)
	if err != nil {
		log.Fatal(err)
	}

	//设置请求头
	req.Header.Set("Accept", "text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8")
	req.Header.Set("Accept-Language", "zh-CN, zh;q=0.8")
	req.Header.Set("Connection", "keep-alive")
	req.Header.Set("Accept-Encoding", "gzip, deflate, sdch")

	//发送请求
	client := &http.Client{}
	res, err := client.Do(req)
	defer res.Body.Close()
	if err != nil {
		log.Fatal(err)
	}

	if res.StatusCode == http.StatusOK {
		//打印响应头
		h := res.Header
		head, _ := json.MarshalIndent(h, "", "    ")
		fmt.Println(string(head))
		fmt.Println("\n\n\n")

		//解压
		body, err:= gzip.NewReader(res.Body)
		defer body.Close()
		if err != nil {
			log.Fatal(err)
		}

		//读取响应body
		ctx, err := ioutil.ReadAll(body)
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println(string(ctx))
	} else {
		fmt.Println(res.StatusCode)
	}
}

//http.Get()  发送请求
func demo2() {
	url := "https://golang.google.cn/"
	res, err := http.Get(url)
	if err != nil {
		fmt.Fprintf(os.Stderr, "fetch: %v\n", err)
	}
	b, err := ioutil.ReadAll(res.Body)
	res.Body.Close()
	if err != nil {
		fmt.Fprintf(os.Stderr, "fetch: reading %s: %v\n", url, err)
		os.Exit(-1)
	}
	fmt.Printf("%s\n", b)
}

中间件

在http服务中,中间件通常用与处理一些公共的逻辑,如请求统计,日志处理,异常处理,限速,验证,等通用逻辑等。中间件通常进行一些包裹函数的行为,为被包裹函数提供添加一些功能或行为。即把一个处理器函数handler传入到中间件,增加一些通用处理,再返回一个handler。

中间件把业务代码和非业务代码分离, 非业务的代码通常都是在http请求处理前和响应完成之后做一些事情。这个很适合用中间件来包装处理。

func middleware(next http.Handler) http.Handler{
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
        // 执行handler之前的逻辑
        next.ServeHTTP(w, r)
        // 执行完毕handler后的逻辑
    })
}


//echo框架 通过自定义中间件统一添加响应头字段
func ServerHeader(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		c.Response().Header().Set(echo.HeaderServer, "added by middleware")
		return next(c)
	}
}

net/http/pprof

socket

推荐一个调试工具,被誉为网络工具中有“瑞士军刀”的NetCat。 连接服务端99端口:nc 127.0.0.1 99 监听端口 99 :nc -l 127.0.0.1 99

web

对于简单的web应用,业务路由等不复杂的话,net/http包足以满足需求,默认的net/http包中的mux不支持带参数的路由,对于复杂的应用,建议还是使用web框架。

Go的Web框架大致可以分为两类:Router框架MVC类框架

如Beego、echo,gin等框架。框架的选择要结合业务特点,开发人员技术体系,框架的社区活跃度,技术生态等因素权衡。框架只是为了帮我们更快速地构建业务,提升开发效率。掌握好编程语言和计算机等核心知识才能适应变化。

路由

httprouter

标准库net/http 路由mux有其不足之处。httprouter 是一个高性能、可扩展、功能强大的HTTP路由。 httprouter 为所有的HTTP Method 提供了快捷的使用方式。httprouter提供的命名参数的支持,可以很方便的帮助我们开发Restful API。

func server1() {
	router := httprouter.New()
	router.GET("/", handler1)
	router.POST("/p", handler2)
	log.Fatal(http.ListenAndServe(":88", router))
}

func handler1(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	w.Write([]byte("handler1111111\n"))
}

func handler2(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	w.Write([]byte("handler2222222\n"))
}

请求校验

import (
	"fmt"
	"gopkg.in/go-playground/validator.v9"
	"log"
)

//更多校验规则使用,参考源码中使用示例
type Params struct {
	Uid int `validate:"min=10"`
	Email string `validate:"email"`			//邮箱校验
	Name string `validate:"required"`		//必填参数
	Range int `validate:"min=10,max=100"` 	//最小值,最大值
	Max  int `validate:"max=100"`
	Required  string `validate:"required"`	//必填参数
	Len       string `validate:"len=10"`	//字符串长度10
}

func vDemo1() {
	v := validator.New()

	p := Params{
		Uid:   12121,
		Email: "hxx@cc.com",
		Name: "Sword",
		Range: 11,
		Required: "必填参数",
		Len: "abcdefgaaa",
	}

	err := v.Struct(p)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("%v\n", p)
}

其他

内存

用户空间内存

  • 程序区:存放程序语句
  • 静态存储区:全局变量,局部静态变量,在程序开始执行时给全局变量分配存储区,程序执行完毕就释放。
  • 动态存储区:函数形参变量(在调用函数时给形参分配存储空间),局部动态变量(autoregister),函数调用现场保护和返回地址等。

堆内存与栈内存

栈(satck):

  • 由操作系统自动分配和释放,程序员无法控制,速度极快。
  • 栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址。
  • 压栈出栈都有专门的处理器指令执行,栈的效率比较高。
  • 程序运行时由编译器自动分配的一块连续的内存,存放函数的参数值,局部变量的值等。
  • 栈区向地址减小的方向增长。

堆(heap):

  • 内存开辟另一块不连续的存储区域。一般由程序员分配释放,若程序员不释放,程序结束时由系统回收。亦称动态内存分配。
  • 地址不连续,容易碎片化。
  • 是由malloc、new等关键字分配的内存,(不同语言不同,go中在堆还是栈上由编译器决定)一般速度比较慢,不过用起来最方便,堆都是动态分配的,没有静态分配的堆。
  • 堆的效率比栈要低得多。

编译

内联调用: (inlining call)不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处,适用于功能简单,规模较小又使用频繁的函数。内联是以代码复制为代价,省去了函数调用的开销,从而提高函数的执行效率,但是将使程序的总代码量增大,消耗更多的内存空间。

输出编译器优化信息:-gcflags -m

import (
	"fmt"
)

func main() {
	runList()
}

func runList() {
	a1()
}

func a1() {
	i := 10
	fmt.Println("func a1", i)
	a2(i)
}

func a2(i int) {
	fmt.Println("func a2", i)
}

//编译输出优化信息 go build -gcflags -m main.go
./main.go:24:6: can inline a2
./main.go:25:13: inlining call to fmt.Println
./main.go:20:13: inlining call to fmt.Println
./main.go:21:4: inlining call to a2
./main.go:21:4: inlining call to fmt.Println
./main.go:12:6: can inline runList
./main.go:8:6: can inline main
./main.go:9:9: inlining call to runList
./main.go:20:14: "func a1" escapes to heap
./main.go:20:14: i escapes to heap
./main.go:20:13: a1 []interface {} literal does not escape
./main.go:20:13: io.Writer(os.Stdout) escapes to heap
./main.go:21:4: "func a2" escapes to heap
./main.go:21:4: i escapes to heap
./main.go:21:4: a1 []interface {} literal does not escape
./main.go:21:4: io.Writer(os.Stdout) escapes to heap
./main.go:25:14: "func a2" escapes to heap
./main.go:25:14: i escapes to heap
./main.go:25:13: a2 []interface {} literal does not escape
./main.go:25:13: io.Writer(os.Stdout) escapes to heap
<autogenerated>:1: (*File).close .this does not escape

垃圾回收

Go语言运行时有一个独立的进程,即垃圾收集器(GC),会专门处理垃圾回收。通过显式调用 runtime.GC()可主动进行垃圾回收。当内存资源不足时调用 runtime.GC(),立即进行GC操作,但此时程序可能会有短时的性能下降。 runtime.SetFinalizer() runtime.ReadMemStats()

内存,GC 性能 编译 socket grpc 架构 汇编、cgo 标准库 优秀源码学习

更新时间: