文章74
标签2
分类7

章节11_01_GC【基础篇完】

一. GC

  • GC英文全称 garbage collector
  • Go语言GC是相对C/C++语言非常重要的改进
  • 一些常用GC算法

    • 引用计算法.当对象被引用时计算器加一.不被引用计数器减一

      • PHP和Object-C使用
      • 相互引用无法回收
      • 计数增加消耗
    • Mark And Sweep 标记和清除算法.停止程序运行,递归遍历对象,进行标记.标记完成后将所有没有引用的对象进行清除

      • 由于标记需要停止程序(Stop the world),当对象特别多时,标记和清除过程比较耗时(可能几百毫秒),很难接受
    • 三色标记法:是Mark And Sweep的改进版.从逻辑上分为白色区(未搜索),灰色区(正搜索),黑色区(已搜索).灰色区内容是子引用没有进行搜索,黑色区表示子引用存在
    • 分代收集.一般情况都有三代,例如java中新生代,老年代,永久代.当新生代中带有阈值时会把对象放入到老年代,相同道理老年代内容达到阈值会放入到永久代

二.Go语言中的GC

  • Go语言中采用Stop The World方式
  • Golang每个版本基本上都会对GC进行优化,从Golang1.5开始支持并发(concurrent )收集,从1.8版本已经把STW时间优化到了100微妙,通常只需要10微妙以下.且在1.10版本时再次优化减少GC对CPU占用
  • Go语言中GC是自动运行的,在下列情况下会触发GC

    • 当需要申请内存时,发现GC是上次GC两倍时会触发
    • 每2分钟自动运行一次GC
  • GC调优

    • 小对象复用,局部变量尽量少声明,多个小对象可以放入到结构体,方便GC扫描
    • 少用string的”+”
  • 在runtime包下mgc.go中明确的说明了Golang的GC的解释
// Garbage collector (GC).
//
// The GC runs concurrently with mutator threads, is type accurate (aka precise), allows multiple
// GC thread to run in parallel. It is a concurrent mark and sweep that uses a write barrier. It is
// non-generational and non-compacting. Allocation is done using size segregated per P allocation
// areas to minimize fragmentation while eliminating locks in the common case.
//
// The algorithm decomposes into several steps.
// This is a high level description of the algorithm being used. For an overview of GC a good
// place to start is Richard Jones' gchandbook.org.
//
// The algorithm's intellectual heritage includes Dijkstra's on-the-fly algorithm, see
// Edsger W. Dijkstra, Leslie Lamport, A. J. Martin, C. S. Scholten, and E. F. M. Steffens. 1978.
// On-the-fly garbage collection: an exercise in cooperation. Commun. ACM 21, 11 (November 1978),
// 966-975.
// For journal quality proofs that these steps are complete, correct, and terminate see
// Hudson, R., and Moss, J.E.B. Copying Garbage Collection without stopping the world.
// Concurrency and Computation: Practice and Experience 15(3-5), 2003.
//
// 1. GC performs sweep termination.
//
//    a. Stop the world. This causes all Ps to reach a GC safe-point.
//
//    b. Sweep any unswept spans. There will only be unswept spans if
//    this GC cycle was forced before the expected time.
//
// 2. GC performs the "mark 1" sub-phase. In this sub-phase, Ps are
// allowed to locally cache parts of the work queue.
//
//    a. Prepare for the mark phase by setting gcphase to _GCmark
//    (from _GCoff), enabling the write barrier, enabling mutator
//    assists, and enqueueing root mark jobs. No objects may be
//    scanned until all Ps have enabled the write barrier, which is
//    accomplished using STW.
//
//    b. Start the world. From this point, GC work is done by mark
//    workers started by the scheduler and by assists performed as
//    part of allocation. The write barrier shades both the
//    overwritten pointer and the new pointer value for any pointer
//    writes (see mbarrier.go for details). Newly allocated objects
//    are immediately marked black.
//
//    c. GC performs root marking jobs. This includes scanning all
//    stacks, shading all globals, and shading any heap pointers in
//    off-heap runtime data structures. Scanning a stack stops a
//    goroutine, shades any pointers found on its stack, and then
//    resumes the goroutine.
//
//    d. GC drains the work queue of grey objects, scanning each grey
//    object to black and shading all pointers found in the object
//    (which in turn may add those pointers to the work queue).
//
// 3. Once the global work queue is empty (but local work queue caches
// may still contain work), GC performs the "mark 2" sub-phase.
//
//    a. GC stops all workers, disables local work queue caches,
//    flushes each P's local work queue cache to the global work queue
//    cache, and reenables workers.
//
//    b. GC again drains the work queue, as in 2d above.
//
// 4. Once the work queue is empty, GC performs mark termination.
//
//    a. Stop the world.
//
//    b. Set gcphase to _GCmarktermination, and disable workers and
//    assists.
//
//    c. Drain any remaining work from the work queue (typically there
//    will be none).
//
//    d. Perform other housekeeping like flushing mcaches.
//
// 5. GC performs the sweep phase.
//
//    a. Prepare for the sweep phase by setting gcphase to _GCoff,
//    setting up sweep state and disabling the write barrier.
//
//    b. Start the world. From this point on, newly allocated objects
//    are white, and allocating sweeps spans before use if necessary.
//
//    c. GC does concurrent sweeping in the background and in response
//    to allocation. See description below.
//
// 6. When sufficient allocation has taken place, replay the sequence
// starting with 1 above. See discussion of GC rate below.

章节10_07_select

一. select简介

  • Golang中select和switch结构特别像,但是select中case的条件只能是I/O
  • select 的语法(condition是条件)
select{
  case condition:
  case condition:
  default:
}
  • select执行过程:

    • 每个case必须是一个IO操作
    • 哪个case可以执行就执行哪个
    • 多个case都可以执行,随机执行一个
    • 所有case都不能执行时,执行default
    • 所有case都不能执行,且没有default,将会阻塞
  • 代码示例
func main() {
   runtime.GOMAXPROCS(1)
   ch1 := make(chan int, 1)
   ch2 := make(chan string, 1)
   ch1 <- 1
   ch2 <- "hello"
   select {
   case value := <-ch1:
      fmt.Println(value)
   case value := <-ch2:
      fmt.Println(value)
   }
}
  • select多和for循环结合使用,下面例子演示出了一直在接收消息的例子
