• 类型断言

x.T x接口类型值,T类型。

// 变量类型不是任何的接口类型时,我们就需要先把它转成某个接口类型的值
// 可以使用
interface{}(x).(map[int]string)
interface{}(x).([]string)
  • 数组和切片
x := 11
s := []int{3, 6, 8, 11, 45, 7, 47, 49, 55, 59, 70} 
//sort.Slice(s, func(i, j int) bool {
// return s[i] < s[j]
//})
sort.Ints(s)
fmt.Println(s)
sort.Sort(sort.Reverse(sort.IntSlice(s)))
//fmt.Println(s)
// 顺序数组,小于i全部不满足,大于i全部满足。返回满足条件的最小i
// 返回true向左,false向右
pos := sort.Search(len(s), func(i int) bool {
   fmt.Println(s[i])
   return s[i] <= x
})
fmt.Println(pos)
if pos < len(s) && s[pos] == x {
   fmt.Println(x, " 在 s 中的位置为:", pos)
} else {
   fmt.Println("s 不包含元素 ", x)
}
  • 字典

    map[string]int

  1. key 必须为值类型,不能为切片,方法,字典

  2. 按key生成哈希值,所以速度与key的长度相关

  3. 非并发安全。 可以加锁操作或者用sync.Map。

  • 通道1
  1. 缓冲通道:发送副本值到通道, 接收方从通道取。

  2. 空通道(有接收方等待时)或非缓冲通道:直接复制发送副本到接受方

  3. 发送方控制通道关闭。 发送完,即可关闭。接收方仍可取,取完即关闭。

  4. 浅复制,数组全复制(值类型)。切片即指针复制

  • 通道2

select case 条件,先全部求值,再选中。多个符合,伪随机。

select语句中发现某个通道已关闭,那么应该怎样屏蔽掉它所在的分支?

答:将其置为非缓存通道或 nil

  • 函数

一等公民

参数值类型,copy,不影响原值

参数引用类型,影响原值

complexArray1 := [3][]string{
  []string{"d", "e", "f"},
  []string{"g", "h", "i"},
  []string{"j", "k", "l"},
}

这种参数,第一层是数组,修改不影响原值。第二层切片元素修改,影响原值

  • 结构体

嵌入字段,同名的就会屏蔽,即使一个为方法,一个为字段

嵌入字段或方法,有同名的,越底层越可能被屏蔽

如果处于同一个层级的多个嵌入字段拥有同名的字段或方法,那么从被嵌入类型的值那里,选择此名称的时候就会引发一个编译错误,因为编译器无法确定被选择的成员到底是哪一个

一个自定义数据类型的方法集合中仅会包含它的所有值方法,而该类型的指针类型的方法集合却囊括了前者的所有方法,包括所有值方法和所有指针方法

我的总结:

直接调用,可以调用值方法,也可以调用指针方法。

实现接口的类型。值申明仅可调用值方法。地址申明可以调用值或指针方法。

  • 接口

接口有实际类型及实际值。具体看赋值给它的变量

dog := Dog{"little pig"}
var pet Pet = dog
dog.SetName("monster")
(&dog).SetName("monster") //或者这样

pet.name 仍然是 little pig。 1.因为赋值操作,给的是副本,与原值脱离。

2.而且接口类型pet 存储的是 该变量 dog 的动态类型会与它的动态值一起被存储在一个专用的数据结构中(即iface的实例)

怎样才能让一个接口变量的值真正为nil呢?要么只声明它但不做初始化,要么直接把字面量nil赋给它

如果我们把一个值为nil的某个实现类型的变量赋给了接口变量,那么在这个接口变量上仍然可以调用该接口的方法吗?如果可以,有哪些注意事项?如果不可以,原因是什么?

答:可以调用。但是请注意,这个被调用的方法在此时所持有的接收者的值是nil。因此,如果该方法引用了其接收者的某个字段,那么就会引发 panic!

  • 指针

不可变的值不可寻址。常量、基本类型的值字面量、字符串变量的值、函数以及方法的字面量都是如此。其实这样规定也有安全性方面的考虑。

绝大多数被视为临时结果的值都是不可寻址的。算术操作的结果值属于临时结果,针对值字面量的表达式结果值也属于临时结果。但有一个例外,对切片字面量的索引结果值虽然也属于临时结果,但却是可寻址的。

若拿到某值的指针可能会破坏程序的一致性,那么就是不安全的,该值就不可寻址。由于字典的内部机制,对字典的索引结果值的取址操作都是不安全的。另外,获取由字面量或标识符代表的函数或方法的地址显然也是不安全的

