go数据结构&常用包 - Go语言中文社区

go数据结构&常用包


struct

package main

import (
	"fmt"
)

type parent struct {
	a, b int
}

type child struct {
	c, d int
	*parent
}

func main() {
	child_1 := child{1, 2, &parent{3, 4}}
	fmt.Println(child_1.a, child_1.d, child_1.parent.a, child_1.parent.b)
	// fmt.Printf("%#vn", child_1)
}

// 输出结果:
// 3 2 3 4

​ struct利用type来表示自定义类型,struct可以继承(暂且这样说吧),在上面的例子中,child继承了parent的所有字段(a,b),parent作为了child的一个你名字段,在child实例中可以直接用成员操作符来访问a,b,也可以使用child.parent.a来访问属性。这就引出了这样一种情况:当parent和child包含同名字段e,此时child.e访问的即为child内部的字段,child.parent.e访问的即为parent内部的字段。

关于结构体定义和初始化的一些讨论:

package main

import (
	"fmt"
)
// 此方法定义的结构体只可以用一次
var s1 = struct {
	name string
	age  int
}{
	name: "Sam",
	age:  18,
}

type student2 struct {
	name string
	age  int
}

func main() {
	s2 := student2{"Kate", 19}
	fmt.Printf("%vn%vn", s1, s2)
}
// PS E:mygosrc> go run struct2.go
// {Sam 18}
// {Kate 19}

interface

interface是一组method签名的组合,我们通过interface来定义对象的一组行为。 任意的类型都实现了空interface(我们这样定义:interface{}),也就是包含0个method的interface。

  • interface类型
package main

import (
	"fmt"
	"math"
)
// 自定义类型type
type Abser interface {
	Abs() float64
}
// MyFloat也是自定义类型,不是float64的别名
type MyFloat float64
// MyFloat实现 Abs方法,则该自定义类型实现了Abser接口
func (f MyFloat) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}

type Vertex struct {
	X, Y float64
}
// Vertex*实现了Abs方法,表示自定义类型Vertex的指针实现了Abser接口
func (v *Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
	var a Abser
	f := MyFloat(-math.Sqrt2)
	v := &Vertex{3, 4}

	a = f
	fmt.Println(a.Abs())
	a = v
	fmt.Println(a.Abs())
}

// 输出结果
// PS E:mygosrc> go run interface.go
// 1.4142135623730951
// 5
  • interface值

如果我们定义了一个interface的变量,那么这个变量里面可以存实现这个interface的任意类型的对象。如上述代码中,a中可以存储MyFloat类型的变量,也可以存储&Vertex类型的变量。

  • 空interface

空interface(interface{})不包含任何的method,正因为如此,所有的类型都实现了空interface。空interface对于描述起不到任何的作用(因为它不包含任何的method),但是空interface在我们需要存储任意类型的数值的时候相当有用,因为它可以存储任意类型的数值。它有点类似于C语言的void*类型。

// 定义a为空接口
var a interface{}
var i int = 5
s := "Hello world"
// a可以存储任意类型的数值
a = i
a = s
  • interface函数参数

interface的变量可以持有任意实现该interface类型的对象,这给我们编写函数(包括method)提供了一些额外的思考,我们是不是可以通过定义interface参数,让函数接受各种类型的参数。 比如fmt包中的Stringer接口:(详情在后续的stringers中)