package main

import (
    "fmt"
)

func main() {
    ch := make(chan int)
    for i := 1; i <= 5; i++ {
        go func(arg int) {
            ch <- arg
        }(i)
    }
  //如果是一直接受消息,应该是死循环for{},下面代码中是明确知道消息个数
    for i := 1; i <= 5; i++ {
        select {
        case c := <-ch:
            fmt.Println("取出数据", c)
        default:
            //没有default会出现死锁
        }
    }
    fmt.Println("程序执行结束")
}
  • break可以对select生效,如果for中嵌套select,break选择最近结构

章节10_06_deadlock和buff channel

一. 死锁

  • 在主goroutine中向无缓存channel添加内容或在主goroutine中向channel添加内容且添加内容的个数已经大于channel缓存个数就会产生死锁
fatal error : all goroutines are asleep -deadlock!
  • 死锁:在程序中多个进程(Golang中goroutine)由于相互竞争资源而产生的阻塞(等待)状态,而这种状态一直保持下去,此时称这个线程是死锁状态
  • 在Golang中使用无缓存channel时一定要注意.以下是一个最简单的死锁程序

    • 主协程中有ch<-1,无缓存channel无论添加还是取出数据都会阻塞goroutine,当前程序无其他代码,主goroutine会一直被阻塞下去,此时主goroutine就是死锁状态
func main() {
   ch := make(chan int)
   ch <- 1
}
  • 而下面代码就不会产生死锁

    • 通过代码示例可以看出,在使用无缓存channel时,特别要注意的是在主协程中有操作channel代码
package main

import (
   "time"
   "fmt"
)

func main() {
   ch := make(chan int)
   go func() {
      ch <- 1
      fmt.Println("执行goroutine")
   }()
   time.Sleep(5e9)
   fmt.Println("程序执行结束")
}

二. 有缓存通道

  • 创建一个有缓存通道
func main() {
   ch := make(chan int, 3) //缓存大小3,里面消息个数小于等于3时都不会阻塞goroutine
   ch <- 1
   ch <- 2
   ch <- 3
   ch <- 4 //此行出现死锁,超过缓存大小数量
}
  • 在Golang中有缓存channel的缓存大小是不能改变的,但是只要不超过缓存数量大小,都不会出现阻塞状态
package main

import "fmt"

func main() {
   ch := make(chan int, 3) //缓存大小3,里面消息个数小于等于3时都不会阻塞goroutine
   ch <- 1
   fmt.Println(<-ch)
   ch <- 2
   fmt.Println(<-ch)
   ch <- 3
   ch <- 4
   fmt.Println(len(ch))//输出2,表示channel中有两个消息
   fmt.Println(cap(ch))//输出3,表示缓存大小总量为3
}

章节10_05_channel

一. channel

  • 线程通信在每个编程语言中都是重难点,在Golang中提供了语言级别的goroutine之间通信:channel
  • channel不同的翻译资料叫法不一样.常见的几种叫法

    • 管道
    • 信道
    • 通道
  • channel是进程内通信方式,每个channel只能传递一个类型的值.这个类型需要在声明channel时指定
  • channel在Golang中主要的两个作用

    • 同步
    • 通信
  • Go语言中channel的关键字是chan
  • 声明channel的语法
var 名称 chan 类型
var 名称 chan <- 类型 //只写
var 名称 <- chan 类型//只读
名称:=make(chan int) //无缓存channel
名称:=make(chan int,0)//无缓存channel
名称:=make(chan int,100)//有缓存channel
  • 操作channel的语法:(假设定义一个channel名称为ch)
ch <- 值 //向ch中添加一个值
<- ch //从ch中取出一个值
a:=<-ch //从ch中取出一个值并赋值给a
a,b:=<-ch//从ch中取出一个值赋值给a,如果ch已经关闭或ch中没有值,b为false

二. 代码示例

  • 简单无缓存通道代码示例

    • 此代码中如果没有从channel中取值c,d=<-ch语句,程序结束时go func并没有执行
    • 下面代码示例演示了同步操作,类似与WaitGroup功能,保证程序结束时goroutine已经执行完成
    • 向goroutine中添加内容的代码会阻塞goroutine执行,所以要把ch<-1放入到goroutine有效代码最后一行
    • 无论是向channel存数据还是取数据都会阻塞
    • close(channel)关闭channel,关闭后只读不可写
package main

import (
   "fmt"
)

func main() {
   ch := make(chan int)
   go func() {
      fmt.Println("进入goroutine")
      // 添加一个内容后控制台输出:1 true
      //ch<-1

      //关闭ch控制台输出:0 false
      close(ch)
   }()
   c, d := <-ch 
   fmt.Println(c, d)
   fmt.Println("程序执行结束")
}
  • 使用channel实现goroutine之间通信

    • channel其实就是消息通信机制实现方案,在Golang中没有使用共享内存完成线程通信,而是使用channel实现goroutine之间通信.
package main

import (
   "fmt"
)

func main() {
   //用于goroutine之间传递数据
   ch := make(chan string)
   //用于控制程序执行
   ch2 := make(chan string)
   go func() {
      fmt.Println("执行第一个goroutine,等待第二个goroutine传递数据")
      content := <-ch
      fmt.Println("接收到的数据为:", content)
      ch2 <- "第一个"
   }()
   go func() {
      fmt.Println("进入到第二个,开始传递数据")
      ch <- "内容随意"
      close(ch)
      fmt.Println("发送数据完成")
      ch2 <- "第二个"
   }()
   result1 := <-ch2
   fmt.Println(result1, "执行完成")
   result2 := <-ch2
   fmt.Println(result2, "执行完成")
   fmt.Println("程序执行结束")
}
  • 可以使用for range获取channel中内容

    • 不需要确定channel中数据个数
func main() {
   ch:=make(chan string)
   ch2:=make(chan int)
   go func() {
      for i:=97;i<97+26;i++{
         ch <- strconv.Itoa(i)
      }
      ch2<-1
   }()

   go func() {
      for c := range ch{
         fmt.Println("取出来的",c)
      }
   }()
   <-ch2
   fmt.Println("程序结束")
}
  • channel是安全的.多个goroutine同时操作时,同一时间只能有一个goroutine存取数据
