mirror of
https://github.com/HDT3213/godis.git
synced 2025-10-05 16:57:06 +08:00
quick list
This commit is contained in:
@@ -20,7 +20,7 @@ func EntityToCmd(key string, entity *database.DataEntity) *protocol.MultiBulkRep
|
|||||||
switch val := entity.Data.(type) {
|
switch val := entity.Data.(type) {
|
||||||
case []byte:
|
case []byte:
|
||||||
cmd = stringToCmd(key, val)
|
cmd = stringToCmd(key, val)
|
||||||
case *List.LinkedList:
|
case List.List:
|
||||||
cmd = listToCmd(key, val)
|
cmd = listToCmd(key, val)
|
||||||
case *set.Set:
|
case *set.Set:
|
||||||
cmd = setToCmd(key, val)
|
cmd = setToCmd(key, val)
|
||||||
@@ -44,7 +44,7 @@ func stringToCmd(key string, bytes []byte) *protocol.MultiBulkReply {
|
|||||||
|
|
||||||
var rPushAllCmd = []byte("RPUSH")
|
var rPushAllCmd = []byte("RPUSH")
|
||||||
|
|
||||||
func listToCmd(key string, list *List.LinkedList) *protocol.MultiBulkReply {
|
func listToCmd(key string, list List.List) *protocol.MultiBulkReply {
|
||||||
args := make([][]byte, 2+list.Len())
|
args := make([][]byte, 2+list.Len())
|
||||||
args[0] = rPushAllCmd
|
args[0] = rPushAllCmd
|
||||||
args[1] = []byte(key)
|
args[1] = []byte(key)
|
||||||
|
@@ -106,7 +106,7 @@ func (handler *Handler) rewrite2RDB(ctx *RewriteCtx) error {
|
|||||||
switch obj := entity.Data.(type) {
|
switch obj := entity.Data.(type) {
|
||||||
case []byte:
|
case []byte:
|
||||||
err = encoder.WriteStringObject(key, obj, opts...)
|
err = encoder.WriteStringObject(key, obj, opts...)
|
||||||
case *List.LinkedList:
|
case List.List:
|
||||||
vals := make([][]byte, 0, obj.Len())
|
vals := make([][]byte, 0, obj.Len())
|
||||||
obj.ForEach(func(i int, v interface{}) bool {
|
obj.ForEach(func(i int, v interface{}) bool {
|
||||||
bytes, _ := v.([]byte)
|
bytes, _ := v.([]byte)
|
||||||
|
@@ -68,7 +68,7 @@ func execType(db *DB, args [][]byte) redis.Reply {
|
|||||||
switch entity.Data.(type) {
|
switch entity.Data.(type) {
|
||||||
case []byte:
|
case []byte:
|
||||||
return protocol.MakeStatusReply("string")
|
return protocol.MakeStatusReply("string")
|
||||||
case *list.LinkedList:
|
case list.List:
|
||||||
return protocol.MakeStatusReply("list")
|
return protocol.MakeStatusReply("list")
|
||||||
case dict.Dict:
|
case dict.Dict:
|
||||||
return protocol.MakeStatusReply("hash")
|
return protocol.MakeStatusReply("hash")
|
||||||
|
@@ -9,26 +9,26 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (db *DB) getAsList(key string) (*List.LinkedList, protocol.ErrorReply) {
|
func (db *DB) getAsList(key string) (List.List, protocol.ErrorReply) {
|
||||||
entity, ok := db.GetEntity(key)
|
entity, ok := db.GetEntity(key)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
bytes, ok := entity.Data.(*List.LinkedList)
|
list, ok := entity.Data.(List.List)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, &protocol.WrongTypeErrReply{}
|
return nil, &protocol.WrongTypeErrReply{}
|
||||||
}
|
}
|
||||||
return bytes, nil
|
return list, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *DB) getOrInitList(key string) (list *List.LinkedList, isNew bool, errReply protocol.ErrorReply) {
|
func (db *DB) getOrInitList(key string) (list List.List, isNew bool, errReply protocol.ErrorReply) {
|
||||||
list, errReply = db.getAsList(key)
|
list, errReply = db.getAsList(key)
|
||||||
if errReply != nil {
|
if errReply != nil {
|
||||||
return nil, false, errReply
|
return nil, false, errReply
|
||||||
}
|
}
|
||||||
isNew = false
|
isNew = false
|
||||||
if list == nil {
|
if list == nil {
|
||||||
list = &List.LinkedList{}
|
list = List.NewQuickList()
|
||||||
db.PutEntity(key, &database.DataEntity{
|
db.PutEntity(key, &database.DataEntity{
|
||||||
Data: list,
|
Data: list,
|
||||||
})
|
})
|
||||||
@@ -259,11 +259,17 @@ func execLRem(db *DB, args [][]byte) redis.Reply {
|
|||||||
|
|
||||||
var removed int
|
var removed int
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
removed = list.RemoveAllByVal(value)
|
removed = list.RemoveAllByVal(func(a interface{}) bool {
|
||||||
|
return utils.Equals(a, value)
|
||||||
|
})
|
||||||
} else if count > 0 {
|
} else if count > 0 {
|
||||||
removed = list.RemoveByVal(value, count)
|
removed = list.RemoveByVal(func(a interface{}) bool {
|
||||||
|
return utils.Equals(a, value)
|
||||||
|
}, count)
|
||||||
} else {
|
} else {
|
||||||
removed = list.ReverseRemoveByVal(value, -count)
|
removed = list.ReverseRemoveByVal(func(a interface{}) bool {
|
||||||
|
return utils.Equals(a, value)
|
||||||
|
}, -count)
|
||||||
}
|
}
|
||||||
|
|
||||||
if list.Len() == 0 {
|
if list.Len() == 0 {
|
||||||
|
@@ -40,7 +40,7 @@ func dumpRDB(dec *core.Decoder, mdb *MultiDB) error {
|
|||||||
})
|
})
|
||||||
case rdb.ListType:
|
case rdb.ListType:
|
||||||
listObj := o.(*rdb.ListObject)
|
listObj := o.(*rdb.ListObject)
|
||||||
list := &List.LinkedList{}
|
list := List.NewQuickList()
|
||||||
for _, v := range listObj.Values {
|
for _, v := range listObj.Values {
|
||||||
list.Add(v)
|
list.Add(v)
|
||||||
}
|
}
|
||||||
|
24
datastruct/list/interface.go
Normal file
24
datastruct/list/interface.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package list
|
||||||
|
|
||||||
|
// Expected check whether given item is equals to expected value
|
||||||
|
type Expected func(a interface{}) bool
|
||||||
|
|
||||||
|
// Consumer traverses list.
|
||||||
|
// It receives index and value as params, returns true to continue traversal, while returns false to break
|
||||||
|
type Consumer func(i int, v interface{}) bool
|
||||||
|
|
||||||
|
type List interface {
|
||||||
|
Add(val interface{})
|
||||||
|
Get(index int) (val interface{})
|
||||||
|
Set(index int, val interface{})
|
||||||
|
Insert(index int, val interface{})
|
||||||
|
Remove(index int) (val interface{})
|
||||||
|
RemoveLast() (val interface{})
|
||||||
|
RemoveAllByVal(expected Expected) int
|
||||||
|
RemoveByVal(expected Expected, count int) int
|
||||||
|
ReverseRemoveByVal(expected Expected, count int) int
|
||||||
|
Len() int
|
||||||
|
ForEach(consumer Consumer)
|
||||||
|
Contains(expected Expected) bool
|
||||||
|
Range(start int, stop int) []interface{}
|
||||||
|
}
|
@@ -1,7 +1,5 @@
|
|||||||
package list
|
package list
|
||||||
|
|
||||||
import "github.com/hdt3213/godis/lib/utils"
|
|
||||||
|
|
||||||
// LinkedList is doubly linked list
|
// LinkedList is doubly linked list
|
||||||
type LinkedList struct {
|
type LinkedList struct {
|
||||||
first *node
|
first *node
|
||||||
@@ -150,7 +148,7 @@ func (list *LinkedList) RemoveLast() (val interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RemoveAllByVal removes all elements with the given val
|
// RemoveAllByVal removes all elements with the given val
|
||||||
func (list *LinkedList) RemoveAllByVal(val interface{}) int {
|
func (list *LinkedList) RemoveAllByVal(expected Expected) int {
|
||||||
if list == nil {
|
if list == nil {
|
||||||
panic("list is nil")
|
panic("list is nil")
|
||||||
}
|
}
|
||||||
@@ -159,7 +157,7 @@ func (list *LinkedList) RemoveAllByVal(val interface{}) int {
|
|||||||
var nextNode *node
|
var nextNode *node
|
||||||
for n != nil {
|
for n != nil {
|
||||||
nextNode = n.next
|
nextNode = n.next
|
||||||
if utils.Equals(n.val, val) {
|
if expected(n.val) {
|
||||||
list.removeNode(n)
|
list.removeNode(n)
|
||||||
removed++
|
removed++
|
||||||
}
|
}
|
||||||
@@ -170,7 +168,7 @@ func (list *LinkedList) RemoveAllByVal(val interface{}) int {
|
|||||||
|
|
||||||
// RemoveByVal removes at most `count` values of the specified value in this list
|
// RemoveByVal removes at most `count` values of the specified value in this list
|
||||||
// scan from left to right
|
// scan from left to right
|
||||||
func (list *LinkedList) RemoveByVal(val interface{}, count int) int {
|
func (list *LinkedList) RemoveByVal(expected Expected, count int) int {
|
||||||
if list == nil {
|
if list == nil {
|
||||||
panic("list is nil")
|
panic("list is nil")
|
||||||
}
|
}
|
||||||
@@ -179,7 +177,7 @@ func (list *LinkedList) RemoveByVal(val interface{}, count int) int {
|
|||||||
var nextNode *node
|
var nextNode *node
|
||||||
for n != nil {
|
for n != nil {
|
||||||
nextNode = n.next
|
nextNode = n.next
|
||||||
if utils.Equals(n.val, val) {
|
if expected(n.val) {
|
||||||
list.removeNode(n)
|
list.removeNode(n)
|
||||||
removed++
|
removed++
|
||||||
}
|
}
|
||||||
@@ -193,7 +191,7 @@ func (list *LinkedList) RemoveByVal(val interface{}, count int) int {
|
|||||||
|
|
||||||
// ReverseRemoveByVal removes at most `count` values of the specified value in this list
|
// ReverseRemoveByVal removes at most `count` values of the specified value in this list
|
||||||
// scan from right to left
|
// scan from right to left
|
||||||
func (list *LinkedList) ReverseRemoveByVal(val interface{}, count int) int {
|
func (list *LinkedList) ReverseRemoveByVal(expected Expected, count int) int {
|
||||||
if list == nil {
|
if list == nil {
|
||||||
panic("list is nil")
|
panic("list is nil")
|
||||||
}
|
}
|
||||||
@@ -202,7 +200,7 @@ func (list *LinkedList) ReverseRemoveByVal(val interface{}, count int) int {
|
|||||||
var prevNode *node
|
var prevNode *node
|
||||||
for n != nil {
|
for n != nil {
|
||||||
prevNode = n.prev
|
prevNode = n.prev
|
||||||
if utils.Equals(n.val, val) {
|
if expected(n.val) {
|
||||||
list.removeNode(n)
|
list.removeNode(n)
|
||||||
removed++
|
removed++
|
||||||
}
|
}
|
||||||
@@ -224,7 +222,7 @@ func (list *LinkedList) Len() int {
|
|||||||
|
|
||||||
// ForEach visits each element in the list
|
// ForEach visits each element in the list
|
||||||
// if the consumer returns false, the loop will be break
|
// if the consumer returns false, the loop will be break
|
||||||
func (list *LinkedList) ForEach(consumer func(int, interface{}) bool) {
|
func (list *LinkedList) ForEach(consumer Consumer) {
|
||||||
if list == nil {
|
if list == nil {
|
||||||
panic("list is nil")
|
panic("list is nil")
|
||||||
}
|
}
|
||||||
@@ -241,10 +239,10 @@ func (list *LinkedList) ForEach(consumer func(int, interface{}) bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Contains returns whether the given value exist in the list
|
// Contains returns whether the given value exist in the list
|
||||||
func (list *LinkedList) Contains(val interface{}) bool {
|
func (list *LinkedList) Contains(expected Expected) bool {
|
||||||
contains := false
|
contains := false
|
||||||
list.ForEach(func(i int, actual interface{}) bool {
|
list.ForEach(func(i int, actual interface{}) bool {
|
||||||
if actual == val {
|
if expected(actual) {
|
||||||
contains = true
|
contains = true
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package list
|
package list
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/hdt3213/godis/lib/utils"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -30,12 +31,33 @@ func TestAdd(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkLinkedList_Add(b *testing.B) {
|
||||||
|
list := Make()
|
||||||
|
for i := 0; i < pageSize*10; i++ {
|
||||||
|
list.Add(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLinkedList_Range(b *testing.B) {
|
||||||
|
list := Make()
|
||||||
|
for i := 0; i < pageSize*10; i++ {
|
||||||
|
list.Add(i)
|
||||||
|
}
|
||||||
|
list.ForEach(func(i int, v interface{}) bool {
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestLinkedList_Contains(t *testing.T) {
|
func TestLinkedList_Contains(t *testing.T) {
|
||||||
list := Make(1, 2, 3, 4)
|
list := Make(1, 2, 3, 4)
|
||||||
if !list.Contains(1) {
|
if !list.Contains(func(a interface{}) bool {
|
||||||
|
return a == 1
|
||||||
|
}) {
|
||||||
t.Error("expect true actual false")
|
t.Error("expect true actual false")
|
||||||
}
|
}
|
||||||
if list.Contains(-1) {
|
if list.Contains(func(a interface{}) bool {
|
||||||
|
return a == -1
|
||||||
|
}) {
|
||||||
t.Error("expect false actual true")
|
t.Error("expect false actual true")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,7 +103,9 @@ func TestRemoveVal(t *testing.T) {
|
|||||||
list.Add(i)
|
list.Add(i)
|
||||||
}
|
}
|
||||||
for index := 0; index < list.Len(); index++ {
|
for index := 0; index < list.Len(); index++ {
|
||||||
list.RemoveAllByVal(index)
|
list.RemoveAllByVal(func(a interface{}) bool {
|
||||||
|
return utils.Equals(a, index)
|
||||||
|
})
|
||||||
list.ForEach(func(i int, v interface{}) bool {
|
list.ForEach(func(i int, v interface{}) bool {
|
||||||
intVal, _ := v.(int)
|
intVal, _ := v.(int)
|
||||||
if intVal == index {
|
if intVal == index {
|
||||||
@@ -97,7 +121,9 @@ func TestRemoveVal(t *testing.T) {
|
|||||||
list.Add(i)
|
list.Add(i)
|
||||||
}
|
}
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
list.RemoveByVal(i, 1)
|
list.RemoveByVal(func(a interface{}) bool {
|
||||||
|
return utils.Equals(a, i)
|
||||||
|
}, 1)
|
||||||
}
|
}
|
||||||
list.ForEach(func(i int, v interface{}) bool {
|
list.ForEach(func(i int, v interface{}) bool {
|
||||||
intVal, _ := v.(int)
|
intVal, _ := v.(int)
|
||||||
@@ -107,7 +133,9 @@ func TestRemoveVal(t *testing.T) {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
list.RemoveByVal(i, 1)
|
list.RemoveByVal(func(a interface{}) bool {
|
||||||
|
return utils.Equals(a, i)
|
||||||
|
}, 1)
|
||||||
}
|
}
|
||||||
if list.Len() != 0 {
|
if list.Len() != 0 {
|
||||||
t.Error("test fail: expected 0, actual: " + strconv.Itoa(list.Len()))
|
t.Error("test fail: expected 0, actual: " + strconv.Itoa(list.Len()))
|
||||||
@@ -119,7 +147,9 @@ func TestRemoveVal(t *testing.T) {
|
|||||||
list.Add(i)
|
list.Add(i)
|
||||||
}
|
}
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
list.ReverseRemoveByVal(i, 1)
|
list.ReverseRemoveByVal(func(a interface{}) bool {
|
||||||
|
return a == i
|
||||||
|
}, 1)
|
||||||
}
|
}
|
||||||
list.ForEach(func(i int, v interface{}) bool {
|
list.ForEach(func(i int, v interface{}) bool {
|
||||||
intVal, _ := v.(int)
|
intVal, _ := v.(int)
|
||||||
@@ -129,7 +159,9 @@ func TestRemoveVal(t *testing.T) {
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
list.ReverseRemoveByVal(i, 1)
|
list.ReverseRemoveByVal(func(a interface{}) bool {
|
||||||
|
return a == i
|
||||||
|
}, 1)
|
||||||
}
|
}
|
||||||
if list.Len() != 0 {
|
if list.Len() != 0 {
|
||||||
t.Error("test fail: expected 0, actual: " + strconv.Itoa(list.Len()))
|
t.Error("test fail: expected 0, actual: " + strconv.Itoa(list.Len()))
|
||||||
|
383
datastruct/list/quicklist.go
Normal file
383
datastruct/list/quicklist.go
Normal file
@@ -0,0 +1,383 @@
|
|||||||
|
package list
|
||||||
|
|
||||||
|
import "container/list"
|
||||||
|
|
||||||
|
// pageSize must be even
|
||||||
|
const pageSize = 1024
|
||||||
|
|
||||||
|
// QuickList is a linked list of page (which type is []interface{})
|
||||||
|
// QuickList has better performance than LinkedList of Add, Range and memory usage
|
||||||
|
type QuickList struct {
|
||||||
|
data *list.List // list of []interface{}
|
||||||
|
size int
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterator of QuickList, move between [-1, ql.Len()]
|
||||||
|
type iterator struct {
|
||||||
|
node *list.Element
|
||||||
|
offset int
|
||||||
|
ql *QuickList
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewQuickList() *QuickList {
|
||||||
|
l := &QuickList{
|
||||||
|
data: list.New(),
|
||||||
|
}
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add adds value to the tail
|
||||||
|
func (ql *QuickList) Add(val interface{}) {
|
||||||
|
ql.size++
|
||||||
|
if ql.data.Len() == 0 { // empty list
|
||||||
|
page := make([]interface{}, 0, pageSize)
|
||||||
|
page = append(page, val)
|
||||||
|
ql.data.PushBack(page)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// assert list.data.Back() != nil
|
||||||
|
backNode := ql.data.Back()
|
||||||
|
backPage := backNode.Value.([]interface{})
|
||||||
|
if len(backPage) == cap(backPage) { // full page, create new page
|
||||||
|
page := make([]interface{}, 0, pageSize)
|
||||||
|
page = append(page, val)
|
||||||
|
ql.data.PushBack(page)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// append into page
|
||||||
|
backPage = append(backPage, val)
|
||||||
|
backNode.Value = backPage
|
||||||
|
}
|
||||||
|
|
||||||
|
// find returns page and in-page-offset of given index
|
||||||
|
func (ql *QuickList) find(index int) *iterator {
|
||||||
|
if ql == nil {
|
||||||
|
panic("list is nil")
|
||||||
|
}
|
||||||
|
if index < 0 || index >= ql.size {
|
||||||
|
panic("index out of bound")
|
||||||
|
}
|
||||||
|
var n *list.Element
|
||||||
|
var page []interface{}
|
||||||
|
var pageBeg int
|
||||||
|
if index < ql.size/2 {
|
||||||
|
// search from front
|
||||||
|
n = ql.data.Front()
|
||||||
|
pageBeg = 0
|
||||||
|
for {
|
||||||
|
// assert: n != nil
|
||||||
|
page = n.Value.([]interface{})
|
||||||
|
if pageBeg+len(page) > index {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
pageBeg += len(page)
|
||||||
|
n = n.Next()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// search from back
|
||||||
|
n = ql.data.Back()
|
||||||
|
pageBeg = ql.size
|
||||||
|
for {
|
||||||
|
page = n.Value.([]interface{})
|
||||||
|
pageBeg -= len(page)
|
||||||
|
if pageBeg <= index {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
n = n.Prev()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pageOffset := index - pageBeg
|
||||||
|
return &iterator{
|
||||||
|
node: n,
|
||||||
|
offset: pageOffset,
|
||||||
|
ql: ql,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *iterator) get() interface{} {
|
||||||
|
return iter.page()[iter.offset]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *iterator) page() []interface{} {
|
||||||
|
return iter.node.Value.([]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// next returns whether iter is in bound
|
||||||
|
func (iter *iterator) next() bool {
|
||||||
|
page := iter.page()
|
||||||
|
if iter.offset < len(page)-1 {
|
||||||
|
iter.offset++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// move to next page
|
||||||
|
if iter.node == iter.ql.data.Back() {
|
||||||
|
// already at last node
|
||||||
|
iter.offset = len(page)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
iter.offset = 0
|
||||||
|
iter.node = iter.node.Next()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// prev returns whether iter is in bound
|
||||||
|
func (iter *iterator) prev() bool {
|
||||||
|
if iter.offset > 0 {
|
||||||
|
iter.offset--
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// move to prev page
|
||||||
|
if iter.node == iter.ql.data.Front() {
|
||||||
|
// already at first page
|
||||||
|
iter.offset = -1
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
iter.node = iter.node.Prev()
|
||||||
|
prevPage := iter.node.Value.([]interface{})
|
||||||
|
iter.offset = len(prevPage) - 1
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *iterator) atEnd() bool {
|
||||||
|
if iter.ql.data.Len() == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if iter.node != iter.ql.data.Back() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
page := iter.page()
|
||||||
|
return iter.offset == len(page)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *iterator) atBegin() bool {
|
||||||
|
if iter.ql.data.Len() == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if iter.node != iter.ql.data.Front() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return iter.offset == -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns value at the given index
|
||||||
|
func (ql *QuickList) Get(index int) (val interface{}) {
|
||||||
|
iter := ql.find(index)
|
||||||
|
return iter.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *iterator) set(val interface{}) {
|
||||||
|
page := iter.page()
|
||||||
|
page[iter.offset] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set updates value at the given index, the index should between [0, list.size]
|
||||||
|
func (ql *QuickList) Set(index int, val interface{}) {
|
||||||
|
iter := ql.find(index)
|
||||||
|
iter.set(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ql *QuickList) Insert(index int, val interface{}) {
|
||||||
|
if index == ql.size { // insert at
|
||||||
|
ql.Add(val)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
iter := ql.find(index)
|
||||||
|
page := iter.node.Value.([]interface{})
|
||||||
|
if len(page) < pageSize {
|
||||||
|
// insert into not full page
|
||||||
|
page = append(page[:iter.offset+1], page[iter.offset:]...)
|
||||||
|
page[iter.offset] = val
|
||||||
|
iter.node.Value = page
|
||||||
|
ql.size++
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// insert into a full page may cause memory copy, so we split a full page into two half pages
|
||||||
|
var nextPage []interface{}
|
||||||
|
nextPage = append(nextPage, page[pageSize/2:]...) // pageSize must be even
|
||||||
|
page = page[:pageSize/2]
|
||||||
|
if iter.offset < len(page) {
|
||||||
|
page = append(page[:iter.offset+1], page[iter.offset:]...)
|
||||||
|
page[iter.offset] = val
|
||||||
|
} else {
|
||||||
|
i := iter.offset - pageSize/2
|
||||||
|
nextPage = append(nextPage[:i+1], nextPage[i:]...)
|
||||||
|
nextPage[i] = val
|
||||||
|
}
|
||||||
|
// store current page and next page
|
||||||
|
iter.node.Value = page
|
||||||
|
ql.data.InsertAfter(nextPage, iter.node)
|
||||||
|
ql.size++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *iterator) remove() interface{} {
|
||||||
|
page := iter.page()
|
||||||
|
val := page[iter.offset]
|
||||||
|
page = append(page[:iter.offset], page[iter.offset+1:]...)
|
||||||
|
if len(page) > 0 {
|
||||||
|
// page is not empty, update iter.offset only
|
||||||
|
iter.node.Value = page
|
||||||
|
if iter.offset == len(page) {
|
||||||
|
// removed page[-1], node should move to next page
|
||||||
|
if iter.node != iter.ql.data.Back() {
|
||||||
|
iter.node = iter.node.Next()
|
||||||
|
iter.offset = 0
|
||||||
|
}
|
||||||
|
// else: assert iter.atEnd() == true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// page is empty, update iter.node and iter.offset
|
||||||
|
if iter.node == iter.ql.data.Back() {
|
||||||
|
// removed last element, ql is empty now
|
||||||
|
iter.ql.data.Remove(iter.node)
|
||||||
|
iter.node = nil
|
||||||
|
iter.offset = 0
|
||||||
|
} else {
|
||||||
|
nextNode := iter.node.Next()
|
||||||
|
iter.ql.data.Remove(iter.node)
|
||||||
|
iter.node = nextNode
|
||||||
|
iter.offset = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
iter.ql.size--
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove removes value at the given index
|
||||||
|
func (ql *QuickList) Remove(index int) interface{} {
|
||||||
|
iter := ql.find(index)
|
||||||
|
return iter.remove()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of elements in list
|
||||||
|
func (ql *QuickList) Len() int {
|
||||||
|
return ql.size
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveLast removes the last element and returns its value
|
||||||
|
func (ql *QuickList) RemoveLast() interface{} {
|
||||||
|
if ql.Len() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ql.size--
|
||||||
|
lastNode := ql.data.Back()
|
||||||
|
lastPage := lastNode.Value.([]interface{})
|
||||||
|
if len(lastPage) == 1 {
|
||||||
|
ql.data.Remove(lastNode)
|
||||||
|
return lastPage[0]
|
||||||
|
}
|
||||||
|
val := lastPage[len(lastPage)-1]
|
||||||
|
lastPage = lastPage[:len(lastPage)-1]
|
||||||
|
lastNode.Value = lastPage
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAllByVal removes all elements with the given val
|
||||||
|
func (ql *QuickList) RemoveAllByVal(expected Expected) int {
|
||||||
|
iter := ql.find(0)
|
||||||
|
removed := 0
|
||||||
|
for !iter.atEnd() {
|
||||||
|
if expected(iter.get()) {
|
||||||
|
iter.remove()
|
||||||
|
removed++
|
||||||
|
} else {
|
||||||
|
iter.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return removed
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveByVal removes at most `count` values of the specified value in this list
|
||||||
|
// scan from left to right
|
||||||
|
func (ql *QuickList) RemoveByVal(expected Expected, count int) int {
|
||||||
|
if ql.size == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
iter := ql.find(0)
|
||||||
|
removed := 0
|
||||||
|
for !iter.atEnd() {
|
||||||
|
if expected(iter.get()) {
|
||||||
|
iter.remove()
|
||||||
|
removed++
|
||||||
|
if removed == count {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
iter.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return removed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ql *QuickList) ReverseRemoveByVal(expected Expected, count int) int {
|
||||||
|
if ql.size == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
iter := ql.find(ql.size - 1)
|
||||||
|
removed := 0
|
||||||
|
for !iter.atBegin() {
|
||||||
|
if expected(iter.get()) {
|
||||||
|
iter.remove()
|
||||||
|
removed++
|
||||||
|
if removed == count {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
iter.prev()
|
||||||
|
}
|
||||||
|
return removed
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForEach visits each element in the list
|
||||||
|
// if the consumer returns false, the loop will be break
|
||||||
|
func (ql *QuickList) ForEach(consumer Consumer) {
|
||||||
|
if ql == nil {
|
||||||
|
panic("list is nil")
|
||||||
|
}
|
||||||
|
if ql.Len() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
iter := ql.find(0)
|
||||||
|
i := 0
|
||||||
|
for {
|
||||||
|
goNext := consumer(i, iter.get())
|
||||||
|
if !goNext {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
if !iter.next() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ql *QuickList) Contains(expected Expected) bool {
|
||||||
|
contains := false
|
||||||
|
ql.ForEach(func(i int, actual interface{}) bool {
|
||||||
|
if expected(actual) {
|
||||||
|
contains = true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return contains
|
||||||
|
}
|
||||||
|
|
||||||
|
// Range returns elements which index within [start, stop)
|
||||||
|
func (ql *QuickList) Range(start int, stop int) []interface{} {
|
||||||
|
if start < 0 || start >= ql.Len() {
|
||||||
|
panic("`start` out of range")
|
||||||
|
}
|
||||||
|
if stop < start || stop > ql.Len() {
|
||||||
|
panic("`stop` out of range")
|
||||||
|
}
|
||||||
|
sliceSize := stop - start
|
||||||
|
slice := make([]interface{}, 0, sliceSize)
|
||||||
|
iter := ql.find(start)
|
||||||
|
i := 0
|
||||||
|
for i < sliceSize {
|
||||||
|
slice = append(slice, iter.get())
|
||||||
|
iter.next()
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return slice
|
||||||
|
}
|
261
datastruct/list/quicklist_test.go
Normal file
261
datastruct/list/quicklist_test.go
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
package list
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hdt3213/godis/lib/utils"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestQuickList_Add(t *testing.T) {
|
||||||
|
list := NewQuickList()
|
||||||
|
for i := 0; i < pageSize*10; i++ {
|
||||||
|
list.Add(i)
|
||||||
|
}
|
||||||
|
for i := 0; i < pageSize*10; i++ {
|
||||||
|
v := list.Get(i).(int)
|
||||||
|
if v != i {
|
||||||
|
t.Errorf("wrong value at: %d", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
list.ForEach(func(i int, v interface{}) bool {
|
||||||
|
if v != i {
|
||||||
|
t.Errorf("wrong value at: %d", i)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkQuickList_Add(b *testing.B) {
|
||||||
|
list := NewQuickList()
|
||||||
|
for i := 0; i < pageSize*10; i++ {
|
||||||
|
list.Add(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkQuickList_Range(b *testing.B) {
|
||||||
|
list := NewQuickList()
|
||||||
|
for i := 0; i < pageSize*10; i++ {
|
||||||
|
list.Add(i)
|
||||||
|
}
|
||||||
|
list.ForEach(func(i int, v interface{}) bool {
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuickList_Set(t *testing.T) {
|
||||||
|
list := NewQuickList()
|
||||||
|
for i := 0; i < pageSize*10; i++ {
|
||||||
|
list.Add(i)
|
||||||
|
}
|
||||||
|
for i := 0; i < pageSize*10; i++ {
|
||||||
|
list.Set(i, 2*i)
|
||||||
|
}
|
||||||
|
for i := 0; i < pageSize*10; i++ {
|
||||||
|
v := list.Get(i).(int)
|
||||||
|
if v != 2*i {
|
||||||
|
t.Errorf("wrong value at: %d", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuickList_Insert(t *testing.T) {
|
||||||
|
list := NewQuickList()
|
||||||
|
for i := 0; i < pageSize*10; i++ {
|
||||||
|
list.Insert(0, i)
|
||||||
|
}
|
||||||
|
for i := 0; i < pageSize*10; i++ {
|
||||||
|
v := list.Get(i).(int)
|
||||||
|
if v != pageSize*10-i-1 {
|
||||||
|
t.Errorf("wrong value at: %d", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert into second half page
|
||||||
|
list = NewQuickList()
|
||||||
|
for i := 0; i < pageSize; i++ {
|
||||||
|
list.Add(0)
|
||||||
|
}
|
||||||
|
for i := 0; i < pageSize; i++ {
|
||||||
|
list.Insert(pageSize-1, i+1)
|
||||||
|
}
|
||||||
|
for i := pageSize - 1; i < list.size; i++ {
|
||||||
|
v := list.Get(i).(int)
|
||||||
|
if v != 2*pageSize-1-i {
|
||||||
|
t.Errorf("wrong value at: %d", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuickList_RemoveLast(t *testing.T) {
|
||||||
|
list := NewQuickList()
|
||||||
|
size := pageSize * 10
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
list.Add(i)
|
||||||
|
}
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
v := list.RemoveLast()
|
||||||
|
if v != size-i-1 {
|
||||||
|
t.Errorf("wrong value at: %d", i)
|
||||||
|
}
|
||||||
|
if list.Len() != size-i-1 {
|
||||||
|
t.Errorf("wrong len: %d", list.Len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuickListRemoveVal(t *testing.T) {
|
||||||
|
list := NewQuickList()
|
||||||
|
size := pageSize * 10
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
list.Add(i)
|
||||||
|
list.Add(i)
|
||||||
|
}
|
||||||
|
for index := 0; index < list.Len(); index++ {
|
||||||
|
list.RemoveAllByVal(func(a interface{}) bool {
|
||||||
|
return utils.Equals(a, index)
|
||||||
|
})
|
||||||
|
list.ForEach(func(i int, v interface{}) bool {
|
||||||
|
intVal, _ := v.(int)
|
||||||
|
if intVal == index {
|
||||||
|
t.Error("remove test fail: found " + strconv.Itoa(index) + " at index: " + strconv.Itoa(i))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
list = NewQuickList()
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
list.Add(i)
|
||||||
|
list.Add(i)
|
||||||
|
}
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
list.RemoveByVal(func(a interface{}) bool {
|
||||||
|
return utils.Equals(a, i)
|
||||||
|
}, 1)
|
||||||
|
}
|
||||||
|
list.ForEach(func(i int, v interface{}) bool {
|
||||||
|
intVal, _ := v.(int)
|
||||||
|
if intVal != i {
|
||||||
|
t.Error("test fail: expected " + strconv.Itoa(i) + ", actual: " + strconv.Itoa(intVal))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
list.RemoveByVal(func(a interface{}) bool {
|
||||||
|
return utils.Equals(a, i)
|
||||||
|
}, 1)
|
||||||
|
}
|
||||||
|
if list.Len() != 0 {
|
||||||
|
t.Error("test fail: expected 0, actual: " + strconv.Itoa(list.Len()))
|
||||||
|
}
|
||||||
|
|
||||||
|
list = NewQuickList()
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
list.Add(i)
|
||||||
|
list.Add(i)
|
||||||
|
}
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
list.ReverseRemoveByVal(func(a interface{}) bool {
|
||||||
|
return utils.Equals(a, i)
|
||||||
|
}, 1)
|
||||||
|
}
|
||||||
|
list.ForEach(func(i int, v interface{}) bool {
|
||||||
|
intVal, _ := v.(int)
|
||||||
|
if intVal != i {
|
||||||
|
t.Error("test fail: expected " + strconv.Itoa(i) + ", actual: " + strconv.Itoa(intVal))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
list.ReverseRemoveByVal(func(a interface{}) bool {
|
||||||
|
return utils.Equals(a, i)
|
||||||
|
}, 1)
|
||||||
|
}
|
||||||
|
if list.Len() != 0 {
|
||||||
|
t.Error("test fail: expected 0, actual: " + strconv.Itoa(list.Len()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuickList_Contains(t *testing.T) {
|
||||||
|
list := NewQuickList()
|
||||||
|
list.Add(1)
|
||||||
|
list.Add(2)
|
||||||
|
list.Add(3)
|
||||||
|
if !list.Contains(func(a interface{}) bool {
|
||||||
|
return a == 1
|
||||||
|
}) {
|
||||||
|
t.Error("expect true actual false")
|
||||||
|
}
|
||||||
|
if list.Contains(func(a interface{}) bool {
|
||||||
|
return a == -1
|
||||||
|
}) {
|
||||||
|
t.Error("expect false actual true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuickList_Range(t *testing.T) {
|
||||||
|
list := NewQuickList()
|
||||||
|
size := 10
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
list.Add(i)
|
||||||
|
}
|
||||||
|
for start := 0; start < size; start++ {
|
||||||
|
for stop := start; stop < size; stop++ {
|
||||||
|
slice := list.Range(start, stop)
|
||||||
|
if len(slice) != stop-start {
|
||||||
|
t.Error("expected " + strconv.Itoa(stop-start) + ", get: " + strconv.Itoa(len(slice)) +
|
||||||
|
", range: [" + strconv.Itoa(start) + "," + strconv.Itoa(stop) + "]")
|
||||||
|
}
|
||||||
|
sliceIndex := 0
|
||||||
|
for i := start; i < stop; i++ {
|
||||||
|
val := slice[sliceIndex]
|
||||||
|
intVal, _ := val.(int)
|
||||||
|
if intVal != i {
|
||||||
|
t.Error("expected " + strconv.Itoa(i) + ", get: " + strconv.Itoa(intVal) +
|
||||||
|
", range: [" + strconv.Itoa(start) + "," + strconv.Itoa(stop) + "]")
|
||||||
|
}
|
||||||
|
sliceIndex++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuickList_Remove(t *testing.T) {
|
||||||
|
list := NewQuickList()
|
||||||
|
size := pageSize * 10
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
list.Add(i)
|
||||||
|
}
|
||||||
|
for i := size - 1; i >= 0; i-- {
|
||||||
|
list.Remove(i)
|
||||||
|
if i != list.Len() {
|
||||||
|
t.Error("remove test fail: expected size " + strconv.Itoa(i) + ", actual: " + strconv.Itoa(list.Len()))
|
||||||
|
}
|
||||||
|
list.ForEach(func(i int, v interface{}) bool {
|
||||||
|
intVal, _ := v.(int)
|
||||||
|
if intVal != i {
|
||||||
|
t.Error("remove test fail: expected " + strconv.Itoa(i) + ", actual: " + strconv.Itoa(intVal))
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuickList_Prev(t *testing.T) {
|
||||||
|
list := NewQuickList()
|
||||||
|
size := pageSize * 10
|
||||||
|
for i := 0; i < size; i++ {
|
||||||
|
list.Add(i)
|
||||||
|
}
|
||||||
|
iter := list.find(size - 1)
|
||||||
|
i := size - 1
|
||||||
|
for !iter.atBegin() {
|
||||||
|
v := iter.get()
|
||||||
|
if v != i {
|
||||||
|
t.Errorf("wrong value at %d", i)
|
||||||
|
}
|
||||||
|
iter.prev()
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
}
|
@@ -3,6 +3,7 @@ package pubsub
|
|||||||
import (
|
import (
|
||||||
"github.com/hdt3213/godis/datastruct/list"
|
"github.com/hdt3213/godis/datastruct/list"
|
||||||
"github.com/hdt3213/godis/interface/redis"
|
"github.com/hdt3213/godis/interface/redis"
|
||||||
|
"github.com/hdt3213/godis/lib/utils"
|
||||||
"github.com/hdt3213/godis/redis/protocol"
|
"github.com/hdt3213/godis/redis/protocol"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
@@ -36,7 +37,9 @@ func subscribe0(hub *Hub, channel string, client redis.Connection) bool {
|
|||||||
subscribers = list.Make()
|
subscribers = list.Make()
|
||||||
hub.subs.Put(channel, subscribers)
|
hub.subs.Put(channel, subscribers)
|
||||||
}
|
}
|
||||||
if subscribers.Contains(client) {
|
if subscribers.Contains(func(a interface{}) bool {
|
||||||
|
return a == client
|
||||||
|
}) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
subscribers.Add(client)
|
subscribers.Add(client)
|
||||||
@@ -54,7 +57,9 @@ func unsubscribe0(hub *Hub, channel string, client redis.Connection) bool {
|
|||||||
raw, ok := hub.subs.Get(channel)
|
raw, ok := hub.subs.Get(channel)
|
||||||
if ok {
|
if ok {
|
||||||
subscribers, _ := raw.(*list.LinkedList)
|
subscribers, _ := raw.(*list.LinkedList)
|
||||||
subscribers.RemoveAllByVal(client)
|
subscribers.RemoveAllByVal(func(a interface{}) bool {
|
||||||
|
return utils.Equals(a, client)
|
||||||
|
})
|
||||||
|
|
||||||
if subscribers.Len() == 0 {
|
if subscribers.Len() == 0 {
|
||||||
// clean
|
// clean
|
||||||
|
@@ -150,7 +150,7 @@ func AssertMultiBulkReplySize(t *testing.T, actual redis.Reply, expected int) {
|
|||||||
func printStack() string {
|
func printStack() string {
|
||||||
_, file, no, ok := runtime.Caller(2)
|
_, file, no, ok := runtime.Caller(2)
|
||||||
if ok {
|
if ok {
|
||||||
return fmt.Sprintf("at %s#%d", file, no)
|
return fmt.Sprintf("at %s:%d", file, no)
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user