&a <=> unsafe.Pointer <=> uintptr ,应用到地址转换

// unsafe.Offsetof是uintptr类型的,查看变量相对地址偏移量
n := Num{i: "EDDYCJY", j: 1}
nPointer := unsafe.Pointer(&n)
niPointer := (*string)(nPointer)
*niPointer = "煎鱼"
njPointer := (*int64)(unsafe.Pointer(uintptr(nPointer) + unsafe.Offsetof(n.j)))
*njPointer = 6
fmt.Printf("n.i: %s, n.j: %d", n.i, n.j)
  • go语句

主goroutine 运行完,即结束,可能go 语句还没运行完。

func main() {
   m := 10
   ch := make(chan struct{})
   for i := 0; i < m; i++ {
      go func(i int) {
         ch <- struct{}{}
         fmt.Println(i)
      }(i)
      <-ch
   }
   close(ch)
}




func main2() {
   var count uint32
   trigger := func(i uint32, fn func()) {
      for {
         if n := atomic.LoadUint32(&count); n == i {
            fn()
            atomic.AddUint32(&count, 1)
            break
         }
         time.Sleep(time.Nanosecond)
      }
   }
   var m uint32 = 10
   for i := uint32(0); i < m; i++ {
      go func(i uint32) {
         fn := func() {
            fmt.Println(i)
         }
         trigger(i, fn)
      }(i)
   }
   trigger(10, func() {})
}
  • for、if、switch
numbers2 := [...]int{1, 2, 3, 4, 5, 6}
maxIndex2 := len(numbers2) - 1
for i, e := range numbers2 {
  if i == maxIndex2 {
    numbers2[0] += e
  } else {
    numbers2[i+1] += e
  }
}
fmt.Println(numbers2)

1.range表达式的结果值是会被复制的,实际迭代时并不会使用原值. 这里的 range 右边只会取值一次,在遍历一开始。所以修改了numbers2[1]后,遍历到 i=1时,numbers2[1]还取旧值2.

switch:

一旦某个case子句被选中,其中的附带在case表达式后边的那些语句就会被执行。与此同时,其他的所有case子句都会被忽略。和select不同

fallthrough可以穿越到下一个case执行语句

// 可以通过执行
value2 := [...]int8{0, 1, 2, 3, 4, 5, 6}
switch value2[4] {
case 0, 1:
  fmt.Println("0 or 1")
case 2, 3:
  fmt.Println("2 or 3")
case 4, 5, 6:
  fmt.Println("4 or 5 or 6")
}

1.如果case表达式中子表达式的结果值是无类型的常量,那么它的类型会被自动地转换为switch表达式的结果类型,又由于上述那几个整数都可以被转换为int8类型的值,所以对这些表达式的结果值进行判等操作是没有问题的 2.case表达式的子表达式的结果值不能重复

  • 错误处理

1.类型断言 或 switch语句判断错误

func underlyingError(err error) error {
  switch err := err.(type) {
  case *os.PathError:
    return err.Err
  case *os.LinkError:
    return err.Err
  case *os.SyscallError:
    return err.Err
  case *exec.Error:
    return err.Err
  }
  return err
}

2.可以创建 立体的错误类型体系 和 扁平的错误值列表

  • panic、defer、recover

1.报错信息的解读:panic 详情会在控制权传播的过程中,被逐渐地积累和完善,并且,控制权会一级一级地沿着调用栈的反方向传播至顶端

2.创建panic,参数是空接口类型,所以只要是能够序列化的类型都可以

3.可以在可能出现panic的位置前,使用defer + recover 处理错误

func main() {
		defer func() {
			fmt.Println("Enter defer function.")
			// recover函数的正确用法。
			if p := recover(); p != nil {
				fmt.Printf("panic: %s\n", p)
			}
		}()
		// recover函数的错误用法。
		fmt.Printf("no panic: %v\n", recover())
		// 引发panic。
		panic(errors.New("something wrong"))
	
		// recover函数的错误用法。
		p := recover()
		fmt.Printf("panic: %s\n", p)
	
		fmt.Println("Exit function main.")
	}

4.多个defer的执行顺序。与出现顺序完全相反,。 先进后出

  • 测试的规则和流程1

测试源码文件的主名称应该以被测源码文件的主名称为前导,并且必须以“_test”为后缀。例如,如果被测源码文件的名称为 demo52.go,那么针对它的测试源码文件的名称就应该是 demo52_test.go