package main

import (
   "time"
   "fmt"
)

func main() {
   ch := make(chan int)

   for i := 1; i < 5; i++ {
      go func(j int) {
         fmt.Println(j, "开始")
         ch <- j
         fmt.Println(j, "结束")
      }(i)
   }

   for j := 1; j < 5; j++ {
      time.Sleep(2 * time.Second)
      <-ch
   }
}

章节10_04_互斥锁和读写锁

一.互斥锁

  • Go语言中多个协程操作一个变量时会出现冲突的问题
  • go run -race 可以查看竞争
  • 可以使用sync.Mutex对内容加锁
  • 互斥锁的使用场景

    • 多个goroutine访问同一个函数(代码段)
    • 这个函数操作一个全局变量
    • 为了保证共享变量安全性,值合法性
  • 使用互斥锁模拟售票窗口
package main

import (
   "fmt"
   "sync"
   "time"
   "math/rand"
)

var (
   //票数
   num = 100
   wg  sync.WaitGroup
   //互斥锁
   mu sync.Mutex
)

func sellTicker(i int) {
   defer wg.Done()
   for {
      //加锁,多个goroutine互斥
      mu.Lock()
      if num >= 1 {
         fmt.Println("第", i, "个窗口卖了", num)
         num = num - 1
      }
      //解锁
      mu.Unlock()

      if num <= 0 {
         break
      }
      //添加休眠,防止结果可能出现在一个goroutine中
      time.Sleep(time.Duration(rand.Int63n(1000) * 1e6))
   }

}

func main() {
   //设置随机数种子
   rand.Seed(time.Now().UnixNano())
   //计算器的起始值和票数相同
   wg.Add(4)
   go sellTicker(1)
   go sellTicker(2)
   go sellTicker(3)
   go sellTicker(4)
   wg.Wait()

   fmt.Println("所有票卖完")
}

二.RWMutex读写锁

  • RWMutex 源码如下
// There is a modified copy of this file in runtime/rwmutex.go.
// If you make any changes here, see if you should make them there.

// A RWMutex is a reader/writer mutual exclusion lock.
// The lock can be held by an arbitrary number of readers or a single writer.
// The zero value for a RWMutex is an unlocked mutex.
//
// A RWMutex must not be copied after first use.
//
// If a goroutine holds a RWMutex for reading and another goroutine might
// call Lock, no goroutine should expect to be able to acquire a read lock
// until the initial read lock is released. In particular, this prohibits
// recursive read locking. This is to ensure that the lock eventually becomes
// available; a blocked Lock call excludes new readers from acquiring the
// lock.
type RWMutex struct {
    w           Mutex  // held if there are pending writers
    writerSem   uint32 // semaphore for writers to wait for completing readers
    readerSem   uint32 // semaphore for readers to wait for completing writers
    readerCount int32  // number of pending readers
    readerWait  int32  // number of departing readers
}
  • Go语言标准库中API如下
type RWMutex
  func (rw *RWMutex) Lock()//禁止其他协程读写
  func (rw *RWMutex) Unlock()
  func (rw *RWMutex) RLock()//禁止其他协程写入,只能读取
  func (rw *RWMutex) RUnlock()
  func (rw *RWMutex) RLocker() Locker
  • Go语言中的map不是线程安全的,多个goroutine同时操作会出现错误.
  • RWMutex可以添加多个读锁或一个写锁.读写锁不能同时存在.

    • map在并发下读写就需要结合读写锁完成
    • 互斥锁表示锁的代码同一时间只能有一个人goroutine运行,而读写锁表示在锁范围内数据的读写操作
package main

import (
   "fmt"
   "sync"
   "strconv"
)

func main() {
   var rwm sync.RWMutex
   m := make(map[string]string)
   var wg sync.WaitGroup
   wg.Add(10)
   for i := 0; i < 10; i++ {
      go func(j int) {
         //没有锁在map时可能出现问题
         rwm.Lock()
         m["key"+strconv.Itoa(j)] = "value" + strconv.Itoa(j)
         fmt.Println(m)
         rwm.Unlock()
         wg.Done()
      }(i)
   }
   wg.Wait()
   fmt.Println("程序结束")
}

章节10_03_WaitGroup

一.WaitGroup简介

  • Golang中sync包提供了基本同步基元,如互斥锁等.除了Once和WaitGroup类型, 大部分都只适用于低水平程序线程,高水平同步线程使用channel通信更好一些
  • WaitGroup直译为等待组,其实就是计数器,只要计数器中有内容将一直阻塞
  • 在Golang中WaitGroup存在于sync包中,在sync包中类型都是不应该被拷贝的.源码定义如下
// A WaitGroup waits for a collection of goroutines to finish.
// The main goroutine calls Add to set the number of
// goroutines to wait for. Then each of the goroutines
// runs and calls Done when finished. At the same time,
// Wait can be used to block until all goroutines have finished.
//
// A WaitGroup must not be copied after first use.
type WaitGroup struct {
    noCopy noCopy

    // 64-bit value: high 32 bits are counter, low 32 bits are waiter count.
    // 64-bit atomic operations require 64-bit alignment, but 32-bit
    // compilers do not ensure it. So we allocate 12 bytes and then use
    // the aligned 8 bytes in them as state.
    state1 [12]byte
    sema   uint32
}
  • Go语言标准库中WaitGroup只有三个方法

    • Add(delta int)表示向内部计数器添加增量(delta),其中参数delta可以是负数
    • Done()表示减少WaitGroup计数器的值,应当在程序最后执行.相当于Add(-1)
    • Wait()表示阻塞直到WaitGroup计数器为0
type WaitGroup
  func (wg *WaitGroup) Add(delta int)
  func (wg *WaitGroup) Done()
  func (wg *WaitGroup) Wait()

二.代码示例

  • 使用WaitGroup可以有效解决goroutine未执行完成主协程执行完成,导致程序结束,goroutine未执行问题
package main

import (
   "fmt"
   "sync"
)

var wg sync.WaitGroup

