11. 面向对象和内存

  • Go 语言没有类概念,而是松耦合的类型,方法以及接口的实现;
  • Go 语言用接口实现多态;
  • 指针变量在 32 位计算机上占用 4B 内存,在 64 位机器计算机上占用 8B 内存,并且与它所指向的值大小无关;
  • 不能得到一个数字或常量的地址;
  • 什么时候用指针参数,什么时候用值参数
    • 在需要改变参数的值或者避免复制大批量数据而节省内存时都会选择指针
    • 指针的频繁使用也会导致性能下降,指针也可以指向另外一个指针,并且可以进行任意深度的嵌套,形成多级的间接引用,但会使代码结构不清晰;
  • new 和 make
    • new 返回指针,make 返回值;
    • new 分配内存,并且置为空值;make 完成内存分配和初始化;
  • 当内存资源不足时,调用 runetime.GC(),此函数的执行会让系统立即释放不再使用的变量或对象占用的内存;
  • func Set Finalizer(obj interface{}, finalizer interface{}) 有两个参数
    • obj 必须指向指针类型
    • finalizer 是一个函数,参数类型是 obj 的类型,没有返回值;
  • GOGC 可以用于设置垃圾回收百分比,如果车给你需占用内存达到 (1 + GOGC/100)MB 的两倍时,即可触发 GC。

12. 并发处理

  • MPG
  • 关闭 channel 后,无法再向通道发送数据(否则引发运行时错误),但是,可以接续从 channel 接收数据
  • 对于 nil channel,无论是发送还是接收都会被阻塞
  • Mutex
    • 同一个协程中同步调用使用 Lock 加锁后,不能再对其加锁,否则又是一个运行时异常
    • 如果使用 Unlock 前没有加锁,就会引发一个运行错误;
    • 已经锁定的 Mutex 不与 goroutine 绑定,可以在 goroutine A 中加锁, goroutineB 中解锁。
    • Mutex 零值是可以操作的锁了
  • 读写锁
    • 如果未加(读/写)锁就解锁,会引发一个运行时异常;
    • 写解锁时,会试图唤醒所有因进行读锁定而被阻塞的协程;
    • 读解锁时,会试图唤醒所有因进行写锁定而被阻塞的协程;
    • sync.RWMutex 零值也是可以操作的锁了
  • sync.WaitGroup
    • Add(-1) 和 Done() 效果一致
  • sync.Once
    • sync.Once(f func()) 能保证 Do() 方法只执行一次
  • sync.Map
    • 在 Go1.9 中增加了一个新的特性:sync.Map

13. 测试与调优

  • 断言函数
    • t.Fail:标记这个测试失败,并且继续执行其他的(包括该文件中的)
    • t.FailNow:标记这个测试失败,并且该文件中的测试都不执行了,继续下一个文件的测试
    • t.Log:将格式化的日志打印到错误日志中
    • t.Fatal:先执行(3),然后再执行 (2)
  • 调优
    • 如果使用 testing 包进行基准测试功能,则可以用 go test 的标准的 -cpuprofile-memprofile 标志向指定文件写入 CPU 和内存使用情况报告;
    • pprof

14. 系统标准库

反射

  • 反射是应用程序动态检查其所拥有的结构,尤其是类型对象的一种能力
  • Go 中反射机制就是运行时动态检测调用对象的方法和属性
  • 反射的 value 是实际变量值,type 是实际变量类型,不能是接口类型
  • Type 的主要方法:
    • Kind 返回的是一个常量,便是具体的类型的底曾类型
    • Elem() 反馈指针/数组/切片 字典通道等类型
  • 反射针对性能有一定影响,避免使用
  • Type 返回的是静态类型(MyInt),kind 返回的是基础类型(int)
  • 通过 CanSet() 判断原始反射对系那个 v reflect.Value 是否可以写,CanAddr 是否可寻址
  • 虽然反射可以越过 Go 语言的导出规则限制读取结构提中未导出的成员,但是不能修改他们