测试分为三类;功能测试、性能测试、示例测试

性能测试为串行、其他的可以为并发执行,默认也是串行

对于功能测试函数来说,其名称必须以Test为前缀,并且参数列表中只应有一个*testing.T类型的参数声明。

对于性能测试函数来说,其名称必须以Benchmark为前缀,并且唯一参数的类型必须是*testing.B类型的。

对于示例测试函数来说,其名称必须以Example为前缀,但对函数的参数列表没有强制规定

  • 测试的规则和流程2
go clean -cache //清除所有缓存
go clean -testcache // 清除测试缓存


t.Fail() //结果显示失败,但继续执行
t.FailNow() //立即结束
t.Log() //打印日志
t.Logt() //打印日志
t.Fatal() t.Fatalf() //结果显示失败,继续执行,并打印日志
t.Error() t.Errorf() //立即结束并打印日志

图片

go test -bench=. -run=^$ puzzlers/article20/q3
// -bench=后跟正则,表示执行的性能测试方法
// -run==后跟正则,表示执行的功能测试函数。 不加-run,所有功能测试函数会被执行
// -parallel 功能测试函数最大并发数,仅对功能测试有用
// 也可在代码里显式调用 t.Parallel
    // demo.go 被测试代码,不用去读,只用知道事被测试的就行
    //GetPrimes 用于获取小于或等于参数max的所有质数。
	// 本函数使用的是爱拉托逊斯筛选法(Sieve Of Eratosthenes)。
	func GetPrimes(max int) []int {
		if max <= 1 {
			return []int{}
		}
		marks := make([]bool, max)
		var count int
		squareRoot := int(math.Sqrt(float64(max)))
		for i := 2; i <= squareRoot; i++ {
			if marks[i] == false {
				for j := i * i; j < max; j += i {
					if marks[j] == false {
						marks[j] = true
						count++
					}
				}
			}
		}
		primes := make([]int, 0, max-count)
		for i := 2; i < max; i++ {
			if marks[i] == false {
				primes = append(primes, i)
			}
		}
		return primes
	}
// demo_test.go 性能测试
func BenchmarkGetPrimes(b *testing.B) {
		for i := 0; i < b.N; i++ {
			GetPrimes(1000)
		}
	}

图片

// 有三个计时器 
//b.StopTimer() 结束
// b.StartTimer() 开始
// b.ResetTimer 去除之前时间计算
func BenchmarkGetPrimes(b *testing.B) {
 b.StopTimer()
 time.Sleep(time.Millisecond * 500) // 模拟某个耗时但与被测程序关系不大的操作。
 max := 10000
 b.StartTimer()


 for i := 0; i < b.N; i++ {
  GetPrimes(max)
 }
}
  • 锁与读写锁
// 一般锁
var mu sync.Mutex
mu.Lock()
_, err := writer.Write([]byte(data))
if err != nil {
 log.Printf("error: %s [%d]", err, id)
}
mu.Unlock()

锁注意事项:

不要重复锁定互斥锁; 不要忘记解锁互斥锁,必要时使用defer语句; 不要对尚未锁定或者已解锁的互斥锁解锁; 不要在多个函数之间直接传,会产生副本

读写锁:

定义:sync.RWMutex类型中的Lock方法和Unlock方法分别用于对写锁进行锁定和解锁,而它的RLock方法和RUnlock方法则分别用于对读锁进行锁定和解锁。

1.对于某个受到读写锁保护的共享资源,多个写操作不能同时进行,写操作和读操作也不能同时进行,但多个读操作却可以同时进行。

2.对读锁进行解锁,只会在没有其他读锁锁定的前提下,唤醒“因试图锁定写锁,而被阻塞的 goroutine”;并且,最终只会有一个被唤醒的 goroutine 能够成功完成对写锁的锁定,其他的 goroutine 还要在原处继续等待

锁源码分析

互斥锁:

阻塞/唤醒:悲观锁,不浪费CPU,适用于竞争激烈的场景

自旋+ CAS:乐观锁,不愿让出当前CPU,适用于竞争强度低的场景