func main() {

   for i := 1; i <= 3; i++ {
      wg.Add(1)
      go demo(i)
   }
   //阻塞,知道WaitGroup队列中所有任务执行结束时自动解除阻塞
   fmt.Println("开始阻塞")
   wg.Wait()
   fmt.Println("任务执行结束,解除阻塞")

}

func demo(index int) {
   for i := 1; i <= 5; i++ {
      fmt.Printf("第%d次执行,i的值为:%d\n", index, i)
   }
   wg.Done()
}

章节10_02_goroutine

一.goroutine简介

  • Golang中最迷人的一个优点就是从语言层面就支持并发
  • 在Golang中的goroutine(协程)类似于其他语言的线程
  • 并发和并行

    • 并行(parallelism)指不同的代码片段同时在不同的物理处理器上支持
    • 并发(concurrency)指同时管理多个事情,物理处理器上可能运行某个内容一半后就处理其他事情
    • 在一般看来并发的性能要好于并行.因为计算机的物理资源是固定的,较少的,而程序需要执行的内容是很多的.所以并发是”以较少的资源去去做更多事情”
  • 几种主流并发模型

    • 多线程,每个线程只处理一个请求,只有请求结束后,对应的线程才会接收下一个请求.这种模式在高并发下,性能开销极大.
    • 基于回调的异步IO.在程序运行过程中可能产生大量回调导致维护成本加大,程序执行流程也不便于思维
    • 协程.不需要抢占式调用,可以有效提升线程任务的并发性,弥补了多线程模式的缺点;Golang在语言层面就支持,而其他语言很少支持
  • goroutine的语法

    • 表达式可以是一条语句
    • 表达式也可以是函数,函数返回值即使有,也无效,当函数执行完成此goroutine自动结束
    go 表达式

二. 代码示例

  • 对比多次调用函数和使用goroutine的效果
package main

import "fmt"
import "time"

func main() {
   //正常调用,输出3遍1 2 3 4 5(每个数字后换行)
   //for i:=1; i<=3; i++ {
   // go demo()
   //}

   /*
   添加go关键字后发现控制台什么也没有输出
   原因:把demo()设置到协程后没等到函数执行,主
   线程执行结束
    */
   for i := 1; i <= 3; i++ {
      go demo(i)
   }
}

func demo(index int) {
   for i := 1; i <= 5; i++ {
      fmt.Printf("第%d次执行,i的值为:%d\n", index, i)
   }
}
  • 添加休眠等待goroutine执行结束
  • 这种方式很大的问题就是休眠时间,如果休眠时间设置过小,可能goroutine并没有执行完成,如果休眠时间设置过大,影响程序执行执行.找到的本次执行的休眠时间,下次程序执行时这个休眠时间可能”过大”或”过小"
  • 通过程序运行结果发现每次执行结果都不一定是一样的,因为每个demo()都是并发执行
package main

import "fmt"
import "time"

func main() {
   //正常调用,输出3遍1 2 3 4 5(每个数字后换行)
   //for i:=1; i<=3; i++ {
   // go demo()
   //}

   /*
   添加go关键字后发现控制台什么也没有输出
   原因:把demo()设置到协程后没等到函数执行,主
   线程执行结束
    */
   for i := 1; i <= 3; i++ {
      go demo(i)
   }

   /*
   添加休眠,让主线程等待协程执行结束.
   具体休眠时间需要根据计算机性能去估计
   次数没有固定值
    */
   time.Sleep(3e9)
   fmt.Println("程序执行结束")
}

func demo(index int) {
   for i := 1; i <= 5; i++ {
      fmt.Printf("第%d次执行,i的值为:%d\n", index, i)
   }
}

章节10_01_线程休眠和计时器

一.线程休眠

  • Go语言中main()函数为主线程(协程),程序是从上向下执行的
  • 可以通过time包下的Sleep(n)让程序阻塞多少纳秒
   fmt.Println("1")
   //单位是纳秒,表示阻塞多长时间
   //e9表示10的9次方
   time.Sleep(1e9)
   fmt.Println("2")

二.延迟执行

  • 延迟指定时间后执行一次,但是需要注意在触发时程序没有结束
  fmt.Println("开始")
   //2秒后执行匿名函数
   time.AfterFunc(2e9, func() {
      fmt.Println("延迟延迟触发")
   })
   time.Sleep(10e9)//一定要休眠,否则程序结束了
   fmt.Println("结束")

章节9_10_日志

一.日志简介

  • 使用开发工具时,控制台打印的信息就是日志信息
  • 项目最终发布后是没有开发工具的,而需要记录日志应该把信息输出到文件中,这个功能也是日志的功能
  • 在Go语言标准的log包提供了对日志的支持
  • 有三种级别日志输出

    • Print() 输出日志信息
    • Panic() 打印日志信息,并触发panic,日志信息为Panic信息
    • Fatal() 打印日志信息后调用os.Exit(1)
  • 所有日志信息打印时都带有时间,且颜色为红色
  • 每种级别日志打印都提供了三个函数

    • Println()
    • Print()
    • Printf()
  • 日志文件扩展名为log

二.普通日志信息打印

  • 官方源码如下
func Println(v ...interface{}) {
    std.Output(2, fmt.Sprintln(v...))
}
  • 直接使用log包调用Println()即可
log.Println("打印日志信息")

三.Panic日志信息打印

  • 通过源码可以看出在日志信息打印后调用了panic()函数,且日志信息为panic信息
// Panicln is equivalent to Println() followed by a call to panic().
func Panicln(v ...interface{}) {
    s := fmt.Sprintln(v...)
    std.Output(2, s)
    panic(s)
}
  • 执行后输出日志信息,同时也会触发panic
log.Panicln("打印日志信息")

四.致命日志信息

  • 打印日志后,终止程序
// Fatal is equivalent to Print() followed by a call to os.Exit(1).
func Fatal(v ...interface{}) {
    std.Output(2, fmt.Sprint(v...))
    os.Exit(1)
}
  • 执行日志打印后,程序被终止
log.Fatal("打印日志信息")

五.打印日志信息到文件中

  • Go语言标准库支持输出日志信息到文件中.
  • 输出日志时的几种状态