unsafe

  • Go 语言的指针与 int 类型在内存中占用的字节数相同,ArbitraryType 类型的变量有可以是指针
  • uintptr 这个基础类型的字节长度与 int 一致
  • uintptr 和 unsafe.Pinter 可以相互转换

Sort

  • sort 包中实现了几种基本的排序算法
  • search 返回能够使用 F(i) == true 成立的最小下标 i,并且会假定如果 F(i) == true,则 F(i+1) == true
  • 如果 x 不存在,search(x)返回切片的长度

IO

  • 内核中的缓冲:无论进程是否提供缓冲,内核都是提供缓冲的,系统对磁盘的读写都会提供一个缓冲(内核高速缓冲),将数据写入到块缓冲进行排队,当块缓冲达到一定的量时,才把数据写入磁盘;
  • 进程中的缓冲:是对输入输出流进行改进,提供一个流缓冲,当调用一个函数向磁盘写数据时,才把数据写入缓冲区,当达到某个条件,这时候才会把数据一次送往内核提供的块缓冲中,再经块缓冲写入磁盘。
  • bufio 读同一个文件耗费时间最少

15. 网络服务

上下文

  • 使用上下文时需要遵循以下规则
    • 上下文变量需要作为地一个参数使用,一般命名为 ctx
    • 不要传入一个 nil 的上下文,如果不确定,用 context.TODO
    • 使用上下文的 Value 下给你管方法之传递请求的相关元数据,不要传递可选参数
    • 同样的上下文可以用来传递给不同的协程,上下文在多个协程中是安全的

16. 数据格式与存储

Json

  • 在 Go 中,Marshal 默认是设置 escapeHTML = true,会自动把< > 等字符转换成 \u003e
  • encoding/json 包提供 DecoderEncoder 类型,NewDecoderNewEncoder 函数分别封装了 io.Readerio.Writer 接口
  • json.RawMessage 将在解码后继续以字节数组方式存在

SQL

  • 在 Go 中对数据类型要求很严格,一般查询数据时先定义数据类型,但是查询数据库中的数据有三种状态:存在值,存在零值和未赋值 NULL,因此可以将待查询的数据类型定义为 sql.NullXXX 类型,通过判断 Valid 值来判断查询到的值是赋值状态还是未赋值 NULL 状态

LevelDB & BotlDB

  • LevelDB 的性能不可预知,在数据量小的时候性能很好,但随着数据量的增加,性能会越来越糟糕。并且做合并的县城也会在服务器上出现问题;
    • LevelDB 实现了一个日志结构化的 MergeTree,让它不需要每次有数据更新就将数据写入到磁盘中
    • 将有序的 KV 对存储在不同的文件中,在 db 目录下有很多数据文件,通过 “层级” 把它们分开
    • 周期性地将较小的文件合并为较大的文件,这让随机写速度很快
  • BotlDB 会在数据文件中获取一个文件锁,所以多个进程不能同时打开同一个数据库
  • BoltDB 支持完全可序列化的 ACID 事务
    • 数据保存在内存映射的文件中,没有 WAL,没有线程压缩和垃圾回收
    • 通过 COW 技术,可以实现无锁的读写并发,但是不能实现无锁的写写并发,注定了性能超高,但是*写×性能一般,适合读多写少的场景

17. 网络爬虫

  • 爬虫框架:Colly
    • Colly
    • 特点
      • 清晰的 API
      • 快速
      • 管理每个域的请求延迟和最大并发数,抓取频率
      • 自动 cookie 和会话处理
      • 同步/异步/并行抓取
      • 高速缓存
      • 自动处理非 Unicode 编码
      • 支持 Robots.txt
      • 定制 Agent 信息
  • HTML 处理工具:Goquery

18. Web 框架:Gin

  • gin > Martini
  • 特点:
    • 运行速度快
    • 分组的路由
    • 良好的异常捕获和错误处理
    • 非常好的支持中间件和 Json