自旋模式转为阻塞模式的条件:
  • 首选乐观,4次自旋,未获取到锁,转阻塞/唤醒;

  • CPU单核或仅有单个P调度器,直接阻塞/唤醒;

  • 当前P执行队列中仍有待执行G(避免因自旋影响到 GMP 调度效率);

    饥饿模式

      饥饿:非公平机制,导致阻塞队列中存在长时间没获取到锁的goroutine,从而陷入饥饿;
    
      饥饿模式:存在饥饿状态的goroutine时,会将抢锁流程从非公平转为公布。
    

    两种模式

      正常/非饥饿模式:有goroutine被唤醒时,当前正占用时间片的goroutine、各个阻塞队列头部的goroutine抢锁,抢锁失败回到阻塞队列头部。
    
      (新goroutine一般更有优势,因为正占有这CPU时间片,且可能存在多个)
    
    
      饥饿模式:严格按照阻塞队列依次取锁,新goroutine不再抢锁,进入队尾排队。
    

    默认正常模式;

    正常模式->饥饿模式:阻塞队列中存在goroutine等锁超过1ms而不得时;

    饥饿模式->正常模式:阻塞队列已清空,或取得锁的goroutine等锁时间低于1ms时

    小结:正常模式,性能更好,所以优先,不用频繁让出CPU,上下文切换。饥饿模式正好相反,但是能避免长时间抢不到锁的不公平情况。

读写锁:

  • 读读可以共存

  • 读写,写写不可共存。

  • 按顺序依次去获取锁,写完优先唤醒读。

举例:

读 读 读 写 读 读 写 写 读 读

首先3个读顺利拿到读锁,写1会取得写错,readCount = 3-(1 << 30), 读4、5拿不到读锁,写2、3阻塞;三个读中最后一个结束时唤醒写1,写1解锁前,其他的请求锁都是阻塞,写1解锁后,读4、5会拿到读锁,写2、3pk,其一拿到写锁,假设写2拿到锁,但会由于readerCount为正值,等待,4、5解锁后,写2获取到锁,写2解锁后,优先唤醒读6、7,6、7结束,写3拿到锁。
  • 条件变量 sync.Cond**

已锁情况下,再锁会等待。

已解锁情况下,再解锁会panic

  • 原子操作**

可使用的类型有int32、int64、uint32、uint64、uintptr,以及unsafe.Pointer。不过,针对unsafe.Pointer类型,该包并未提供进行原子加法操作的函数。此外,sync/atomic包还提供了一个名为Value的类型,它可以被用来存储任意类型的值

uint32类型做减法操作,atomic.AddUint32函数的第二个参数值:^uint32(-N-1))

atomic.Value的使用

type Person struct {
    name string
    age  int
}
// 全局变量(简单处理)
var p Person
func update(name stringage int) {
    // 更新第一个字段
    p.name = name
    // 加点随机性
    time.Sleep(time.Millisecond*200)
    // 更新第二个字段
    p.age = age
}
//方式2,原子操作
var p atomic.Value
func update2(name stringage int) {
    lp := &Person{}
    // 更新第一个字段
    lp.name = name
    // 加点随机性
    time.Sleep(time.Millisecond * 200)
    // 更新第二个字段
    lp.age = age
    // 原子设置到全局变量
    p.Store(lp)
}


func main() {
    wg := sync.WaitGroup{}
    wg.Add(10)
    // 10 个协程并发更新
    for i := 0i < 10i++ {
        nameage := fmt.Sprintf("nobody:%v"i), i
        go func() {
            defer wg.Done()
            update(nameage)
        }()
    }
    wg.Wait()
    // 结果是啥?你能猜到吗?
    fmt.Printf("p.name=%s\np.age=%v\n"p.namep.age)
    //方式2
    _p := p.Load().(*Person)
    fmt.Printf("p.name=%s\np.age=%v\n"_p.name_p.age)
}
  • sync.WaitGroup和sync.Once**

  • context.Context类型

// 可撤销的 
// WithDeadline函数和WithTimeout函数则都可以被用来产生一个会定时撤销的parent的子值
// deadline是针对某个具体时间,而timeout是针对当前时间的延时来定义自动撤销时间
cxt, cancelFunc := context.WithCancel(context.Background())
// WithValue可以带值
valueCtx:=context.WithValue(ctx,key,"【监控1】")

一旦当前的Context值被撤销,这里的接收通道就会被立即关闭。对于一个未包含任何元素值的通道来说,它的关闭会使任何针对它的接收操作立即结束