const (
    Ldate         = 1 << iota     // the date in the local time zone: 2009/01/23
    Ltime                         // the time in the local time zone: 01:23:23
    Lmicroseconds                 // microsecond resolution: 01:23:23.123123.  assumes Ltime.
    Llongfile                     // full file name and line number: /a/b/c/d.go:23
    Lshortfile                    // final file name element and line number: d.go:23. overrides Llongfile
    LUTC                          // if Ldate or Ltime is set, use UTC rather than the local time zone
    LstdFlags     = Ldate | Ltime // initial values for the standard logger
)
  • 代码如下
    f, _ := os.OpenFile("D:/golog.log", os.O_APPEND|os.O_CREATE, 07777)
    defer f.Close()
    logger := log.New(f, "[info]\t", log.Ltime)
    logger.Println("输出日志信息")

章节9_09_XML文件生成

一.生成XML

  • 生成XML只要在学习下encoding/xml包下的Marshal()函数,结合输入流就可以完成xml文件生成
  • 在encoding/xml中有常量,常量中是xml文档头
const (
    // Header is a generic XML header suitable for use with the output of Marshal.
    // This is not automatically added to any output of this package,
    // it is provided as a convenience.
    Header = `<?xml version="1.0" encoding="UTF-8"?>` + "\n"
)

二.代码示例

  • 使用Marshal()函数生成的[]byte没有格式化
  • 使用MarshalIndent()可以对内容进行格式化

    • 第一个参数:结构体对象
    • 第二个参数:每行的前缀
    • 第三个参数:层级缩进内容
type People struct {
    XMLName xml.Name `xml:"people"`
    Id      int      `xml:"id,attr"`
    Name    string   `xml:"name"`
    Address string   `xml:"address"`
}

func main() {
    peo := People{Id: 123, Name: "smallming", Address: "北京海淀"}
    b, _ := xml.MarshalIndent(peo, "", "    ")
    b = append([]byte(xml.Header), b...)
    ioutil.WriteFile("D:/peo.xml", b, 0666)
    fmt.Println("程序结束")
}

章节9_08_XML文件内容读取

一.Go语言标准库提供的API

  • 在encoding/xml包下提供了对XML序列化和反序列化的API
  • 使用Unmarshal可以直接把XML字节切片数据转换为结构体
  • 转换时按照特定的转换规则进行转换,且数据类型可以自动转换
* 如果结构体字段的类型为字符串或者[]byte,且标签为",innerxml",
  Unmarshal函数直接将对应原始XML文本写入该字段,其余规则仍适用。
* 如果结构体字段类型为xml.Name且名为XMLName,Unmarshal会将元素名写入该字段
* 如果字段XMLName的标签的格式为"name"或"namespace-URL name",
  XML元素必须有给定的名字(以及可选的名字空间),否则Unmarshal会返回错误。
* 如果XML元素的属性的名字匹配某个标签",attr"为字段的字段名,或者匹配某个标签为"name,attr"
  的字段的标签名,Unmarshal会将该属性的值写入该字段。
* 如果XML元素包含字符数据,该数据会存入结构体中第一个具有标签",chardata"的字段中,
  该字段可以是字符串类型或者[]byte类型。如果没有这样的字段,字符数据会丢弃。
* 如果XML元素包含注释,该数据会存入结构体中第一个具有标签",comment"的字段中,
  该字段可以是字符串类型或者[]byte类型。如果没有这样的字段,字符数据会丢弃。
* 如果XML元素包含一个子元素,其名称匹配格式为"a"或"a>b>c"的标签的前缀,反序列化会深入
  XML结构中寻找具有指定名称的元素,并将最后端的元素映射到该标签所在的结构体字段。
  以">"开始的标签等价于以字段名开始并紧跟着">" 的标签。
* 如果XML元素包含一个子元素,其名称匹配某个结构体类型字段的XMLName字段的标签名,
  且该结构体字段本身没有显式指定标签名,Unmarshal会将该元素映射到该字段。
* 如果XML元素的包含一个子元素,其名称匹配够格结构体字段的字段名,且该字段没有任何模式选项
  (",attr"、",chardata"等),Unmarshal会将该元素映射到该字段。
* 如果XML元素包含的某个子元素不匹配以上任一条,而存在某个字段其标签为",any",
  Unmarshal会将该元素映射到该字段。
* 匿名字段被处理为其字段好像位于外层结构体中一样。
* 标签为"-"的结构体字段永不会被反序列化填写。

二. XML文件读取

  • 给定XML文件内容如下
<?xml version="1.0" encoding="UTF-8" ?>
<people id="888">
    <name>smallming</name>
    <address>北京海淀</address>
</people>
  • 新建结构体,装载XML数据

    • 结构体中属性首字母必须大写,否则无法装配
type People struct {
    XMLName xml.Name `xml:"people"`
    Id      int      `xml:"id,attr"`
    Name    string   `xml:"name"`
    Address string   `xml:"address"`
}

func main() {
    peo := new(People)
    b, err := ioutil.ReadFile("demo.xml")
    fmt.Println(string(b))
    fmt.Println("111:", err)
    err = xml.Unmarshal(b, peo)
    fmt.Println("2222", err)
    fmt.Println(peo)
}

三.多层嵌套XML文件读取

  • 给定XML中数据如下
<?xml version="1.0" encoding="UTF-8" ?>
<peoples version="0.9">
    <people id="888">
        <name>smallming</name>
        <address>北京海淀</address>
    </people>
    <people id="998">
        <name>佳明哥</name>
        <address>北京朝阳</address>
    </people>
</peoples>
  • 编写读取XML数据代码
type Peoples struct {
    XMLName xml.Name `xml:"peoples"`
    Version string   `xml:"version,attr"`
    Peos    []People `xml:"people"`
}

type People struct {
    XMLName xml.Name `xml:"people"`
    Id      int      `xml:"id,attr"`
    Name    string   `xml:"name"`
    Address string   `xml:"address"`
}

func main() {
    peo := new(Peoples)
    b, err := ioutil.ReadFile("demo.xml")
    fmt.Println(string(b))
    fmt.Println("111:", err)
    err = xml.Unmarshal(b, peo)
    fmt.Println("2222", err)
    fmt.Println(peo)
}

章节9_07_XML文件简介

一.XML文件简介

  • 英文全称:Extensible Markup Language
  • 中文全称:可扩展标记语言
  • 用途:

    • 数据存储
    • 数据交互
    • 配置文件
  • 优点:

    • 跨平台性
    • 数据工整,易读