type Stringer interface {
	 String() string
}
  • interface变量存储的类型

    判断interface变量里面存储的熟知的类型的方法:

    • Comma-ok断言:

      Go语言里面有一个语法,可以直接判断是否是该类型的变量: value, ok = element.(T),这里value就是变量的值,ok是一个bool类型,element是interface变量,T是断言的类型。

      如果element里面确实存储了T类型的数值,那么ok返回true,否则返回false。

      package main
      
      	import (
      		"fmt"
      		"strconv"
      	)
      
      	type Element interface{}
      	type List [] Element
      
      	type Person struct {
      		name string
      		age int
      	}
      
      	//定义了String方法,实现了fmt.Stringer
      	func (p Person) String() string {
      		return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
      	}
      
      	func main() {
      		list := make(List, 3)
      		list[0] = 1 // an int
      		list[1] = "Hello" // a string
      		list[2] = Person{"Dennis", 70}
      
      		for index, element := range list {
      			if value, ok := element.(int); ok {
      				fmt.Printf("list[%d] is an int and its value is %dn", index, value)
      			} else if value, ok := element.(string); ok {
      				fmt.Printf("list[%d] is a string and its value is %sn", index, value)
      			} else if value, ok := element.(Person); ok {
      				fmt.Printf("list[%d] is a Person and its value is %sn", index, value)
      			} else {
      				fmt.Printf("list[%d] is of a different typen", index)
      			}
      		}
      	}
      
    • switch测试:

      package main
      
      	import (
      		"fmt"
      		"strconv"
      	)
      
      	type Element interface{}
      	type List [] Element
      
      	type Person struct {
      		name string
      		age int
      	}
      
      	//打印
      	func (p Person) String() string {
      		return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
      	}
      
      	func main() {
      		list := make(List, 3)
      		list[0] = 1 //an int
      		list[1] = "Hello" //a string
      		list[2] = Person{"Dennis", 70}
      
      		for index, element := range list{
      			switch value := element.(type) {
      				case int:
      					fmt.Printf("list[%d] is an int and its value is %dn", index, value)
      				case string:
      					fmt.Printf("list[%d] is a string and its value is %sn", index, value)
      				case Person:
      					fmt.Printf("list[%d] is a Person and its value is %sn", index, value)
      				default:
      					fmt.Println("list[%d] is of a different type", index)
      			}
      		}
      	}
      

    这里有一点需要强调的是:element.(type)语法不能在switch外的任何逻辑里面使用,如果你要在switch外面判断一个类型就使用comma-ok

  • 嵌入interface

    如果一个interface1作为interface2的一个嵌入字段,那么interface2隐式的包含了interface1里面的method。

    我们可以看到源码包container/heap里面有这样的一个定义

    type Interface interface {
    	sort.Interface //嵌入字段sort.Interface
    	Push(x interface{}) //a Push method to push elements into the heap
    	Pop() interface{} //a Pop elements that pops elements from the heap
    }
    

    我们看到sort.Interface其实就是嵌入字段,把sort.Interface的所有method给隐式的包含进来了。也就是下面三个方法:

    type Interface interface {
    	// Len is the number of elements in the collection.
    	Len() int
    	// Less returns whether the element with index i should sort
    	// before the element with index j.
    	Less(i, j int) bool
    	// Swap swaps the elements with indexes i and j.
    	Swap(i, j int)
    }
    

errors

error 包实现了用于错误处理的函数.

package main

import (
	"errors"
	"fmt"
)

func main() {
    // New 返回一个按给定文本格式化的错误。
	err := errors.New("emit macho dwarf: elf header corrupted")
	if err != nil {
		fmt.Println(err)
	}
	// fmt 包的 Errorf 函数让我们使用该包的格式化特性来创建描述性的错误信息。
	const name, id = "bimmler", 17
	err = fmt.Errorf("user %q (id %d) not found", name, id)
	if err != nil {
		fmt.Println(err)
	}
}
// 输出结果
// PS E:mygosrc> go run error.go
// CreateFile error.go: The system cannot find the file specified.