撤销方法 cancelFunc 一旦执行,cxt及所有子层就会接受到通知。<-cxt.Done()是个通道,会关闭,所以也会立即返回

    func main() {
		coordinateWithContext()
	}
	
	func coordinateWithContext() {
		total := 12
		var num int32
		fmt.Printf("The number: %d [with context.Context]\n", num)
		cxt, cancelFunc := context.WithCancel(context.Background())
		for i := 1; i <= total; i++ {
			go addNum(&num, i, func() {
				if atomic.LoadInt32(&num) == int32(total) {
					cancelFunc()
				}
			})
		}
		<-cxt.Done()
		fmt.Println("End.")
	}
	
	// addNum 用于原子地增加一次numP所指的变量的值。
	func addNum(numP *int32, id int, deferFunc func()) {
		defer func() {
			deferFunc()
		}()
		for i := 0; ; i++ {
			currNum := atomic.LoadInt32(numP)
			newNum := currNum + 1
			time.Sleep(time.Millisecond * 200)
			if atomic.CompareAndSwapInt32(numP, currNum, newNum) {
				fmt.Printf("The number: %d [%d-%d]\n", newNum, id, i)
				break
			} else {
				//fmt.Printf("The CAS operation failed. [%d-%d]\n", id, i)
			}
		}
	}
  • 临时对象池sync.Pool**

  • sync.Map***

  • unicode

rune = int32



str := "Go爱好者"
for i, c := range str {
 fmt.Printf("%d: %q [% x]\n", i, c, []byte(string(c)))
}


//结果
0: 'G' [47]
1: 'o' [6f]
2: '爱' [e7 88 b1]
5: '好' [e5 a5 bd]
8: '者' [e8 80 85]
  • string包和字符串操作

string.Reader

实现了 io.Reader(Read 方法),io.ReaderAt(ReadAt 方法),io.Seeker(Seek 方法),io.WriterTo(WriteTo 方法),io.ByteReader(ReadByte 方法),io.ByteScanner(ReadByte 和 UnreadByte 方法),io.RuneReader(ReadRune 方法) 和 io.RuneScanner(ReadRune 和 UnreadRune 方法)

type Reader struct {
  s        string    // Reader 读取的数据来源
  i        int // current reading index(当前读的索引位置)
  prevRune int // index of previous rune; or < 0(前一个读取的 rune 索引位置)
}


func NewReader(s string) *Reader{}

string.Builder

该类型实现了 io 包下的 Writer, ByteWriter, StringWriter 等接口,可以向该对象内写入数据,Builder 没有实现 Reader 等接口,所以该类型不可读,但提供了 String 方法可以获取对象内的数据

type Builder struct {
    addr *Builder // of receiver, to detect copies by value
    buf  []byte
}


// 该方法向 b 写入一个字节
func (b *Builder) WriteByte(c byte) error{}
// WriteRune 方法向 b 写入一个字符
func (b *Builder) WriteRune(r rune) (int, error){}
// WriteRune 方法向 b 写入字节数组 p
func (b *Builder) Write(p []byte) (int, error){}
// WriteRune 方法向 b 写入字符串 s
func (b *Builder) WriteString(s string) (int, error){}
// Len 方法返回 b 的数据长度。
func (b *Builder) Len() int{}
// Cap 方法返回 b 的 cap。
func (b *Builder) Cap() int{}
// Grow 方法将 b 的 cap 至少增加 n (可能会更多)。如果 n 为负数,会导致 panic。
func (b *Builder) Grow(n int){}
// Reset 方法将 b 清空 b 的所有内容。
func (b *Builder) Reset(){}
// String 方法将 b 的数据以 string 类型返回。
func (b *Builder) String() string{}
b := strings.Builder{}
_ = b.WriteByte('7')
n, _ := b.WriteRune('夕')
fmt.Println(n)
n, _ = b.Write([]byte("Hello, World"))
fmt.Println(n)
n, _ = b.WriteString("你好,世界")
fmt.Println(n)
fmt.Println(b.Len())
fmt.Println(b.Cap())
b.Grow(100)
fmt.Println(b.Len())
fmt.Println(b.Cap())
fmt.Println(b.String())
b.Reset()
fmt.Println(b.String())

strings.Builder有 Write、WriteString、WriteRune、WriteByte方法 Builder类型的优势:底层是数组

已存在的内容不可变,但可以拼接更多的内容;

减少了内存分配和内容拷贝的次数;

可将内容重置,可重用值。

扩容:

容量扩容规则是。基础容量*2+扩容量n

// 这里的基础容量是8,扩容10个,最终为 2*8+10 = 16 
var builder1 strings.Builder
// 省略若干代码。
builder1.WriteString("LA")
fmt.Println("Grow the builder ...")
builder1.Grow(10)
fmt.Printf("The cap of contents in the builder is %d.\n", builder1.Cap())

Builder的使用约束: 在已被真正使用后就不可再被复制;