二.XML文档结构

  • XML文档结构

    • 第一行:XML头,版本和编码
    • 第二行:DTD可选,能够检查XML内容是否满足要求
    • 最外层标签<peoples> 称为元素节点,根节点
    • <people>...<people> 整个标签称为元素节点
    • id=”1” 属性节点e
    • 标签中文字:文本节点
  • 示例如下

三.语法要求

  • 严格区分大小写
  • 标签必须正确嵌套,必须正确关闭
  • 必须有根节点
  • 属性值必须有双引号
  • 注释:<!-- -->
  • 文本节点出现特殊字符,需要使用实体引用替换

章节9_06_反射

一.反射介绍

  • 在Go语言标准库中reflect包提供了运行时反射,程序运行过程中动态操作结构体
  • 当变量存储结构体属性名称,想要对结构体这个属性赋值或查看时,就可以使用反射.
  • 反射还可以用作判断变量类型
  • 整个reflect包中最重要的两个类型

    • reflect.Type 类型
    • reflect.Value 值
  • 获取到Type和Value的函数

    • reflect.TypeOf(interface{}) 返回Type
    • reflect.ValueOf(interface{}) 返回值Value

二.代码示例

  • 判断变量类型
   a:=1.5
   fmt.Println(reflect.TypeOf(a))
  • 获取结构体属性的值
ype People struct {
   Id   int
   Name string
}

func main() {
   fmt.Println("asdf")

   peo := People{1, "张三"}

   //获取peo的值
   v := reflect.ValueOf(peo)
   //获取属性个数,如果v不是结构体类型panic
   fmt.Println(v.NumField())

   //获取第0个属性,id,并转换为int64类型
   fmt.Println(v.Field(0).Int())
   //获取第1个属性,转换换为string类型
   fmt.Println(v.Field(1).String())

   //根据名字获取类型,并把类型名称转换为string类型
   idValue := v.FieldByName("Id")
   fmt.Println(idValue.Kind().String())

}
  • 设置结构体属性的值时要传递结构体指针,否者无法获取设置的结构体对象

    • 反射直射结构体属性时,要求属性名首字母必须大写,否则无法设置
package main

import (
   "fmt"
   "reflect"
)

type People struct {
   Id   int
   Name string
}

func main() {
   fmt.Println("asdf")
   peo := People{1, "张三"}

   /*
   反射时获取peo的地址.
   Elem()获取指针指向地址的封装.
   地址的值必须调用Elem()才可以继续操作
    */
   v := reflect.ValueOf(&peo).Elem()

   fmt.Println(v.FieldByName("Id").CanSet())
   v.FieldByName("Id").SetInt(123)
   v.FieldByName("Name").SetString("李四")
   fmt.Println(peo)
}
  • 结构体支持标记(tag),标记通常都是通过反射技术获取到.结构体标记语法
type 结构体名称 struct{
  属性名 类型 `key:"Value"`
}
  • 获取结构体标记(tag)
type People struct {
    Name    string `xml:"name"`
    Address string `xml:"address"`
}

func main() {
    t:=reflect.TypeOf(People{})
    name,_:=t.FieldByName("Name")
    fmt.Println(name.Tag)//获取完整标记
    fmt.Println(name.Tag.Get("xml"))//获取标记中xml对应内容
}

章节9_05_ioutil

一. ioutil包

  • ioutil包下提供了对文件读写的工具函数,通过这些函数快速实现文件的读写操作
  • ioutil包下提供的函数比较少,但是都是很方便使用的函数
func NopCloser(r io.Reader) io.ReadCloser
func ReadAll(r io.Reader) ([]byte, error)
func ReadFile(filename string) ([]byte, error)
func WriteFile(filename string, data []byte, perm os.FileMode) error
func ReadDir(dirname string) ([]os.FileInfo, error)
func TempDir(dir, prefix string) (name string, err error)
func TempFile(dir, prefix string) (f *os.File, err error)

二.代码演示

  • 打开完文件后可以使用ReadAll把文件中所有内容都读取到
    f, err := os.Open("D:/go.txt")
    defer f.Close()
    if err != nil {
        fmt.Println(err)
        return
    }
    b, err := ioutil.ReadAll(f)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println("文件中内容:\n", string(b))
  • 也可以直接读取文件中内容
    b, err := ioutil.ReadFile("D:/go.txt")
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println(string(b))
  • 写文件也很简单,直接使用WriteFile函数即可,但是源码中已经规定此文件只能是可写状态,且不是尾加数据
    err := ioutil.WriteFile("D:/abc.txt", []byte("内容123123"), 0666)
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println("数据写入成功")
  • 还提供了快速获取某个文件夹中所有文件信息的函数
    fs,_:=ioutil.ReadDir("D:/")
    for _,n := range fs {
        fmt.Println(n.Name())
    }

章节9_04_Writer

一. 输入流

  • 输入流就是把程序中数据写出到外部资源
  • Go语言标准库中输出流是Writer接口
// Writer is the interface that wraps the basic Write method.
//
// Write writes len(p) bytes from p to the underlying data stream.
// It returns the number of bytes written from p (0 <= n <= len(p))
// and any error encountered that caused the write to stop early.
// Write must return a non-nil error if it returns n < len(p).
// Write must not modify the slice data, even temporarily.
//
// Implementations must not retain p.
type Writer interface {
    Write(p []byte) (n int, err error)
}

