From 1e3a081158394489c0ddbb732e20f9d9b3553c04 Mon Sep 17 00:00:00 2001 From: Hunk <54zhua@gmail.com> Date: Fri, 21 Nov 2025 16:56:35 +0800 Subject: [PATCH 1/8] add generic list map feature --- container/gmap/gmap_list_k_v_map.go | 629 ++++++++++++++++++++++++++++ container/gmap/gmap_list_map.go | 456 ++++---------------- 2 files changed, 717 insertions(+), 368 deletions(-) create mode 100644 container/gmap/gmap_list_k_v_map.go diff --git a/container/gmap/gmap_list_k_v_map.go b/container/gmap/gmap_list_k_v_map.go new file mode 100644 index 00000000000..7153788d78e --- /dev/null +++ b/container/gmap/gmap_list_k_v_map.go @@ -0,0 +1,629 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gmap + +import ( + "bytes" + "fmt" + + "github.com/gogf/gf/v2/container/glist" + "github.com/gogf/gf/v2/container/gvar" + "github.com/gogf/gf/v2/internal/deepcopy" + "github.com/gogf/gf/v2/internal/empty" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/internal/rwmutex" + "github.com/gogf/gf/v2/util/gconv" +) + +// ListKVMap is a map that preserves insertion-order. +// +// It is backed by a hash table to store values and doubly-linked list to store ordering. +// +// Structure is not thread safe. +// +// Reference: http://en.wikipedia.org/wiki/Associative_array +type ListKVMap[K comparable, V any] struct { + mu rwmutex.RWMutex + data map[K]*glist.TElement[*gListKVMapNode[K, V]] + list *glist.TList[*gListKVMapNode[K, V]] +} + +type gListKVMapNode[K comparable, V any] struct { + key K + value V +} + +// NewListKVMap returns an empty link map. +// ListKVMap is backed by a hash table to store values and doubly-linked list to store ordering. +// The parameter `safe` is used to specify whether using map in concurrent-safety, +// which is false in default. +func NewListKVMap[K comparable, V any](safe ...bool) *ListKVMap[K, V] { + return &ListKVMap[K, V]{ + mu: rwmutex.Create(safe...), + data: make(map[K]*glist.TElement[*gListKVMapNode[K, V]]), + list: glist.NewT[*gListKVMapNode[K, V]](), + } +} + +// NewListKVMapFrom returns a link map from given map `data`. +// Note that, the param `data` map will be set as the underlying data map(no deep copy), +// there might be some concurrent-safe issues when changing the map outside. +func NewListKVMapFrom[K comparable, V any](data map[K]V, safe ...bool) *ListKVMap[K, V] { + m := NewListKVMap[K, V](safe...) + m.Sets(data) + return m +} + +// Iterator is alias of IteratorAsc. +func (m *ListKVMap[K, V]) Iterator(f func(key K, value V) bool) { + m.IteratorAsc(f) +} + +// IteratorAsc iterates the map readonly in ascending order with given callback function `f`. +// If `f` returns true, then it continues iterating; or false to stop. +func (m *ListKVMap[K, V]) IteratorAsc(f func(key K, value V) bool) { + m.mu.RLock() + defer m.mu.RUnlock() + if m.list != nil { + m.list.IteratorAsc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool { + return f(e.Value.key, e.Value.value) + }) + } +} + +// IteratorDesc iterates the map readonly in descending order with given callback function `f`. +// If `f` returns true, then it continues iterating; or false to stop. +func (m *ListKVMap[K, V]) IteratorDesc(f func(key K, value V) bool) { + m.mu.RLock() + defer m.mu.RUnlock() + if m.list != nil { + m.list.IteratorDesc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool { + return f(e.Value.key, e.Value.value) + }) + } +} + +// Clone returns a new link map with copy of current map data. +func (m *ListKVMap[K, V]) Clone(safe ...bool) *ListKVMap[K, V] { + return NewListKVMapFrom(m.Map(), safe...) +} + +// Clear deletes all data of the map, it will remake a new underlying data map. +func (m *ListKVMap[K, V]) Clear() { + m.mu.Lock() + m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]]) + m.list = glist.NewT[*gListKVMapNode[K, V]]() + m.mu.Unlock() +} + +// Replace the data of the map with given `data`. +func (m *ListKVMap[K, V]) Replace(data map[K]V) { + m.mu.Lock() + m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]]) + m.list = glist.NewT[*gListKVMapNode[K, V]]() + for key, value := range data { + if e, ok := m.data[key]; !ok { + m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value}) + } else { + e.Value = &gListKVMapNode[K, V]{key, value} + } + } + m.mu.Unlock() +} + +// Map returns a copy of the underlying data of the map. +func (m *ListKVMap[K, V]) Map() map[K]V { + m.mu.RLock() + var data map[K]V + if m.list != nil { + data = make(map[K]V, len(m.data)) + m.list.IteratorAsc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool { + data[e.Value.key] = e.Value.value + return true + }) + } + m.mu.RUnlock() + return data +} + +// MapStrAny returns a copy of the underlying data of the map as map[string]any. +func (m *ListKVMap[K, V]) MapStrAny() map[string]any { + m.mu.RLock() + var data map[string]any + if m.list != nil { + data = make(map[string]any, len(m.data)) + m.list.IteratorAsc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool { + data[gconv.String(e.Value.key)] = e.Value.value + return true + }) + } + m.mu.RUnlock() + return data +} + +// FilterEmpty deletes all key-value pair of which the value is empty. +func (m *ListKVMap[K, V]) FilterEmpty() { + m.mu.Lock() + if m.list != nil { + var keys = make([]K, 0, m.list.Size()) + m.list.IteratorAsc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool { + if empty.IsEmpty(e.Value.value) { + keys = append(keys, e.Value.key) + } + return true + }) + + if len(keys) > 0 { + for _, key := range keys { + if e, ok := m.data[key]; ok { + delete(m.data, key) + m.list.Remove(e) + } + } + } + } + m.mu.Unlock() +} + +// Set sets key-value to the map. +func (m *ListKVMap[K, V]) Set(key K, value V) { + m.mu.Lock() + if m.data == nil { + m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]]) + m.list = glist.NewT[*gListKVMapNode[K, V]]() + } + if e, ok := m.data[key]; !ok { + m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value}) + } else { + e.Value = &gListKVMapNode[K, V]{key, value} + } + m.mu.Unlock() +} + +// Sets batch sets key-values to the map. +func (m *ListKVMap[K, V]) Sets(data map[K]V) { + m.mu.Lock() + if m.data == nil { + m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]]) + m.list = glist.NewT[*gListKVMapNode[K, V]]() + } + for key, value := range data { + if e, ok := m.data[key]; !ok { + m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value}) + } else { + e.Value = &gListKVMapNode[K, V]{key, value} + } + } + m.mu.Unlock() +} + +// Search searches the map with given `key`. +// Second return parameter `found` is true if key was found, otherwise false. +func (m *ListKVMap[K, V]) Search(key K) (value V, found bool) { + m.mu.RLock() + if m.data != nil { + if e, ok := m.data[key]; ok { + value = e.Value.value + found = ok + } + } + m.mu.RUnlock() + return +} + +// Get returns the value by given `key`. +func (m *ListKVMap[K, V]) Get(key K) (value V) { + m.mu.RLock() + if m.data != nil { + if e, ok := m.data[key]; ok { + value = e.Value.value + } + } + m.mu.RUnlock() + return +} + +// Pop retrieves and deletes an item from the map. +func (m *ListKVMap[K, V]) Pop() (key K, value V) { + m.mu.Lock() + defer m.mu.Unlock() + for k, e := range m.data { + value = e.Value.value + delete(m.data, k) + m.list.Remove(e) + return k, value + } + return +} + +// Pops retrieves and deletes `size` items from the map. +// It returns all items if size == -1. +func (m *ListKVMap[K, V]) Pops(size int) map[K]V { + 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[K]V, size) + for k, e := range m.data { + value := e.Value.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 `key`, +// or else just return the existing value. +// +// When setting value, if `value` is type of `func() interface {}`, +// it will be executed with mutex.Lock of the map, +// and its return value will be set to the map with `key`. +// +// It returns value with given `key`. +func (m *ListKVMap[K, V]) doSetWithLockCheck(key K, value V) V { + m.mu.Lock() + defer m.mu.Unlock() + + return m.doSetWithLockCheckWithoutLock(key, value) +} + +func (m *ListKVMap[K, V]) doSetWithLockCheckWithoutLock(key K, value V) V { + if m.data == nil { + m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]]) + m.list = glist.NewT[*gListKVMapNode[K, V]]() + } + if e, ok := m.data[key]; ok { + return e.Value.value + } + if any(value) != nil { + m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value}) + } + return value +} + +// GetOrSet returns the value by key, +// or sets value with given `value` if it does not exist and then returns this value. +func (m *ListKVMap[K, V]) GetOrSet(key K, value V) V { + if v, ok := m.Search(key); !ok { + return m.doSetWithLockCheck(key, value) + } else { + return v + } +} + +// GetOrSetFunc returns the value by key, +// or sets value with returned value of callback function `f` if it does not exist +// and then returns this value. +func (m *ListKVMap[K, V]) GetOrSetFunc(key K, f func() V) V { + if v, ok := m.Search(key); !ok { + return m.doSetWithLockCheck(key, f()) + } else { + return v + } +} + +// GetOrSetFuncLock returns the value by key, +// or sets value with returned value of callback function `f` if it does not exist +// and then returns this value. +// +// GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f` +// with mutex.Lock of the map. +func (m *ListKVMap[K, V]) GetOrSetFuncLock(key K, f func() V) V { + if v, ok := m.Search(key); !ok { + m.mu.Lock() + defer m.mu.Unlock() + + return m.doSetWithLockCheckWithoutLock(key, f()) + } else { + return v + } +} + +// GetVar returns a Var with the value by given `key`. +// The returned Var is un-concurrent safe. +func (m *ListKVMap[K, V]) GetVar(key K) *gvar.Var { + return gvar.New(m.Get(key)) +} + +// GetVarOrSet returns a Var with result from GetVarOrSet. +// The returned Var is un-concurrent safe. +func (m *ListKVMap[K, V]) GetVarOrSet(key K, value V) *gvar.Var { + return gvar.New(m.GetOrSet(key, value)) +} + +// GetVarOrSetFunc returns a Var with result from GetOrSetFunc. +// The returned Var is un-concurrent safe. +func (m *ListKVMap[K, V]) GetVarOrSetFunc(key K, f func() V) *gvar.Var { + return gvar.New(m.GetOrSetFunc(key, f)) +} + +// GetVarOrSetFuncLock returns a Var with result from GetOrSetFuncLock. +// The returned Var is un-concurrent safe. +func (m *ListKVMap[K, V]) GetVarOrSetFuncLock(key K, f func() V) *gvar.Var { + return gvar.New(m.GetOrSetFuncLock(key, f)) +} + +// SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true. +// It returns false if `key` exists, and `value` would be ignored. +func (m *ListKVMap[K, V]) SetIfNotExist(key K, value V) bool { + if !m.Contains(key) { + m.doSetWithLockCheck(key, value) + return true + } + return false +} + +// SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true. +// It returns false if `key` exists, and `value` would be ignored. +func (m *ListKVMap[K, V]) SetIfNotExistFunc(key K, f func() V) bool { + if !m.Contains(key) { + m.doSetWithLockCheck(key, f()) + return true + } + return false +} + +// SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true. +// It returns false if `key` exists, and `value` would be ignored. +// +// SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that +// it executes function `f` with mutex.Lock of the map. +func (m *ListKVMap[K, V]) SetIfNotExistFuncLock(key K, f func() V) bool { + if !m.Contains(key) { + m.mu.Lock() + defer m.mu.Unlock() + + m.doSetWithLockCheckWithoutLock(key, f()) + return true + } + return false +} + +// Remove deletes value from map by given `key`, and return this deleted value. +func (m *ListKVMap[K, V]) Remove(key K) (value V) { + m.mu.Lock() + if m.data != nil { + if e, ok := m.data[key]; ok { + value = e.Value.value + delete(m.data, key) + m.list.Remove(e) + } + } + m.mu.Unlock() + return +} + +// Removes batch deletes values of the map by keys. +func (m *ListKVMap[K, V]) Removes(keys []K) { + m.mu.Lock() + if m.data != nil { + for _, key := range keys { + if e, ok := m.data[key]; ok { + delete(m.data, key) + m.list.Remove(e) + } + } + } + m.mu.Unlock() +} + +// Keys returns all keys of the map as a slice in ascending order. +func (m *ListKVMap[K, V]) Keys() []K { + m.mu.RLock() + var ( + keys = make([]K, m.list.Len()) + index = 0 + ) + if m.list != nil { + m.list.IteratorAsc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool { + keys[index] = e.Value.key + index++ + return true + }) + } + m.mu.RUnlock() + return keys +} + +// Values returns all values of the map as a slice. +func (m *ListKVMap[K, V]) Values() []V { + m.mu.RLock() + var ( + values = make([]V, m.list.Len()) + index = 0 + ) + if m.list != nil { + m.list.IteratorAsc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool { + values[index] = e.Value.value + index++ + return true + }) + } + m.mu.RUnlock() + return values +} + +// Contains checks whether a key exists. +// It returns true if the `key` exists, or else false. +func (m *ListKVMap[K, V]) Contains(key K) (ok bool) { + m.mu.RLock() + if m.data != nil { + _, ok = m.data[key] + } + m.mu.RUnlock() + return +} + +// Size returns the size of the map. +func (m *ListKVMap[K, V]) Size() (size int) { + m.mu.RLock() + size = len(m.data) + m.mu.RUnlock() + return +} + +// IsEmpty checks whether the map is empty. +// It returns true if map is empty, or else false. +func (m *ListKVMap[K, V]) IsEmpty() bool { + return m.Size() == 0 +} + +// Flip exchanges key-value of the map to value-key. +func (m *ListKVMap[K, V]) Flip() error { + data := m.Map() + m.Clear() + for key, value := range data { + var ( + newKey K + newValue V + ) + + if err := gconv.Scan(value, &newKey); err != nil { + return err + } + + if err := gconv.Scan(key, &newValue); err != nil { + return err + } + m.Set(newKey, newValue) + } + + return nil +} + +// Merge merges two link maps. +// The `other` map will be merged into the map `m`. +func (m *ListKVMap[K, V]) Merge(other *ListKVMap[K, V]) { + m.mu.Lock() + defer m.mu.Unlock() + if m.data == nil { + m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]]) + m.list = glist.NewT[*gListKVMapNode[K, V]]() + } + if other != m { + other.mu.RLock() + defer other.mu.RUnlock() + } + var node *gListKVMapNode[K, V] + other.list.IteratorAsc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool { + node = e.Value + if e, ok := m.data[node.key]; !ok { + m.data[node.key] = m.list.PushBack(&gListKVMapNode[K, V]{node.key, node.value}) + } else { + e.Value = &gListKVMapNode[K, V]{node.key, node.value} + } + return true + }) +} + +// String returns the map as a string. +func (m *ListKVMap[K, V]) String() string { + if m == nil { + return "" + } + b, _ := m.MarshalJSON() + return string(b) +} + +// MarshalJSON implements the interface MarshalJSON for json.Marshal. +func (m ListKVMap[K, V]) MarshalJSON() (jsonBytes []byte, err error) { + if m.data == nil { + return []byte("null"), nil + } + buffer := bytes.NewBuffer(nil) + buffer.WriteByte('{') + m.Iterator(func(key K, value V) bool { + valueBytes, valueJSONErr := json.Marshal(value) + if valueJSONErr != nil { + err = valueJSONErr + return false + } + if buffer.Len() > 1 { + buffer.WriteByte(',') + } + fmt.Fprintf(buffer, `"%v":%s`, key, valueBytes) + return true + }) + buffer.WriteByte('}') + return buffer.Bytes(), nil +} + +// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal. +func (m *ListKVMap[K, V]) UnmarshalJSON(b []byte) error { + m.mu.Lock() + defer m.mu.Unlock() + if m.data == nil { + m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]]) + m.list = glist.NewT[*gListKVMapNode[K, V]]() + } + var data map[string]V + if err := json.UnmarshalUseNumber(b, &data); err != nil { + return err + } + var kvData map[K]V + if err := gconv.Scan(data, &kvData); err != nil { + return err + } + for key, value := range kvData { + if e, ok := m.data[key]; !ok { + m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value}) + } else { + e.Value = &gListKVMapNode[K, V]{key, value} + } + } + return nil +} + +// UnmarshalValue is an interface implement which sets any type of value for map. +func (m *ListKVMap[K, V]) UnmarshalValue(value any) (err error) { + m.mu.Lock() + defer m.mu.Unlock() + if m.data == nil { + m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]]) + m.list = glist.NewT[*gListKVMapNode[K, V]]() + } + var dataMap map[K]V + if err = gconv.Scan(value, &dataMap); err != nil { + return + } + for k, v := range dataMap { + if e, ok := m.data[k]; !ok { + m.data[k] = m.list.PushBack(&gListKVMapNode[K, V]{k, v}) + } else { + e.Value = &gListKVMapNode[K, V]{k, v} + } + } + return +} + +// DeepCopy implements interface for deep copy of current type. +func (m *ListKVMap[K, V]) DeepCopy() any { + if m == nil { + return nil + } + m.mu.RLock() + defer m.mu.RUnlock() + data := make(map[any]any, len(m.data)) + if m.list != nil { + m.list.IteratorAsc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool { + data[e.Value.key] = deepcopy.Copy(e.Value.value) + return true + }) + } + return NewListKVMapFrom(data, m.mu.IsSafe()) +} diff --git a/container/gmap/gmap_list_map.go b/container/gmap/gmap_list_map.go index 5f90aaa41f1..1529f113198 100644 --- a/container/gmap/gmap_list_map.go +++ b/container/gmap/gmap_list_map.go @@ -7,15 +7,9 @@ package gmap import ( - "bytes" - "fmt" + "sync" - "github.com/gogf/gf/v2/container/glist" "github.com/gogf/gf/v2/container/gvar" - "github.com/gogf/gf/v2/internal/deepcopy" - "github.com/gogf/gf/v2/internal/empty" - "github.com/gogf/gf/v2/internal/json" - "github.com/gogf/gf/v2/internal/rwmutex" "github.com/gogf/gf/v2/util/gconv" ) @@ -27,15 +21,11 @@ import ( // // Reference: http://en.wikipedia.org/wiki/Associative_array type ListMap struct { - mu rwmutex.RWMutex - data map[any]*glist.Element - list *glist.List + *ListKVMap[any, any] + once sync.Once } -type gListMapNode struct { - key any - value any -} +type gListMapNode = gListKVMapNode[any, any] // NewListMap returns an empty link map. // ListMap is backed by a hash table to store values and doubly-linked list to store ordering. @@ -43,9 +33,7 @@ type gListMapNode struct { // which is false in default. func NewListMap(safe ...bool) *ListMap { return &ListMap{ - mu: rwmutex.Create(safe...), - data: make(map[any]*glist.Element), - list: glist.New(), + ListKVMap: NewListKVMap[any, any](safe...), } } @@ -58,6 +46,15 @@ func NewListMapFrom(data map[any]any, safe ...bool) *ListMap { return m } +// lazyInit lazily initializes the list map. +func (m *ListMap) lazyInit() { + m.once.Do(func() { + if m.ListKVMap == nil { + m.ListKVMap = NewListKVMap[any, any](false) + } + }) +} + // Iterator is alias of IteratorAsc. func (m *ListMap) Iterator(f func(key, value any) bool) { m.IteratorAsc(f) @@ -66,29 +63,15 @@ func (m *ListMap) Iterator(f func(key, value any) bool) { // IteratorAsc iterates the map readonly in ascending order with given callback function `f`. // If `f` returns true, then it continues iterating; or false to stop. func (m *ListMap) IteratorAsc(f func(key any, value any) bool) { - m.mu.RLock() - defer m.mu.RUnlock() - if m.list != nil { - var node *gListMapNode - m.list.IteratorAsc(func(e *glist.Element) bool { - node = e.Value.(*gListMapNode) - return f(node.key, node.value) - }) - } + m.lazyInit() + m.ListKVMap.IteratorAsc(f) } // IteratorDesc iterates the map readonly in descending order with given callback function `f`. // If `f` returns true, then it continues iterating; or false to stop. func (m *ListMap) IteratorDesc(f func(key any, value any) bool) { - m.mu.RLock() - defer m.mu.RUnlock() - if m.list != nil { - var node *gListMapNode - m.list.IteratorDesc(func(e *glist.Element) bool { - node = e.Value.(*gListMapNode) - return f(node.key, node.value) - }) - } + m.lazyInit() + m.ListKVMap.IteratorDesc(f) } // Clone returns a new link map with copy of current map data. @@ -98,232 +81,85 @@ func (m *ListMap) Clone(safe ...bool) *ListMap { // Clear deletes all data of the map, it will remake a new underlying data map. func (m *ListMap) Clear() { - m.mu.Lock() - m.data = make(map[any]*glist.Element) - m.list = glist.New() - m.mu.Unlock() + m.lazyInit() + m.ListKVMap.Clear() } // Replace the data of the map with given `data`. func (m *ListMap) Replace(data map[any]any) { - m.mu.Lock() - m.data = make(map[any]*glist.Element) - m.list = glist.New() - for key, value := range data { - if e, ok := m.data[key]; !ok { - m.data[key] = m.list.PushBack(&gListMapNode{key, value}) - } else { - e.Value = &gListMapNode{key, value} - } - } - m.mu.Unlock() + m.lazyInit() + m.ListKVMap.Replace(data) } // Map returns a copy of the underlying data of the map. func (m *ListMap) Map() map[any]any { - m.mu.RLock() - var node *gListMapNode - var data map[any]any - if m.list != nil { - data = make(map[any]any, len(m.data)) - m.list.IteratorAsc(func(e *glist.Element) bool { - node = e.Value.(*gListMapNode) - data[node.key] = node.value - return true - }) - } - m.mu.RUnlock() - return data + m.lazyInit() + return m.ListKVMap.Map() } // MapStrAny returns a copy of the underlying data of the map as map[string]any. func (m *ListMap) MapStrAny() map[string]any { - m.mu.RLock() - var node *gListMapNode - var data map[string]any - if m.list != nil { - data = make(map[string]any, len(m.data)) - m.list.IteratorAsc(func(e *glist.Element) bool { - node = e.Value.(*gListMapNode) - data[gconv.String(node.key)] = node.value - return true - }) - } - m.mu.RUnlock() - return data + m.lazyInit() + return m.ListKVMap.MapStrAny() } // FilterEmpty deletes all key-value pair of which the value is empty. func (m *ListMap) FilterEmpty() { - m.mu.Lock() - if m.list != nil { - var ( - keys = make([]any, 0) - node *gListMapNode - ) - m.list.IteratorAsc(func(e *glist.Element) bool { - node = e.Value.(*gListMapNode) - if empty.IsEmpty(node.value) { - keys = append(keys, node.key) - } - return true - }) - if len(keys) > 0 { - for _, key := range keys { - if e, ok := m.data[key]; ok { - delete(m.data, key) - m.list.Remove(e) - } - } - } - } - m.mu.Unlock() + m.lazyInit() + m.ListKVMap.FilterEmpty() } // Set sets key-value to the map. func (m *ListMap) Set(key any, value any) { - m.mu.Lock() - if m.data == nil { - m.data = make(map[any]*glist.Element) - m.list = glist.New() - } - if e, ok := m.data[key]; !ok { - m.data[key] = m.list.PushBack(&gListMapNode{key, value}) - } else { - e.Value = &gListMapNode{key, value} - } - m.mu.Unlock() + m.lazyInit() + m.ListKVMap.Set(key, value) } // Sets batch sets key-values to the map. func (m *ListMap) Sets(data map[any]any) { - m.mu.Lock() - if m.data == nil { - m.data = make(map[any]*glist.Element) - m.list = glist.New() - } - for key, value := range data { - if e, ok := m.data[key]; !ok { - m.data[key] = m.list.PushBack(&gListMapNode{key, value}) - } else { - e.Value = &gListMapNode{key, value} - } - } - m.mu.Unlock() + m.lazyInit() + m.ListKVMap.Sets(data) } // Search searches the map with given `key`. // Second return parameter `found` is true if key was found, otherwise false. func (m *ListMap) Search(key any) (value any, found bool) { - m.mu.RLock() - if m.data != nil { - if e, ok := m.data[key]; ok { - value = e.Value.(*gListMapNode).value - found = ok - } - } - m.mu.RUnlock() - return + m.lazyInit() + return m.ListKVMap.Search(key) } // Get returns the value by given `key`. func (m *ListMap) Get(key any) (value any) { - m.mu.RLock() - if m.data != nil { - if e, ok := m.data[key]; ok { - value = e.Value.(*gListMapNode).value - } - } - m.mu.RUnlock() - return + m.lazyInit() + return m.ListKVMap.Get(key) } // Pop retrieves and deletes an item from the map. func (m *ListMap) Pop() (key, value any) { - 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 + m.lazyInit() + return m.ListKVMap.Pop() } // Pops retrieves and deletes `size` items from the map. // It returns all items if size == -1. func (m *ListMap) Pops(size int) map[any]any { - 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[any]any, 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 `key`, -// or else just return the existing value. -// -// When setting value, if `value` is type of `func() interface {}`, -// it will be executed with mutex.Lock of the map, -// and its return value will be set to the map with `key`. -// -// It returns value with given `key`. -func (m *ListMap) doSetWithLockCheck(key any, value any) any { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[any]*glist.Element) - m.list = glist.New() - } - if e, ok := m.data[key]; ok { - return e.Value.(*gListMapNode).value - } - if f, ok := value.(func() any); ok { - value = f() - } - if value != nil { - m.data[key] = m.list.PushBack(&gListMapNode{key, value}) - } - return value + m.lazyInit() + return m.ListKVMap.Pops(size) } // GetOrSet returns the value by key, // or sets value with given `value` if it does not exist and then returns this value. func (m *ListMap) GetOrSet(key any, value any) any { - if v, ok := m.Search(key); !ok { - return m.doSetWithLockCheck(key, value) - } else { - return v - } + m.lazyInit() + return m.ListKVMap.GetOrSet(key, value) } // GetOrSetFunc returns the value by key, // or sets value with returned value of callback function `f` if it does not exist // and then returns this value. func (m *ListMap) GetOrSetFunc(key any, f func() any) any { - if v, ok := m.Search(key); !ok { - return m.doSetWithLockCheck(key, f()) - } else { - return v - } + m.lazyInit() + return m.ListKVMap.GetOrSetFunc(key, f) } // GetOrSetFuncLock returns the value by key, @@ -333,55 +169,50 @@ func (m *ListMap) GetOrSetFunc(key any, f func() any) any { // GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f` // with mutex.Lock of the map. func (m *ListMap) GetOrSetFuncLock(key any, f func() any) any { - if v, ok := m.Search(key); !ok { - return m.doSetWithLockCheck(key, f) - } else { - return v - } + m.lazyInit() + return m.ListKVMap.GetOrSetFuncLock(key, f) } // GetVar returns a Var with the value by given `key`. // The returned Var is un-concurrent safe. func (m *ListMap) GetVar(key any) *gvar.Var { - return gvar.New(m.Get(key)) + m.lazyInit() + return m.ListKVMap.GetVar(key) } // GetVarOrSet returns a Var with result from GetVarOrSet. // The returned Var is un-concurrent safe. func (m *ListMap) GetVarOrSet(key any, value any) *gvar.Var { - return gvar.New(m.GetOrSet(key, value)) + m.lazyInit() + return m.ListKVMap.GetVarOrSet(key, value) } // GetVarOrSetFunc returns a Var with result from GetOrSetFunc. // The returned Var is un-concurrent safe. func (m *ListMap) GetVarOrSetFunc(key any, f func() any) *gvar.Var { - return gvar.New(m.GetOrSetFunc(key, f)) + m.lazyInit() + return m.ListKVMap.GetVarOrSetFunc(key, f) } // GetVarOrSetFuncLock returns a Var with result from GetOrSetFuncLock. // The returned Var is un-concurrent safe. func (m *ListMap) GetVarOrSetFuncLock(key any, f func() any) *gvar.Var { - return gvar.New(m.GetOrSetFuncLock(key, f)) + m.lazyInit() + return m.ListKVMap.GetVarOrSetFuncLock(key, f) } // SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true. // It returns false if `key` exists, and `value` would be ignored. func (m *ListMap) SetIfNotExist(key any, value any) bool { - if !m.Contains(key) { - m.doSetWithLockCheck(key, value) - return true - } - return false + m.lazyInit() + return m.ListKVMap.SetIfNotExist(key, value) } // SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true. // It returns false if `key` exists, and `value` would be ignored. func (m *ListMap) SetIfNotExistFunc(key any, f func() any) bool { - if !m.Contains(key) { - m.doSetWithLockCheck(key, f()) - return true - } - return false + m.lazyInit() + return m.ListKVMap.SetIfNotExistFunc(key, f) } // SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true. @@ -390,100 +221,52 @@ func (m *ListMap) SetIfNotExistFunc(key any, f func() any) bool { // SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that // it executes function `f` with mutex.Lock of the map. func (m *ListMap) SetIfNotExistFuncLock(key any, f func() any) bool { - if !m.Contains(key) { - m.doSetWithLockCheck(key, f) - return true - } - return false + m.lazyInit() + return m.ListKVMap.SetIfNotExistFuncLock(key, f) } // Remove deletes value from map by given `key`, and return this deleted value. func (m *ListMap) Remove(key any) (value any) { - m.mu.Lock() - if m.data != nil { - if e, ok := m.data[key]; ok { - value = e.Value.(*gListMapNode).value - delete(m.data, key) - m.list.Remove(e) - } - } - m.mu.Unlock() - return + m.lazyInit() + return m.ListKVMap.Remove(key) } // Removes batch deletes values of the map by keys. func (m *ListMap) Removes(keys []any) { - m.mu.Lock() - if m.data != nil { - for _, key := range keys { - if e, ok := m.data[key]; ok { - delete(m.data, key) - m.list.Remove(e) - } - } - } - m.mu.Unlock() + m.lazyInit() + m.ListKVMap.Removes(keys) } // Keys returns all keys of the map as a slice in ascending order. func (m *ListMap) Keys() []any { - m.mu.RLock() - var ( - keys = make([]any, m.list.Len()) - index = 0 - ) - if m.list != nil { - m.list.IteratorAsc(func(e *glist.Element) bool { - keys[index] = e.Value.(*gListMapNode).key - index++ - return true - }) - } - m.mu.RUnlock() - return keys + m.lazyInit() + return m.ListKVMap.Keys() } // Values returns all values of the map as a slice. func (m *ListMap) Values() []any { - m.mu.RLock() - var ( - values = make([]any, m.list.Len()) - index = 0 - ) - if m.list != nil { - m.list.IteratorAsc(func(e *glist.Element) bool { - values[index] = e.Value.(*gListMapNode).value - index++ - return true - }) - } - m.mu.RUnlock() - return values + m.lazyInit() + return m.ListKVMap.Values() } // Contains checks whether a key exists. // It returns true if the `key` exists, or else false. func (m *ListMap) Contains(key any) (ok bool) { - m.mu.RLock() - if m.data != nil { - _, ok = m.data[key] - } - m.mu.RUnlock() - return + m.lazyInit() + return m.ListKVMap.Contains(key) } // Size returns the size of the map. func (m *ListMap) Size() (size int) { - m.mu.RLock() - size = len(m.data) - m.mu.RUnlock() - return + m.lazyInit() + return m.ListKVMap.Size() } // IsEmpty checks whether the map is empty. // It returns true if map is empty, or else false. func (m *ListMap) IsEmpty() bool { - return m.Size() == 0 + m.lazyInit() + return m.ListKVMap.IsEmpty() } // Flip exchanges key-value of the map to value-key. @@ -498,90 +281,35 @@ func (m *ListMap) Flip() { // Merge merges two link maps. // The `other` map will be merged into the map `m`. func (m *ListMap) Merge(other *ListMap) { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[any]*glist.Element) - m.list = glist.New() - } - if other != m { - other.mu.RLock() - defer other.mu.RUnlock() - } - var node *gListMapNode - other.list.IteratorAsc(func(e *glist.Element) bool { - node = e.Value.(*gListMapNode) - if e, ok := m.data[node.key]; !ok { - m.data[node.key] = m.list.PushBack(&gListMapNode{node.key, node.value}) - } else { - e.Value = &gListMapNode{node.key, node.value} - } - return true - }) + m.lazyInit() + other.lazyInit() + m.ListKVMap.Merge(other.ListKVMap) } // String returns the map as a string. func (m *ListMap) String() string { - if m == nil { - return "" - } - b, _ := m.MarshalJSON() - return string(b) + m.lazyInit() + return m.ListKVMap.String() } // MarshalJSON implements the interface MarshalJSON for json.Marshal. func (m ListMap) MarshalJSON() (jsonBytes []byte, err error) { - if m.data == nil { - return []byte("null"), nil - } - buffer := bytes.NewBuffer(nil) - buffer.WriteByte('{') - m.Iterator(func(key, value any) bool { - valueBytes, valueJSONErr := json.Marshal(value) - if valueJSONErr != nil { - err = valueJSONErr - return false - } - if buffer.Len() > 1 { - buffer.WriteByte(',') - } - fmt.Fprintf(buffer, `"%v":%s`, key, valueBytes) - return true - }) - buffer.WriteByte('}') - return buffer.Bytes(), nil + return m.ListKVMap.MarshalJSON() } // UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal. func (m *ListMap) UnmarshalJSON(b []byte) error { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[any]*glist.Element) - m.list = glist.New() - } - var data map[string]any - if err := json.UnmarshalUseNumber(b, &data); err != nil { - return err - } - for key, value := range data { - if e, ok := m.data[key]; !ok { - m.data[key] = m.list.PushBack(&gListMapNode{key, value}) - } else { - e.Value = &gListMapNode{key, value} - } - } - return nil + m.lazyInit() + return m.ListKVMap.UnmarshalJSON(b) } // UnmarshalValue is an interface implement which sets any type of value for map. func (m *ListMap) UnmarshalValue(value any) (err error) { + m.lazyInit() + m.mu.Lock() defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[any]*glist.Element) - m.list = glist.New() - } + for k, v := range gconv.Map(value) { if e, ok := m.data[k]; !ok { m.data[k] = m.list.PushBack(&gListMapNode{k, v}) @@ -597,16 +325,8 @@ func (m *ListMap) DeepCopy() any { if m == nil { return nil } - m.mu.RLock() - defer m.mu.RUnlock() - data := make(map[any]any, len(m.data)) - if m.list != nil { - var node *gListMapNode - m.list.IteratorAsc(func(e *glist.Element) bool { - node = e.Value.(*gListMapNode) - data[node.key] = deepcopy.Copy(node.value) - return true - }) + m.lazyInit() + return &ListMap{ + ListKVMap: m.ListKVMap.DeepCopy().(*ListKVMap[any, any]), } - return NewListMapFrom(data, m.mu.IsSafe()) } From 034b9a33d2346f6153a4e7c3418a91a0e1cdc7ef Mon Sep 17 00:00:00 2001 From: Hunk Zhu <54zhua@gmail.com> Date: Fri, 21 Nov 2025 17:32:04 +0800 Subject: [PATCH 2/8] Update container/gmap/gmap_list_k_v_map.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- container/gmap/gmap_list_k_v_map.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/container/gmap/gmap_list_k_v_map.go b/container/gmap/gmap_list_k_v_map.go index 7153788d78e..81017cb6291 100644 --- a/container/gmap/gmap_list_k_v_map.go +++ b/container/gmap/gmap_list_k_v_map.go @@ -23,7 +23,7 @@ import ( // // It is backed by a hash table to store values and doubly-linked list to store ordering. // -// Structure is not thread safe. +// Thread-safety is optional and controlled by the `safe` parameter during initialization. // // Reference: http://en.wikipedia.org/wiki/Associative_array type ListKVMap[K comparable, V any] struct { From 6abe166725d8a802eaebfaac1be131ea1d9405a0 Mon Sep 17 00:00:00 2001 From: Hunk Zhu <54zhua@gmail.com> Date: Fri, 21 Nov 2025 17:35:20 +0800 Subject: [PATCH 3/8] Update container/gmap/gmap_list_k_v_map.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- container/gmap/gmap_list_k_v_map.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/container/gmap/gmap_list_k_v_map.go b/container/gmap/gmap_list_k_v_map.go index 81017cb6291..ac42448f68a 100644 --- a/container/gmap/gmap_list_k_v_map.go +++ b/container/gmap/gmap_list_k_v_map.go @@ -106,11 +106,7 @@ func (m *ListKVMap[K, V]) Replace(data map[K]V) { m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]]) m.list = glist.NewT[*gListKVMapNode[K, V]]() for key, value := range data { - if e, ok := m.data[key]; !ok { - m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value}) - } else { - e.Value = &gListKVMapNode[K, V]{key, value} - } + m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value}) } m.mu.Unlock() } From 425e788fca3822d2a859e5b74d4dcc26e6066433 Mon Sep 17 00:00:00 2001 From: Hunk Zhu <54zhua@gmail.com> Date: Fri, 21 Nov 2025 17:37:59 +0800 Subject: [PATCH 4/8] Update container/gmap/gmap_list_k_v_map.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- container/gmap/gmap_list_k_v_map.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/container/gmap/gmap_list_k_v_map.go b/container/gmap/gmap_list_k_v_map.go index ac42448f68a..4b1f5207974 100644 --- a/container/gmap/gmap_list_k_v_map.go +++ b/container/gmap/gmap_list_k_v_map.go @@ -266,10 +266,6 @@ func (m *ListKVMap[K, V]) Pops(size int) map[K]V { // if not exists, set value to the map with given `key`, // or else just return the existing value. // -// When setting value, if `value` is type of `func() interface {}`, -// it will be executed with mutex.Lock of the map, -// and its return value will be set to the map with `key`. -// // It returns value with given `key`. func (m *ListKVMap[K, V]) doSetWithLockCheck(key K, value V) V { m.mu.Lock() From f5ae99fdc0e655693c631575e1e2b7f43f911878 Mon Sep 17 00:00:00 2001 From: Hunk <54zhua@gmail.com> Date: Mon, 24 Nov 2025 11:08:44 +0800 Subject: [PATCH 5/8] improve code --- container/gmap/gmap_list_k_v_map.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/container/gmap/gmap_list_k_v_map.go b/container/gmap/gmap_list_k_v_map.go index 4b1f5207974..ad8812c2a48 100644 --- a/container/gmap/gmap_list_k_v_map.go +++ b/container/gmap/gmap_list_k_v_map.go @@ -610,10 +610,10 @@ func (m *ListKVMap[K, V]) DeepCopy() any { } m.mu.RLock() defer m.mu.RUnlock() - data := make(map[any]any, len(m.data)) + data := make(map[K]V, len(m.data)) if m.list != nil { m.list.IteratorAsc(func(e *glist.TElement[*gListKVMapNode[K, V]]) bool { - data[e.Value.key] = deepcopy.Copy(e.Value.value) + data[e.Value.key] = deepcopy.Copy(e.Value.value).(V) return true }) } From 5557bbb0236656ed8ccf846f541879ae9a056354 Mon Sep 17 00:00:00 2001 From: hailaz <739476267@qq.com> Date: Sat, 29 Nov 2025 20:08:15 +0800 Subject: [PATCH 6/8] =?UTF-8?q?feat(container/gmap):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=20NewListKVMapFrom=20=E5=87=BD=E6=95=B0=E4=BB=A5=E5=A4=8D?= =?UTF-8?q?=E5=88=B6=E6=95=B0=E6=8D=AE=E6=98=A0=E5=B0=84=EF=BC=8C=E9=81=BF?= =?UTF-8?q?=E5=85=8D=E5=8E=9F=E5=A7=8B=E6=98=A0=E5=B0=84=E6=9B=B4=E6=94=B9?= =?UTF-8?q?=E5=BD=B1=E5=93=8D=E9=93=BE=E6=8E=A5=E6=98=A0=E5=B0=84=20fix(co?= =?UTF-8?q?ntainer/gmap):=20=E4=BF=AE=E5=A4=8D=20GetOrSetFuncLock=20?= =?UTF-8?q?=E5=92=8C=20SetIfNotExistFuncLock=20=E4=B8=AD=E7=9A=84=E5=B9=B6?= =?UTF-8?q?=E5=8F=91=E5=AE=89=E5=85=A8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- container/gmap/gmap_list_k_v_map.go | 42 +++++++++++++++++++---------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/container/gmap/gmap_list_k_v_map.go b/container/gmap/gmap_list_k_v_map.go index ad8812c2a48..f3dc554b4f8 100644 --- a/container/gmap/gmap_list_k_v_map.go +++ b/container/gmap/gmap_list_k_v_map.go @@ -50,8 +50,8 @@ func NewListKVMap[K comparable, V any](safe ...bool) *ListKVMap[K, V] { } // NewListKVMapFrom returns a link map from given map `data`. -// Note that, the param `data` map will be set as the underlying data map(no deep copy), -// there might be some concurrent-safe issues when changing the map outside. +// Note that, the param `data` map will be copied to the underlying data structure, +// so changes to the original map will not affect the link map. func NewListKVMapFrom[K comparable, V any](data map[K]V, safe ...bool) *ListKVMap[K, V] { m := NewListKVMap[K, V](safe...) m.Sets(data) @@ -316,14 +316,21 @@ func (m *ListKVMap[K, V]) GetOrSetFunc(key K, f func() V) V { // GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f` // with mutex.Lock of the map. func (m *ListKVMap[K, V]) GetOrSetFuncLock(key K, f func() V) V { - if v, ok := m.Search(key); !ok { - m.mu.Lock() - defer m.mu.Unlock() + m.mu.Lock() + defer m.mu.Unlock() - return m.doSetWithLockCheckWithoutLock(key, f()) - } else { - return v + if m.data == nil { + m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]]) + m.list = glist.NewT[*gListKVMapNode[K, V]]() + } + if e, ok := m.data[key]; ok { + return e.Value.value + } + value := f() + if any(value) != nil { + m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value}) } + return value } // GetVar returns a Var with the value by given `key`. @@ -376,14 +383,21 @@ func (m *ListKVMap[K, V]) SetIfNotExistFunc(key K, f func() V) bool { // SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that // it executes function `f` with mutex.Lock of the map. func (m *ListKVMap[K, V]) SetIfNotExistFuncLock(key K, f func() V) bool { - if !m.Contains(key) { - m.mu.Lock() - defer m.mu.Unlock() + m.mu.Lock() + defer m.mu.Unlock() - m.doSetWithLockCheckWithoutLock(key, f()) - return true + if m.data == nil { + m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]]) + m.list = glist.NewT[*gListKVMapNode[K, V]]() } - return false + if _, ok := m.data[key]; ok { + return false + } + value := f() + if any(value) != nil { + m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value}) + } + return true } // Remove deletes value from map by given `key`, and return this deleted value. From 426a85f5d4f9e681f9ed34ef11e7dedc3eef832f Mon Sep 17 00:00:00 2001 From: hailaz <739476267@qq.com> Date: Sat, 29 Nov 2025 20:08:51 +0800 Subject: [PATCH 7/8] test(container/gmap): ut --- .../gmap_z_unit_list_k_v_map_race_test.go | 326 ++++ .../gmap/gmap_z_unit_list_k_v_map_test.go | 1343 +++++++++++++++++ 2 files changed, 1669 insertions(+) create mode 100644 container/gmap/gmap_z_unit_list_k_v_map_race_test.go create mode 100644 container/gmap/gmap_z_unit_list_k_v_map_test.go diff --git a/container/gmap/gmap_z_unit_list_k_v_map_race_test.go b/container/gmap/gmap_z_unit_list_k_v_map_race_test.go new file mode 100644 index 00000000000..d3156866201 --- /dev/null +++ b/container/gmap/gmap_z_unit_list_k_v_map_race_test.go @@ -0,0 +1,326 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gmap_test + +import ( + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/test/gtest" +) + +// Test_ListKVMap_GetOrSetFuncLock_Race tests the atomicity of GetOrSetFuncLock. +// This test ensures that the callback function is only executed once even under +// high concurrency, which verifies that the function holds the lock during the +// entire check-and-set operation. +func Test_ListKVMap_GetOrSetFuncLock_Race(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int](true) + key := "counter" + callCount := int32(0) + goroutines := 100 + + var wg sync.WaitGroup + wg.Add(goroutines) + + // Start multiple goroutines trying to set the same key + for i := 0; i < goroutines; i++ { + go func() { + defer wg.Done() + m.GetOrSetFuncLock(key, func() int { + // Increment call count atomically + atomic.AddInt32(&callCount, 1) + // Simulate some work + time.Sleep(time.Microsecond) + return 100 + }) + }() + } + + wg.Wait() + + // The callback should only be called once because of proper locking + t.Assert(atomic.LoadInt32(&callCount), 1) + t.Assert(m.Get(key), 100) + t.Assert(m.Size(), 1) + }) +} + +// Test_ListKVMap_SetIfNotExistFuncLock_Race tests the atomicity of SetIfNotExistFuncLock. +// This test ensures that only one goroutine can successfully set the value and +// execute the callback function, even under high concurrency. +func Test_ListKVMap_SetIfNotExistFuncLock_Race(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int](true) + key := "counter" + callCount := int32(0) + successCount := int32(0) + goroutines := 100 + + var wg sync.WaitGroup + wg.Add(goroutines) + + // Start multiple goroutines trying to set the same key + for i := 0; i < goroutines; i++ { + go func() { + defer wg.Done() + success := m.SetIfNotExistFuncLock(key, func() int { + // Increment call count atomically + atomic.AddInt32(&callCount, 1) + // Simulate some work + time.Sleep(time.Microsecond) + return 200 + }) + if success { + atomic.AddInt32(&successCount, 1) + } + }() + } + + wg.Wait() + + // The callback should only be called once + t.Assert(atomic.LoadInt32(&callCount), 1) + // Only one goroutine should succeed + t.Assert(atomic.LoadInt32(&successCount), 1) + t.Assert(m.Get(key), 200) + t.Assert(m.Size(), 1) + }) +} + +// Test_ListKVMap_GetOrSetFuncLock_MultipleKeys tests GetOrSetFuncLock with different keys. +// This ensures that operations on different keys don't interfere with each other. +func Test_ListKVMap_GetOrSetFuncLock_MultipleKeys(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int](true) + keys := []string{"key1", "key2", "key3", "key4", "key5"} + callCounts := make([]int32, len(keys)) + goroutines := 20 + + var wg sync.WaitGroup + + // For each key, start multiple goroutines + for i, key := range keys { + keyIndex := i + for j := 0; j < goroutines; j++ { + wg.Add(1) + go func(idx int, k string) { + defer wg.Done() + m.GetOrSetFuncLock(k, func() int { + atomic.AddInt32(&callCounts[idx], 1) + time.Sleep(time.Microsecond) + return (idx + 1) * 100 + }) + }(keyIndex, key) + } + } + + wg.Wait() + + // Each key's callback should only be called once + for _, count := range callCounts { + t.Assert(atomic.LoadInt32(&count), 1) + } + + // Verify all keys are set correctly + for i, key := range keys { + t.Assert(m.Get(key), (i+1)*100) + } + t.Assert(m.Size(), len(keys)) + }) +} + +// Test_ListKVMap_SetIfNotExistFuncLock_MultipleKeys tests SetIfNotExistFuncLock with different keys. +func Test_ListKVMap_SetIfNotExistFuncLock_MultipleKeys(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[int, string](true) + keys := []int{1, 2, 3, 4, 5} + callCounts := make([]int32, len(keys)) + successCounts := make([]int32, len(keys)) + goroutines := 20 + + var wg sync.WaitGroup + + // For each key, start multiple goroutines + for i, key := range keys { + keyIndex := i + for j := 0; j < goroutines; j++ { + wg.Add(1) + go func(idx int, k int) { + defer wg.Done() + success := m.SetIfNotExistFuncLock(k, func() string { + atomic.AddInt32(&callCounts[idx], 1) + time.Sleep(time.Microsecond) + return gtest.DataContent() + }) + if success { + atomic.AddInt32(&successCounts[idx], 1) + } + }(keyIndex, key) + } + } + + wg.Wait() + + // Each key's callback should only be called once + for _, count := range callCounts { + t.Assert(atomic.LoadInt32(&count), 1) + } + + // Each key should have exactly one successful set + for _, count := range successCounts { + t.Assert(atomic.LoadInt32(&count), 1) + } + + t.Assert(m.Size(), len(keys)) + }) +} + +// Test_ListKVMap_GetOrSetFuncLock_NilValue tests that nil values are handled correctly. +func Test_ListKVMap_GetOrSetFuncLock_NilValue(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, *int](true) + key := "nilKey" + callCount := int32(0) + + var wg sync.WaitGroup + goroutines := 50 + wg.Add(goroutines) + + for i := 0; i < goroutines; i++ { + go func() { + defer wg.Done() + m.GetOrSetFuncLock(key, func() *int { + atomic.AddInt32(&callCount, 1) + return nil + }) + }() + } + + wg.Wait() + + // Callback should be called once + t.Assert(atomic.LoadInt32(&callCount), 1) + // Typed nil pointer (*int)(nil) is stored because any(value) != nil for typed nil + // This is a Go language feature: typed nil is not the same as interface nil + t.Assert(m.Contains(key), true) + t.Assert(m.Get(key), (*int)(nil)) + t.Assert(m.Size(), 1) + }) +} + +// Test_ListKVMap_SetIfNotExistFuncLock_NilValue tests that nil values are handled correctly. +func Test_ListKVMap_SetIfNotExistFuncLock_NilValue(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, *string](true) + key := "nilKey" + callCount := int32(0) + successCount := int32(0) + + var wg sync.WaitGroup + goroutines := 50 + wg.Add(goroutines) + + for i := 0; i < goroutines; i++ { + go func() { + defer wg.Done() + success := m.SetIfNotExistFuncLock(key, func() *string { + atomic.AddInt32(&callCount, 1) + return nil + }) + if success { + atomic.AddInt32(&successCount, 1) + } + }() + } + + wg.Wait() + + // Callback should be called once + t.Assert(atomic.LoadInt32(&callCount), 1) + // Should report success once + t.Assert(atomic.LoadInt32(&successCount), 1) + // Typed nil pointer (*string)(nil) is stored because any(value) != nil for typed nil + t.Assert(m.Contains(key), true) + t.Assert(m.Get(key), (*string)(nil)) + t.Assert(m.Size(), 1) + }) +} + +// Test_ListKVMap_GetOrSetFuncLock_ExistingKey tests behavior when key already exists. +func Test_ListKVMap_GetOrSetFuncLock_ExistingKey(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int](true) + key := "existing" + m.Set(key, 999) + + callCount := int32(0) + goroutines := 50 + + var wg sync.WaitGroup + wg.Add(goroutines) + + for i := 0; i < goroutines; i++ { + go func() { + defer wg.Done() + val := m.GetOrSetFuncLock(key, func() int { + atomic.AddInt32(&callCount, 1) + return 123 + }) + // Should always get the existing value + t.Assert(val, 999) + }() + } + + wg.Wait() + + // Callback should never be called since key exists + t.Assert(atomic.LoadInt32(&callCount), 0) + t.Assert(m.Get(key), 999) + }) +} + +// Test_ListKVMap_SetIfNotExistFuncLock_ExistingKey tests behavior when key already exists. +func Test_ListKVMap_SetIfNotExistFuncLock_ExistingKey(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int](true) + key := "existing" + m.Set(key, 888) + + callCount := int32(0) + successCount := int32(0) + goroutines := 50 + + var wg sync.WaitGroup + wg.Add(goroutines) + + for i := 0; i < goroutines; i++ { + go func() { + defer wg.Done() + success := m.SetIfNotExistFuncLock(key, func() int { + atomic.AddInt32(&callCount, 1) + return 456 + }) + if success { + atomic.AddInt32(&successCount, 1) + } + }() + } + + wg.Wait() + + // Callback should never be called since key exists + t.Assert(atomic.LoadInt32(&callCount), 0) + // No goroutine should succeed + t.Assert(atomic.LoadInt32(&successCount), 0) + // Original value should remain + t.Assert(m.Get(key), 888) + }) +} diff --git a/container/gmap/gmap_z_unit_list_k_v_map_test.go b/container/gmap/gmap_z_unit_list_k_v_map_test.go new file mode 100644 index 00000000000..7495438c462 --- /dev/null +++ b/container/gmap/gmap_z_unit_list_k_v_map_test.go @@ -0,0 +1,1343 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package gmap_test + +import ( + "sync" + "testing" + + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/test/gtest" + "github.com/gogf/gf/v2/util/gconv" +) + +func Test_ListKVMap_NewListKVMap(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, string]() + t.Assert(m.Size(), 0) + t.Assert(m.IsEmpty(), true) + }) + + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, string](true) + t.Assert(m.Size(), 0) + t.Assert(m.IsEmpty(), true) + }) +} + +func Test_ListKVMap_NewListKVMapFrom(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + data := map[string]string{"a": "1", "b": "2"} + m := gmap.NewListKVMapFrom(data) + t.Assert(m.Size(), 2) + t.Assert(m.Get("a"), "1") + t.Assert(m.Get("b"), "2") + }) + + gtest.C(t, func(t *gtest.T) { + data := map[int]int{1: 10, 2: 20} + m := gmap.NewListKVMapFrom(data, true) + t.Assert(m.Size(), 2) + t.Assert(m.Get(1), 10) + t.Assert(m.Get(2), 20) + }) +} + +func Test_ListKVMap_Set_Get(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, string]() + m.Set("a", "1") + t.Assert(m.Get("a"), "1") + t.Assert(m.Size(), 1) + + m.Set("b", "2") + t.Assert(m.Get("b"), "2") + t.Assert(m.Size(), 2) + + // Set existing key + m.Set("a", "10") + t.Assert(m.Get("a"), "10") + t.Assert(m.Size(), 2) + }) + + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[int, int]() + m.Set(1, 100) + m.Set(2, 200) + t.Assert(m.Get(1), 100) + t.Assert(m.Get(2), 200) + }) +} + +func Test_ListKVMap_Sets(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, string]() + m.Sets(map[string]string{"a": "1", "b": "2", "c": "3"}) + t.Assert(m.Size(), 3) + t.Assert(m.Get("a"), "1") + t.Assert(m.Get("b"), "2") + t.Assert(m.Get("c"), "3") + }) + + gtest.C(t, func(t *gtest.T) { + data := map[string]string{"x": "10", "y": "20"} + m := gmap.NewListKVMapFrom(data) + m.Sets(map[string]string{"a": "1", "b": "2"}) + t.Assert(m.Size(), 4) + t.Assert(m.Get("x"), "10") + t.Assert(m.Get("a"), "1") + }) +} + +func Test_ListKVMap_Search(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string]string{"a": "1", "b": "2"}) + + v, found := m.Search("a") + t.Assert(found, true) + t.Assert(v, "1") + + v, found = m.Search("c") + t.Assert(found, false) + t.Assert(v, "") + }) + + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[int, string]() + v, found := m.Search(1) + t.Assert(found, false) + t.Assert(v, "") + }) +} + +func Test_ListKVMap_Contains(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string]string{"a": "1", "b": "2"}) + t.Assert(m.Contains("a"), true) + t.Assert(m.Contains("b"), true) + t.Assert(m.Contains("c"), false) + }) +} + +func Test_ListKVMap_Remove(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string]string{"a": "1", "b": "2"}) + t.Assert(m.Size(), 2) + + v := m.Remove("a") + t.Assert(v, "1") + t.Assert(m.Contains("a"), false) + t.Assert(m.Size(), 1) + + v = m.Remove("c") + t.Assert(v, "") + t.Assert(m.Size(), 1) + }) +} + +func Test_ListKVMap_Removes(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string]string{"a": "1", "b": "2", "c": "3"}) + t.Assert(m.Size(), 3) + + m.Removes([]string{"a", "c"}) + t.Assert(m.Size(), 1) + t.Assert(m.Contains("a"), false) + t.Assert(m.Contains("c"), false) + t.Assert(m.Contains("b"), true) + }) + + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string]string{"a": "1", "b": "2"}) + m.Removes([]string{"x", "y"}) + t.Assert(m.Size(), 2) + }) +} + +func Test_ListKVMap_Pop(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string]string{"a": "1", "b": "2"}) + t.Assert(m.Size(), 2) + + k, v := m.Pop() + t.AssertIN(k, []string{"a", "b"}) + t.AssertIN(v, []string{"1", "2"}) + t.Assert(m.Size(), 1) + }) + + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, string]() + k, v := m.Pop() + t.Assert(k, "") + t.Assert(v, "") + }) +} + +func Test_ListKVMap_Pops(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string]string{"a": "1", "b": "2", "c": "3"}) + t.Assert(m.Size(), 3) + + popped := m.Pops(2) + t.Assert(len(popped), 2) + t.Assert(m.Size(), 1) + }) + + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string]string{"a": "1", "b": "2", "c": "3"}) + popped := m.Pops(-1) + t.Assert(len(popped), 3) + t.Assert(m.Size(), 0) + }) + + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string]string{"a": "1", "b": "2"}) + popped := m.Pops(10) + t.Assert(len(popped), 2) + t.Assert(m.Size(), 0) + }) + + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, string]() + popped := m.Pops(1) + t.AssertNil(popped) + }) +} + +func Test_ListKVMap_Keys(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string]string{"a": "1", "b": "2", "c": "3"}) + keys := m.Keys() + t.Assert(len(keys), 3) + t.AssertIN("a", keys) + t.AssertIN("b", keys) + t.AssertIN("c", keys) + }) + + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[int, string]() + keys := m.Keys() + t.Assert(len(keys), 0) + }) +} + +func Test_ListKVMap_Values(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string]string{"a": "1", "b": "2", "c": "3"}) + values := m.Values() + t.Assert(len(values), 3) + t.AssertIN("1", values) + t.AssertIN("2", values) + t.AssertIN("3", values) + }) + + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int]() + values := m.Values() + t.Assert(len(values), 0) + }) +} + +func Test_ListKVMap_Size(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, string]() + t.Assert(m.Size(), 0) + + m.Set("a", "1") + t.Assert(m.Size(), 1) + + m.Set("b", "2") + t.Assert(m.Size(), 2) + + m.Remove("a") + t.Assert(m.Size(), 1) + + m.Clear() + t.Assert(m.Size(), 0) + }) +} + +func Test_ListKVMap_IsEmpty(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, string]() + t.Assert(m.IsEmpty(), true) + + m.Set("a", "1") + t.Assert(m.IsEmpty(), false) + + m.Remove("a") + t.Assert(m.IsEmpty(), true) + }) +} + +func Test_ListKVMap_Clear(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string]string{"a": "1", "b": "2", "c": "3"}) + t.Assert(m.Size(), 3) + + m.Clear() + t.Assert(m.Size(), 0) + t.Assert(m.IsEmpty(), true) + t.Assert(m.Get("a"), "") + }) +} + +func Test_ListKVMap_Map(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string]string{"a": "1", "b": "2"}) + data := m.Map() + t.Assert(data["a"], "1") + t.Assert(data["b"], "2") + t.Assert(len(data), 2) + }) +} + +func Test_ListKVMap_MapStrAny(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string]int{"a": 1, "b": 2}) + data := m.MapStrAny() + t.Assert(len(data), 2) + t.Assert(data["a"], 1) + t.Assert(data["b"], 2) + }) + + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[int]string{1: "a", 2: "b"}) + data := m.MapStrAny() + t.Assert(len(data), 2) + t.Assert(data["1"], "a") + t.Assert(data["2"], "b") + }) +} + +func Test_ListKVMap_FilterEmpty(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string]string{"a": "", "b": "2", "c": "3"}) + t.Assert(m.Size(), 3) + + m.FilterEmpty() + t.Assert(m.Size(), 2) + t.Assert(m.Contains("a"), false) + t.Assert(m.Contains("b"), true) + }) + + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string]int{"a": 0, "b": 1, "c": 2}) + t.Assert(m.Size(), 3) + + m.FilterEmpty() + t.Assert(m.Size(), 2) + t.Assert(m.Contains("a"), false) + }) +} + +func Test_ListKVMap_GetOrSet(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, string]() + + v := m.GetOrSet("a", "1") + t.Assert(v, "1") + t.Assert(m.Get("a"), "1") + + v = m.GetOrSet("a", "10") + t.Assert(v, "1") + t.Assert(m.Get("a"), "1") + }) + + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string]int{"a": 10}) + + v := m.GetOrSet("a", 20) + t.Assert(v, 10) + + v = m.GetOrSet("b", 30) + t.Assert(v, 30) + t.Assert(m.Get("b"), 30) + }) +} + +func Test_ListKVMap_GetOrSetFunc(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, string]() + + v := m.GetOrSetFunc("a", func() string { return "1" }) + t.Assert(v, "1") + + v = m.GetOrSetFunc("a", func() string { return "10" }) + t.Assert(v, "1") + + v = m.GetOrSetFunc("b", func() string { return "2" }) + t.Assert(v, "2") + }) +} + +func Test_ListKVMap_GetOrSetFuncLock(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int]() + counter := 0 + + v := m.GetOrSetFuncLock("a", func() int { + counter++ + return 10 + }) + t.Assert(v, 10) + t.Assert(counter, 1) + + v = m.GetOrSetFuncLock("a", func() int { + counter++ + return 20 + }) + t.Assert(v, 10) + t.Assert(counter, 1) + }) +} + +func Test_ListKVMap_SetIfNotExist(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, string]() + + ok := m.SetIfNotExist("a", "1") + t.Assert(ok, true) + t.Assert(m.Get("a"), "1") + + ok = m.SetIfNotExist("a", "10") + t.Assert(ok, false) + t.Assert(m.Get("a"), "1") + }) +} + +func Test_ListKVMap_SetIfNotExistFunc(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int]() + + ok := m.SetIfNotExistFunc("a", func() int { return 10 }) + t.Assert(ok, true) + t.Assert(m.Get("a"), 10) + + ok = m.SetIfNotExistFunc("a", func() int { return 20 }) + t.Assert(ok, false) + t.Assert(m.Get("a"), 10) + }) +} + +func Test_ListKVMap_SetIfNotExistFuncLock(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, string]() + counter := 0 + + ok := m.SetIfNotExistFuncLock("a", func() string { + counter++ + return "1" + }) + t.Assert(ok, true) + t.Assert(counter, 1) + + ok = m.SetIfNotExistFuncLock("a", func() string { + counter++ + return "2" + }) + t.Assert(ok, false) + t.Assert(counter, 1) + }) +} + +func Test_ListKVMap_GetVar(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string]string{"a": "1", "b": "2"}) + + v := m.GetVar("a") + t.AssertNE(v, nil) + t.Assert(v.Val(), "1") + + v = m.GetVar("c") + t.Assert(v.Val(), nil) + }) +} + +func Test_ListKVMap_GetVarOrSet(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, string]() + + v := m.GetVarOrSet("a", "1") + t.AssertNE(v, nil) + t.Assert(v.Val(), "1") + + v = m.GetVarOrSet("a", "10") + t.Assert(v.Val(), "1") + }) +} + +func Test_ListKVMap_GetVarOrSetFunc(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int]() + + v := m.GetVarOrSetFunc("a", func() int { return 10 }) + t.AssertNE(v, nil) + t.Assert(v.Val(), 10) + + v = m.GetVarOrSetFunc("a", func() int { return 20 }) + t.Assert(v.Val(), 10) + }) +} + +func Test_ListKVMap_GetVarOrSetFuncLock(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, string]() + + v := m.GetVarOrSetFuncLock("a", func() string { return "1" }) + t.AssertNE(v, nil) + t.Assert(v.Val(), "1") + + v = m.GetVarOrSetFuncLock("a", func() string { return "10" }) + t.Assert(v.Val(), "1") + }) +} + +func Test_ListKVMap_Iterator(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + data := map[string]string{"a": "1", "b": "2", "c": "3"} + m := gmap.NewListKVMapFrom(data) + + count := 0 + m.Iterator(func(k string, v string) bool { + t.Assert(data[k], v) + count++ + return true + }) + t.Assert(count, 3) + }) + + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[int]string{1: "a", 2: "b", 3: "c"}) + + count := 0 + m.Iterator(func(k int, v string) bool { + count++ + return count < 2 + }) + t.Assert(count, 2) + }) +} + +func Test_ListKVMap_IteratorAsc(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, string]() + m.Set("k1", "v1") + m.Set("k2", "v2") + m.Set("k3", "v3") + + var keys []string + var values []string + m.IteratorAsc(func(k string, v string) bool { + keys = append(keys, k) + values = append(values, v) + return true + }) + t.Assert(keys, g.Slice{"k1", "k2", "k3"}) + t.Assert(values, g.Slice{"v1", "v2", "v3"}) + }) +} + +func Test_ListKVMap_IteratorDesc(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, string]() + m.Set("k1", "v1") + m.Set("k2", "v2") + m.Set("k3", "v3") + + var keys []string + var values []string + m.IteratorDesc(func(k string, v string) bool { + keys = append(keys, k) + values = append(values, v) + return true + }) + t.Assert(keys, g.Slice{"k3", "k2", "k1"}) + t.Assert(values, g.Slice{"v3", "v2", "v1"}) + }) +} + +func Test_ListKVMap_Replace(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string]string{"a": "1", "b": "2"}) + t.Assert(m.Size(), 2) + + m.Replace(map[string]string{"x": "10", "y": "20", "z": "30"}) + t.Assert(m.Size(), 3) + t.Assert(m.Get("a"), "") + t.Assert(m.Get("x"), "10") + }) +} + +func Test_ListKVMap_Clone(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string]string{"a": "1", "b": "2"}) + m2 := m.Clone() + + t.Assert(m2.Get("a"), "1") + t.Assert(m2.Get("b"), "2") + t.Assert(m2.Size(), 2) + + m.Set("a", "10") + t.Assert(m2.Get("a"), "1") + }) + + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string]int{"a": 1, "b": 2}, false) + m2 := m.Clone(true) + + t.Assert(m2.Size(), 2) + }) +} + +func Test_ListKVMap_Flip(t *testing.T) { + // Test with same type for key and value (string -> string) + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string]string{"a": "1", "b": "2", "c": "3"}) + err := m.Flip() + t.AssertNil(err) + + t.Assert(m.Get("1"), "a") + t.Assert(m.Get("2"), "b") + t.Assert(m.Get("3"), "c") + }) + + // Test with same type for key and value (int -> int) + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[int]int{1: 10, 2: 20}) + err := m.Flip() + t.AssertNil(err) + + t.Assert(m.Get(10), 1) + t.Assert(m.Get(20), 2) + }) +} + +func Test_ListKVMap_Merge(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m1 := gmap.NewListKVMapFrom(map[string]string{"a": "1"}) + m2 := gmap.NewListKVMapFrom(map[string]string{"b": "2", "c": "3"}) + + m1.Merge(m2) + t.Assert(m1.Size(), 3) + t.Assert(m1.Get("a"), "1") + t.Assert(m1.Get("b"), "2") + t.Assert(m1.Get("c"), "3") + }) + + gtest.C(t, func(t *gtest.T) { + m1 := gmap.NewListKVMap[string, int]() + m2 := gmap.NewListKVMapFrom(map[string]int{"a": 10, "b": 20}) + + m1.Merge(m2) + t.Assert(m1.Size(), 2) + t.Assert(m1.Get("a"), 10) + }) + + gtest.C(t, func(t *gtest.T) { + m1 := gmap.NewListKVMapFrom(map[string]string{"a": "1"}) + m2 := gmap.NewListKVMapFrom(map[string]string{"a": "10", "b": "2"}) + + m1.Merge(m2) + t.Assert(m1.Get("a"), "10") + }) +} + +func Test_ListKVMap_Merge_Self(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string]string{"a": "1", "b": "2"}) + m.Merge(m) + t.Assert(m.Size(), 2) + t.Assert(m.Get("a"), "1") + t.Assert(m.Get("b"), "2") + }) +} + +func Test_ListKVMap_String(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string]string{"a": "1"}) + s := m.String() + t.AssertNE(s, "") + t.AssertIN("a", s) + }) + + gtest.C(t, func(t *gtest.T) { + var m *gmap.ListKVMap[string, string] + s := m.String() + t.Assert(s, "") + }) +} + +func Test_ListKVMap_MarshalJSON(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int]() + m.Set("a", 1) + m.Set("b", 2) + b, err := json.Marshal(m) + t.AssertNil(err) + t.AssertNE(b, nil) + + var data map[string]int + err = json.Unmarshal(b, &data) + t.AssertNil(err) + t.Assert(data["a"], 1) + t.Assert(data["b"], 2) + }) + + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom[string, string](nil) + b, err := m.MarshalJSON() + t.AssertNil(err) + t.Assert(string(b), "{}") + }) +} + +func Test_ListKVMap_UnmarshalJSON(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int]() + data := []byte(`{"a":1,"b":2,"c":3}`) + + err := json.UnmarshalUseNumber(data, m) + t.AssertNil(err) + t.Assert(m.Get("a"), 1) + t.Assert(m.Get("b"), 2) + t.Assert(m.Get("c"), 3) + }) + + gtest.C(t, func(t *gtest.T) { + var m gmap.ListKVMap[string, string] + data := []byte(`{"x":"10","y":"20"}`) + + err := json.UnmarshalUseNumber(data, &m) + t.AssertNil(err) + t.Assert(m.Get("x"), "10") + t.Assert(m.Get("y"), "20") + }) +} + +func Test_ListKVMap_UnmarshalValue(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, string]() + err := m.UnmarshalValue(map[string]any{ + "a": "1", + "b": "2", + }) + t.AssertNil(err) + t.Assert(m.Get("a"), "1") + t.Assert(m.Get("b"), "2") + }) +} + +func Test_ListKVMap_DeepCopy(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string][]string{ + "a": {"1", "2"}, + "b": {"3", "4"}, + }) + + n := m.DeepCopy().(*gmap.ListKVMap[string, []string]) + t.Assert(n.Size(), 2) + t.Assert(n.Get("a"), []string{"1", "2"}) + + // Modifying original doesn't affect copy + m.Get("a")[0] = "10" + t.Assert(n.Get("a")[0], "1") + }) + + gtest.C(t, func(t *gtest.T) { + var m *gmap.ListKVMap[string, int] + n := m.DeepCopy() + t.AssertNil(n) + }) +} + +func Test_ListKVMap_Order(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, string]() + m.Set("k1", "v1") + m.Set("k2", "v2") + m.Set("k3", "v3") + t.Assert(m.Keys(), g.Slice{"k1", "k2", "k3"}) + t.Assert(m.Values(), g.Slice{"v1", "v2", "v3"}) + }) +} + +func Test_ListKVMap_Json_Sequence(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int32]() + for i := 'z'; i >= 'a'; i-- { + m.Set(string(i), i) + } + b, err := json.Marshal(m) + t.AssertNil(err) + t.Assert(b, `{"z":122,"y":121,"x":120,"w":119,"v":118,"u":117,"t":116,"s":115,"r":114,"q":113,"p":112,"o":111,"n":110,"m":109,"l":108,"k":107,"j":106,"i":105,"h":104,"g":103,"f":102,"e":101,"d":100,"c":99,"b":98,"a":97}`) + }) + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int32]() + for i := 'a'; i <= 'z'; i++ { + m.Set(string(i), i) + } + b, err := json.Marshal(m) + t.AssertNil(err) + t.Assert(b, `{"a":97,"b":98,"c":99,"d":100,"e":101,"f":102,"g":103,"h":104,"i":105,"j":106,"k":107,"l":108,"m":109,"n":110,"o":111,"p":112,"q":113,"r":114,"s":115,"t":116,"u":117,"v":118,"w":119,"x":120,"y":121,"z":122}`) + }) +} + +// Test Set with nil data +func Test_ListKVMap_Set_NilData(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom[string, string](nil) + m.Set("a", "1") + t.Assert(m.Get("a"), "1") + t.Assert(m.Size(), 1) + }) +} + +// Test Sets with nil data +func Test_ListKVMap_Sets_NilData(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom[string, string](nil) + m.Sets(map[string]string{"a": "1", "b": "2"}) + t.Assert(m.Get("a"), "1") + t.Assert(m.Get("b"), "2") + t.Assert(m.Size(), 2) + }) +} + +// Test GetOrSet with nil value (using any type) +func Test_ListKVMap_GetOrSet_NilValue(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, any]() + v := m.GetOrSet("a", nil) + t.Assert(v, nil) + // nil interface value should not be stored + t.Assert(m.Contains("a"), false) + }) +} + +// Test Search with nil data +func Test_ListKVMap_Search_NilData(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom[string, string](nil) + v, found := m.Search("a") + t.Assert(found, false) + t.Assert(v, "") + }) +} + +// Test Get with nil data +func Test_ListKVMap_Get_NilData(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom[string, int](nil) + v := m.Get("a") + t.Assert(v, 0) + }) +} + +// Test Contains with nil data +func Test_ListKVMap_Contains_NilData(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom[string, string](nil) + t.Assert(m.Contains("a"), false) + }) +} + +// Test Remove with nil data +func Test_ListKVMap_Remove_NilData(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom[string, string](nil) + v := m.Remove("a") + t.Assert(v, "") + }) +} + +// Test Removes with nil data +func Test_ListKVMap_Removes_NilData(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom[string, string](nil) + m.Removes([]string{"a", "b"}) + t.Assert(m.Size(), 0) + }) +} + +// Test Pops with size 0 +func Test_ListKVMap_Pops_ZeroSize(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string]string{"a": "1", "b": "2"}) + popped := m.Pops(0) + t.AssertNil(popped) + t.Assert(m.Size(), 2) + }) +} + +// Test Iterator early break +func Test_ListKVMap_Iterator_Break(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom(map[string]string{"a": "1", "b": "2", "c": "3"}) + count := 0 + m.Iterator(func(k string, v string) bool { + count++ + return false // Break immediately + }) + t.Assert(count, 1) + }) +} + +// Test IteratorAsc with nil list +func Test_ListKVMap_IteratorAsc_NilList(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom[string, string](nil) + count := 0 + m.IteratorAsc(func(k string, v string) bool { + count++ + return true + }) + t.Assert(count, 0) + }) +} + +// Test IteratorDesc with nil list +func Test_ListKVMap_IteratorDesc_NilList(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom[string, string](nil) + count := 0 + m.IteratorDesc(func(k string, v string) bool { + count++ + return true + }) + t.Assert(count, 0) + }) +} + +// Test Map with nil list +func Test_ListKVMap_Map_NilList(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom[string, string](nil) + data := m.Map() + t.Assert(data, "{}") + }) +} + +// Test MapStrAny with nil list +func Test_ListKVMap_MapStrAny_NilList(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom[string, string](nil) + data := m.MapStrAny() + t.Assert(data, "{}") + }) +} + +// Test FilterEmpty with nil list +func Test_ListKVMap_FilterEmpty_NilList(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom[string, string](nil) + m.FilterEmpty() + t.Assert(m.Size(), 0) + }) +} + +// Test Keys with nil list +func Test_ListKVMap_Keys_NilList(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom[string, string](nil) + keys := m.Keys() + t.Assert(len(keys), 0) + }) +} + +// Test Values with nil list +func Test_ListKVMap_Values_NilList(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom[string, string](nil) + values := m.Values() + t.Assert(len(values), 0) + }) +} + +// Test DeepCopy with nil list +func Test_ListKVMap_DeepCopy_NilList(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom[string, string](nil) + n := m.DeepCopy().(*gmap.ListKVMap[string, string]) + t.Assert(n.Size(), 0) + }) +} + +// Concurrent safety tests +func Test_ListKVMap_Concurrent_Safe(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int](true) + ch := make(chan int, 10) + + // Concurrent writes + for i := 0; i < 10; i++ { + go func(idx int) { + m.Set(gconv.String(idx), idx) + ch <- 1 + }(i) + } + + for i := 0; i < 10; i++ { + <-ch + } + + t.Assert(m.Size(), 10) + }) +} + +func Test_ListKVMap_Concurrent_RW(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int](true) + m.Sets(map[string]int{"a": 1, "b": 2, "c": 3}) + + ch := make(chan int, 20) + + // Concurrent reads and writes + for i := 0; i < 10; i++ { + go func() { + _ = m.Get("a") + ch <- 1 + }() + } + + for i := 0; i < 10; i++ { + go func(idx int) { + m.Set(gconv.String(idx), idx) + ch <- 1 + }(i) + } + + for i := 0; i < 20; i++ { + <-ch + } + + t.Assert(m.Size(), 13) + }) +} + +// Test concurrent GetOrSet +func Test_ListKVMap_Concurrent_GetOrSet(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int](true) + ch := make(chan int, 100) + + for i := 0; i < 100; i++ { + go func(idx int) { + m.GetOrSet("key", idx) + ch <- 1 + }(i) + } + + for i := 0; i < 100; i++ { + <-ch + } + + // Only one value should be set + t.Assert(m.Size(), 1) + t.Assert(m.Contains("key"), true) + }) +} + +// Test concurrent SetIfNotExist +func Test_ListKVMap_Concurrent_SetIfNotExist(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int](true) + successCount := 0 + ch := make(chan bool, 100) + + for i := 0; i < 100; i++ { + go func(idx int) { + ok := m.SetIfNotExist("key", idx) + ch <- ok + }(i) + } + + for i := 0; i < 100; i++ { + if <-ch { + successCount++ + } + } + + // Only one goroutine should succeed + t.Assert(successCount, 1) + t.Assert(m.Size(), 1) + }) +} + +// Test concurrent GetOrSetFunc +func Test_ListKVMap_Concurrent_GetOrSetFunc(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int](true) + ch := make(chan int, 100) + + for i := 0; i < 100; i++ { + go func(idx int) { + m.GetOrSetFunc("key", func() int { + return idx + }) + ch <- 1 + }(i) + } + + for i := 0; i < 100; i++ { + <-ch + } + + t.Assert(m.Size(), 1) + }) +} + +// Test concurrent GetOrSetFuncLock +func Test_ListKVMap_Concurrent_GetOrSetFuncLock(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int](true) + ch := make(chan int, 100) + + for i := 0; i < 100; i++ { + go func(idx int) { + m.GetOrSetFuncLock("key", func() int { + return idx + }) + ch <- 1 + }(i) + } + + for i := 0; i < 100; i++ { + <-ch + } + + t.Assert(m.Size(), 1) + }) +} + +// Test concurrent access to doSetWithLockCheck +func Test_ListKVMap_DoSetWithLockCheck_Concurrent(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int](true) + var wg sync.WaitGroup + results := make([]int, 100) + + for i := 0; i < 100; i++ { + wg.Add(1) + go func(idx int) { + defer wg.Done() + v := m.GetOrSet("key", idx) + results[idx] = v + }(i) + } + wg.Wait() + + // All results should be the same (the first value that was set) + firstValue := results[0] + for _, v := range results { + t.Assert(v, firstValue) + } + }) +} + +// Test UnmarshalJSON with invalid JSON +func Test_ListKVMap_UnmarshalJSON_InvalidJSON(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int]() + err := m.UnmarshalJSON([]byte(`{invalid json}`)) + t.AssertNE(err, nil) + }) +} + +// Test MarshalJSON error handling +func Test_ListKVMap_MarshalJSON_Error(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, string]() + m.Set("a", "1") + b, err := m.MarshalJSON() + t.AssertNil(err) + t.Assert(string(b), `{"a":"1"}`) + }) +} + +// Test empty map operations +func Test_ListKVMap_EmptyMapOperations(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int]() + + // Test Keys on empty map + keys := m.Keys() + t.Assert(len(keys), 0) + + // Test Values on empty map + values := m.Values() + t.Assert(len(values), 0) + + // Test MapStrAny on empty map + strAny := m.MapStrAny() + t.Assert(len(strAny), 0) + }) +} + +// Test FilterEmpty with various empty values +func Test_ListKVMap_FilterEmpty_Various(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, any]() + m.Set("nil", nil) + m.Set("zero", 0) + m.Set("empty_string", "") + m.Set("false", false) + m.Set("valid", "value") + m.Set("empty_slice", []int{}) + m.Set("empty_map", map[string]int{}) + + t.Assert(m.Size(), 7) + m.FilterEmpty() + t.Assert(m.Size(), 1) + t.Assert(m.Contains("valid"), true) + }) +} + +// Test Clone with different safe modes +func Test_ListKVMap_Clone_SafeMode(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + // Clone unsafe map to safe + m := gmap.NewListKVMapFrom(map[string]int{"a": 1}, false) + m2 := m.Clone(true) + t.Assert(m2.Get("a"), 1) + }) + + gtest.C(t, func(t *gtest.T) { + // Clone safe map to unsafe + m := gmap.NewListKVMapFrom(map[string]int{"a": 1}, true) + m2 := m.Clone(false) + t.Assert(m2.Get("a"), 1) + }) + + gtest.C(t, func(t *gtest.T) { + // Clone with inherited safe mode + m := gmap.NewListKVMapFrom(map[string]int{"a": 1}, true) + m2 := m.Clone() + t.Assert(m2.Get("a"), 1) + }) +} + +// Test SetIfNotExistFunc returning false when key exists +func Test_ListKVMap_SetIfNotExistFunc_KeyExists(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, int]() + m.Set("a", 1) + + called := false + ok := m.SetIfNotExistFunc("a", func() int { + called = true + return 2 + }) + t.Assert(ok, false) + t.Assert(called, false) // Function should not be called if key exists + t.Assert(m.Get("a"), 1) + }) +} + +// Test struct with ListKVMap for UnmarshalValue +func Test_ListKVMap_UnmarshalValue_Struct(t *testing.T) { + type V struct { + Name string + Map *gmap.ListKVMap[string, string] + } + // JSON + gtest.C(t, func(t *gtest.T) { + var v *V + err := gconv.Struct(map[string]any{ + "name": "john", + "map": []byte(`{"1":"v1","2":"v2"}`), + }, &v) + t.AssertNil(err) + t.Assert(v.Name, "john") + t.Assert(v.Map.Size(), 2) + t.Assert(v.Map.Get("1"), "v1") + t.Assert(v.Map.Get("2"), "v2") + }) + // Map + gtest.C(t, func(t *gtest.T) { + var v *V + err := gconv.Struct(map[string]any{ + "name": "john", + "map": g.MapStrStr{ + "1": "v1", + "2": "v2", + }, + }, &v) + t.AssertNil(err) + t.Assert(v.Name, "john") + t.Assert(v.Map.Size(), 2) + t.Assert(v.Map.Get("1"), "v1") + t.Assert(v.Map.Get("2"), "v2") + }) +} + +// Test GetOrSetFuncLock with nil data +func Test_ListKVMap_GetOrSetFuncLock_NilData(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom[string, string](nil) + v := m.GetOrSetFuncLock("a", func() string { return "1" }) + t.Assert(v, "1") + t.Assert(m.Get("a"), "1") + }) + + // Test with nil value (using any type) + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMap[string, any]() + v := m.GetOrSetFuncLock("a", func() any { return nil }) + t.Assert(v, nil) + // nil interface value should not be stored + t.Assert(m.Contains("a"), false) + }) +} + +// Test SetIfNotExistFuncLock with nil data +func Test_ListKVMap_SetIfNotExistFuncLock_NilData(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom[string, string](nil) + ok := m.SetIfNotExistFuncLock("a", func() string { return "1" }) + t.Assert(ok, true) + t.Assert(m.Get("a"), "1") + }) +} + +// Test Merge with nil data +func Test_ListKVMap_Merge_NilData(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom[string, string](nil) + m2 := gmap.NewListKVMapFrom(map[string]string{"a": "1", "b": "2"}) + m.Merge(m2) + t.Assert(m.Size(), 2) + t.Assert(m.Get("a"), "1") + t.Assert(m.Get("b"), "2") + }) +} + +// Test UnmarshalJSON with nil data +func Test_ListKVMap_UnmarshalJSON_NilData(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom[string, string](nil) + err := m.UnmarshalJSON([]byte(`{"a":"1","b":"2"}`)) + t.AssertNil(err) + t.Assert(m.Size(), 2) + t.Assert(m.Get("a"), "1") + t.Assert(m.Get("b"), "2") + }) +} + +// Test UnmarshalValue with nil data +func Test_ListKVMap_UnmarshalValue_NilData(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewListKVMapFrom[string, string](nil) + err := m.UnmarshalValue(map[string]any{"a": "1", "b": "2"}) + t.AssertNil(err) + t.Assert(m.Size(), 2) + t.Assert(m.Get("a"), "1") + t.Assert(m.Get("b"), "2") + }) +} From 3e1443379642b10bb69755e55762e03734a2da54 Mon Sep 17 00:00:00 2001 From: hailaz <739476267@qq.com> Date: Sat, 29 Nov 2025 20:39:25 +0800 Subject: [PATCH 8/8] =?UTF-8?q?feat(container/gmap):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=20SetIfNotExist=20=E5=92=8C=20SetIfNotExistFunc=20=E6=96=B9?= =?UTF-8?q?=E6=B3=95=EF=BC=8C=E6=B7=BB=E5=8A=A0=E9=94=81=E6=9C=BA=E5=88=B6?= =?UTF-8?q?=E4=BB=A5=E7=A1=AE=E4=BF=9D=E7=BA=BF=E7=A8=8B=E5=AE=89=E5=85=A8?= =?UTF-8?q?=20test(container/gmap):=20=E6=9B=B4=E6=96=B0=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E7=94=A8=E4=BE=8B=E4=BB=A5=E9=AA=8C=E8=AF=81=E7=A9=BA=E6=98=A0?= =?UTF-8?q?=E5=B0=84=E7=9A=84=E8=A1=8C=E4=B8=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- container/gmap/gmap_list_k_v_map.go | 37 ++++++++++++++----- .../gmap/gmap_z_unit_list_k_v_map_test.go | 4 +- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/container/gmap/gmap_list_k_v_map.go b/container/gmap/gmap_list_k_v_map.go index f3dc554b4f8..6bfe2a7e95b 100644 --- a/container/gmap/gmap_list_k_v_map.go +++ b/container/gmap/gmap_list_k_v_map.go @@ -360,21 +360,40 @@ func (m *ListKVMap[K, V]) GetVarOrSetFuncLock(key K, f func() V) *gvar.Var { // SetIfNotExist sets `value` to the map if the `key` does not exist, and then returns true. // It returns false if `key` exists, and `value` would be ignored. func (m *ListKVMap[K, V]) SetIfNotExist(key K, value V) bool { - if !m.Contains(key) { - m.doSetWithLockCheck(key, value) - return true + m.mu.Lock() + defer m.mu.Unlock() + + if m.data == nil { + m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]]) + m.list = glist.NewT[*gListKVMapNode[K, V]]() } - return false + if _, ok := m.data[key]; ok { + return false + } + if any(value) != nil { + m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value}) + } + return true } // SetIfNotExistFunc sets value with return value of callback function `f`, and then returns true. // It returns false if `key` exists, and `value` would be ignored. func (m *ListKVMap[K, V]) SetIfNotExistFunc(key K, f func() V) bool { - if !m.Contains(key) { - m.doSetWithLockCheck(key, f()) - return true + m.mu.Lock() + defer m.mu.Unlock() + + if m.data == nil { + m.data = make(map[K]*glist.TElement[*gListKVMapNode[K, V]]) + m.list = glist.NewT[*gListKVMapNode[K, V]]() + } + if _, ok := m.data[key]; ok { + return false } - return false + value := f() + if any(value) != nil { + m.data[key] = m.list.PushBack(&gListKVMapNode[K, V]{key, value}) + } + return true } // SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true. @@ -549,7 +568,7 @@ func (m *ListKVMap[K, V]) String() string { // MarshalJSON implements the interface MarshalJSON for json.Marshal. func (m ListKVMap[K, V]) MarshalJSON() (jsonBytes []byte, err error) { if m.data == nil { - return []byte("null"), nil + return []byte("{}"), nil } buffer := bytes.NewBuffer(nil) buffer.WriteByte('{') diff --git a/container/gmap/gmap_z_unit_list_k_v_map_test.go b/container/gmap/gmap_z_unit_list_k_v_map_test.go index 7495438c462..7714f532f92 100644 --- a/container/gmap/gmap_z_unit_list_k_v_map_test.go +++ b/container/gmap/gmap_z_unit_list_k_v_map_test.go @@ -920,7 +920,7 @@ func Test_ListKVMap_Map_NilList(t *testing.T) { gtest.C(t, func(t *gtest.T) { m := gmap.NewListKVMapFrom[string, string](nil) data := m.Map() - t.Assert(data, "{}") + t.Assert(len(data), 0) }) } @@ -929,7 +929,7 @@ func Test_ListKVMap_MapStrAny_NilList(t *testing.T) { gtest.C(t, func(t *gtest.T) { m := gmap.NewListKVMapFrom[string, string](nil) data := m.MapStrAny() - t.Assert(data, "{}") + t.Assert(len(data), 0) }) }