由于其内容不是完全不可变的,所以需要使用方自行解决操作冲突和并发安全问题

Reader,可以根据偏移量读取。

// Size总长度。Len 是未读内容的长度,而不是已存内容的总长度
var reader1 strings.Reader
// 省略若干代码。
readingIndex := reader1.Size() - int64(reader1.Len()) // 计算出的已读计数。
  • bytes.Reader
type Reader struct {
    s        []byte
    i        int64 // 当前读取下标
    prevRune int   // 前一个字符的下标,也可能 < 0
}

bytes 包下的 Reader 类型实现了 io 包下的 Reader, ReaderAt, RuneReader, RuneScanner, ByteReader, ByteScanner, ReadSeeker, Seeker, WriterTo 等多个接口。主要用于 Read 数据

// 读取数据至 b 
func (r *Reader) Read(b []byte) (n int, err error) {}
// 读取一个字节
func (r *Reader) ReadByte() (byte, error){}
// 读取一个字符
func (r *Reader) ReadRune() (ch rune, size int, err error){}
// 读取数据至 w
func (r *Reader) WriteTo(w io.Writer) (n int64, err error){}
// 进度下标指向前一个字节,如果 r.i <= 0 返回错误。
func (r *Reader) UnreadByte() {}
// 进度下标指向前一个字符,如果 r.i <= 0 返回错误,且只能在每次 ReadRune 方法后使用一次,否则返回错误。
func (r *Reader) UnreadRune() {}
// 读取 r.s[off:] 的数据至b,该方法忽略进度下标 i,不使用也不修改。
func (r *Reader) ReadAt(b []byte, off int64) (n int, err error) {}
// 根据 whence 的值,修改并返回进度下标 i ,当 whence == 0 ,进度下标修改为 off,当 whence == 1 ,进度下标修改为 i+off,当 whence == 2 ,进度下标修改为 len[s]+off.
// off 可以为负数,whence 的只能为 0,1,2,当 whence 为其他值或计算后的进度下标越界,则返回错误。
func (r *Reader) Seek(offset int64, whence int) (int64, error){}
x:=[]byte("你好,世界")
r1:=bytes.NewReader(x)
d1:=make([]byte,len(x))
n,_:=r1.Read(d1)
fmt.Println(n,string(d1))
r2:=bytes.Reader{}
r2.Reset(x)
d2:=make([]byte,len(x))
n,_=r2.Read(d2)
fmt.Println(n,string(d2))
  • bytes.Buffer

和切片一样,说是扩容,实为新建。扩容会导致创建一个新的底层数组,并把未读内容复制,已读计数置索引为0。

基本都是针对未读内容来说的:

Len()返回的是未读内容长度

type Buffer struct {
    buf      []byte
    off      int   
    lastRead readOp 
}

该类型实现了 io 包下的 ByteScanner, ByteWriter, ReadWriter, Reader, ReaderFrom, RuneReader, RuneScanner, StringWriter, Writer, WriterTo 等接口,可以方便的进行读写操作。

// 读取到字节 delim 后,以字节数组的形式返回该字节及前面读取到的字节。如果遍历 b.buf 也找不到匹配的字节,则返回错误(一般是 EOF)
func (b *Buffer) ReadBytes(delim byte) (line []byte, err error){}
// 读取到字节 delim 后,以字符串的形式返回该字节及前面读取到的字节。如果遍历 b.buf 也找不到匹配的字节,则返回错误(一般是 EOF)
func (b *Buffer) ReadString(delim byte) (line string, err error){}
// 截断 b.buf , 舍弃 b.off+n 之后的数据。n == 0 时,调用 Reset 方法重置该对象,当 n 越界时(n < 0 || n > b.Len() )方法会触发 panic.
func (b *Buffer) Truncate(n int){}


var buffer1 bytes.Buffer
contents := "Simple byte buffer for marshaling data."
fmt.Printf("Writing contents %q ...\n", contents)
buffer1.WriteString(contents)
fmt.Printf("The length of buffer: %d\n", buffer1.Len())
fmt.Printf("The capacity of buffer: %d\n", buffer1.Cap())


// 结果
Writing contents "Simple byte buffer for marshaling data." ...
The length of buffer: 39
The capacity of buffer: 64


p1 := make([]byte, 7)
n, _ := buffer1.Read(p1)
fmt.Printf("%d bytes were read. (call Read)\n", n)
fmt.Printf("The length of buffer: %d\n", buffer1.Len()) //32
fmt.Printf("The capacity of buffer: %d\n", buffer1.Cap()) //64

