quick list

This commit is contained in:
finley
2022-07-12 20:57:13 +08:00
parent ab0754e2a5
commit e6d958716d
12 changed files with 743 additions and 34 deletions

View File

@@ -20,7 +20,7 @@ func EntityToCmd(key string, entity *database.DataEntity) *protocol.MultiBulkRep
switch val := entity.Data.(type) {
case []byte:
cmd = stringToCmd(key, val)
case *List.LinkedList:
case List.List:
cmd = listToCmd(key, val)
case *set.Set:
cmd = setToCmd(key, val)
@@ -44,7 +44,7 @@ func stringToCmd(key string, bytes []byte) *protocol.MultiBulkReply {
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[0] = rPushAllCmd
args[1] = []byte(key)

View File

@@ -106,7 +106,7 @@ func (handler *Handler) rewrite2RDB(ctx *RewriteCtx) error {
switch obj := entity.Data.(type) {
case []byte:
err = encoder.WriteStringObject(key, obj, opts...)
case *List.LinkedList:
case List.List:
vals := make([][]byte, 0, obj.Len())
obj.ForEach(func(i int, v interface{}) bool {
bytes, _ := v.([]byte)

View File

@@ -68,7 +68,7 @@ func execType(db *DB, args [][]byte) redis.Reply {
switch entity.Data.(type) {
case []byte:
return protocol.MakeStatusReply("string")
case *list.LinkedList:
case list.List:
return protocol.MakeStatusReply("list")
case dict.Dict:
return protocol.MakeStatusReply("hash")

View File

@@ -9,26 +9,26 @@ import (
"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)
if !ok {
return nil, nil
}
bytes, ok := entity.Data.(*List.LinkedList)
list, ok := entity.Data.(List.List)
if !ok {
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)
if errReply != nil {
return nil, false, errReply
}
isNew = false
if list == nil {
list = &List.LinkedList{}
list = List.NewQuickList()
db.PutEntity(key, &database.DataEntity{
Data: list,
})
@@ -259,11 +259,17 @@ func execLRem(db *DB, args [][]byte) redis.Reply {
var removed int
if count == 0 {
removed = list.RemoveAllByVal(value)
removed = list.RemoveAllByVal(func(a interface{}) bool {
return utils.Equals(a, value)
})
} else if count > 0 {
removed = list.RemoveByVal(value, count)
removed = list.RemoveByVal(func(a interface{}) bool {
return utils.Equals(a, value)
}, count)
} else {
removed = list.ReverseRemoveByVal(value, -count)
removed = list.ReverseRemoveByVal(func(a interface{}) bool {
return utils.Equals(a, value)
}, -count)
}
if list.Len() == 0 {

View File

@@ -40,7 +40,7 @@ func dumpRDB(dec *core.Decoder, mdb *MultiDB) error {
})
case rdb.ListType:
listObj := o.(*rdb.ListObject)
list := &List.LinkedList{}
list := List.NewQuickList()
for _, v := range listObj.Values {
list.Add(v)
}

View 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{}
}

View File

@@ -1,7 +1,5 @@
package list
import "github.com/hdt3213/godis/lib/utils"
// LinkedList is doubly linked list
type LinkedList struct {
first *node
@@ -150,7 +148,7 @@ func (list *LinkedList) RemoveLast() (val interface{}) {
}
// 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 {
panic("list is nil")
}
@@ -159,7 +157,7 @@ func (list *LinkedList) RemoveAllByVal(val interface{}) int {
var nextNode *node
for n != nil {
nextNode = n.next
if utils.Equals(n.val, val) {
if expected(n.val) {
list.removeNode(n)
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
// 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 {
panic("list is nil")
}
@@ -179,7 +177,7 @@ func (list *LinkedList) RemoveByVal(val interface{}, count int) int {
var nextNode *node
for n != nil {
nextNode = n.next
if utils.Equals(n.val, val) {
if expected(n.val) {
list.removeNode(n)
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
// 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 {
panic("list is nil")
}
@@ -202,7 +200,7 @@ func (list *LinkedList) ReverseRemoveByVal(val interface{}, count int) int {
var prevNode *node
for n != nil {
prevNode = n.prev
if utils.Equals(n.val, val) {
if expected(n.val) {
list.removeNode(n)
removed++
}
@@ -224,7 +222,7 @@ func (list *LinkedList) Len() int {
// ForEach visits each element in the list
// 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 {
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
func (list *LinkedList) Contains(val interface{}) bool {
func (list *LinkedList) Contains(expected Expected) bool {
contains := false
list.ForEach(func(i int, actual interface{}) bool {
if actual == val {
if expected(actual) {
contains = true
return false
}

View File

@@ -1,6 +1,7 @@
package list
import (
"github.com/hdt3213/godis/lib/utils"
"strconv"
"strings"
"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) {
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")
}
if list.Contains(-1) {
if list.Contains(func(a interface{}) bool {
return a == -1
}) {
t.Error("expect false actual true")
}
}
@@ -81,7 +103,9 @@ func TestRemoveVal(t *testing.T) {
list.Add(i)
}
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 {
intVal, _ := v.(int)
if intVal == index {
@@ -97,7 +121,9 @@ func TestRemoveVal(t *testing.T) {
list.Add(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 {
intVal, _ := v.(int)
@@ -107,7 +133,9 @@ func TestRemoveVal(t *testing.T) {
return true
})
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 {
t.Error("test fail: expected 0, actual: " + strconv.Itoa(list.Len()))
@@ -119,7 +147,9 @@ func TestRemoveVal(t *testing.T) {
list.Add(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 {
intVal, _ := v.(int)
@@ -129,7 +159,9 @@ func TestRemoveVal(t *testing.T) {
return true
})
for i := 0; i < 10; i++ {
list.ReverseRemoveByVal(i, 1)
list.ReverseRemoveByVal(func(a interface{}) bool {
return a == i
}, 1)
}
if list.Len() != 0 {
t.Error("test fail: expected 0, actual: " + strconv.Itoa(list.Len()))

View 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
}

View 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--
}
}

View File

@@ -3,6 +3,7 @@ package pubsub
import (
"github.com/hdt3213/godis/datastruct/list"
"github.com/hdt3213/godis/interface/redis"
"github.com/hdt3213/godis/lib/utils"
"github.com/hdt3213/godis/redis/protocol"
"strconv"
)
@@ -36,7 +37,9 @@ func subscribe0(hub *Hub, channel string, client redis.Connection) bool {
subscribers = list.Make()
hub.subs.Put(channel, subscribers)
}
if subscribers.Contains(client) {
if subscribers.Contains(func(a interface{}) bool {
return a == client
}) {
return false
}
subscribers.Add(client)
@@ -54,7 +57,9 @@ func unsubscribe0(hub *Hub, channel string, client redis.Connection) bool {
raw, ok := hub.subs.Get(channel)
if ok {
subscribers, _ := raw.(*list.LinkedList)
subscribers.RemoveAllByVal(client)
subscribers.RemoveAllByVal(func(a interface{}) bool {
return utils.Equals(a, client)
})
if subscribers.Len() == 0 {
// clean

View File

@@ -150,7 +150,7 @@ func AssertMultiBulkReplySize(t *testing.T, actual redis.Reply, expected int) {
func printStack() string {
_, file, no, ok := runtime.Caller(2)
if ok {
return fmt.Sprintf("at %s#%d", file, no)
return fmt.Sprintf("at %s:%d", file, no)
}
return ""
}