栈的特性
栈是一种FILO类型的数据结构,FILO 即 Fisrt In Last Out,也就是先进后出,也可以说是后进先出。
栈实现的三种方式
- 利用Golang的slice
package mainimport ("fmt""sync")// Item the type of the stacktype Item interface{}// ItemStack the stack of Itemstype ItemStack struct {items []Itemlock sync.RWMutex}// New creates a new ItemStackfunc NewStack() *ItemStack {s := &ItemStack{}s.items = []Item{}return s}// Pirnt prints all the elementsfunc (s *ItemStack) Print() {fmt.Println(s.items)}// Push adds an Item to the top of the stackfunc (s *ItemStack) Push(t Item) {s.lock.Lock()s.lock.Unlock()s.items = append(s.items, t)}// Pop removes an Item from the top of the stackfunc (s *ItemStack) Pop() Item {s.lock.Lock()defer s.lock.Unlock()if len(s.items) == 0 {return nil}item := s.items[len(s.items)-1]s.items = s.items[0 : len(s.items)-1]return item}
此方式特点:
- 利用Golang的Slice,足够简单。
- 允许添加任意类型的元素。
- Push和Pop有加锁处理,线程安全。
- 在一些文章里(Reddit)提到,使用slice作为Stack的底层支持,速度更快。但是,slice最大的问题其实是存在一个共用底层数组的的问题,哪怕你不断的Pop,但可能对于Golang来说,其占用的内存,并不一定减少。
性能测试
package mainimport ("testing")var stack *ItemStackfunc init() {stack = NewStack()}func Benchmark_Push(b *testing.B) {for i := 0; i < b.N; i++ { //use b.N for loopingstack.Push("test")}}func Benchmark_Pop(b *testing.B) {b.StopTimer()for i := 0; i < b.N; i++ { //use b.N for loopingstack.Push("test")}b.StartTimer()for i := 0; i < b.N; i++ { //use b.N for loopingstack.Pop()}}
- 利用Golang的container/list内置包 ```go package main
import ( “container/list” “sync” )
type Stack struct { list list.List lock sync.RWMutex }
func NewStack() *Stack { list := list.New() l := &sync.RWMutex{} return &Stack{list, l} }
func (stack *Stack) Push(value interface{}) { stack.lock.Lock() defer stack.lock.Unlock() stack.list.PushBack(value) }
func (stack *Stack) Pop() interface{} { stack.lock.Lock() defer stack.lock.Unlock() e := stack.list.Back() if e != nil { stack.list.Remove(e) return e.Value } return nil }
func (stack *Stack) Peak() interface{} { e := stack.list.Back() if e != nil { return e.Value }
return nil
}
func (stack *Stack) Len() int { return stack.list.Len() }
func (stack *Stack) Empty() bool { return stack.list.Len() == 0 }
container/list 是一个链表。用链表模拟栈,要么都向链表的最后做push和pop,要么都向链表的起点做push和pop3. godoc的实现参考(自定义数据结构实现)<br />```gopackage mainimport ("sync")type (Stack struct {top *nodelength intlock *sync.RWMutex}node struct {value interface{}prev *node})// Create a new stackfunc NewStack() *Stack {return &Stack{nil, 0, &sync.RWMutex{}}}// Return the number of items in the stackfunc (this *Stack) Len() int {return this.length}// View the top item on the stackfunc (this *Stack) Peek() interface{} {if this.length == 0 {return nil}return this.top.value}// Pop the top item of the stack and return itfunc (this *Stack) Pop() interface{} {this.lock.Lock()defer this.lock.Unlock()if this.length == 0 {return nil}n := this.topthis.top = n.prevthis.length--return n.value}// Push a value onto the top of the stackfunc (this *Stack) Push(value interface{}) {this.lock.Lock()defer this.lock.Unlock()n := &node{value, this.top}this.top = nthis.length++}
此方式特点:
- 实现比较精巧,唯一需要理解的地方就是 node 结构体中,prev 的部分,它指向的是前一个node成员。
- 允许添加任意类型的元素。
- Push和Pop是线程安全的。
| 特性对比 | push速度 | pop速度 | push内存分配 | pop内存分配 |
|---|---|---|---|---|
| 基于slice | 222ns/op | 65ns/op | 94B/op | 0B/op |
| container/list链表 | 222ns/op | 73.5ns/op | 48B/op | 0B/op |
| 自定义数据结构 | 178ns/op | 75ns/op | 32B/op | 0B/op |
