Java 內置了豐富的容器類,不一樣容器用於處理各類業務場景。 Go 雖然語言設計上和 Java 有不少類似的地方, 但原生並無支持太多容器類的數據結構,只有 map 和 slice。標準庫的 container
package 對容器數據結構作了擴展,支持堆(Heap)、鏈表(LinkedList) 和循環鏈表(Circular List)3個容器。html
熟悉 C++ 和 Java 對容器應該都有清晰的瞭解, 它是現代編程實踐中不可或缺的一部分,具備多種形式, 通常被描述爲具備操做容器內容的方法的對象。Go 提供的基本容器主要有6個:git
內置容器:golang
container標準庫(pkg/container
):算法
slice 、map 和 channel 是 Go 最多見、也是內置的容器數據結構,其餘容器都在標準庫的 container
包下。在使用 container
三個容器的時候,沒必要再費心實現數據結構相關的算法。同時,由於container
提供的容器支持的入參類型都是 interface{}
, 因此只要實現了容器的 interface
, 就能夠處理任何類型的值。編程
鏈表容器 list 的代碼是一個雙向鏈表的實現。list 維護兩個結構體:Element 和 List:數組
// Element is an element of a linked list. type Element struct { // Next and previous pointers in the doubly-linked list of elements. // To simplify the implementation, internally a list l is implemented // as a ring, such that &l.root is both the next element of the last // list element (l.Back()) and the previous element of the first list // element (l.Front()). next, prev *Element // The list to which this element belongs. list *List // The value stored with this element. Value interface{} } // List represents a doubly linked list. // The zero value for List is an empty list ready to use. type List struct { root Element // sentinel list element, only &root, root.prev, and root.next are used len int // current list length excluding (this) sentinel element }
當經過 list.New()
建立一個 list 時,會初始化一個 Element 做爲 Root Pointer,它要麼指向列表的初始元素,要麼爲 nil
。每個 Element 除了數據字段Value
外,還有 prev
和 next
分別指向 直接前驅 和 直接後繼, 來容許用戶在 list 中先後移動元素。緩存
list 容器支持的方法以下:數據結構
type Element func (e *Element) Next() *Element func (e *Element) Prev() *Element type List func New() *List func (l *List) Back() *Element // 最後一個元素 func (l *List) Front() *Element // 第一個元素 func (l *List) Init() *List // 鏈表初始化 func (l *List) InsertAfter(v interface{}, mark *Element) *Element // 在某個元素後插入 func (l *List) InsertBefore(v interface{}, mark *Element) *Element // 在某個元素前插入 func (l *List) Len() int // 在鏈表長度 func (l *List) MoveAfter(e, mark *Element) // 把 e 元素移動到 mark 以後 func (l *List) MoveBefore(e, mark *Element) // 把 e 元素移動到 mark 以前 func (l *List) MoveToBack(e *Element) // 把 e 元素移動到隊列最後 func (l *List) MoveToFront(e *Element) // 把 e 元素移動到隊列最頭部 func (l *List) PushBack(v interface{}) *Element // 在隊列最後插入元素 func (l *List) PushBackList(other *List) // 在隊列最後插入接上新隊列 func (l *List) PushFront(v interface{}) *Element // 在隊列頭部插入元素 func (l *List) PushFrontList(other *List) // 在隊列頭部插入接上新隊列 func (l *List) Remove(e *Element) interface{} // 刪除某個元素
下面是 list 的一個簡單例子:app
package main import ( "container/list" "fmt" ) func main() { // Create a new list and put some numbers in it. l := list.New() e4 := l.PushBack(4) e1 := l.PushFront(1) l.InsertBefore(3, e4) l.InsertAfter(2, e1) // Iterate through list and print its contents. for e := l.Front(); e != nil; e = e.Next() { fmt.Printf(".2d",e.Value) } }
這段代碼的輸出是: 1234。在初始化 list 後,在尾結點插入4,在頭結點插入1;再在4前插入3,在1後插入2,因此結果是1234。ide
list 在插入、刪除數據的時間複雜度在 $O(1)$; 隨機查找效率較低,爲 $O(N)$ (slice 隨機查找的時間效率爲 $O(1)$)
鏈表容器常見的應用場景是用於作 LRU 緩存。
循環鏈表容器 ring 是一個沒有頭節點和尾節點的鏈表,這裏能夠當作是一個簡化版的 list。ring 維護一個結構體 Ring:
// A Ring is an element of a circular list, or ring. // Rings do not have a beginning or end; a pointer to any ring element // serves as reference to the entire ring. Empty rings are represented // as nil Ring pointers. The zero value for a Ring is a one-element // ring with a nil Value. // type Ring struct { next, prev *Ring Value interface{} // for use by client; untouched by this library }
但跟 list 不一樣的是 ring 的方法是不同的:
type Ring func New(n int) *Ring // 初始化環 func (r *Ring) Do(f func(interface{})) // 循環環進行操做 func (r *Ring) Len() int // 環長度 func (r *Ring) Link(s *Ring) *Ring // 鏈接兩個環 func (r *Ring) Move(n int) *Ring // 指針從當前元素開始向後移動或者向前(n 能夠爲負數) func (r *Ring) Next() *Ring // 當前元素的下個元素 func (r *Ring) Prev() *Ring // 當前元素的上個元素 func (r *Ring) Unlink(n int) *Ring // 從當前元素開始,刪除 n 個元素
下面是 ring 的一個簡單例子:
package main import ( "container/ring" "fmt" ) func main() { // Create a new ring of size 5 r := ring.New(5) // Get the length of the ring n := r.Len() // Initialize the ring with some integer values for i := 0; i < n; i++ { r.Value = i r = r.Next() } // Iterate through the ring and print its contents r.Do(func(p interface{}) { fmt.Printf("%d", p.(int)) }) }
這段代碼的輸出是01234。 在初始化 ring 後, 對每一個元素的 Value 賦值, 由於 ring 提供 Do 方法,因此遍歷到當前元素的是時候,執行 Print 函數打印結果。
從 ring 的實現上能夠知道相關操做的時間複雜度在$O(N)$。由於 ring 節點沒有頭尾區分和 FIFO 的特性,因此一個能用到的應用場景是環形緩衝區:在不消費資源的狀況下提供對緩衝區的互斥訪問 。
堆(Heap)就是用數組實現的徹底二叉樹。根據堆的特性能夠分爲兩種:最大堆和最小堆,二者的區別在於節點的排序方式上:
Go 的堆容器 heap 在實現上是一個最小堆,heap 維護一個 Interface 接口:
// Note that Push and Pop in this interface are for package heap's // implementation to call. To add and remove things from the heap, // use heap.Push and heap.Pop. type Interface interface { sort.Interface Push(x interface{}) // add x as element Len() Pop() interface{} // remove and return element Len() - 1. }
除了 出堆方法Push()
和 入堆方法push()
, Interface 內聯了 sort.Interface
, 它實現了三個方法:
// A type, typically a collection, that satisfies sort.Interface can be // sorted by the routines in this package. The methods require that the // elements of the collection be enumerated by an integer index. type Interface interface { // Len is the number of elements in the collection. Len() int // Less reports 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)
只要實現了 Interface 方法的數據類型, 就知足構建最小堆條件:
!h.Less(j, i) for 0 <= i < h.Len() and 2*i+1 <= j <= 2*i+2 and j < h.Len()
經過 heap.Init()
函數構建一個最小堆(按先序遍歷排序的 slice),內部實現的 up()
和 down()
分別來對 堆來進行 上調整 和 下調整。
Push()
:當往堆中插入一個元素的時候,這個元素插入到最右子樹的最後一個節點中,而後調用up()
向上保證最小堆。Pop()
:當要從堆中推出一個元素的時候,先把這個元素和右子樹最後一個節點交換,而後彈出最後一個節點,而後對 root 調用 down()
,向下保證最小堆。heap 容器支持的方法以下:
func Fix(h Interface, i int) // 在 i 位置更新數據,重建最小堆 func Init(h Interface) // 初始化,把 h 構建成最小堆 func Pop(h Interface) interface{} // 出堆操做 func Push(h Interface, x interface{}) // 入堆操做 func Remove(h Interface, i int) interface{} // 移除第 i 個元素
下面是使用 heap 容器的一個例子, 構建一個優先級隊列 pq:
package main import ( "container/heap" "fmt" ) type PriorityQueue []*Item func (pq PriorityQueue) Len() int { return len(pq) } func (pq PriorityQueue) Less(i, j int) bool { return pq[i].priority > pq[j].priority } func (pq PriorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i] pq[i].index = i pq[j].index = j } func (pq *PriorityQueue) Push(x interface{}) { n := len(*pq) item := x.(*Item) item.index = n *pq = append(*pq, item) } func (pq *PriorityQueue) Pop() interface{} { old := *pq n := len(old) item := old[n-1] old[n-1] = nil // avoid memory leak item.index = -1 // for safety *pq = old[0 : n-1] return item } func (pq *PriorityQueue) update(item *Item, value string, priority int) { item.value = value item.priority = priority heap.Fix(pq, item.index) } func main() { items := map[string]int{ "banana": 3, "apple": 2, "pear": 4, } pq := make(PriorityQueue, len(items)) i := 0 for value, priority := range items { pq[i] = &Item{ value: value, priority: priority, index: i, } i++ } heap.Init(&pq) // insert a new item item := &Item{ value: "orange", priority: 1, } heap.Push(&pq, item) pq.update(item, item.value, 5) fmt.Printf("\nheap length: %d\n", len(pq)) for pq.Len() > 0 { i := heap.Pop(&pq).(*Item) fmt.Printf("%.2d:%s\t", i.priority, i.value) } fmt.Printf("\nheap length: %d\n", len(pq)) }
這段代碼的輸出是05:orange 04:pear 03:banana 02:apple
。 首先是定義一個 PriorityQueue
的結構體數組做爲隊列,有個 priority
字段標識優先級,在 Less()
方法裏比較兩個元素的優先級,隊列 update()
用於更新元素的優先級。而後每次在執行 heap.Pop(&pq).(*Item)
操做,會把最小堆裏高priority
元素出堆。
heap 在初始化的時候,時間複雜度在 $O(N)$;入堆、出堆、移動元素和重建最小堆的時間複雜度都是 $O(logN)$
堆容器在實際應用上是比較常見的, 在生產上常常用於實現優先級隊列 和 高性能定時器。
本文主要梳理了 Go 的 容器數據結構,分析標準庫裏 container
包實現的三個容器:list、ring 還有較複雜的 heap, 介紹它們的實現、特性和使用場景。雖然 slice 和 map 做爲在 Go 中是常常被使用到的容器,但若是在實際開發中發現這兩個數據結構並不知足咱們的需求,能夠在 pkg/container
下搜索是否有可用到的數據結構。