zoukankan      html  css  js  c++  java
  • go语言入门

    引言

    Go


    Go语言是谷歌2009发布的编程语言,它是一种并发的、带垃圾回收的、快速编译的语言。

    它结合了解释型语言的游刃有余,动态类型语言的开发效率,以及静态类型的安全性。它也打算成为现代的,支持网络与多核计算的语言。要满足这些目标,需要解决一些语言上的问题:一个富有表达能力但轻量级的类型系统,并发与垃圾回收机制,严格的依赖规范等等。这些无法通过库或工具解决好,因此Go也就应运而生了。

    优势

    1. 语法简单,上手快;
    2. 性能高,编译快,开发效率也不低;
    3. 丰富的标准库;
    4. 原生支持并发,协程模型是非常优秀的服务端模型,同时也适合网络调用;
    5. 部署方便,编译包小,除 glibc 外没有其他外部依赖;
    6. 自带完善的工具链, 大大提高了团队协作效率和一致性。 比如 gofmt ,gofix,,govet 等工具。

    Docker等很多 Go 产品的流行,更证明了 Go 语言的优秀。

    适用场景

    - 服务器编程,如:处理日志、数据打包、虚拟机处理、文件系统等;

    - 分布式系统,数据库代理器等;

    - 内存数据库,google开发的groupcache,couchbase的部分组建;

    - 云平台,目前国外很多云平台在采用Go开发,如:CloudFoundy(VMware推出的业界第一个开源PaaS云平台)的部分组建。

    缺点

    1. Go的import包不支持版本,有时候升级容易导致项目不可运行,需要自己控制相应的版本信息;

    2. Go的goroutine一旦启动之后,不同的goroutine之间切换不是受程序控制,需要严谨的逻辑;

    3. 没什么太多应用场景非要 Golang 才能做的

        3.1 开发 web 没有 php ruby 成熟、快速

        3.2 开发 server 没有 java 现成解决方案多


    GO指南

    环境搭建

    安装Golang的SDK


    (1) http://www.golangtc.com/download
    (2) 安装完成之后,打开终端,输入go、或者go version查看安装版本

    配置Go环境变量

            配置Go环境变量GOPATH和GOBIN

      (1)打开终端,cd ~

      (2)查看是否有.bash_profile文件:

         ls -all

      (3)有则跳过此步,没有则:

        1)创建:touch .bash_profile

        2)编辑:open -e .bash_profile

        3)自定义GOPATH和GOBIN位置:

    GOPATH:日常开发的根目录。GOBIN:是GOPATH下的bin目录。

    export GOPATH=/Users/yuan/go
    export GOBIN=$GOPATH/bin
    export PATH=$PATH:$GOBIN

      (4)编译:source .bash_profile

      (5)*查看Go环境变量:go env


    开发工具配置


    sublime text


    一定要先配置好Go环境变量GOPATH和GOBIN,再安装此插件,要不插件会提示找不到GOPATH和GOBIN;

    选用 sublime text 安装 gosublime 插件进行开发( golang 语法高亮提示)

      (1)安装 package controll(若已安装,请跳过)
      使用Ctrl+`快捷键或者通过View->Show Console菜单打开命令行,粘贴如下代码:

    import urllib.request,os; pf = 'Package Control.sublime-package'; ipp = sublime.installed_packages_path(); urllib.request.install_opener( urllib.request.build_opener( urllib.request.ProxyHandler()) ); open(os.path.join(ipp, pf), 'wb').write(urllib.request.urlopen( 'http://sublime.wbond.net/' + pf.replace(' ','%20')).read()) 2


      (2)install go sublime
    Command+shift+p 输入并选择packageControl: install package
    然后输入并选择goSublime
    安装完成就OK啦~~


    Gogland


    选择Gogland, 下载安装即可,3个月
    https://www.jetbrains.com/go/download/

    LiteIDE


    国产IDE 
    http://golangtc.com/download/liteide


    小试牛刀


    在你的gopath目录下,新建main.go文件即可以进行编码了。

    package main
    import (
    	"fmt"
    )
    func main() {
    	fmt.Println("hello gopher~")
    }
    
    



    代码编写完成之后,使用command+b打开sublime text终端

    (一)编译+执行
    使用go build main.go对其进行编译,编译通过的结果信息如下:

    [ `go build main.go` | done: 420.495985ms ]
    提示编译成功之后,再执行shell命令,执行刚刚编译之后的文件./main即可看到运行结果:

    [ `./main` | done: 10.532868ms ]
    hello go

    (二)直接执行
    如果仅仅是只需要看到运行的结果,而不产生可执行文件(文件名和项目名一样)则在sublime text终端中直接使用go run xxx.go即可:

    [ `go run main.go` | done: 314.476988ms ]
    hello go


    基础

    包、变量、函数

    package main
    import "fmt"
    
    func main() {
    	var hello string = "Hello"
    	who := "gopher"
    	var s = hello+"," + who
    	fmt.Println(s)
    }


    1. 每个 Go 程序都是由包组成的。
    2. 程序运行的入口是包 main 。
    3. 按照惯例,包名与导入路径的最后一个目录一致。例如,"math/rand" 包由 package rand 语句开始。

    变量

    • 变量声明使用关键字var
    • 初始值存在时可省略类型声明
    • 短赋值语句:= 可以用于替代 var 的隐式类型声明(:=结构不能使用在函数外,函数外的每个语法块都必须以关键字开始)
    var name1 string //声明变量
    name1 = "tom" //给变量赋值
    var name2 string = "tom" //声明变量+赋值
    var name3 = "tom" // 声明时同时赋值,可以省略变量类型
    name4 := "tom" //短赋值语句
    // 多个变量
    var x, y, z int
    var c, python, java bool
    var x, y, z int = 1, 2, 3
    var c, python, java = true, false, "no!"
    c, python, java := true, false, "no!"


    函数

    • 可以返回任意数量的返回值
    • 类型声明在变量名之后
    • 同一类型的多个参数,最后一个参数需声明类型
    func swap(x, y string) (string, string) {
    	return y, x
    }



    • 命名返回值的参数
    func split(sum int) (x, y int) {
    	x = sum * 4/9
    	y = sum - x
    	return
    }



    基本类型

    bool
    string
    int int8 int16 int32 int64
    uint uint8 uint16 uint32 uint64 uintptr
    byte // uint8 的别名
    rune // int32 的别名
    // 代表一个Unicode码位
    
    float32 float64
    complex64 complex128

    常量

    const Pi = 3.14
    const World = "世界"
    const Truth = true


    运算符


    http://www.yiibai.com/go/go_operators.html

    注意:

    • 没有++i,--i,只有i++、i--
    • 不能使用i++、i--对变量直接赋值


    流程控制


    for


    Go 只有一种循环结构—— for 循环

    for i := 0; i < 10; i++ {
    	//do something
    }
    i := 0
    for ; i < 1000; {
    	//do something
    }
    for i < 1000 {
    	//do something
    } 
    for {
    	//死循环
    }

    if else

    if x < 0 {
    	return x
    }
    if v := 0; v < 5 {
    	return v
    }
    return 9

    switch


    case 语句匹配后会自动终止(无需break),除非用 fallthrough 语句作为结尾,则会强制执行下一条case的语句或者default语句,而不判断expression。

    	x := 2
    	switch x {
    	case 1:
    		fmt.Println(1)
    	case 2:
    		fmt.Println(2)
    		fallthrough
    	case 3:
    		fmt.Println(x > 1)
    	default:
    		fmt.Println("default")
    	}
    
    // 结果
    // 2
    // true



    defer


    延迟(defer)处理
    Defer用于确保在稍后的程序中,执行函数调用。
    defer语句在封装函数(main)结束时执行。

    package main
    import "fmt"
    import "os"
    func main() {
    	f := createFile("defer-test.txt")
    	defer closeFile(f)
    	writeFile(f)
    }
    func createFile(p string) *os.File {
    	fmt.Println("creating")
    	f, err := os.Create(p)
    	if err != nil {
    		panic(err)
    	}
    	return f
    }
    func writeFile(f *os.File) {
    	fmt.Println("writing")
    	fmt.Fprintln(f, "data")
    }
    func closeFile(f *os.File) {
    	fmt.Println("closing")
    	f.Close()
    }
    
    // output
    creating
    writing
    closing

    复杂类型


    struct


    要定义结构,必须使用type和struct语句。struct语句定义了一个新的数据类型,在程序中有多个成员。type语句在例子中绑定一个类型为struct的名字。 struct语句的格式如下:

    type person struct {
    	name string
    	age int
    }


    访问结构体成员使 .

    package main
    import "fmt"
    type person struct {
    	name string
    	age  int
    }
    func main() {
    	fmt.Println(person{"Bob", 20})
    	fmt.Println(person{name: "Alice", age: 30})
    	fmt.Println(person{name: "Fred"})
    	fmt.Println(&person{name: "Ann", age: 40})
    	s := person{name: "Sean", age: 50}
    	fmt.Println(s.name)
    	sp := &s
    	fmt.Println(sp.age)
    	sp.age = 51
    	fmt.Println(sp.age)
    }
    
    
    #output
    {Bob 20}
    {Alice 30}
    {Fred 0}
    &{Ann 40}
    Sean
    50
    51

    slice


    因为切片(Slice)是数组上的抽象。 它实际上使用数组作为底层结构体.len()函数返回切片中存在的元素数量,其中cap()函数返回切片(Slice)的容量(大小),即可容纳多少个元素。

    package main
    import "fmt"
    func main() {
    	var numbers = make([]int, 3, 5)
    	printSlice(numbers)
    }
    func printSlice(x []int) {
    	fmt.Printf("len=%d cap=%d slice=%v
    ", len(x), cap(x), x)
    }
    
    
    //output
    len=3 cap=5 slice=[0 0 0]

    map

    var map_variable map[key_data_type]value_data_type
    map_variable = make(map[key_data_type]value_data_type)

    map_variable := make(map[key_data_type]value_data_type)


    delete(map_variable, key)

    package main
    import "fmt"
    func main() {
    	var countryCapitalMap map[string]string = make(map[string]string)
    	/* create a map*/
    	// countryCapitalMap = make(map[string]string)
    	/* insert key-value pairs in the map*/
    	countryCapitalMap["France"] = "Paris"
    	countryCapitalMap["Italy"] = "Rome"
    	countryCapitalMap["Japan"] = "Tokyo"
    	countryCapitalMap["India"] = "New Delhi"
    	/* print map using keys*/
    	for country := range countryCapitalMap {
    		fmt.Println("Capital of", country, "is", countryCapitalMap[country])
    	}
    	/* test if entry is present in the map or not*/
    	capital, ok := countryCapitalMap["United States"]
    	/* if ok is true, entry is present otherwise entry is absent*/
    	if ok {
    		fmt.Println("Capital of United States is", capital)
    	} else {
    		fmt.Println("Capital of United States is not present")
    	}
    	/* delete an entry */
    	delete(countryCapitalMap, "France")
    	fmt.Println("Entry for France is deleted")
    	fmt.Println("Updated map")
    	/* print map */
    	for country := range countryCapitalMap {
    		fmt.Println("Capital of", country, "is", countryCapitalMap[country])
    	}
    }

    range

    range函数可以用来遍历array,slice和map。

    当用于遍历array和slice时,range函数返回索引和元素;

    当用于遍历map的时候,range函数返回key和value。

    package main
    import "fmt"
    func main() {
    	nums := []int{2, 3, 4}
    	sum := 0
    	for _, num := range nums {
    		sum += num
    	}
    	fmt.Println("sum:", sum)
    	for i, num := range nums {
    		if num == 3 {
    			fmt.Println("index:", i)
    		}
    	}
    	kvs := map[string]string{"a": "apple", "b": "banana"}
    	for k, v := range kvs {
    		fmt.Printf("%s -> %s
    ", k, v)
    	}
    	for k := range kvs {
    		fmt.Println("key:", k)
    	}
    }
    

    方法和接口

    方法

    Go中没有类,但是可以为结构体定义方法,方法就是一类带有特殊的接受者参数的函数。

    方法接受者位于func关键字和方法名之间。
    可以为非结构体类型声明方法,但不能为其它包内定义的类型的接收者声明方法,且不能为内建类型声明方法。

    package main
    import "fmt"
    func add(x int, y int) int {
    	return x + y
    }
    func main() {
    	fmt.Println(add(42, 13))
    }
    package main
    import "fmt"
    type rect struct {
    	width, height int
    }
    func (r *rect) area() int {
    	return r.width * r.height
    }
    func (r rect) perim() int {
    	return 2*r.width + 2*r.height
    }
    func main() {
    	r := rect{ 10, height: 5}
    	fmt.Println("area: ", r.area())
    	fmt.Println("perim:", r.perim())
    	rp := &r
    	fmt.Println("area: ", rp.area())
    	fmt.Println("perim:", rp.perim())
    }
    
    
    
    // output
      area:  50
      perim: 30
      area:  50
      perim: 30

    函数是完全闭包的

    package main
    import "fmt"
    // 函数 adder 返回一个闭包。每个闭包被绑定到自己的 sum 变量上
    func adder() func(int) int {
    	sum := 0
    	return func(x int) int {
    		sum += x
    		return sum
    	}
    }
    func main() {
    	pos, neg := adder(), adder()
    	for i := 0; i < 10; i++ {
    		fmt.Println(
    			pos(i),
    			neg(-2*i),
    		)
    	}
    }
    
    

    接口

    接口类型是由一组方法签名的集合。
    接口类型的值可以保存任何实现了接口方法的变量。
    类型通过实现了一个接口的所有方法来实现这个接口,而不需要专门的显示声明也就是”implements”关键字来声明。

    package main

    import "fmt"
    import "math"

    type geometry interface {
    area() float64
    perim() float64
    }
    type rect struct {
    width, height float64
    }
    type circle struct {
    radius float64
    }

    func (r rect) area() float64 {
    return r.width * r.height
    }
    func (r rect) perim() float64 {
    return 2*r.width + 2*r.height
    }
    func (c circle) area() float64 {
    return math.Pi * c.radius * c.radius
    }
    func (c circle) perim() float64 {
    return 2 * math.Pi * c.radius
    }
    func measure(g geometry) {
    fmt.Println(g)
    fmt.Println(g.area())
    fmt.Println(g.perim())
    }
    func main() {
    r := rect{ 3, height: 4}
    c := circle{radius: 5}
    measure(r)
    measure(c)
    }

    // output
    {3 4}
    12
    14
    {5}
    78.53981633974483
    31.41592653589793


    error


    Go编程提供了一个非常简单的错误处理框架,以及内置的错误接口类型,如下声明:

    type error interface {
    	Error() string
    }


    Go函数通常返回错误作为最后一个返回值。 可使用errors.New来构造一个基本的错误消息

    package main
    import "errors"
    import "fmt"
    import "math"
    func Sqrt(value float64) (float64, error) {
    	if value < 0 {
    		return 0, errors.New("Math: negative number passed to Sqrt")
    	}
    	return math.Sqrt(value), nil
    }
    func main() {
    	result, err := Sqrt(-1)
    	if err != nil {
    		fmt.Println(err)
    	} else {
    		fmt.Println(result)
    	}
    	result, err = Sqrt(9)
    	if err != nil {
    		fmt.Println(err)
    	} else {
    		fmt.Println(result)
    	}
    }
    
    

    并发

    goroutine

    goroutine 是由 Go 运行时环境管理的轻量级线程。
    使用 go f(x, y, z) 开启一个新的 goroutine 执行。

    package main
    import "fmt"
    func f(from string) {
    	for i := 0; i < 3; i++ {
    		fmt.Println(from, ":", i)
    	}
    }
    func main() {
    	f("direct")
    	go f("goroutine")
    	go func(msg string) {
    		fmt.Println(msg)
    	}("going")
    	var input string
    	fmt.Scanln(&input)
    	fmt.Println("done")
    }
    
    
    // output
    
    $ go run goroutines.go
    direct : 0
    direct : 1
    direct : 2
    goroutine : 0
    going
    goroutine : 1
    goroutine : 2
    <enter>
    done

    channel

    channel 是有类型的管道,可以用 channel 操作符 <- 对其发送或者接收值。

    ch <- v // 将 v 送入 channel ch。
    v := <-ch // 从 ch 接收,并且赋值给 v。

    默认情况下,在另一端准备好之前,发送和接收都会阻塞。这使得 goroutine 可以在没有明确的锁或竞态变量的情况下进行同步。

    package main
    import "fmt"
    func main() {
    	messages := make(chan string)
    	go func() { messages <- "ping" }()
    	msg := <-messages
    	fmt.Println(msg)
    }
    
    

    channel 可以是带缓冲的。为 make 提供第二个参数作为缓冲长度来初始化一个缓冲 channel:

    ch := make(chan int, 100)
    向缓冲 channel 发送数据的时候,只有在缓冲区满的时候才会阻塞。当缓冲区清空的时候接受阻塞。

    package main
    import "fmt"
    func main() {
    	c := make(chan int, 2)
    	c <- 1
    	c <- 2
    	fmt.Println(<-c)
    	fmt.Println(<-c)
    }
    
    

    close

    发送者可以 close 一个 channel 来表示再没有值会被发送了。接收者可以通过赋值语句的第二参数来测试 channel 是否被关闭:当没有值可以接收并且 channel 已经被关闭,那么

    v, ok := <-ch
    ok 会被设置为 false。

    注意: 只有发送者才能关闭 channel,而不是接收者。向一个已经关闭的 channel 发送数据会引起 panic。

    package main
    import "fmt"
    func main() {
    	jobs := make(chan int, 5)
    	done := make(chan bool)
    	go func() {
    		for {
    			j, more := <-jobs
    			if more {
    				fmt.Println("received job", j)
    			} else {
    				fmt.Println("received all jobs")
    				done <- true
    				return
    			}
    		}
    	}()
    	for j := 1; j <= 3; j++ {
    		jobs <- j
    		fmt.Println("sent job", j)
    	}
    	close(jobs)
    	fmt.Println("sent all jobs")
    	<-done
    }
    
    

    select


    Go语言的选择(select)可等待多个通道操作。将goroutine和channel与select结合是Go语言的一个强大功能。

    select 会阻塞,直到条件分支中的某个可以继续执行,这时就会执行那个条件分支。如果有多个都准备好的时候,会随机选一个。

    package main
    import "time"
    import "fmt"
    func main() {
    	c1 := make(chan string)
    	c2 := make(chan string)
    	c3 := make(chan string)
    	t1 := time.Now().UnixNano()
    	go func() {
    		time.Sleep(time.Second * 1)
    		c1 <- "one"
    	}()
    	go func() {
    		time.Sleep(time.Second * 2)
    		c2 <- "two"
    	}()
    	go func() {
    		time.Sleep(time.Second * 2)
    		c3 <- "three"
    	}()
    	for i := 0; i < 3; i++ {
    		select {
    		case msg1 := <-c1:
    			fmt.Println("received", msg1)
    		case msg2 := <-c2:
    			fmt.Println("received", msg2)
    		case msg3 := <-c3:
    			fmt.Println("received", msg3)
    		}
    	}
    	t2 := time.Now().UnixNano()
    	dt := (t2 - t1) / 1e6
    	fmt.Println(dt)
    }
    

    Goroutine 调度

    结构——M, P, S
        Go的调度器内部有三个重要的结构:M,P,S
              

             M: 内核OS线程
             G: 一个goroutine,它有自己的栈,instruction pointer和其他信息(正在等待的channel等等),用于调度。
             P: 代表调度的上下文,可以把它看做一个局部的调度器,使go代码在一个线程上跑,它是实现从N:1到N:M映射的关键。
                 P的数量可以通过GOMAXPROCS()来设置,它其实也就代表了真正的并发度,即有多少个goroutine可以同时运行。
     
    线程阻塞——投奔其他线程
    • 图中看到,当一个OS线程M0陷入阻塞时,P转而在OS线程M1上运行。调度器保证有足够的线程来运行所以的context P。
     
    • 当MO返回时,它必须尝试取得一个context P来运行goroutine,一般情况下,它会从其他的OS线程那里steal偷一个context过来,
             如果没有偷到的话,它就把goroutine放在一个global runqueue里,然后自己就去睡大觉了(放入线程池里)。Contexts们也会周期性的检查global runqueue。


    分配不均——steal work

    1. global runqueue

    2. 其他的P
     
    常见开发陷阱
    • 开大括号不能放在单独的一行
    • 未使用的变量
    • 未使用的Imports
    • 不支持前置版本的自增和自减,也无法在表达式中使用这两个操作符。

             。。。

  • 相关阅读:
    How to create jar for Android Library Project
    Very large tabs in eclipse panes on Ubuntu
    64bit Ubuntu, Android AAPT, R.java
    Linux(Ubuntu)下如何安装JDK
    Configure xterm Fonts and Colors for Your Eyeball
    建立、配置和使用Activity——启动其他Activity并返回结果
    建立、配置和使用Activity——使用Bundle在Activity之间交换数据
    建立、配置和使用Activity——启动、关闭Activity
    建立、配置和使用Activity——Activity
    异步任务(AsyncTask)
  • 原文地址:https://www.cnblogs.com/1488boss/p/10864117.html
Copyright © 2011-2022 走看看