http

  • web使用示例:

    package main
    
    import (
    	"fmt"
    	"net/http"
    	"strings"
    	"log"
    )
    
    func sayhelloName(w http.ResponseWriter, r *http.Request) {
    	r.ParseForm()  //解析参数,默认是不会解析的
    	fmt.Println(r.Form)  //这些信息是输出到服务器端的打印信息
    	fmt.Println("path", r.URL.Path)
    	fmt.Println("scheme", r.URL.Scheme)
    	fmt.Println(r.Form["url_long"])
    	for k, v := range r.Form {
    		fmt.Println("key:", k)
    		fmt.Println("val:", strings.Join(v, ""))
    	}
    	fmt.Fprintf(w, "Hello astaxie!") //这个写入到w的是输出到客户端的
    }
    
    func main() {
    	http.HandleFunc("/", sayhelloName) //设置访问的路由
    	err := http.ListenAndServe(":9090", nil) //设置监听的端口
    	if err != nil {
    		log.Fatal("ListenAndServe: ", err)
    	}
    }
    
  • 服务器端概念:

    Request: 用户请求的信息,用来解析用户请求,包括get、post、cookie、url等信息。

    Response:服务器需要反馈给客户端的信息

    Conn:用户的每次请求链接

    Handler:处理请求和生成返回信息的处理逻辑

  • http包运行机制:

    如下图所示,是Go实现Web服务的工作模式的流程图

    img

    图3.9 http包执行流程

    1. 创建Listen Socket, 监听指定的端口, 等待客户端请求到来。
    2. Listen Socket接受客户端的请求, 得到Client Socket, 接下来通过Client Socket与客户端通信。
    3. 处理客户端的请求, 首先从Client Socket读取HTTP请求的协议头, 如果是POST方法, 还可能要读取客户端提交的数据, 然后交给相应的handler处理请求, handler处理完毕准备好客户端需要的数据, 通过Client Socket写给客户端。

    这整个的过程里面我们只要了解清楚下面三个问题,也就知道Go是如何让Web运行起来了

    • 如何监听端口?
    • 如何接收客户端请求?
    • 如何分配handler?

    前面小节的代码里面我们可以看到,Go是通过一个函数ListenAndServe来处理这些事情的,这个底层其实这样处理的:初始化一个server对象,然后调用了net.Listen("tcp", addr),也就是底层用TCP协议搭建了一个服务,然后监控我们设置的端口。

    下面代码来自Go的http包的源码,通过下面的代码我们可以看到整个的http处理过程:

    func (srv *Server) Serve(l net.Listener) error {
    	defer l.Close()
    	var tempDelay time.Duration // how long to sleep on accept failure
    	for {
    		rw, e := l.Accept()
    		if e != nil {
    			if ne, ok := e.(net.Error); ok && ne.Temporary() {
    				if tempDelay == 0 {
    					tempDelay = 5 * time.Millisecond
    				} else {
    					tempDelay *= 2
    				}
    				if max := 1 * time.Second; tempDelay > max {
    					tempDelay = max
    				}
    				log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay)
    				time.Sleep(tempDelay)
    				continue
    			}
    			return e
    		}
    		tempDelay = 0
    		c, err := srv.newConn(rw)
    		if err != nil {
    			continue
    		}
    		go c.serve()
    	}
    }
    

    监控之后如何接收客户端的请求呢?上面代码执行监控端口之后,调用了srv.Serve(net.Listener)函数,这个函数就是处理接收客户端的请求信息。这个函数里面起了一个for{},首先通过Listener接收请求,其次创建一个Conn,最后单独开了一个goroutine,把这个请求的数据当做参数扔给这个conn去服务:go c.serve()。这个就是高并发体现了,用户的每一次请求都是在一个新的goroutine去服务,相互不影响。

    那么如何具体分配到相应的函数来处理请求呢?conn首先会解析request:c.readRequest(),然后获取相应的handler:handler := c.server.Handler,也就是我们刚才在调用函数ListenAndServe时候的第二个参数,我们前面例子传递的是nil,也就是为空,那么默认获取handler = DefaultServeMux,那么这个变量用来做什么的呢?对,这个变量就是一个路由器,它用来匹配url跳转到其相应的handle函数,那么这个我们有设置过吗?有,我们调用的代码里面第一句不是调用了http.HandleFunc("/", sayhelloName)嘛。这个作用就是注册了请求/的路由规则,当请求uri为"/",路由就会转到函数sayhelloName,DefaultServeMux会调用ServeHTTP方法,这个方法内部其实就是调用sayhelloName本身,最后通过写入response的信息反馈到客户端。

    详细的整个流程如下图所示:

    img

    上述分析回答了之前的三个问题。

    【补充】当ListenAndServe的第二个参数不为nil时(外部实现的路由器):

    package main
    
    import (
    	"fmt"
    	"log"
    	"net/http"
    )
    
    type Hello struct{}
    
    func (h Hello) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    	fmt.Fprint(w, "Hello!")
    }
    
    func main() {
    	var h Hello
    	err := http.ListenAndServe("localhost:3000", h)
    	if err != nil {
    		log.Fatal(err)
    	}
    }
    
  • Go的http包详解

    Go的http有两个核心功能:Conn、ServeMux

    • Conn的goroutine

      与我们一般编写的http服务器不同, Go为了实现高并发和高性能, 使用了goroutines来处理Conn的读写事件, 这样每个请求都能保持独立,相互不会阻塞,可以高效的响应网络事件。这是Go高效的保证。

      Go在等待客户端请求里面是这样写的:

      c, err := srv.newConn(rw)
      if err != nil {
      	continue
      }
      go c.serve()
      

      这里我们可以看到客户端的每次请求都会创建一个Conn,这个Conn里面保存了该次请求的信息,然后再传递到对应的handler,该handler中便可以读取到相应的header信息,这样保证了每个请求的独立性。

    • ServeMux的自定义

      我们前面小节讲述conn.server的时候,其实内部是调用了http包默认的路由器,通过路由器把本次请求的信息传递到了后端的处理函数。那么这个路由器是怎么实现的呢?

      它的结构如下:

      type ServeMux struct {
      	mu sync.RWMutex   //锁,由于请求涉及到并发处理,因此这里需要一个锁机制
      	m  map[string]muxEntry  // 路由规则,一个string对应一个mux实体,这里的string就是注册的路由表达式
      	hosts bool // 是否在任意的规则中带有host信息
      }
      

      下面看一下muxEntry

      type muxEntry struct {
      	explicit bool   // 是否精确匹配
      	h        Handler // 这个路由表达式对应哪个handler
      	pattern  string  //匹配字符串
      }
      

      接着看一下Handler的定义

      type Handler interface {
      	ServeHTTP(ResponseWriter, *Request)  // 路由实现器
      }
      

      Handler是一个接口,但是前一小节中的sayhelloName函数并没有实现ServeHTTP这个接口,为什么能添加呢?原来在http包里面还定义了一个类型HandlerFunc,我们定义的函数sayhelloName就是这个HandlerFunc调用之后的结果,这个类型默认就实现了ServeHTTP这个接口,即我们调用了HandlerFunc(f),强制类型转换f成为HandlerFunc类型,这样f就拥有了ServeHTTP方法。

      type HandlerFunc func(ResponseWriter, *Request)
      
      // ServeHTTP calls f(w, r).
      func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
      	f(w, r)
      }
      

      路由器里面存储好了相应的路由规则之后,那么具体的请求又是怎么分发的呢?请看下面的代码,默认的路由器实现了ServeHTTP

      func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
      	if r.RequestURI == "*" {
      		w.Header().Set("Connection", "close")
      		w.WriteHeader(StatusBadRequest)
      		return
      	}
      	h, _ := mux.Handler(r)
      	h.ServeHTTP(w, r)
      }
      

      如上所示路由器接收到请求之后,如果是*那么关闭链接,不然调用mux.Handler(r)返回对应设置路由的处理Handler,然后执行h.ServeHTTP(w, r)

      也就是调用对应路由的handler的ServerHTTP接口,那么mux.Handler®怎么处理的呢?

      func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
      	if r.Method != "CONNECT" {
      		if p := cleanPath(r.URL.Path); p != r.URL.Path {
      			_, pattern = mux.handler(r.Host, p)
      			return RedirectHandler(p, StatusMovedPermanently), pattern
      		}
      	}	
      	return mux.handler(r.Host, r.URL.Path)
      }
      
      func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
      	mux.mu.RLock()
      	defer mux.mu.RUnlock()
      
      	// Host-specific pattern takes precedence over generic ones
      	if mux.hosts {
      		h, pattern = mux.match(host + path)
      	}
      	if h == nil {
      		h, pattern = mux.match(path)
      	}
      	if h == nil {
      		h, pattern = NotFoundHandler(), ""
      	}
      	return
      }
      

      原来他是根据用户请求的URL和路由器里面存储的map去匹配的,当匹配到之后返回存储的handler,调用这个handler的ServeHTTP接口就可以执行到相应的函数了。

      通过上面这个介绍,我们了解了整个路由过程,Go其实支持外部实现的路由器 ListenAndServe的第二个参数就是用以配置外部路由器的,它是一个Handler接口,即外部路由器只要实现了Handler接口就可以,我们可以在自己实现的路由器的ServeHTTP里面实现自定义路由功能。

      如下代码所示,我们自己实现了一个简易的路由器

      package main
      
      import (
      	"fmt"
      	"net/http"
      )
      
      type MyMux struct {
      }
      
      func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
      	if r.URL.Path == "/" {
      		sayhelloName(w, r)
      		return
      	}
      	http.NotFound(w, r)
      	return
      }
      
      func sayhelloName(w http.ResponseWriter, r *http.Request) {
      	fmt.Fprintf(w, "Hello myroute!")
      }
      
      func main() {
      	mux := &MyMux{}
      	http.ListenAndServe(":9090", mux)
      }
      
  • Go代码的执行流程

    通过对http包的分析之后,现

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/liuyh73/article/details/82889052
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