二.代码操作

  • 注意:输入流时不要使用os.Open()因为这种方式获取的文件是只读的
    fp := "D:/go.txt"
    /*
    第三个参数表示文件权限
    第 1 位在权限中总是为 0
    第 2 位为 0 表示文件不可以被读, 为 1 表示可以被读
    第 3 位为 0 表示文件不可以被写, 为 1 表示可以被写
    第 4 位为 0 表示文件不可以被执行, 为 1 表示可以被执行
    整理如下:
       0(0000): 不可读写,不能被执行
       1(0001): 不可读写,能被执行
       2(0010): 可写不可读,不能被执行
       3(0011): 可写不可读,能被执行
       4(0100): 可读不可写,不能被执行
       5(0101): 可读不可写,能被执行
       6(0110): 可读写,不能执行
       7(0111): 可读写,可执行

    0666:
    第一个 0 表示这个数是 八进制
    第一个 6 表示文件拥有者有读写权限,但没有执行权限
    第二个 6 表示文件拥有者同组用户有读写权限,但没有执行权限
    第三个 6 表示其它用户有读写权限,但没有执行权限

     */

    //第二个参数表示文件内容追加
    //第三个参数表示创建文件时文件权限
    f, err := os.OpenFile(fp, os.O_APPEND, 0660)
    defer f.Close()
    if err != nil {
        fmt.Println("文件不存在,创建文件")
        f, _ = os.Create(fp)
    }

    /*
    内容中识别特殊字符
    \r\n 换行
    \t 缩进
     */

    /*
    使用文件对象重写的Writer接口,参数是[]byte
     */
    f.Write([]byte("使用Writer接口写数据\r\n"))

    /*
    使用stringWriter接口的方法,参数是字符串,使用更方便
     */
    f.WriteString("写了\t一段\r\n内容123")
    fmt.Println("程序执行结束")

章节9_03_Reader

一.输入流

  • 流(stream)是应用程序和外部资源进行数据交互的纽带
  • 流分为输入流和输出流,输入和输出都是相对于程序,把外部数据传入到程序中叫做输入,反之叫做输出流
  • 输入流(Input Stream),输入流(Output Stream) 平时所说的I/O流
  • 在Go语言标准库中io包下是Reader接口表示输入流,只要实现这个接口就属于输入流
// Reader is the interface that wraps the basic Read method.
//
// Read reads up to len(p) bytes into p. It returns the number of bytes
// read (0 <= n <= len(p)) and any error encountered. Even if Read
// returns n < len(p), it may use all of p as scratch space during the call.
// If some data is available but not len(p) bytes, Read conventionally
// returns what is available instead of waiting for more.
//
// When Read encounters an error or end-of-file condition after
// successfully reading n > 0 bytes, it returns the number of
// bytes read. It may return the (non-nil) error from the same call
// or return the error (and n == 0) from a subsequent call.
// An instance of this general case is that a Reader returning
// a non-zero number of bytes at the end of the input stream may
// return either err == EOF or err == nil. The next Read should
// return 0, EOF.
//
// Callers should always process the n > 0 bytes returned before
// considering the error err. Doing so correctly handles I/O errors
// that happen after reading some bytes and also both of the
// allowed EOF behaviors.
//
// Implementations of Read are discouraged from returning a
// zero byte count with a nil error, except when len(p) == 0.
// Callers should treat a return of 0 and nil as indicating that
// nothing happened; in particular it does not indicate EOF.
//
// Implementations must not retain p.
type Reader interface {
    Read(p []byte) (n int, err error)
}

二.代码演示

  • 可以使用strings包下的NewReader创建字符串流
    r := strings.NewReader("hello 世界")
    b := make([]byte, r.Size())//创建字节切片,存放流中数据,根据流数据大小创建切片大小
    n, err := r.Read(b)//把流中数据读取到切片中
    if err != nil {
        fmt.Println("读取失败,", err)
        return
    }
    fmt.Println("读取数据长度,", n)

    fmt.Println("流中数据",string(b))//以字符串形式输入切片中数据
  • 最常用的是文件流,把外部文件中数据读取到程序中
    f, err := os.Open("D:/go.txt")//打开文件
    defer f.Close()
    if err != nil {
        fmt.Println("文件读取失败,", err)
        return
    }
    fileInfo, err := f.Stat()//获取文件信息
    if err != nil {
        fmt.Println("文件信息获取失败,", err)
        return
    }
    b := make([]byte, fileInfo.Size())//根据文件中数据大小创建切片
    _, err = f.Read(b)//读取数据到切片中
    if err != nil {
        fmt.Println("文件流读取失败:", err)
        return
    }
    fmt.Println("文件中内容为:", string(b))//以字符串形式输入切片中数据

章节9_02_系统文件和目录

一. os包内容介绍

  • 使用os包中内容进行操作系统文件或目录
  • File结构体表示操作系统文件(夹)
// File represents an open file descriptor.
type File struct {
    *file // os specific
}
// file is the real representation of *File.
// The extra level of indirection ensures that no clients of os
// can overwrite this data, which could cause the finalizer
// to close the wrong file descriptor.
type file struct {
    pfd     poll.FD
    name    string
    dirinfo *dirInfo // nil unless directory being read
}
  • 操作系统的文件都是有权限控制的,包含可读,可写等,在os包中FileMode表示文件权限,本质是uint32,可取值都以常量形式提供
// A FileMode represents a file's mode and permission bits.
// The bits have the same definition on all systems, so that
// information about files can be moved from one system
// to another portably. Not all bits apply to all systems.
// The only required bit is ModeDir for directories.
type FileMode uint32
// The defined file mode bits are the most significant bits of the FileMode.
// The nine least-significant bits are the standard Unix rwxrwxrwx permissions.
// The values of these bits should be considered part of the public API and
// may be used in wire protocols or disk representations: they must not be
// changed, although new bits might be added.
const (
    // The single letters are the abbreviations
    // used by the String method's formatting.
    ModeDir        FileMode = 1 << (32 - 1 - iota) // d: is a directory
    ModeAppend                                     // a: append-only
    ModeExclusive                                  // l: exclusive use
    ModeTemporary                                  // T: temporary file; Plan 9 only
    ModeSymlink                                    // L: symbolic link
    ModeDevice                                     // D: device file
    ModeNamedPipe                                  // p: named pipe (FIFO)
    ModeSocket                                     // S: Unix domain socket
    ModeSetuid                                     // u: setuid
    ModeSetgid                                     // g: setgid
    ModeCharDevice                          // c: Unix character device, when ModeDevice is set
    ModeSticky                                     // t: sticky

    // Mask for the type bits. For regular files, none will be set.
    ModeType = ModeDir | ModeSymlink | ModeNamedPipe | ModeSocket | ModeDevice

    ModePerm FileMode = 0777 // Unix permission bits
)
  • FIleInfo是一个interface表示文件的信息
