前言
golang是进行底层服务架构的强而有力的语言,高效,高性能,天生的高并发适用性优点。作为底层架构的开发,经常要使用底层的数据结构来实现高性能的编程,虽然在golang原生就封装了切片,map等常用的数据结构,虽然他们有着非常不错的适用场景,但是对待一些对性能有比较要求的场景下,他们的实现还是略显单薄, 作为底层开发的需要,boot4go对针对一些特殊高性能的数据结构有封装了自己的数据结构。今天就一起来分享一下这些常用的数据结构
LOOPQUEUE
看名字就可以知道这个是个环形队列, 大名鼎鼎的disruptor,就是这个数据结构的原型, Disruptor是英国外汇交易公司LMAX开发的一个高性能队列,研发的初衷是解决内存队列的延迟问题,说起高性能队列,大家比较容易想到的就是Kafka,RocketMQ这些消息队列,Disruptor和他们本质的区别,Kafka都是服务间级别的队列, Disruptor是线程间的队列,极而言之就是应用内部级别的队列,使用内存实现的队列,比如Java里的Queue接口的相关实现, Disruptor使用一个环形的链表数据结构来维持队列的数据,
以下是主要对外接口的实现
func NewLoopQueue[T any](size int) *loopQueue[T] {
return &loopQueue[T]{
items: make([]*CacheUnit[T], size),
size: size,
}
}
func (lq *loopQueue[T]) Get() *T {
if lq.IsEmpty() {
return nil
}
w := lq.items[lq.head]
return w.obj
}
func (lq *loopQueue[T]) Detach() *T {
if lq.IsEmpty() {
return nil
}
w := lq.items[lq.head]
lq.items[lq.head] = nil
lq.head++
if lq.head == lq.size {
lq.head = 0
}
lq.isFull = false
return w.obj
}
func (lq *loopQueue[T]) Insert(t *T) error {
if lq.size == 0 {
return errQueueIsReleased
}
if lq.isFull {
return errQueueIsFull
}
if lq.items[lq.tail] == nil {
lq.items[lq.tail] = &CacheUnit[T]{obj: t}
} else {
lq.items[lq.tail].obj = t
}
lq.items[lq.tail].lastUsedDuration = time.Now()
lq.tail++
if lq.tail == lq.size {
lq.tail = 0
}
if lq.tail == lq.head {
lq.isFull = true
}
return nil
}
注:
LoopQueue类似Disruptor一样,以无锁队列的方式实现, 所以是线程非安全的,如果需要保证线程安全,操作上要在调用上进行原子操作。
ByteBufferPool
基于GOlang的原生ByteBuffer上的封装, ByteBuffer在基于网络编程中是非常常用的数据结构,提供了一个内存Buffer的操作,一般用来保证Net.Conn的Reader和Writer操作,用来接收[]byte和操作[]byte. 原生的ByteBuffer功能也比较实用, 稍微欠缺的就是没有池的概念,这样在使用过程中,申请内存空间和师傅内存空间都会有一定的开销,也会影响到go的gc,所以就在原有基础上进行了池化的功能,同时对ByteBuffer进行了实现,比ByteBuffer更加高效。
以下是主要对外接口的实现
// The function appends all the data read from r to b.
func (b *ByteBuffer) ReadFrom(r io.Reader) (int64, error)
// WriteTo implements io.WriterTo.
func (b *ByteBuffer) WriteTo(w io.Writer) (int64, error)
func (b *ByteBuffer) Flip(n int) (rtn []byte)
func (b *ByteBuffer) Compact(w io.Writer, nwrite int) (int64, error)
// Write implements io.Writer - it appends p to ByteBuffer.B
func (b *ByteBuffer) Write(p []byte) (int, error)
// The purpose of this function is bytes.Buffer compatibility.
func (b *ByteBuffer) Bytes() []byte
// The function always returns nil.
func (b *ByteBuffer) WriteByte(c byte) error
// Get returns an empty byte buffer from the pool.
//
// Got byte buffer may be returned to the pool via Put call.
// This reduces the number of memory allocations required for byte buffer
// management.
func Get() *ByteBuffer { return defaultPool.Get()
// Put returns byte buffer to the pool.
//
// ByteBuffer.B mustn't be touched after returning it to the pool.
// Otherwise, data races will occur.
func Put(b *ByteBuffer)
Bitmap
以下是主要对外接口的实现
//Bitmap Bitmap结构体
type Bitmap struct {
vals []byte
size uint16
}
//Set 将offset位置的值设置为value(0/1)
func (b *Bitmap) Set(offset uint16, value uint8) bool {
if b.size < offset {
return false
}
index, pos := offset>>3, offset&0x07
if value == 0 {
b.vals[index] &^= 0x01 << pos
} else {
b.vals[index] |= 0x01 << pos
}
return true
}
//Get 获取offset位置处的value值
func (b *Bitmap) Get(offset uint16) uint8 {
if b.size < offset {
return 0
}
index, pos := offset>>3, offset&0x07
return (b.vals[index] >> pos) & 0x01
}
STACK
以下是主要对外接口的实现
type stack[T any] struct {
items []*CacheUnit[T]
expiry []*CacheUnit[T]
size int
}
func (s *stack[T]) Get() *T {
l := s.Len()
if l == 0 {
return nil
}
w := s.items[l-1]
return w.obj
}
func (s *stack[T]) Detach() *T {
l := s.Len()
if l == 0 {
return nil
}
w := s.items[l-1]
s.items[l-1] = nil // avoid memory leaks
s.items = s.items[:l-1]
return w.obj
}
func (s *stack[T]) Insert(t *T) error {
var o []*CacheUnit[T] = s.items
var v *CacheUnit[T] = &CacheUnit[T]{obj: t, lastUsedDuration: time.Now()}
s.items = append(o, v)
return nil
}
TRIE前缀树
Trie前缀树是针对特殊场景的一种数据结构,是Key Value数据结构的一种加强场景的数据结构,根据Key的前缀的层次进行一个树形结构的构造,可以根据 匹配符 # + 来进行树形结构的搜索
比如Key
a/b/c1
a/b/c2
a/b2/c1
a/b2/c2
a1/b3
就会构造出
a---
b---
c1
c2
b2---
c1
c2
a1----
b3
这样的树结构
如果使用a/#进行key的搜索, 就可以搜索出a树下所有的key
如果使用a/+进行key的搜索,就可以搜索出a树下下一层的所有的key,但不包含下下层的key
如果对MQTT的Topic和TopicFilter比较了解的话,应该就比较熟悉这样的数据结构。
以下是主要对外接口的实现
// TrieNode trie Node
type TrieNode[T any] struct {
children map[string]any
Data *T
parent any // pointer of parent node
Key string
}
// NewTrie create a new trie tree
func NewTrie[T any]() *TrieNode[T] {
o := newNode[T]()
return o
}
// AddData add data
func (t *TrieNode[T]) AddData(key string, data *T) error {
keySlice := strings.Split(key, "/")
var pNode = t
for _, lv := range keySlice {
if _, ok := pNode.children[lv]; !ok {
pNode.children[lv] = pNode.NewChild()
}
pNode = pNode.children[lv].(*TrieNode[T])
}
pNode.Data = data
pNode.Key = key
return nil
}
// Find walk through the tire and return the node that represent the key
// return nil if not found
func (t *TrieNode[T]) Find(key string) *TrieNode[T]
func (t *TrieNode[T]) LoopKey(key string, fn OnMatch[T])
结束语
以上的数据结构都是boot4go里进行底层架构编程常用的包的封装,Github项目地址,可以搜索boot4go-util。
欢迎大家关注