自动扩容相关:

名词解释:内容容器容量、长度、内容容器剩余容量、未读长度、另需长度

a : 内容容器剩余容量

b : 所需长度,即未读长度(现有长度)+另需长度

c : 容器总容量

a >= b,说明容量够用,只增加长度,不扩容。

a < b, 但,c/2 >b。说明容量也挺大,那就重置已读的,把未读及另需的内容,复制到前端。

还不满足,就新建底层数组。长度为c *2 +另需

对于处在零值状态的Buffer值来说,如果第一次扩容时的另需字节数不大于64,那么该值就会基于一个预先定义好的、长度为64的字节数组来创建内容容器

bytes.Buffer中Bytes、Next方法可能导致内存泄漏

如果我们有意或无意地把这些结果值传到了外界,那么外界就有可能通过它们操纵相关联Buffer值的内容



contents := "ab"
buffer1 := bytes.NewBufferString(contents)
fmt.Printf("The capacity of new buffer with contents %q: %d\n",
 contents, buffer1.Cap()) // 内容容器的容量为:8。
unreadBytes := buffer1.Bytes()
fmt.Printf("The unread bytes of the buffer: %v\n", unreadBytes) // 未读内容为:[97 98]。




buffer1.WriteString("cdefg")
fmt.Printf("The capacity of buffer: %d\n", buffer1.Cap()) // 内容容器的容量仍为:8。
unreadBytes = unreadBytes[:cap(unreadBytes)]
fmt.Printf("The unread bytes of the buffer: %v\n", unreadBytes) // 基于前面获取到的结果值可得,未读内容为:[97 98 99 100 101 102 103 0]。




unreadBytes[len(unreadBytes)-2] = byte('X') // 'X'的ASCII编码为88。
fmt.Printf("The unread bytes of the buffer: %v\n", buffer1.Bytes()) // 未读内容变为了:[97 98 99 100 101 102 88]。


// 扩容了,所以后续unreadBytes[len(unreadBytes)-2] = byte('X')操作,
//不会影响unreadBytes值
contents = "cdedefghijk"
fmt.Printf("Write contents %q ...\n", contents)
buffer1.WriteString(contents)
fmt.Printf("3The capacity of buffer: %d\n", buffer1.Cap())
  • io包

io.WriterTo 从属性读,写到参数

io.ReaderFrom 从参数读,写到属性

strings.Reader类型 Seek 方法,该方法主要用于寻找并设定下一次读取或写入时的起始索引位置

comment := "lalalallalllalal"


// 定义Reader类型的数据
reader1 := strings.NewReader(comment)


//Seek 从当前位置(io.SeekCurrent)   偏移offset1位读起。
offset1 = int64(53)
index1, err = reader1.Seek(offset1, io.SeekCurrent)


// 从reader1,读取num1位数据
reader2 := io.LimitReader(reader1, num1)


// 读取到切片buf2,10位
buf2 := make([]byte, 10)
n, err = reader2.Read(buf2)


// 从reader1偏移offset2位读起,共读num2位
io.NewSectionReader(reader1, offset2, num2)


writer1 := new(strings.Builder)
// 读写器。从reader1读,写入到writer1。没用缓冲。读和写对应
reader4 := io.TeeReader(reader1, writer1)
buf4 := make([]byte, 40)
//返回读取到位数
n, err = reader4.Read(buf4)


reader5a := strings.NewReader("aaaaaaaa.")
reader5b := strings.NewReader("bbbbbbbbbbbbbbbbbbbbbbbbbbb.")
reader5c := strings.NewReader("ccccccccccccccc.")
reader5d := strings.NewReader("dddddd.")
// 合并读取
reader5 := io.MultiReader(reader5a, reader5b, reader5c, reader5d)
buf5 := make([]byte, 50)
for i := 0; i < 8; i++ {
  // 每次最多读取一个Reader
   n, err = reader5.Read(buf5)
   executeIfNoErr(err, func() {
      fmt.Printf("6Read(%d): %q\n", n, buf5[:n])
   })
}


// 管道操作
pReader, pWriter := io.Pipe()
_ := interface{}(pReader).(io.ReadCloser)
_ := interface{}(pWriter).(io.WriteCloser)