// A FileInfo describes a file and is returned by Stat and Lstat.
type FileInfo interface {
    Name() string       // base name of the file
    Size() int64        // length in bytes for regular files; system-dependent for others
    Mode() FileMode     // file mode bits
    ModTime() time.Time // modification time
    IsDir() bool        // abbreviation for Mode().IsDir()
    Sys() interface{}   // underlying data source (can return nil)
}

二. 资源路径

  • 在获取系统资源时资源路径分为相对路径和绝对路径
  • 相对路径:在Go语言中相对路径用于是GOPATH,也就是项目的根目录
  • 绝对路径:磁盘根目录开始表示资源详细路径的描述

三.代码示例

  • Go语言标准库中提供了两种创建文件夹的方式
    /*
    要求文件夹不存在且父目录必须存在,才能创建
     */
    //error := os.Mkdir("D:/godir", os.ModeDir)
    //if error != nil {
    //    fmt.Println("文件夹创建失败",error)
    //    return
    //}
    //fmt.Println("文件夹创建成功")


    /*
    如果文件夹已经存在,不报错,保留原文件夹
    如果父目录不存在帮助创建
     */
    error := os.MkdirAll("D:/godir/a/b", os.ModeDir)
    if error != nil {
        fmt.Println("文件夹创建失败",error)
        return
    }
    fmt.Println("文件夹创建成功")
  • 创建空文件
    /*
    创建文件时要求文件目录必须已经存在
    如果文件已经存在则会创建一个空文件覆盖之前的文件
     */
    file, err := os.Create("D:/godir/test.txt")
    if err != nil {
        fmt.Println("文件创建失败,", err)
        return
    }
    fmt.Println("文件创建成功",file.Name())
  • 重命名文件或文件夹
    /*
    第一个参数:原文件夹名称,要求此路径是必须存在的
    第二个参数:新文件夹名称
     */
    err := os.Rename("D:/godir", "D:/godir1")
    if err != nil {
        fmt.Println("重命名文件夹失败,", err)
        return
    }
    fmt.Println("文件夹重命名成功")

    /*
    重命名文件和重命名文件夹用法相同
     */
    err = os.Rename("D:/godir1/test.txt", "D:/godir1/test1.txt")
    if err != nil {
        fmt.Println("重命名文件失败,", err)
        return
    }
    fmt.Println("文件重命名成功")
  • 获取文件(夹)信息
    f, err := os.Open("D:/godir1/test1.txt")
    defer f.Close() //文件打开后要关闭,释放资源
    if err != nil {
        fmt.Println("打开文件失败", err)
        return
    }
    fileInfo, err := f.Stat()
    if err != nil {
        fmt.Println("获取文件信息失败", err)
        return
    }
    fmt.Println(fileInfo.Name())    //文件名
    fmt.Println(fileInfo.IsDir())   //是否是文件夹,返回bool,true表示文件夹,false表示文件
    fmt.Println(fileInfo.Mode())    //文件权限
    fmt.Println(fileInfo.ModTime()) //修改时间
    fmt.Println(fileInfo.Size())    //文件大小
  • 删除文件或文件夹
    /*
    删除的内容只能是一个文件或空文件夹且必须存在
     */
    //err := os.Remove("D:/godir1/a")
    //if err != nil {
    //    fmt.Println("文件删除失败", err)
    //    return
    //}
    //fmt.Println("删除成功")

    /*
    只要文件夹存在,删除文件夹.
    无论文件夹是否有内容都会删除
    如果删除目标是文件,则删除文件
     */
    err := os.RemoveAll("D:/godir1/a.txt")
    if err != nil {
        fmt.Println("删除失败", err)
        return
    }
    fmt.Println("删除成功")

章节9_01_获取操作系统用户信息

一.os包结构介绍

  • Go语言标准库中os包提供了不依赖平台的操作系统接口
  • 设计为Unix风格的,而错误处理是go风格的,失败的调用会返回错误值而非错误码。通常错误值里包含更多信息
  • os包及子包功能
-- os 包
  --os/exec 包,负责执行外部命令.
  --os/signal对输入信息的访问
  --os/user 通过名称或ID    查询用户账户
  • 在os/user中提供了User结构体,表示操作系统用户

    • Uid 用户id
    • Gid 所属组id
    • Username 用户名
    • Name 所属组名
    • HomeDir 用户对应文件夹路径
// User represents a user account.
type User struct {
    // Uid is the user ID.
    // On POSIX systems, this is a decimal number representing the uid.
    // On Windows, this is a security identifier (SID) in a string format.
    // On Plan 9, this is the contents of /dev/user.
    Uid string
    // Gid is the primary group ID.
    // On POSIX systems, this is a decimal number representing the gid.
    // On Windows, this is a SID in a string format.
    // On Plan 9, this is the contents of /dev/user.
    Gid string
    // Username is the login name.
    Username string
    // Name is the user's real or display name.
    // It might be blank.
    // On POSIX systems, this is the first (or only) entry in the GECOS field
    // list.
    // On Windows, this is the user's display name.
    // On Plan 9, this is the contents of /dev/user.
    Name string
    // HomeDir is the path to the user's home directory (if they have one).
    HomeDir string
}
  • 在os/user中的Group表示用户所属组

    • Gid 组的id
    • Name 组的名称
// Group represents a grouping of users.
//
// On POSIX systems Gid contains a decimal number representing the group ID.
type Group struct {
    Gid  string // group ID
    Name string // group name
}
  • 整个os/user包中内容比较少,提供了两个错误类型和获取当前用户,查找用户
type UnknownUserError
  func (e UnknownUserError) Error() string
type UnknownUserIdError
  func (e UnknownUserIdError) Error() string
type User
  func Current() (*User, error)
  func Lookup(username string) (*User, error)
  func LookupId(uid string) (*User, error)

二.代码示例

  • 可以获取当前用户或查找用户后获取用户信息
   //获取当前登录用户
   //u,_:=user.Current()
   /*
   Lookup()参数是用户名,按照用户名查找指定用户对象
   注意:必须使用完整名称不可以只写zhang
    */
   u, _ := user.Lookup(`LAPTOP-M7D47U95\zhang`)
   fmt.Println(u.Name)
   fmt.Println(u.Gid)
   fmt.Println(u.HomeDir)
   fmt.Println(u.Uid)
   fmt.Println(u.Username)