diff --git a/.example/net/ghttp/server/session/session.go b/.example/net/ghttp/server/session/session.go index e41215bd9..111c73eec 100644 --- a/.example/net/ghttp/server/session/session.go +++ b/.example/net/ghttp/server/session/session.go @@ -9,7 +9,7 @@ import ( func main() { s := g.Server() - s.SetSessionMaxAge(2 * time.Second) + s.SetSessionMaxAge(61 * time.Second) s.BindHandler("/set", func(r *ghttp.Request) { r.Session.Set("time", gtime.Second()) r.Response.Write("ok") diff --git a/container/gmap/gmap_hash_any_any_map.go b/container/gmap/gmap_hash_any_any_map.go index 028824304..edb207ae3 100644 --- a/container/gmap/gmap_hash_any_any_map.go +++ b/container/gmap/gmap_hash_any_any_map.go @@ -141,6 +141,41 @@ func (m *AnyAnyMap) Get(key interface{}) interface{} { return val } +// Pop retrieves and deletes an item from the map. +func (m *AnyAnyMap) Pop() (key, value interface{}) { + m.mu.Lock() + defer m.mu.Unlock() + for key, value = range m.data { + delete(m.data, key) + return + } + return +} + +// Pops retrieves and deletes items from the map. +// It returns all items if size == -1. +func (m *AnyAnyMap) Pops(size int) map[interface{}]interface{} { + m.mu.Lock() + defer m.mu.Unlock() + if size > len(m.data) || size == -1 { + size = len(m.data) + } + if size == 0 { + return nil + } + index := 0 + newMap := make(map[interface{}]interface{}, size) + for k, v := range m.data { + delete(m.data, k) + newMap[k] = v + index++ + if index == size { + break + } + } + return newMap +} + // doSetWithLockCheck checks whether value of the key exists with mutex.Lock, // if not exists, set value to the map with given , // or else just return the existing value. @@ -321,10 +356,7 @@ func (m *AnyAnyMap) Size() int { // IsEmpty checks whether the map is empty. // It returns true if map is empty, or else false. func (m *AnyAnyMap) IsEmpty() bool { - m.mu.RLock() - empty := len(m.data) == 0 - m.mu.RUnlock() - return empty + return m.Size() == 0 } // Clear deletes all data of the map, it will remake a new underlying data map. diff --git a/container/gmap/gmap_hash_int_any_map.go b/container/gmap/gmap_hash_int_any_map.go index 5cfc29850..4b51e9985 100644 --- a/container/gmap/gmap_hash_int_any_map.go +++ b/container/gmap/gmap_hash_int_any_map.go @@ -141,6 +141,41 @@ func (m *IntAnyMap) Get(key int) interface{} { return val } +// Pop retrieves and deletes an item from the map. +func (m *IntAnyMap) Pop() (key int, value interface{}) { + m.mu.Lock() + defer m.mu.Unlock() + for key, value = range m.data { + delete(m.data, key) + return + } + return +} + +// Pops retrieves and deletes items from the map. +// It returns all items if size == -1. +func (m *IntAnyMap) Pops(size int) map[int]interface{} { + m.mu.Lock() + defer m.mu.Unlock() + if size > len(m.data) || size == -1 { + size = len(m.data) + } + if size == 0 { + return nil + } + index := 0 + newMap := make(map[int]interface{}, size) + for k, v := range m.data { + delete(m.data, k) + newMap[k] = v + index++ + if index == size { + break + } + } + return newMap +} + // doSetWithLockCheck checks whether value of the key exists with mutex.Lock, // if not exists, set value to the map with given , // or else just return the existing value. @@ -321,10 +356,7 @@ func (m *IntAnyMap) Size() int { // IsEmpty checks whether the map is empty. // It returns true if map is empty, or else false. func (m *IntAnyMap) IsEmpty() bool { - m.mu.RLock() - empty := len(m.data) == 0 - m.mu.RUnlock() - return empty + return m.Size() == 0 } // Clear deletes all data of the map, it will remake a new underlying data map. diff --git a/container/gmap/gmap_hash_int_int_map.go b/container/gmap/gmap_hash_int_int_map.go index b2d7b9d6d..78c22f502 100644 --- a/container/gmap/gmap_hash_int_int_map.go +++ b/container/gmap/gmap_hash_int_int_map.go @@ -139,6 +139,41 @@ func (m *IntIntMap) Get(key int) int { return val } +// Pop retrieves and deletes an item from the map. +func (m *IntIntMap) Pop() (key, value int) { + m.mu.Lock() + defer m.mu.Unlock() + for key, value = range m.data { + delete(m.data, key) + return + } + return +} + +// Pops retrieves and deletes items from the map. +// It returns all items if size == -1. +func (m *IntIntMap) Pops(size int) map[int]int { + m.mu.Lock() + defer m.mu.Unlock() + if size > len(m.data) || size == -1 { + size = len(m.data) + } + if size == 0 { + return nil + } + index := 0 + newMap := make(map[int]int, size) + for k, v := range m.data { + delete(m.data, k) + newMap[k] = v + index++ + if index == size { + break + } + } + return newMap +} + // doSetWithLockCheck checks whether value of the key exists with mutex.Lock, // if not exists, set value to the map with given , // or else just return the existing value. @@ -298,10 +333,7 @@ func (m *IntIntMap) Size() int { // IsEmpty checks whether the map is empty. // It returns true if map is empty, or else false. func (m *IntIntMap) IsEmpty() bool { - m.mu.RLock() - empty := len(m.data) == 0 - m.mu.RUnlock() - return empty + return m.Size() == 0 } // Clear deletes all data of the map, it will remake a new underlying data map. diff --git a/container/gmap/gmap_hash_int_str_map.go b/container/gmap/gmap_hash_int_str_map.go index ce3f53918..f9ddd5f38 100644 --- a/container/gmap/gmap_hash_int_str_map.go +++ b/container/gmap/gmap_hash_int_str_map.go @@ -139,6 +139,41 @@ func (m *IntStrMap) Get(key int) string { return val } +// Pop retrieves and deletes an item from the map. +func (m *IntStrMap) Pop() (key int, value string) { + m.mu.Lock() + defer m.mu.Unlock() + for key, value = range m.data { + delete(m.data, key) + return + } + return +} + +// Pops retrieves and deletes items from the map. +// It returns all items if size == -1. +func (m *IntStrMap) Pops(size int) map[int]string { + m.mu.Lock() + defer m.mu.Unlock() + if size > len(m.data) || size == -1 { + size = len(m.data) + } + if size == 0 { + return nil + } + index := 0 + newMap := make(map[int]string, size) + for k, v := range m.data { + delete(m.data, k) + newMap[k] = v + index++ + if index == size { + break + } + } + return newMap +} + // doSetWithLockCheck checks whether value of the key exists with mutex.Lock, // if not exists, set value to the map with given , // or else just return the existing value. @@ -298,10 +333,7 @@ func (m *IntStrMap) Size() int { // IsEmpty checks whether the map is empty. // It returns true if map is empty, or else false. func (m *IntStrMap) IsEmpty() bool { - m.mu.RLock() - empty := len(m.data) == 0 - m.mu.RUnlock() - return empty + return m.Size() == 0 } // Clear deletes all data of the map, it will remake a new underlying data map. diff --git a/container/gmap/gmap_hash_str_any_map.go b/container/gmap/gmap_hash_str_any_map.go index 6ece9ccb3..2a9491437 100644 --- a/container/gmap/gmap_hash_str_any_map.go +++ b/container/gmap/gmap_hash_str_any_map.go @@ -135,6 +135,41 @@ func (m *StrAnyMap) Get(key string) interface{} { return val } +// Pop retrieves and deletes an item from the map. +func (m *StrAnyMap) Pop() (key string, value interface{}) { + m.mu.Lock() + defer m.mu.Unlock() + for key, value = range m.data { + delete(m.data, key) + return + } + return +} + +// Pops retrieves and deletes items from the map. +// It returns all items if size == -1. +func (m *StrAnyMap) Pops(size int) map[string]interface{} { + m.mu.Lock() + defer m.mu.Unlock() + if size > len(m.data) || size == -1 { + size = len(m.data) + } + if size == 0 { + return nil + } + index := 0 + newMap := make(map[string]interface{}, size) + for k, v := range m.data { + delete(m.data, k) + newMap[k] = v + index++ + if index == size { + break + } + } + return newMap +} + // doSetWithLockCheck checks whether value of the key exists with mutex.Lock, // if not exists, set value to the map with given , // or else just return the existing value. @@ -317,10 +352,7 @@ func (m *StrAnyMap) Size() int { // IsEmpty checks whether the map is empty. // It returns true if map is empty, or else false. func (m *StrAnyMap) IsEmpty() bool { - m.mu.RLock() - empty := len(m.data) == 0 - m.mu.RUnlock() - return empty + return m.Size() == 0 } // Clear deletes all data of the map, it will remake a new underlying data map. diff --git a/container/gmap/gmap_hash_str_int_map.go b/container/gmap/gmap_hash_str_int_map.go index a536e2be5..c2d338bb5 100644 --- a/container/gmap/gmap_hash_str_int_map.go +++ b/container/gmap/gmap_hash_str_int_map.go @@ -139,6 +139,41 @@ func (m *StrIntMap) Get(key string) int { return val } +// Pop retrieves and deletes an item from the map. +func (m *StrIntMap) Pop() (key string, value int) { + m.mu.Lock() + defer m.mu.Unlock() + for key, value = range m.data { + delete(m.data, key) + return + } + return +} + +// Pops retrieves and deletes items from the map. +// It returns all items if size == -1. +func (m *StrIntMap) Pops(size int) map[string]int { + m.mu.Lock() + defer m.mu.Unlock() + if size > len(m.data) || size == -1 { + size = len(m.data) + } + if size == 0 { + return nil + } + index := 0 + newMap := make(map[string]int, size) + for k, v := range m.data { + delete(m.data, k) + newMap[k] = v + index++ + if index == size { + break + } + } + return newMap +} + // doSetWithLockCheck checks whether value of the key exists with mutex.Lock, // if not exists, set value to the map with given , // or else just return the existing value. @@ -300,10 +335,7 @@ func (m *StrIntMap) Size() int { // IsEmpty checks whether the map is empty. // It returns true if map is empty, or else false. func (m *StrIntMap) IsEmpty() bool { - m.mu.RLock() - empty := len(m.data) == 0 - m.mu.RUnlock() - return empty + return m.Size() == 0 } // Clear deletes all data of the map, it will remake a new underlying data map. diff --git a/container/gmap/gmap_hash_str_str_map.go b/container/gmap/gmap_hash_str_str_map.go index 3d0944cea..89abbce52 100644 --- a/container/gmap/gmap_hash_str_str_map.go +++ b/container/gmap/gmap_hash_str_str_map.go @@ -139,6 +139,41 @@ func (m *StrStrMap) Get(key string) string { return val } +// Pop retrieves and deletes an item from the map. +func (m *StrStrMap) Pop() (key, value string) { + m.mu.Lock() + defer m.mu.Unlock() + for key, value = range m.data { + delete(m.data, key) + return + } + return +} + +// Pops retrieves and deletes items from the map. +// It returns all items if size == -1. +func (m *StrStrMap) Pops(size int) map[string]string { + m.mu.Lock() + defer m.mu.Unlock() + if size > len(m.data) || size == -1 { + size = len(m.data) + } + if size == 0 { + return nil + } + index := 0 + newMap := make(map[string]string, size) + for k, v := range m.data { + delete(m.data, k) + newMap[k] = v + index++ + if index == size { + break + } + } + return newMap +} + // doSetWithLockCheck checks whether value of the key exists with mutex.Lock, // if not exists, set value to the map with given , // or else just return the existing value. @@ -300,10 +335,7 @@ func (m *StrStrMap) Size() int { // IsEmpty checks whether the map is empty. // It returns true if map is empty, or else false. func (m *StrStrMap) IsEmpty() bool { - m.mu.RLock() - empty := len(m.data) == 0 - m.mu.RUnlock() - return empty + return m.Size() == 0 } // Clear deletes all data of the map, it will remake a new underlying data map. diff --git a/container/gmap/gmap_list_map.go b/container/gmap/gmap_list_map.go index b3807792e..f4539fe42 100644 --- a/container/gmap/gmap_list_map.go +++ b/container/gmap/gmap_list_map.go @@ -189,6 +189,45 @@ func (m *ListMap) Get(key interface{}) (value interface{}) { return } +// Pop retrieves and deletes an item from the map. +func (m *ListMap) Pop() (key, value interface{}) { + m.mu.Lock() + defer m.mu.Unlock() + for k, e := range m.data { + value = e.Value.(*gListMapNode).value + delete(m.data, k) + m.list.Remove(e) + return k, value + } + return +} + +// Pops retrieves and deletes items from the map. +// It returns all items if size == -1. +func (m *ListMap) Pops(size int) map[interface{}]interface{} { + m.mu.Lock() + defer m.mu.Unlock() + if size > len(m.data) || size == -1 { + size = len(m.data) + } + if size == 0 { + return nil + } + index := 0 + newMap := make(map[interface{}]interface{}, size) + for k, e := range m.data { + value := e.Value.(*gListMapNode).value + delete(m.data, k) + m.list.Remove(e) + newMap[k] = value + index++ + if index == size { + break + } + } + return newMap +} + // doSetWithLockCheck checks whether value of the key exists with mutex.Lock, // if not exists, set value to the map with given , // or else just return the existing value. diff --git a/container/gmap/gmap_z_unit_any_any_test.go b/container/gmap/gmap_z_unit_any_any_test.go index 42305c4f5..39af9a5fb 100644 --- a/container/gmap/gmap_z_unit_any_any_test.go +++ b/container/gmap/gmap_z_unit_any_any_test.go @@ -8,6 +8,7 @@ package gmap_test import ( "encoding/json" + "github.com/gogf/gf/container/garray" "github.com/gogf/gf/frame/g" "github.com/gogf/gf/util/gconv" "testing" @@ -221,3 +222,56 @@ func Test_AnyAnyMap_Json(t *testing.T) { gtest.Assert(m.Get("k2"), data["k2"]) }) } + +func Test_AnyAnyMap_Pop(t *testing.T) { + gtest.Case(t, func() { + m := gmap.NewAnyAnyMapFrom(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + }) + gtest.Assert(m.Size(), 2) + + k1, v1 := m.Pop() + gtest.AssertIN(k1, g.Slice{"k1", "k2"}) + gtest.AssertIN(v1, g.Slice{"v1", "v2"}) + gtest.Assert(m.Size(), 1) + k2, v2 := m.Pop() + gtest.AssertIN(k2, g.Slice{"k1", "k2"}) + gtest.AssertIN(v2, g.Slice{"v1", "v2"}) + gtest.Assert(m.Size(), 0) + + gtest.AssertNE(k1, k2) + gtest.AssertNE(v1, v2) + }) +} + +func Test_AnyAnyMap_Pops(t *testing.T) { + gtest.Case(t, func() { + m := gmap.NewAnyAnyMapFrom(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + }) + gtest.Assert(m.Size(), 3) + + kArray := garray.New() + vArray := garray.New() + for k, v := range m.Pops(1) { + gtest.AssertIN(k, g.Slice{"k1", "k2", "k3"}) + gtest.AssertIN(v, g.Slice{"v1", "v2", "v3"}) + kArray.Append(k) + vArray.Append(v) + } + gtest.Assert(m.Size(), 2) + for k, v := range m.Pops(2) { + gtest.AssertIN(k, g.Slice{"k1", "k2", "k3"}) + gtest.AssertIN(v, g.Slice{"v1", "v2", "v3"}) + kArray.Append(k) + vArray.Append(v) + } + gtest.Assert(m.Size(), 0) + + gtest.Assert(kArray.Unique().Len(), 3) + gtest.Assert(vArray.Unique().Len(), 3) + }) +} diff --git a/container/gmap/gmap_z_unit_int_any_test.go b/container/gmap/gmap_z_unit_int_any_test.go index a9c2df3e6..4f06189f9 100644 --- a/container/gmap/gmap_z_unit_int_any_test.go +++ b/container/gmap/gmap_z_unit_int_any_test.go @@ -8,6 +8,7 @@ package gmap_test import ( "encoding/json" + "github.com/gogf/gf/container/garray" "github.com/gogf/gf/frame/g" "testing" @@ -203,3 +204,56 @@ func Test_IntAnyMap_Json(t *testing.T) { gtest.Assert(m.Get(2), data[2]) }) } + +func Test_IntAnyMap_Pop(t *testing.T) { + gtest.Case(t, func() { + m := gmap.NewIntAnyMapFrom(g.MapIntAny{ + 1: "v1", + 2: "v2", + }) + gtest.Assert(m.Size(), 2) + + k1, v1 := m.Pop() + gtest.AssertIN(k1, g.Slice{1, 2}) + gtest.AssertIN(v1, g.Slice{"v1", "v2"}) + gtest.Assert(m.Size(), 1) + k2, v2 := m.Pop() + gtest.AssertIN(k2, g.Slice{1, 2}) + gtest.AssertIN(v2, g.Slice{"v1", "v2"}) + gtest.Assert(m.Size(), 0) + + gtest.AssertNE(k1, k2) + gtest.AssertNE(v1, v2) + }) +} + +func Test_IntAnyMap_Pops(t *testing.T) { + gtest.Case(t, func() { + m := gmap.NewIntAnyMapFrom(g.MapIntAny{ + 1: "v1", + 2: "v2", + 3: "v3", + }) + gtest.Assert(m.Size(), 3) + + kArray := garray.New() + vArray := garray.New() + for k, v := range m.Pops(1) { + gtest.AssertIN(k, g.Slice{1, 2, 3}) + gtest.AssertIN(v, g.Slice{"v1", "v2", "v3"}) + kArray.Append(k) + vArray.Append(v) + } + gtest.Assert(m.Size(), 2) + for k, v := range m.Pops(2) { + gtest.AssertIN(k, g.Slice{1, 2, 3}) + gtest.AssertIN(v, g.Slice{"v1", "v2", "v3"}) + kArray.Append(k) + vArray.Append(v) + } + gtest.Assert(m.Size(), 0) + + gtest.Assert(kArray.Unique().Len(), 3) + gtest.Assert(vArray.Unique().Len(), 3) + }) +} diff --git a/container/gmap/gmap_z_unit_int_int_test.go b/container/gmap/gmap_z_unit_int_int_test.go index 4e376ecff..070a3b628 100644 --- a/container/gmap/gmap_z_unit_int_int_test.go +++ b/container/gmap/gmap_z_unit_int_int_test.go @@ -8,6 +8,7 @@ package gmap_test import ( "encoding/json" + "github.com/gogf/gf/container/garray" "github.com/gogf/gf/frame/g" "testing" @@ -206,3 +207,56 @@ func Test_IntIntMap_Json(t *testing.T) { gtest.Assert(m.Get(2), data[2]) }) } + +func Test_IntIntMap_Pop(t *testing.T) { + gtest.Case(t, func() { + m := gmap.NewIntIntMapFrom(g.MapIntInt{ + 1: 11, + 2: 22, + }) + gtest.Assert(m.Size(), 2) + + k1, v1 := m.Pop() + gtest.AssertIN(k1, g.Slice{1, 2}) + gtest.AssertIN(v1, g.Slice{11, 22}) + gtest.Assert(m.Size(), 1) + k2, v2 := m.Pop() + gtest.AssertIN(k2, g.Slice{1, 2}) + gtest.AssertIN(v2, g.Slice{11, 22}) + gtest.Assert(m.Size(), 0) + + gtest.AssertNE(k1, k2) + gtest.AssertNE(v1, v2) + }) +} + +func Test_IntIntMap_Pops(t *testing.T) { + gtest.Case(t, func() { + m := gmap.NewIntIntMapFrom(g.MapIntInt{ + 1: 11, + 2: 22, + 3: 33, + }) + gtest.Assert(m.Size(), 3) + + kArray := garray.New() + vArray := garray.New() + for k, v := range m.Pops(1) { + gtest.AssertIN(k, g.Slice{1, 2, 3}) + gtest.AssertIN(v, g.Slice{11, 22, 33}) + kArray.Append(k) + vArray.Append(v) + } + gtest.Assert(m.Size(), 2) + for k, v := range m.Pops(2) { + gtest.AssertIN(k, g.Slice{1, 2, 3}) + gtest.AssertIN(v, g.Slice{11, 22, 33}) + kArray.Append(k) + vArray.Append(v) + } + gtest.Assert(m.Size(), 0) + + gtest.Assert(kArray.Unique().Len(), 3) + gtest.Assert(vArray.Unique().Len(), 3) + }) +} diff --git a/container/gmap/gmap_z_unit_int_str_test.go b/container/gmap/gmap_z_unit_int_str_test.go index b348ccbfe..541946b1a 100644 --- a/container/gmap/gmap_z_unit_int_str_test.go +++ b/container/gmap/gmap_z_unit_int_str_test.go @@ -8,6 +8,7 @@ package gmap_test import ( "encoding/json" + "github.com/gogf/gf/container/garray" "github.com/gogf/gf/frame/g" "testing" @@ -207,3 +208,56 @@ func Test_IntStrMap_Json(t *testing.T) { gtest.Assert(m.Get(2), data[2]) }) } + +func Test_IntStrMap_Pop(t *testing.T) { + gtest.Case(t, func() { + m := gmap.NewIntStrMapFrom(g.MapIntStr{ + 1: "v1", + 2: "v2", + }) + gtest.Assert(m.Size(), 2) + + k1, v1 := m.Pop() + gtest.AssertIN(k1, g.Slice{1, 2}) + gtest.AssertIN(v1, g.Slice{"v1", "v2"}) + gtest.Assert(m.Size(), 1) + k2, v2 := m.Pop() + gtest.AssertIN(k2, g.Slice{1, 2}) + gtest.AssertIN(v2, g.Slice{"v1", "v2"}) + gtest.Assert(m.Size(), 0) + + gtest.AssertNE(k1, k2) + gtest.AssertNE(v1, v2) + }) +} + +func Test_IntStrMap_Pops(t *testing.T) { + gtest.Case(t, func() { + m := gmap.NewIntStrMapFrom(g.MapIntStr{ + 1: "v1", + 2: "v2", + 3: "v3", + }) + gtest.Assert(m.Size(), 3) + + kArray := garray.New() + vArray := garray.New() + for k, v := range m.Pops(1) { + gtest.AssertIN(k, g.Slice{1, 2, 3}) + gtest.AssertIN(v, g.Slice{"v1", "v2", "v3"}) + kArray.Append(k) + vArray.Append(v) + } + gtest.Assert(m.Size(), 2) + for k, v := range m.Pops(2) { + gtest.AssertIN(k, g.Slice{1, 2, 3}) + gtest.AssertIN(v, g.Slice{"v1", "v2", "v3"}) + kArray.Append(k) + vArray.Append(v) + } + gtest.Assert(m.Size(), 0) + + gtest.Assert(kArray.Unique().Len(), 3) + gtest.Assert(vArray.Unique().Len(), 3) + }) +} diff --git a/container/gmap/gmap_z_unit_list_map_test.go b/container/gmap/gmap_z_unit_list_map_test.go index a8f75838c..4b1c53b36 100644 --- a/container/gmap/gmap_z_unit_list_map_test.go +++ b/container/gmap/gmap_z_unit_list_map_test.go @@ -8,6 +8,7 @@ package gmap_test import ( "encoding/json" + "github.com/gogf/gf/container/garray" "github.com/gogf/gf/util/gconv" "testing" @@ -177,3 +178,56 @@ func Test_ListMap_Json(t *testing.T) { gtest.Assert(m.Get("k2"), data["k2"]) }) } + +func Test_ListMap_Pop(t *testing.T) { + gtest.Case(t, func() { + m := gmap.NewListMapFrom(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + }) + gtest.Assert(m.Size(), 2) + + k1, v1 := m.Pop() + gtest.AssertIN(k1, g.Slice{"k1", "k2"}) + gtest.AssertIN(v1, g.Slice{"v1", "v2"}) + gtest.Assert(m.Size(), 1) + k2, v2 := m.Pop() + gtest.AssertIN(k2, g.Slice{"k1", "k2"}) + gtest.AssertIN(v2, g.Slice{"v1", "v2"}) + gtest.Assert(m.Size(), 0) + + gtest.AssertNE(k1, k2) + gtest.AssertNE(v1, v2) + }) +} + +func Test_ListMap_Pops(t *testing.T) { + gtest.Case(t, func() { + m := gmap.NewListMapFrom(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + }) + gtest.Assert(m.Size(), 3) + + kArray := garray.New() + vArray := garray.New() + for k, v := range m.Pops(1) { + gtest.AssertIN(k, g.Slice{"k1", "k2", "k3"}) + gtest.AssertIN(v, g.Slice{"v1", "v2", "v3"}) + kArray.Append(k) + vArray.Append(v) + } + gtest.Assert(m.Size(), 2) + for k, v := range m.Pops(2) { + gtest.AssertIN(k, g.Slice{"k1", "k2", "k3"}) + gtest.AssertIN(v, g.Slice{"v1", "v2", "v3"}) + kArray.Append(k) + vArray.Append(v) + } + gtest.Assert(m.Size(), 0) + + gtest.Assert(kArray.Unique().Len(), 3) + gtest.Assert(vArray.Unique().Len(), 3) + }) +} diff --git a/container/gmap/gmap_z_unit_str_any_test.go b/container/gmap/gmap_z_unit_str_any_test.go index 831e07108..e5a05ec95 100644 --- a/container/gmap/gmap_z_unit_str_any_test.go +++ b/container/gmap/gmap_z_unit_str_any_test.go @@ -8,6 +8,7 @@ package gmap_test import ( "encoding/json" + "github.com/gogf/gf/container/garray" "github.com/gogf/gf/frame/g" "testing" @@ -215,3 +216,56 @@ func Test_StrAnyMap_Json(t *testing.T) { gtest.Assert(m.Get("k2"), data["k2"]) }) } + +func Test_StrAnyMap_Pop(t *testing.T) { + gtest.Case(t, func() { + m := gmap.NewStrAnyMapFrom(g.MapStrAny{ + "k1": "v1", + "k2": "v2", + }) + gtest.Assert(m.Size(), 2) + + k1, v1 := m.Pop() + gtest.AssertIN(k1, g.Slice{"k1", "k2"}) + gtest.AssertIN(v1, g.Slice{"v1", "v2"}) + gtest.Assert(m.Size(), 1) + k2, v2 := m.Pop() + gtest.AssertIN(k2, g.Slice{"k1", "k2"}) + gtest.AssertIN(v2, g.Slice{"v1", "v2"}) + gtest.Assert(m.Size(), 0) + + gtest.AssertNE(k1, k2) + gtest.AssertNE(v1, v2) + }) +} + +func Test_StrAnyMap_Pops(t *testing.T) { + gtest.Case(t, func() { + m := gmap.NewStrAnyMapFrom(g.MapStrAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + }) + gtest.Assert(m.Size(), 3) + + kArray := garray.New() + vArray := garray.New() + for k, v := range m.Pops(1) { + gtest.AssertIN(k, g.Slice{"k1", "k2", "k3"}) + gtest.AssertIN(v, g.Slice{"v1", "v2", "v3"}) + kArray.Append(k) + vArray.Append(v) + } + gtest.Assert(m.Size(), 2) + for k, v := range m.Pops(2) { + gtest.AssertIN(k, g.Slice{"k1", "k2", "k3"}) + gtest.AssertIN(v, g.Slice{"v1", "v2", "v3"}) + kArray.Append(k) + vArray.Append(v) + } + gtest.Assert(m.Size(), 0) + + gtest.Assert(kArray.Unique().Len(), 3) + gtest.Assert(vArray.Unique().Len(), 3) + }) +} diff --git a/container/gmap/gmap_z_unit_str_int_test.go b/container/gmap/gmap_z_unit_str_int_test.go index 2cd1ae1dd..ba20d8103 100644 --- a/container/gmap/gmap_z_unit_str_int_test.go +++ b/container/gmap/gmap_z_unit_str_int_test.go @@ -8,6 +8,7 @@ package gmap_test import ( "encoding/json" + "github.com/gogf/gf/container/garray" "github.com/gogf/gf/frame/g" "testing" @@ -218,3 +219,56 @@ func Test_StrIntMap_Json(t *testing.T) { gtest.Assert(m.Get("k2"), data["k2"]) }) } + +func Test_StrIntMap_Pop(t *testing.T) { + gtest.Case(t, func() { + m := gmap.NewStrIntMapFrom(g.MapStrInt{ + "k1": 11, + "k2": 22, + }) + gtest.Assert(m.Size(), 2) + + k1, v1 := m.Pop() + gtest.AssertIN(k1, g.Slice{"k1", "k2"}) + gtest.AssertIN(v1, g.Slice{11, 22}) + gtest.Assert(m.Size(), 1) + k2, v2 := m.Pop() + gtest.AssertIN(k2, g.Slice{"k1", "k2"}) + gtest.AssertIN(v2, g.Slice{11, 22}) + gtest.Assert(m.Size(), 0) + + gtest.AssertNE(k1, k2) + gtest.AssertNE(v1, v2) + }) +} + +func Test_StrIntMap_Pops(t *testing.T) { + gtest.Case(t, func() { + m := gmap.NewStrIntMapFrom(g.MapStrInt{ + "k1": 11, + "k2": 22, + "k3": 33, + }) + gtest.Assert(m.Size(), 3) + + kArray := garray.New() + vArray := garray.New() + for k, v := range m.Pops(1) { + gtest.AssertIN(k, g.Slice{"k1", "k2", "k3"}) + gtest.AssertIN(v, g.Slice{11, 22, 33}) + kArray.Append(k) + vArray.Append(v) + } + gtest.Assert(m.Size(), 2) + for k, v := range m.Pops(2) { + gtest.AssertIN(k, g.Slice{"k1", "k2", "k3"}) + gtest.AssertIN(v, g.Slice{11, 22, 33}) + kArray.Append(k) + vArray.Append(v) + } + gtest.Assert(m.Size(), 0) + + gtest.Assert(kArray.Unique().Len(), 3) + gtest.Assert(vArray.Unique().Len(), 3) + }) +} diff --git a/container/gmap/gmap_z_unit_str_str_test.go b/container/gmap/gmap_z_unit_str_str_test.go index 3ff191d7d..1105d5475 100644 --- a/container/gmap/gmap_z_unit_str_str_test.go +++ b/container/gmap/gmap_z_unit_str_str_test.go @@ -8,6 +8,7 @@ package gmap_test import ( "encoding/json" + "github.com/gogf/gf/container/garray" "github.com/gogf/gf/frame/g" "testing" @@ -215,3 +216,56 @@ func Test_StrStrMap_Json(t *testing.T) { gtest.Assert(m.Get("k2"), data["k2"]) }) } + +func Test_StrStrMap_Pop(t *testing.T) { + gtest.Case(t, func() { + m := gmap.NewStrStrMapFrom(g.MapStrStr{ + "k1": "v1", + "k2": "v2", + }) + gtest.Assert(m.Size(), 2) + + k1, v1 := m.Pop() + gtest.AssertIN(k1, g.Slice{"k1", "k2"}) + gtest.AssertIN(v1, g.Slice{"v1", "v2"}) + gtest.Assert(m.Size(), 1) + k2, v2 := m.Pop() + gtest.AssertIN(k2, g.Slice{"k1", "k2"}) + gtest.AssertIN(v2, g.Slice{"v1", "v2"}) + gtest.Assert(m.Size(), 0) + + gtest.AssertNE(k1, k2) + gtest.AssertNE(v1, v2) + }) +} + +func Test_StrStrMap_Pops(t *testing.T) { + gtest.Case(t, func() { + m := gmap.NewStrStrMapFrom(g.MapStrStr{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + }) + gtest.Assert(m.Size(), 3) + + kArray := garray.New() + vArray := garray.New() + for k, v := range m.Pops(1) { + gtest.AssertIN(k, g.Slice{"k1", "k2", "k3"}) + gtest.AssertIN(v, g.Slice{"v1", "v2", "v3"}) + kArray.Append(k) + vArray.Append(v) + } + gtest.Assert(m.Size(), 2) + for k, v := range m.Pops(2) { + gtest.AssertIN(k, g.Slice{"k1", "k2", "k3"}) + gtest.AssertIN(v, g.Slice{"v1", "v2", "v3"}) + kArray.Append(k) + vArray.Append(v) + } + gtest.Assert(m.Size(), 0) + + gtest.Assert(kArray.Unique().Len(), 3) + gtest.Assert(vArray.Unique().Len(), 3) + }) +} diff --git a/container/gset/gset_any_set.go b/container/gset/gset_any_set.go index 9346d6bc6..79c7dbc92 100644 --- a/container/gset/gset_any_set.go +++ b/container/gset/gset_any_set.go @@ -373,24 +373,30 @@ func (set *Set) Sum() (sum int) { // Pops randomly pops an item from set. func (set *Set) Pop() interface{} { - set.mu.RLock() - defer set.mu.RUnlock() + set.mu.Lock() + defer set.mu.Unlock() for k, _ := range set.data { + delete(set.data, k) return k } return nil } // Pops randomly pops items from set. +// It returns all items if size == -1. func (set *Set) Pops(size int) []interface{} { - set.mu.RLock() - defer set.mu.RUnlock() - if size > len(set.data) { + set.mu.Lock() + defer set.mu.Unlock() + if size > len(set.data) || size == -1 { size = len(set.data) } + if size <= 0 { + return nil + } index := 0 array := make([]interface{}, size) for k, _ := range set.data { + delete(set.data, k) array[index] = k index++ if index == size { diff --git a/container/gset/gset_int_set.go b/container/gset/gset_int_set.go index c831e1caf..ab8bb8856 100644 --- a/container/gset/gset_int_set.go +++ b/container/gset/gset_int_set.go @@ -346,24 +346,30 @@ func (set *IntSet) Sum() (sum int) { // Pops randomly pops an item from set. func (set *IntSet) Pop() int { - set.mu.RLock() - defer set.mu.RUnlock() + set.mu.Lock() + defer set.mu.Unlock() for k, _ := range set.data { + delete(set.data, k) return k } return 0 } // Pops randomly pops items from set. +// It returns all items if size == -1. func (set *IntSet) Pops(size int) []int { - set.mu.RLock() - defer set.mu.RUnlock() - if size > len(set.data) { + set.mu.Lock() + defer set.mu.Unlock() + if size > len(set.data) || size == -1 { size = len(set.data) } + if size <= 0 { + return nil + } index := 0 array := make([]int, size) for k, _ := range set.data { + delete(set.data, k) array[index] = k index++ if index == size { diff --git a/container/gset/gset_str_set.go b/container/gset/gset_str_set.go index caec5641b..887a000ae 100644 --- a/container/gset/gset_str_set.go +++ b/container/gset/gset_str_set.go @@ -360,24 +360,30 @@ func (set *StrSet) Sum() (sum int) { // Pops randomly pops an item from set. func (set *StrSet) Pop() string { - set.mu.RLock() - defer set.mu.RUnlock() + set.mu.Lock() + defer set.mu.Unlock() for k, _ := range set.data { + delete(set.data, k) return k } return "" } // Pops randomly pops items from set. +// It returns all items if size == -1. func (set *StrSet) Pops(size int) []string { - set.mu.RLock() - defer set.mu.RUnlock() - if size > len(set.data) { + set.mu.Lock() + defer set.mu.Unlock() + if size > len(set.data) || size == -1 { size = len(set.data) } + if size <= 0 { + return nil + } index := 0 array := make([]string, size) for k, _ := range set.data { + delete(set.data, k) array[index] = k index++ if index == size { diff --git a/container/gset/gset_z_unit_any_test.go b/container/gset/gset_z_unit_any_test.go index 18dd7b246..c53741dff 100644 --- a/container/gset/gset_z_unit_any_test.go +++ b/container/gset/gset_z_unit_any_test.go @@ -261,19 +261,35 @@ func TestSet_Sum(t *testing.T) { func TestSet_Pop(t *testing.T) { gtest.Case(t, func() { - s1 := gset.New(true) - s1.Add(1).Add(2).Add(3).Add(4) - gtest.AssertIN(s1.Pop(), []int{1, 2, 3, 4}) + s := gset.New(true) + s.Add(1).Add(2).Add(3).Add(4) + gtest.Assert(s.Size(), 4) + gtest.AssertIN(s.Pop(), []int{1, 2, 3, 4}) + gtest.Assert(s.Size(), 3) }) } func TestSet_Pops(t *testing.T) { gtest.Case(t, func() { - s1 := gset.New(true) - s1.Add(1).Add(2).Add(3).Add(4) - gtest.AssertIN(s1.Pops(1), []int{1, 2, 3, 4}) - gtest.AssertIN(s1.Pops(6), []int{1, 2, 3, 4}) - gtest.Assert(len(s1.Pops(2)), 2) + s := gset.New(true) + s.Add(1).Add(2).Add(3).Add(4) + gtest.Assert(s.Size(), 4) + gtest.Assert(s.Pops(0), nil) + gtest.AssertIN(s.Pops(1), []int{1, 2, 3, 4}) + gtest.Assert(s.Size(), 3) + a := s.Pops(6) + gtest.Assert(len(a), 3) + gtest.AssertIN(a, []int{1, 2, 3, 4}) + gtest.Assert(s.Size(), 0) + }) + + gtest.Case(t, func() { + s := gset.New(true) + a := []interface{}{1, 2, 3, 4} + s.Add(a...) + gtest.Assert(s.Size(), 4) + gtest.Assert(s.Pops(-2), nil) + gtest.AssertIN(s.Pops(-1), a) }) } diff --git a/container/gset/gset_z_unit_int_test.go b/container/gset/gset_z_unit_int_test.go index e15ae5197..70886c277 100644 --- a/container/gset/gset_z_unit_int_test.go +++ b/container/gset/gset_z_unit_int_test.go @@ -223,11 +223,36 @@ func TestIntSet_Sum(t *testing.T) { func TestIntSet_Pop(t *testing.T) { gtest.Case(t, func() { - s1 := gset.NewIntSet() - s1.Add(4).Add(2).Add(3) - gtest.AssertIN(s1.Pop(), []int{4, 2, 3}) - gtest.AssertIN(s1.Pop(), []int{4, 2, 3}) - gtest.Assert(s1.Size(), 3) + s := gset.NewIntSet() + s.Add(4).Add(2).Add(3) + gtest.Assert(s.Size(), 3) + gtest.AssertIN(s.Pop(), []int{4, 2, 3}) + gtest.AssertIN(s.Pop(), []int{4, 2, 3}) + gtest.Assert(s.Size(), 1) + }) +} + +func TestIntSet_Pops(t *testing.T) { + gtest.Case(t, func() { + s := gset.NewIntSet() + s.Add(1).Add(4).Add(2).Add(3) + gtest.Assert(s.Size(), 4) + gtest.Assert(s.Pops(0), nil) + gtest.AssertIN(s.Pops(1), []int{1, 4, 2, 3}) + gtest.Assert(s.Size(), 3) + a := s.Pops(2) + gtest.Assert(len(a), 2) + gtest.AssertIN(a, []int{1, 4, 2, 3}) + gtest.Assert(s.Size(), 1) + }) + + gtest.Case(t, func() { + s := gset.NewIntSet(true) + a := []int{1, 2, 3, 4} + s.Add(a...) + gtest.Assert(s.Size(), 4) + gtest.Assert(s.Pops(-2), nil) + gtest.AssertIN(s.Pops(-1), a) }) } diff --git a/container/gset/gset_z_unit_str_test.go b/container/gset/gset_z_unit_str_test.go index 45c271991..d1f95c401 100644 --- a/container/gset/gset_z_unit_str_test.go +++ b/container/gset/gset_z_unit_str_test.go @@ -259,20 +259,36 @@ func TestStrSet_Remove(t *testing.T) { func TestStrSet_Pop(t *testing.T) { gtest.Case(t, func() { - s1 := gset.NewStrSetFrom([]string{"a", "b", "c"}, true) - str1 := s1.Pop() - gtest.Assert(strings.Contains("a,b,c", str1), true) + a := []string{"a", "b", "c", "d"} + s := gset.NewStrSetFrom(a, true) + gtest.Assert(s.Size(), 4) + gtest.AssertIN(s.Pop(), a) + gtest.Assert(s.Size(), 3) + gtest.AssertIN(s.Pop(), a) + gtest.Assert(s.Size(), 2) }) } func TestStrSet_Pops(t *testing.T) { gtest.Case(t, func() { - s1 := gset.NewStrSetFrom([]string{"a", "b", "c"}, true) - strs1 := s1.Pops(2) - gtest.AssertIN(strs1, []string{"a", "b", "c"}) - gtest.Assert(len(strs1), 2) - str2 := s1.Pops(7) - gtest.AssertIN(str2, []string{"a", "b", "c"}) + a := []string{"a", "b", "c", "d"} + s := gset.NewStrSetFrom(a, true) + array := s.Pops(2) + gtest.Assert(len(array), 2) + gtest.Assert(s.Size(), 2) + gtest.AssertIN(array, a) + gtest.Assert(s.Pops(0), nil) + gtest.AssertIN(s.Pops(2), a) + gtest.Assert(s.Size(), 0) + }) + + gtest.Case(t, func() { + s := gset.NewStrSet(true) + a := []string{"1", "2", "3", "4"} + s.Add(a...) + gtest.Assert(s.Size(), 4) + gtest.Assert(s.Pops(-2), nil) + gtest.AssertIN(s.Pops(-1), a) }) } diff --git a/os/gsession/gsession_storage.go b/os/gsession/gsession_storage.go index 8a1b10d84..6101a0ba1 100644 --- a/os/gsession/gsession_storage.go +++ b/os/gsession/gsession_storage.go @@ -28,7 +28,7 @@ type Storage interface { RemoveAll() error // GetSession returns the session data map for given session id. - // The parameter specifies the TTL for this session. + // The parameter specifies the TTL for this session. // It returns nil if the TTL is exceeded. GetSession(id string, ttl time.Duration) map[string]interface{} // SetSession updates the data map for specified session id. diff --git a/os/gsession/gsession_storage_file.go b/os/gsession/gsession_storage_file.go index 00815fd3c..210e1b78a 100644 --- a/os/gsession/gsession_storage_file.go +++ b/os/gsession/gsession_storage_file.go @@ -72,8 +72,13 @@ func NewStorageFile(path ...string) *StorageFile { cryptoEnabled: DefaultStorageFileCryptoEnabled, updatingIdSet: gset.NewStrSet(true), } + // Batch updates the TTL for session ids timely. gtimer.AddSingleton(DefaultStorageFileLoopInterval, func() { - for _, id := range s.updatingIdSet.Slice() { + id := "" + for { + if id = s.updatingIdSet.Pop(); id == "" { + break + } s.doUpdateTTL(id) } }) @@ -160,7 +165,6 @@ func (s *StorageFile) GetSession(id string, ttl time.Duration) map[string]interf } // SetSession updates the content for session id. -// Note that the parameter is the serialized bytes for session map. func (s *StorageFile) SetSession(id string, data map[string]interface{}) error { path := s.sessionFilePath(id) content, err := json.Marshal(data)