comments := [][]byte{
   []byte("Pipe creates a synchronous in-memory pipe."),
   []byte("It can be used to connect code expecting an io.Reader "),
   []byte("with code expecting an io.Writer."),
}
// 这里添加这个同步工具纯属为了保证下面示例中的打印语句都能够执行完成。
// 在实际使用中没有必要这样做。
var wg sync.WaitGroup
wg.Add(2)
go func() {
   defer wg.Done()
   for _, d := range comments {
      time.Sleep(time.Millisecond * 500)
      n, err := pWriter.Write(d)
      if err != nil {
         fmt.Printf("write error: %v\n", err)
         break
      }
      fmt.Printf("7Written(%d): %q\n", n, d)
   }
   pWriter.Close()
}()
go func() {
   defer wg.Done()
   wBuf := make([]byte, 55)
   for {
      n, err := pReader.Read(wBuf)
      if err != nil {
         fmt.Printf("read error: %v\n", err)
         break
      }
      fmt.Printf("7Read(%d): %q\n", n, wBuf[:n])
   }
}()
wg.Wait()
  • bufio包
// NewReader ,NewReaderSize用来初始化的方法。
// NewReader 默认4k,NewReaderSize可自定义大


type Reader struct {
   buf          []byte //缓冲区
   rd           io.Reader // io.Reader类型的字段,代表底层读取器
   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
}


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

Peek方法和ReadSlice方法 会调用包级私有方法fill,开始会压缩一次。接着开始填充,在填充缓冲区的时候,fill方法会试图从底层读取器那里,读取足够多的字节,缓冲区没位置,就会压缩。

压缩流程:相当于用 未读到已写的数据 把 已读的数据 覆盖。压缩过程会更新已读、已写计数

图片

bufio.Reader

// NewReader ,NewReaderSize用来初始化的方法。
// NewReader 默认4k,NewReaderSize可自定义大
Peek、Read、ReadSlice和ReadBytes
Peek方法 ,返回[]byte, error。 不会更改已读计数。error可能为bufio.ErrBufferFull,表示缓冲区已满。
Read方法,会更改已读计数。参数值的长度过大,且缓冲区中已无未读字节时,跨过缓冲区并直接向底层读取器索要数
ReadSlice方法会在缓冲区的未读部分中寻找给定的分隔符,并在必要时对缓冲区进行填充
Reader值的ReadLine方法会依赖于它的ReadSlice方法,而其ReadString方法则完全依赖于ReadBytes方法
Reader值的Peek方法、ReadSlice方法和ReadLine方法都可能会造成其缓冲区中的内容的泄露

bufio.Writer

//bufio.Writer类型有一个名为Flush的方法,它的主要功能是把相应缓冲区中暂存的所有数据,都写到底层写入器中
// Write、 WriteByte、WriteRune 都会必要的时候调用它的Flush方法
//Flush方法,将缓冲区的数据填充到底层写入器
//Write方法和ReadFrom方法也会跨过缓冲区,并直接把数据写进其底层写入
  • os包

图片

  • 网络服务

socket类型:

SOCK_DGRAM中的“DGRAM”代表的是 datagram,即数据报文。它是一种有消息边界,但没有逻辑连接的非可靠 socket 类型,我们熟知的基于 UDP 协议的网络通信就属于此类。

而SOCK_STREAM这个 socket 类型,恰恰与SOCK_DGRAM相反。它没有消息边界,但有逻辑连接,能够保证传输的可靠性和数据的有序性,同时还可以实现数据的双向传输。众所周知的基于 TCP 协议的网络通信就属于此类

图片



fd1, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
if err != nil {
  fmt.Printf("socket error: %v\n", err)
  return
}
defer syscall.Close(fd1)
fmt.Printf("The file descriptor of socket:%d\n", fd1)
type dailArgs struct {
   network string
   address string
   timeout time.Duration
}
args := dailArgs{
   "tcp",
   "baidu.com:80",
   time.Second * 2,
}
ts1 := time.Now()
conn, err := net.DialTimeout(args.network, args.address, args.timeout)
ts2 := time.Now()
defer conn.Close()


a := make([]byte, 20)
r,e :=conn.Write(a)
fmt.Println(r,e,a)


fmt.Printf("The local address: %s\n", conn.LocalAddr())
fmt.Printf("The remote address: %s\n", conn.RemoteAddr())
  • http (*有没弄明白的)
url1 := "http://baidu.com"
resp1, err := http.Get(url1)
if err != nil {
   fmt.Printf("request sending error: %v\n", err)
   return
}
defer resp1.Body.Close()
line1 := resp1.Proto + " " + resp1.Status
fmt.Printf("The first line of response:\n%s\n", line1)
  • 性能分析**