From 132a5ab9a3cad1823c266955ddf8ce3c06f5ba6d Mon Sep 17 00:00:00 2001 From: Hunk Zhu <54zhua@gmail.com> Date: Fri, 28 Nov 2025 21:41:30 +0800 Subject: [PATCH] feat(container/gmap): add generic map feature (#4484) add hash kvmap and let other hash map base on it. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: hailaz <739476267@qq.com> --- .github/workflows/scripts/ci-main.sh | 4 +- container/gmap/gmap_hash_any_any_map.go | 422 ++--- container/gmap/gmap_hash_int_any_map.go | 425 ++--- container/gmap/gmap_hash_int_int_map.go | 407 +--- container/gmap/gmap_hash_int_str_map.go | 405 +--- container/gmap/gmap_hash_k_v_map.go | 582 ++++++ container/gmap/gmap_hash_str_any_map.go | 406 ++-- container/gmap/gmap_hash_str_int_map.go | 406 +--- container/gmap/gmap_hash_str_str_map.go | 397 +--- container/gmap/gmap_list_map.go | 2 +- .../gmap/gmap_z_unit_hash_any_any_test.go | 46 + .../gmap/gmap_z_unit_hash_str_any_test.go | 36 + container/gmap/gmap_z_unit_k_v_map_test.go | 1632 +++++++++++++++++ 13 files changed, 2970 insertions(+), 2200 deletions(-) create mode 100644 container/gmap/gmap_hash_k_v_map.go create mode 100644 container/gmap/gmap_z_unit_k_v_map_test.go diff --git a/.github/workflows/scripts/ci-main.sh b/.github/workflows/scripts/ci-main.sh index f721683e8..f06de5009 100755 --- a/.github/workflows/scripts/ci-main.sh +++ b/.github/workflows/scripts/ci-main.sh @@ -52,13 +52,13 @@ for file in `find . -name go.mod`; do # test with coverage if [ "${coverage}" = "coverage" ]; then - go test ./... -race -coverprofile=coverage.out -covermode=atomic -coverpkg=./...,github.com/gogf/gf/... || exit 1 + go test ./... -count=1 -race -coverprofile=coverage.out -covermode=atomic -coverpkg=./...,github.com/gogf/gf/... || exit 1 if grep -q "/gogf/gf/.*/v2" go.mod; then sed -i "s/gogf\/gf\(\/.*\)\/v2/gogf\/gf\/v2\1/g" coverage.out fi else - go test ./... -race || exit 1 + go test ./... -count=1 -race || exit 1 fi cd - diff --git a/container/gmap/gmap_hash_any_any_map.go b/container/gmap/gmap_hash_any_any_map.go index 9d50a4490..09e5d5297 100644 --- a/container/gmap/gmap_hash_any_any_map.go +++ b/container/gmap/gmap_hash_any_any_map.go @@ -7,245 +7,143 @@ package gmap import ( - "reflect" + "sync" "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" ) // AnyAnyMap wraps map type `map[any]any` and provides more map features. type AnyAnyMap struct { - mu rwmutex.RWMutex - data map[any]any + *KVMap[any, any] + once sync.Once } // NewAnyAnyMap creates and returns an empty hash map. // The parameter `safe` is used to specify whether using map in concurrent-safety, // which is false in default. func NewAnyAnyMap(safe ...bool) *AnyAnyMap { - return &AnyAnyMap{ - mu: rwmutex.Create(safe...), - data: make(map[any]any), + m := &AnyAnyMap{ + KVMap: NewKVMap[any, any](safe...), } + return m } // NewAnyAnyMapFrom creates and returns a hash 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 NewAnyAnyMapFrom(data map[any]any, safe ...bool) *AnyAnyMap { - return &AnyAnyMap{ - mu: rwmutex.Create(safe...), - data: data, + m := &AnyAnyMap{ + KVMap: NewKVMapFrom(data, safe...), } + return m +} + +// lazyInit lazily initializes the map. +func (m *AnyAnyMap) lazyInit() { + m.once.Do(func() { + if m.KVMap == nil { + m.KVMap = NewKVMap[any, any](false) + } + }) } // Iterator iterates the hash map readonly with custom callback function `f`. // If `f` returns true, then it continues iterating; or false to stop. func (m *AnyAnyMap) Iterator(f func(k any, v any) bool) { - for k, v := range m.Map() { - if !f(k, v) { - break - } - } + m.lazyInit() + m.KVMap.Iterator(f) } // Clone returns a new hash map with copy of current map data. func (m *AnyAnyMap) Clone(safe ...bool) *AnyAnyMap { - return NewFrom(m.MapCopy(), safe...) + m.lazyInit() + return NewAnyAnyMapFrom(m.MapCopy(), safe...) } // Map returns the underlying data map. // Note that, if it's in concurrent-safe usage, it returns a copy of underlying data, // or else a pointer to the underlying data. func (m *AnyAnyMap) Map() map[any]any { - m.mu.RLock() - defer m.mu.RUnlock() - if !m.mu.IsSafe() { - return m.data - } - data := make(map[any]any, len(m.data)) - for k, v := range m.data { - data[k] = v - } - return data + m.lazyInit() + return m.KVMap.Map() } // MapCopy returns a shallow copy of the underlying data of the hash map. func (m *AnyAnyMap) MapCopy() map[any]any { - m.mu.RLock() - defer m.mu.RUnlock() - data := make(map[any]any, len(m.data)) - for k, v := range m.data { - data[k] = v - } - return data + m.lazyInit() + return m.KVMap.MapCopy() } // MapStrAny returns a copy of the underlying data of the map as map[string]any. func (m *AnyAnyMap) MapStrAny() map[string]any { - m.mu.RLock() - defer m.mu.RUnlock() - data := make(map[string]any, len(m.data)) - for k, v := range m.data { - data[gconv.String(k)] = v - } - return data + m.lazyInit() + return m.KVMap.MapStrAny() } // FilterEmpty deletes all key-value pair of which the value is empty. // Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty. func (m *AnyAnyMap) FilterEmpty() { - m.mu.Lock() - defer m.mu.Unlock() - for k, v := range m.data { - if empty.IsEmpty(v) { - delete(m.data, k) - } - } + m.lazyInit() + m.KVMap.FilterEmpty() } // FilterNil deletes all key-value pair of which the value is nil. func (m *AnyAnyMap) FilterNil() { - m.mu.Lock() - defer m.mu.Unlock() - for k, v := range m.data { - if empty.IsNil(v) { - delete(m.data, k) - } - } + m.lazyInit() + m.KVMap.FilterNil() } // Set sets key-value to the hash map. func (m *AnyAnyMap) Set(key any, value any) { - m.mu.Lock() - if m.data == nil { - m.data = make(map[any]any) - } - m.data[key] = value - m.mu.Unlock() + m.lazyInit() + m.KVMap.Set(key, value) } // Sets batch sets key-values to the hash map. func (m *AnyAnyMap) Sets(data map[any]any) { - m.mu.Lock() - if m.data == nil { - m.data = data - } else { - for k, v := range data { - m.data[k] = v - } - } - m.mu.Unlock() + m.lazyInit() + m.KVMap.Sets(data) } // Search searches the map with given `key`. // Second return parameter `found` is true if key was found, otherwise false. func (m *AnyAnyMap) Search(key any) (value any, found bool) { - m.mu.RLock() - if m.data != nil { - value, found = m.data[key] - } - m.mu.RUnlock() - return + m.lazyInit() + return m.KVMap.Search(key) } // Get returns the value by given `key`. func (m *AnyAnyMap) Get(key any) (value any) { - m.mu.RLock() - if m.data != nil { - value = m.data[key] - } - m.mu.RUnlock() - return + m.lazyInit() + return m.KVMap.Get(key) } // Pop retrieves and deletes an item from the map. func (m *AnyAnyMap) Pop() (key, value any) { - m.mu.Lock() - defer m.mu.Unlock() - for key, value = range m.data { - delete(m.data, key) - return - } - return + m.lazyInit() + return m.KVMap.Pop() } // Pops retrieves and deletes `size` items from the map. // It returns all items if size == -1. func (m *AnyAnyMap) 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 - } - var ( - index = 0 - newMap = make(map[any]any, 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 `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 hash map, -// and its return value will be set to the map with `key`. -// -// It returns value with given `key`. -func (m *AnyAnyMap) doSetWithLockCheck(key any, value any) any { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[any]any) - } - if v, ok := m.data[key]; ok { - return v - } - if f, ok := value.(func() any); ok { - value = f() - } - if value != nil { - m.data[key] = value - } - return value + m.lazyInit() + return m.KVMap.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 *AnyAnyMap) 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.KVMap.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 *AnyAnyMap) 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.KVMap.GetOrSetFunc(key, f) } // GetOrSetFuncLock returns the value by key, @@ -255,55 +153,50 @@ func (m *AnyAnyMap) GetOrSetFunc(key any, f func() any) any { // GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f` // with mutex.Lock of the hash map. func (m *AnyAnyMap) 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.KVMap.GetOrSetFuncLock(key, f) } // GetVar returns a Var with the value by given `key`. // The returned Var is un-concurrent safe. func (m *AnyAnyMap) GetVar(key any) *gvar.Var { - return gvar.New(m.Get(key)) + m.lazyInit() + return m.KVMap.GetVar(key) } // GetVarOrSet returns a Var with result from GetOrSet. // The returned Var is un-concurrent safe. func (m *AnyAnyMap) GetVarOrSet(key any, value any) *gvar.Var { - return gvar.New(m.GetOrSet(key, value)) + m.lazyInit() + return m.KVMap.GetVarOrSet(key, value) } // GetVarOrSetFunc returns a Var with result from GetOrSetFunc. // The returned Var is un-concurrent safe. func (m *AnyAnyMap) GetVarOrSetFunc(key any, f func() any) *gvar.Var { - return gvar.New(m.GetOrSetFunc(key, f)) + m.lazyInit() + return m.KVMap.GetVarOrSetFunc(key, f) } // GetVarOrSetFuncLock returns a Var with result from GetOrSetFuncLock. // The returned Var is un-concurrent safe. func (m *AnyAnyMap) GetVarOrSetFuncLock(key any, f func() any) *gvar.Var { - return gvar.New(m.GetOrSetFuncLock(key, f)) + m.lazyInit() + return m.KVMap.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 *AnyAnyMap) SetIfNotExist(key any, value any) bool { - if !m.Contains(key) { - m.doSetWithLockCheck(key, value) - return true - } - return false + m.lazyInit() + return m.KVMap.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 *AnyAnyMap) SetIfNotExistFunc(key any, f func() any) bool { - if !m.Contains(key) { - m.doSetWithLockCheck(key, f()) - return true - } - return false + m.lazyInit() + return m.KVMap.SetIfNotExistFunc(key, f) } // SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true. @@ -312,119 +205,76 @@ func (m *AnyAnyMap) SetIfNotExistFunc(key any, f func() any) bool { // SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that // it executes function `f` with mutex.Lock of the hash map. func (m *AnyAnyMap) SetIfNotExistFuncLock(key any, f func() any) bool { - if !m.Contains(key) { - m.doSetWithLockCheck(key, f) - return true - } - return false + m.lazyInit() + return m.KVMap.SetIfNotExistFuncLock(key, f) } // Remove deletes value from map by given `key`, and return this deleted value. func (m *AnyAnyMap) Remove(key any) (value any) { - m.mu.Lock() - if m.data != nil { - var ok bool - if value, ok = m.data[key]; ok { - delete(m.data, key) - } - } - m.mu.Unlock() - return + m.lazyInit() + return m.KVMap.Remove(key) } // Removes batch deletes values of the map by keys. func (m *AnyAnyMap) Removes(keys []any) { - m.mu.Lock() - if m.data != nil { - for _, key := range keys { - delete(m.data, key) - } - } - m.mu.Unlock() + m.lazyInit() + m.KVMap.Removes(keys) } // Keys returns all keys of the map as a slice. func (m *AnyAnyMap) Keys() []any { - m.mu.RLock() - defer m.mu.RUnlock() - var ( - keys = make([]any, len(m.data)) - index = 0 - ) - for key := range m.data { - keys[index] = key - index++ - } - return keys + m.lazyInit() + return m.KVMap.Keys() } // Values returns all values of the map as a slice. func (m *AnyAnyMap) Values() []any { - m.mu.RLock() - defer m.mu.RUnlock() - var ( - values = make([]any, len(m.data)) - index = 0 - ) - for _, value := range m.data { - values[index] = value - index++ - } - return values + m.lazyInit() + return m.KVMap.Values() } // Contains checks whether a key exists. // It returns true if the `key` exists, or else false. func (m *AnyAnyMap) Contains(key any) bool { - var ok bool - m.mu.RLock() - if m.data != nil { - _, ok = m.data[key] - } - m.mu.RUnlock() - return ok + m.lazyInit() + return m.KVMap.Contains(key) } // Size returns the size of the map. func (m *AnyAnyMap) Size() int { - m.mu.RLock() - length := len(m.data) - m.mu.RUnlock() - return length + m.lazyInit() + return m.KVMap.Size() } // IsEmpty checks whether the map is empty. // It returns true if map is empty, or else false. func (m *AnyAnyMap) IsEmpty() bool { - return m.Size() == 0 + m.lazyInit() + return m.KVMap.IsEmpty() } // Clear deletes all data of the map, it will remake a new underlying data map. func (m *AnyAnyMap) Clear() { - m.mu.Lock() - m.data = make(map[any]any) - m.mu.Unlock() + m.lazyInit() + m.KVMap.Clear() } // Replace the data of the map with given `data`. func (m *AnyAnyMap) Replace(data map[any]any) { - m.mu.Lock() - m.data = data - m.mu.Unlock() + m.lazyInit() + m.KVMap.Replace(data) } // LockFunc locks writing with given callback function `f` within RWMutex.Lock. func (m *AnyAnyMap) LockFunc(f func(m map[any]any)) { - m.mu.Lock() - defer m.mu.Unlock() - f(m.data) + m.lazyInit() + m.KVMap.LockFunc(f) } // RLockFunc locks reading with given callback function `f` within RWMutex.RLock. func (m *AnyAnyMap) RLockFunc(f func(m map[any]any)) { - m.mu.RLock() - defer m.mu.RUnlock() - f(m.data) + m.lazyInit() + m.KVMap.RLockFunc(f) } // Flip exchanges key-value of the map to value-key. @@ -441,19 +291,8 @@ func (m *AnyAnyMap) Flip() { // Merge merges two hash maps. // The `other` map will be merged into the map `m`. func (m *AnyAnyMap) Merge(other *AnyAnyMap) { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = other.MapCopy() - return - } - if other != m { - other.mu.RLock() - defer other.mu.RUnlock() - } - for k, v := range other.data { - m.data[k] = v - } + m.lazyInit() + m.KVMap.Merge(other.KVMap) } // String returns the map as a string. @@ -461,79 +300,40 @@ func (m *AnyAnyMap) String() string { if m == nil { return "" } - b, _ := m.MarshalJSON() - return string(b) + m.lazyInit() + return m.KVMap.String() } // MarshalJSON implements the interface MarshalJSON for json.Marshal. func (m AnyAnyMap) MarshalJSON() ([]byte, error) { - return json.Marshal(gconv.Map(m.Map())) + m.lazyInit() + return m.KVMap.MarshalJSON() } // UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal. func (m *AnyAnyMap) UnmarshalJSON(b []byte) error { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[any]any) - } - var data map[string]any - if err := json.UnmarshalUseNumber(b, &data); err != nil { - return err - } - for k, v := range data { - m.data[k] = v - } - return nil + m.lazyInit() + return m.KVMap.UnmarshalJSON(b) } // UnmarshalValue is an interface implement which sets any type of value for map. func (m *AnyAnyMap) UnmarshalValue(value any) (err error) { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[any]any) - } - for k, v := range gconv.Map(value) { - m.data[k] = v - } - return + m.lazyInit() + return m.KVMap.UnmarshalValue(value) } // DeepCopy implements interface for deep copy of current type. func (m *AnyAnyMap) DeepCopy() any { - if m == nil { - return nil + m.lazyInit() + return &AnyAnyMap{ + KVMap: m.KVMap.DeepCopy().(*KVMap[any, any]), } - - m.mu.RLock() - defer m.mu.RUnlock() - data := make(map[any]any, len(m.data)) - for k, v := range m.data { - data[k] = deepcopy.Copy(v) - } - return NewFrom(data, m.mu.IsSafe()) } // IsSubOf checks whether the current map is a sub-map of `other`. func (m *AnyAnyMap) IsSubOf(other *AnyAnyMap) bool { - if m == other { - return true - } - m.mu.RLock() - defer m.mu.RUnlock() - other.mu.RLock() - defer other.mu.RUnlock() - for key, value := range m.data { - otherValue, ok := other.data[key] - if !ok { - return false - } - if otherValue != value { - return false - } - } - return true + m.lazyInit() + return m.KVMap.IsSubOf(other.KVMap) } // Diff compares current map `m` with map `other` and returns their different keys. @@ -541,22 +341,6 @@ func (m *AnyAnyMap) IsSubOf(other *AnyAnyMap) bool { // The returned `removedKeys` are the keys that are in map `other` but not in map `m`. // The returned `updatedKeys` are the keys that are both in map `m` and `other` but their values and not equal (`!=`). func (m *AnyAnyMap) Diff(other *AnyAnyMap) (addedKeys, removedKeys, updatedKeys []any) { - m.mu.RLock() - defer m.mu.RUnlock() - other.mu.RLock() - defer other.mu.RUnlock() - - for key := range m.data { - if _, ok := other.data[key]; !ok { - removedKeys = append(removedKeys, key) - } else if !reflect.DeepEqual(m.data[key], other.data[key]) { - updatedKeys = append(updatedKeys, key) - } - } - for key := range other.data { - if _, ok := m.data[key]; !ok { - addedKeys = append(addedKeys, key) - } - } - return + m.lazyInit() + return m.KVMap.Diff(other.KVMap) } diff --git a/container/gmap/gmap_hash_int_any_map.go b/container/gmap/gmap_hash_int_any_map.go index 5b5a4e282..5785d4bc0 100644 --- a/container/gmap/gmap_hash_int_any_map.go +++ b/container/gmap/gmap_hash_int_any_map.go @@ -8,244 +8,143 @@ package gmap import ( - "reflect" + "sync" "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" ) // IntAnyMap implements map[int]any with RWMutex that has switch. type IntAnyMap struct { - mu rwmutex.RWMutex - data map[int]any + *KVMap[int, any] + once sync.Once } // NewIntAnyMap returns an empty IntAnyMap object. // The parameter `safe` is used to specify whether using map in concurrent-safety, // which is false in default. func NewIntAnyMap(safe ...bool) *IntAnyMap { - return &IntAnyMap{ - mu: rwmutex.Create(safe...), - data: make(map[int]any), + m := &IntAnyMap{ + KVMap: NewKVMap[int, any](safe...), } + return m } // NewIntAnyMapFrom creates and returns a hash 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 NewIntAnyMapFrom(data map[int]any, safe ...bool) *IntAnyMap { - return &IntAnyMap{ - mu: rwmutex.Create(safe...), - data: data, + m := &IntAnyMap{ + KVMap: NewKVMapFrom(data, safe...), } + return m +} + +// lazyInit lazily initializes the map. +func (m *IntAnyMap) lazyInit() { + m.once.Do(func() { + if m.KVMap == nil { + m.KVMap = NewKVMap[int, any](false) + } + }) } // Iterator iterates the hash map readonly with custom callback function `f`. // If `f` returns true, then it continues iterating; or false to stop. func (m *IntAnyMap) Iterator(f func(k int, v any) bool) { - for k, v := range m.Map() { - if !f(k, v) { - break - } - } + m.lazyInit() + m.KVMap.Iterator(f) } // Clone returns a new hash map with copy of current map data. -func (m *IntAnyMap) Clone() *IntAnyMap { - return NewIntAnyMapFrom(m.MapCopy(), m.mu.IsSafe()) +func (m *IntAnyMap) Clone(safe ...bool) *IntAnyMap { + m.lazyInit() + return NewIntAnyMapFrom(m.MapCopy(), safe...) } // Map returns the underlying data map. // Note that, if it's in concurrent-safe usage, it returns a copy of underlying data, // or else a pointer to the underlying data. func (m *IntAnyMap) Map() map[int]any { - m.mu.RLock() - defer m.mu.RUnlock() - if !m.mu.IsSafe() { - return m.data - } - data := make(map[int]any, len(m.data)) - for k, v := range m.data { - data[k] = v - } - return data + m.lazyInit() + return m.KVMap.Map() } // MapStrAny returns a copy of the underlying data of the map as map[string]any. func (m *IntAnyMap) MapStrAny() map[string]any { - m.mu.RLock() - data := make(map[string]any, len(m.data)) - for k, v := range m.data { - data[gconv.String(k)] = v - } - m.mu.RUnlock() - return data + m.lazyInit() + return m.KVMap.MapStrAny() } // MapCopy returns a copy of the underlying data of the hash map. func (m *IntAnyMap) MapCopy() map[int]any { - m.mu.RLock() - defer m.mu.RUnlock() - data := make(map[int]any, len(m.data)) - for k, v := range m.data { - data[k] = v - } - return data + m.lazyInit() + return m.KVMap.MapCopy() } // FilterEmpty deletes all key-value pair of which the value is empty. // Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty. func (m *IntAnyMap) FilterEmpty() { - m.mu.Lock() - for k, v := range m.data { - if empty.IsEmpty(v) { - delete(m.data, k) - } - } - m.mu.Unlock() + m.lazyInit() + m.KVMap.FilterEmpty() } // FilterNil deletes all key-value pair of which the value is nil. func (m *IntAnyMap) FilterNil() { - m.mu.Lock() - defer m.mu.Unlock() - for k, v := range m.data { - if empty.IsNil(v) { - delete(m.data, k) - } - } + m.lazyInit() + m.KVMap.FilterNil() } // Set sets key-value to the hash map. func (m *IntAnyMap) Set(key int, val any) { - m.mu.Lock() - if m.data == nil { - m.data = make(map[int]any) - } - m.data[key] = val - m.mu.Unlock() + m.lazyInit() + m.KVMap.Set(key, val) } // Sets batch sets key-values to the hash map. func (m *IntAnyMap) Sets(data map[int]any) { - m.mu.Lock() - if m.data == nil { - m.data = data - } else { - for k, v := range data { - m.data[k] = v - } - } - m.mu.Unlock() + m.lazyInit() + m.KVMap.Sets(data) } // Search searches the map with given `key`. // Second return parameter `found` is true if key was found, otherwise false. func (m *IntAnyMap) Search(key int) (value any, found bool) { - m.mu.RLock() - if m.data != nil { - value, found = m.data[key] - } - m.mu.RUnlock() - return + m.lazyInit() + return m.KVMap.Search(key) } // Get returns the value by given `key`. func (m *IntAnyMap) Get(key int) (value any) { - m.mu.RLock() - if m.data != nil { - value = m.data[key] - } - m.mu.RUnlock() - return + m.lazyInit() + return m.KVMap.Get(key) } // Pop retrieves and deletes an item from the map. func (m *IntAnyMap) Pop() (key int, value any) { - m.mu.Lock() - defer m.mu.Unlock() - for key, value = range m.data { - delete(m.data, key) - return - } - return + m.lazyInit() + return m.KVMap.Pop() } // Pops retrieves and deletes `size` items from the map. // It returns all items if size == -1. func (m *IntAnyMap) Pops(size int) map[int]any { - m.mu.Lock() - defer m.mu.Unlock() - if size > len(m.data) || size == -1 { - size = len(m.data) - } - if size == 0 { - return nil - } - var ( - index = 0 - newMap = make(map[int]any, 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 `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 hash map, -// and its return value will be set to the map with `key`. -// -// It returns value with given `key`. -func (m *IntAnyMap) doSetWithLockCheck(key int, value any) any { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[int]any) - } - if v, ok := m.data[key]; ok { - return v - } - if f, ok := value.(func() any); ok { - value = f() - } - if value != nil { - m.data[key] = value - } - return value + m.lazyInit() + return m.KVMap.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 *IntAnyMap) GetOrSet(key int, value any) any { - if v, ok := m.Search(key); !ok { - return m.doSetWithLockCheck(key, value) - } else { - return v - } + m.lazyInit() + return m.KVMap.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 returns this value. func (m *IntAnyMap) GetOrSetFunc(key int, f func() any) any { - if v, ok := m.Search(key); !ok { - return m.doSetWithLockCheck(key, f()) - } else { - return v - } + m.lazyInit() + return m.KVMap.GetOrSetFunc(key, f) } // GetOrSetFuncLock returns the value by key, @@ -254,55 +153,50 @@ func (m *IntAnyMap) GetOrSetFunc(key int, f func() any) any { // GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f` // with mutex.Lock of the hash map. func (m *IntAnyMap) GetOrSetFuncLock(key int, f func() any) any { - if v, ok := m.Search(key); !ok { - return m.doSetWithLockCheck(key, f) - } else { - return v - } + m.lazyInit() + return m.KVMap.GetOrSetFuncLock(key, f) } // GetVar returns a Var with the value by given `key`. // The returned Var is un-concurrent safe. func (m *IntAnyMap) GetVar(key int) *gvar.Var { - return gvar.New(m.Get(key)) + m.lazyInit() + return m.KVMap.GetVar(key) } // GetVarOrSet returns a Var with result from GetVarOrSet. // The returned Var is un-concurrent safe. func (m *IntAnyMap) GetVarOrSet(key int, value any) *gvar.Var { - return gvar.New(m.GetOrSet(key, value)) + m.lazyInit() + return m.KVMap.GetVarOrSet(key, value) } // GetVarOrSetFunc returns a Var with result from GetOrSetFunc. // The returned Var is un-concurrent safe. func (m *IntAnyMap) GetVarOrSetFunc(key int, f func() any) *gvar.Var { - return gvar.New(m.GetOrSetFunc(key, f)) + m.lazyInit() + return m.KVMap.GetVarOrSetFunc(key, f) } // GetVarOrSetFuncLock returns a Var with result from GetOrSetFuncLock. // The returned Var is un-concurrent safe. func (m *IntAnyMap) GetVarOrSetFuncLock(key int, f func() any) *gvar.Var { - return gvar.New(m.GetOrSetFuncLock(key, f)) + m.lazyInit() + return m.KVMap.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 *IntAnyMap) SetIfNotExist(key int, value any) bool { - if !m.Contains(key) { - m.doSetWithLockCheck(key, value) - return true - } - return false + m.lazyInit() + return m.KVMap.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 *IntAnyMap) SetIfNotExistFunc(key int, f func() any) bool { - if !m.Contains(key) { - m.doSetWithLockCheck(key, f()) - return true - } - return false + m.lazyInit() + return m.KVMap.SetIfNotExistFunc(key, f) } // SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true. @@ -311,119 +205,76 @@ func (m *IntAnyMap) SetIfNotExistFunc(key int, f func() any) bool { // SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that // it executes function `f` with mutex.Lock of the hash map. func (m *IntAnyMap) SetIfNotExistFuncLock(key int, f func() any) bool { - if !m.Contains(key) { - m.doSetWithLockCheck(key, f) - return true - } - return false + m.lazyInit() + return m.KVMap.SetIfNotExistFuncLock(key, f) } // Removes batch deletes values of the map by keys. func (m *IntAnyMap) Removes(keys []int) { - m.mu.Lock() - if m.data != nil { - for _, key := range keys { - delete(m.data, key) - } - } - m.mu.Unlock() + m.lazyInit() + m.KVMap.Removes(keys) } // Remove deletes value from map by given `key`, and return this deleted value. func (m *IntAnyMap) Remove(key int) (value any) { - m.mu.Lock() - if m.data != nil { - var ok bool - if value, ok = m.data[key]; ok { - delete(m.data, key) - } - } - m.mu.Unlock() - return + m.lazyInit() + return m.KVMap.Remove(key) } // Keys returns all keys of the map as a slice. func (m *IntAnyMap) Keys() []int { - m.mu.RLock() - var ( - keys = make([]int, len(m.data)) - index = 0 - ) - for key := range m.data { - keys[index] = key - index++ - } - m.mu.RUnlock() - return keys + m.lazyInit() + return m.KVMap.Keys() } // Values returns all values of the map as a slice. func (m *IntAnyMap) Values() []any { - m.mu.RLock() - var ( - values = make([]any, len(m.data)) - index = 0 - ) - for _, value := range m.data { - values[index] = value - index++ - } - m.mu.RUnlock() - return values + m.lazyInit() + return m.KVMap.Values() } // Contains checks whether a key exists. // It returns true if the `key` exists, or else false. func (m *IntAnyMap) Contains(key int) bool { - var ok bool - m.mu.RLock() - if m.data != nil { - _, ok = m.data[key] - } - m.mu.RUnlock() - return ok + m.lazyInit() + return m.KVMap.Contains(key) } // Size returns the size of the map. func (m *IntAnyMap) Size() int { - m.mu.RLock() - length := len(m.data) - m.mu.RUnlock() - return length + m.lazyInit() + return m.KVMap.Size() } // IsEmpty checks whether the map is empty. // It returns true if map is empty, or else false. func (m *IntAnyMap) IsEmpty() bool { - return m.Size() == 0 + m.lazyInit() + return m.KVMap.IsEmpty() } // Clear deletes all data of the map, it will remake a new underlying data map. func (m *IntAnyMap) Clear() { - m.mu.Lock() - m.data = make(map[int]any) - m.mu.Unlock() + m.lazyInit() + m.KVMap.Clear() } // Replace the data of the map with given `data`. func (m *IntAnyMap) Replace(data map[int]any) { - m.mu.Lock() - m.data = data - m.mu.Unlock() + m.lazyInit() + m.KVMap.Replace(data) } // LockFunc locks writing with given callback function `f` within RWMutex.Lock. func (m *IntAnyMap) LockFunc(f func(m map[int]any)) { - m.mu.Lock() - defer m.mu.Unlock() - f(m.data) + m.lazyInit() + m.KVMap.LockFunc(f) } // RLockFunc locks reading with given callback function `f` within RWMutex.RLock. func (m *IntAnyMap) RLockFunc(f func(m map[int]any)) { - m.mu.RLock() - defer m.mu.RUnlock() - f(m.data) + m.lazyInit() + m.KVMap.RLockFunc(f) } // Flip exchanges key-value of the map to value-key. @@ -440,19 +291,8 @@ func (m *IntAnyMap) Flip() { // Merge merges two hash maps. // The `other` map will be merged into the map `m`. func (m *IntAnyMap) Merge(other *IntAnyMap) { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = other.MapCopy() - return - } - if other != m { - other.mu.RLock() - defer other.mu.RUnlock() - } - for k, v := range other.data { - m.data[k] = v - } + m.lazyInit() + m.KVMap.Merge(other.KVMap) } // String returns the map as a string. @@ -460,81 +300,40 @@ func (m *IntAnyMap) String() string { if m == nil { return "" } - b, _ := m.MarshalJSON() - return string(b) + m.lazyInit() + return m.KVMap.String() } // MarshalJSON implements the interface MarshalJSON for json.Marshal. func (m IntAnyMap) MarshalJSON() ([]byte, error) { - m.mu.RLock() - defer m.mu.RUnlock() - return json.Marshal(m.data) + m.lazyInit() + return m.KVMap.MarshalJSON() } // UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal. func (m *IntAnyMap) UnmarshalJSON(b []byte) error { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[int]any) - } - if err := json.UnmarshalUseNumber(b, &m.data); err != nil { - return err - } - return nil + m.lazyInit() + return m.KVMap.UnmarshalJSON(b) } // UnmarshalValue is an interface implement which sets any type of value for map. func (m *IntAnyMap) UnmarshalValue(value any) (err error) { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[int]any) - } - switch value.(type) { - case string, []byte: - return json.UnmarshalUseNumber(gconv.Bytes(value), &m.data) - default: - for k, v := range gconv.Map(value) { - m.data[gconv.Int(k)] = v - } - } - return + m.lazyInit() + return m.KVMap.UnmarshalValue(value) } // DeepCopy implements interface for deep copy of current type. func (m *IntAnyMap) DeepCopy() any { - if m == nil { - return nil + m.lazyInit() + return &IntAnyMap{ + KVMap: m.KVMap.DeepCopy().(*KVMap[int, any]), } - m.mu.RLock() - defer m.mu.RUnlock() - data := make(map[int]any, len(m.data)) - for k, v := range m.data { - data[k] = deepcopy.Copy(v) - } - return NewIntAnyMapFrom(data, m.mu.IsSafe()) } // IsSubOf checks whether the current map is a sub-map of `other`. func (m *IntAnyMap) IsSubOf(other *IntAnyMap) bool { - if m == other { - return true - } - m.mu.RLock() - defer m.mu.RUnlock() - other.mu.RLock() - defer other.mu.RUnlock() - for key, value := range m.data { - otherValue, ok := other.data[key] - if !ok { - return false - } - if otherValue != value { - return false - } - } - return true + m.lazyInit() + return m.KVMap.IsSubOf(other.KVMap) } // Diff compares current map `m` with map `other` and returns their different keys. @@ -542,22 +341,6 @@ func (m *IntAnyMap) IsSubOf(other *IntAnyMap) bool { // The returned `removedKeys` are the keys that are in map `other` but not in map `m`. // The returned `updatedKeys` are the keys that are both in map `m` and `other` but their values and not equal (`!=`). func (m *IntAnyMap) Diff(other *IntAnyMap) (addedKeys, removedKeys, updatedKeys []int) { - m.mu.RLock() - defer m.mu.RUnlock() - other.mu.RLock() - defer other.mu.RUnlock() - - for key := range m.data { - if _, ok := other.data[key]; !ok { - removedKeys = append(removedKeys, key) - } else if !reflect.DeepEqual(m.data[key], other.data[key]) { - updatedKeys = append(updatedKeys, key) - } - } - for key := range other.data { - if _, ok := m.data[key]; !ok { - addedKeys = append(addedKeys, key) - } - } - return + m.lazyInit() + return m.KVMap.Diff(other.KVMap) } diff --git a/container/gmap/gmap_hash_int_int_map.go b/container/gmap/gmap_hash_int_int_map.go index 7332eea06..80685e01d 100644 --- a/container/gmap/gmap_hash_int_int_map.go +++ b/container/gmap/gmap_hash_int_int_map.go @@ -6,17 +6,12 @@ package gmap -import ( - "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" -) +import "sync" // IntIntMap implements map[int]int with RWMutex that has switch. type IntIntMap struct { - mu rwmutex.RWMutex - data map[int]int + *KVMap[int, int] + once sync.Once } // NewIntIntMap returns an empty IntIntMap object. @@ -24,8 +19,7 @@ type IntIntMap struct { // which is false in default. func NewIntIntMap(safe ...bool) *IntIntMap { return &IntIntMap{ - mu: rwmutex.Create(safe...), - data: make(map[int]int), + KVMap: NewKVMap[int, int](safe...), } } @@ -34,193 +28,109 @@ func NewIntIntMap(safe ...bool) *IntIntMap { // there might be some concurrent-safe issues when changing the map outside. func NewIntIntMapFrom(data map[int]int, safe ...bool) *IntIntMap { return &IntIntMap{ - mu: rwmutex.Create(safe...), - data: data, + KVMap: NewKVMapFrom(data, safe...), } } +// lazyInit lazily initializes the map. +func (m *IntIntMap) lazyInit() { + m.once.Do(func() { + if m.KVMap == nil { + m.KVMap = NewKVMap[int, int](false) + } + }) +} + // Iterator iterates the hash map readonly with custom callback function `f`. // If `f` returns true, then it continues iterating; or false to stop. func (m *IntIntMap) Iterator(f func(k int, v int) bool) { - for k, v := range m.Map() { - if !f(k, v) { - break - } - } + m.lazyInit() + m.KVMap.Iterator(f) } // Clone returns a new hash map with copy of current map data. -func (m *IntIntMap) Clone() *IntIntMap { - return NewIntIntMapFrom(m.MapCopy(), m.mu.IsSafe()) +func (m *IntIntMap) Clone(safe ...bool) *IntIntMap { + m.lazyInit() + return &IntIntMap{KVMap: m.KVMap.Clone(safe...)} } // Map returns the underlying data map. // Note that, if it's in concurrent-safe usage, it returns a copy of underlying data, // or else a pointer to the underlying data. func (m *IntIntMap) Map() map[int]int { - m.mu.RLock() - defer m.mu.RUnlock() - if !m.mu.IsSafe() { - return m.data - } - data := make(map[int]int, len(m.data)) - for k, v := range m.data { - data[k] = v - } - return data + m.lazyInit() + return m.KVMap.Map() } // MapStrAny returns a copy of the underlying data of the map as map[string]any. func (m *IntIntMap) MapStrAny() map[string]any { - m.mu.RLock() - data := make(map[string]any, len(m.data)) - for k, v := range m.data { - data[gconv.String(k)] = v - } - m.mu.RUnlock() - return data + m.lazyInit() + return m.KVMap.MapStrAny() } // MapCopy returns a copy of the underlying data of the hash map. func (m *IntIntMap) MapCopy() map[int]int { - m.mu.RLock() - defer m.mu.RUnlock() - data := make(map[int]int, len(m.data)) - for k, v := range m.data { - data[k] = v - } - return data + m.lazyInit() + return m.KVMap.MapCopy() } // FilterEmpty deletes all key-value pair of which the value is empty. // Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty. func (m *IntIntMap) FilterEmpty() { - m.mu.Lock() - for k, v := range m.data { - if empty.IsEmpty(v) { - delete(m.data, k) - } - } - m.mu.Unlock() + m.lazyInit() + m.KVMap.FilterEmpty() } // Set sets key-value to the hash map. func (m *IntIntMap) Set(key int, val int) { - m.mu.Lock() - if m.data == nil { - m.data = make(map[int]int) - } - m.data[key] = val - m.mu.Unlock() + m.lazyInit() + m.KVMap.Set(key, val) } // Sets batch sets key-values to the hash map. func (m *IntIntMap) Sets(data map[int]int) { - m.mu.Lock() - if m.data == nil { - m.data = data - } else { - for k, v := range data { - m.data[k] = v - } - } - m.mu.Unlock() + m.lazyInit() + m.KVMap.Sets(data) } // Search searches the map with given `key`. // Second return parameter `found` is true if key was found, otherwise false. func (m *IntIntMap) Search(key int) (value int, found bool) { - m.mu.RLock() - if m.data != nil { - value, found = m.data[key] - } - m.mu.RUnlock() - return + m.lazyInit() + return m.KVMap.Search(key) } // Get returns the value by given `key`. func (m *IntIntMap) Get(key int) (value int) { - m.mu.RLock() - if m.data != nil { - value = m.data[key] - } - m.mu.RUnlock() - return + m.lazyInit() + return m.KVMap.Get(key) } // 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 + m.lazyInit() + return m.KVMap.Pop() } // Pops retrieves and deletes `size` 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 - } - var ( - 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 `key`, -// or else just return the existing value. -// -// It returns value with given `key`. -func (m *IntIntMap) doSetWithLockCheck(key int, value int) int { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[int]int) - } - if v, ok := m.data[key]; ok { - return v - } - m.data[key] = value - return value + m.lazyInit() + return m.KVMap.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 *IntIntMap) GetOrSet(key int, value int) int { - if v, ok := m.Search(key); !ok { - return m.doSetWithLockCheck(key, value) - } else { - return v - } + m.lazyInit() + return m.KVMap.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 returns this value. func (m *IntIntMap) GetOrSetFunc(key int, f func() int) int { - if v, ok := m.Search(key); !ok { - return m.doSetWithLockCheck(key, f()) - } else { - return v - } + m.lazyInit() + return m.KVMap.GetOrSetFunc(key, f) } // GetOrSetFuncLock returns the value by key, @@ -229,41 +139,22 @@ func (m *IntIntMap) GetOrSetFunc(key int, f func() int) int { // GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f` // with mutex.Lock of the hash map. func (m *IntIntMap) GetOrSetFuncLock(key int, f func() int) int { - if v, ok := m.Search(key); !ok { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[int]int) - } - if v, ok = m.data[key]; ok { - return v - } - v = f() - m.data[key] = v - return v - } else { - return v - } + m.lazyInit() + return m.KVMap.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 *IntIntMap) SetIfNotExist(key int, value int) bool { - if !m.Contains(key) { - m.doSetWithLockCheck(key, value) - return true - } - return false + m.lazyInit() + return m.KVMap.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 *IntIntMap) SetIfNotExistFunc(key int, f func() int) bool { - if !m.Contains(key) { - m.doSetWithLockCheck(key, f()) - return true - } - return false + m.lazyInit() + return m.KVMap.SetIfNotExistFunc(key, f) } // SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true. @@ -272,126 +163,76 @@ func (m *IntIntMap) SetIfNotExistFunc(key int, f func() int) bool { // SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that // it executes function `f` with mutex.Lock of the hash map. func (m *IntIntMap) SetIfNotExistFuncLock(key int, f func() int) bool { - if !m.Contains(key) { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[int]int) - } - if _, ok := m.data[key]; !ok { - m.data[key] = f() - } - return true - } - return false + m.lazyInit() + return m.KVMap.SetIfNotExistFuncLock(key, f) } // Removes batch deletes values of the map by keys. func (m *IntIntMap) Removes(keys []int) { - m.mu.Lock() - if m.data != nil { - for _, key := range keys { - delete(m.data, key) - } - } - m.mu.Unlock() + m.lazyInit() + m.KVMap.Removes(keys) } // Remove deletes value from map by given `key`, and return this deleted value. func (m *IntIntMap) Remove(key int) (value int) { - m.mu.Lock() - if m.data != nil { - var ok bool - if value, ok = m.data[key]; ok { - delete(m.data, key) - } - } - m.mu.Unlock() - return + m.lazyInit() + return m.KVMap.Remove(key) } // Keys returns all keys of the map as a slice. func (m *IntIntMap) Keys() []int { - m.mu.RLock() - var ( - keys = make([]int, len(m.data)) - index = 0 - ) - for key := range m.data { - keys[index] = key - index++ - } - m.mu.RUnlock() - return keys + m.lazyInit() + return m.KVMap.Keys() } // Values returns all values of the map as a slice. func (m *IntIntMap) Values() []int { - m.mu.RLock() - var ( - values = make([]int, len(m.data)) - index = 0 - ) - for _, value := range m.data { - values[index] = value - index++ - } - m.mu.RUnlock() - return values + m.lazyInit() + return m.KVMap.Values() } // Contains checks whether a key exists. // It returns true if the `key` exists, or else false. func (m *IntIntMap) Contains(key int) bool { - var ok bool - m.mu.RLock() - if m.data != nil { - _, ok = m.data[key] - } - m.mu.RUnlock() - return ok + m.lazyInit() + return m.KVMap.Contains(key) } // Size returns the size of the map. func (m *IntIntMap) Size() int { - m.mu.RLock() - length := len(m.data) - m.mu.RUnlock() - return length + m.lazyInit() + return m.KVMap.Size() } // IsEmpty checks whether the map is empty. // It returns true if map is empty, or else false. func (m *IntIntMap) IsEmpty() bool { - return m.Size() == 0 + m.lazyInit() + return m.KVMap.IsEmpty() } // Clear deletes all data of the map, it will remake a new underlying data map. func (m *IntIntMap) Clear() { - m.mu.Lock() - m.data = make(map[int]int) - m.mu.Unlock() + m.lazyInit() + m.KVMap.Clear() } // Replace the data of the map with given `data`. func (m *IntIntMap) Replace(data map[int]int) { - m.mu.Lock() - m.data = data - m.mu.Unlock() + m.lazyInit() + m.KVMap.Replace(data) } // LockFunc locks writing with given callback function `f` within RWMutex.Lock. func (m *IntIntMap) LockFunc(f func(m map[int]int)) { - m.mu.Lock() - defer m.mu.Unlock() - f(m.data) + m.lazyInit() + m.KVMap.LockFunc(f) } // RLockFunc locks reading with given callback function `f` within RWMutex.RLock. func (m *IntIntMap) RLockFunc(f func(m map[int]int)) { - m.mu.RLock() - defer m.mu.RUnlock() - f(m.data) + m.lazyInit() + m.KVMap.RLockFunc(f) } // Flip exchanges key-value of the map to value-key. @@ -408,19 +249,8 @@ func (m *IntIntMap) Flip() { // Merge merges two hash maps. // The `other` map will be merged into the map `m`. func (m *IntIntMap) Merge(other *IntIntMap) { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = other.MapCopy() - return - } - if other != m { - other.mu.RLock() - defer other.mu.RUnlock() - } - for k, v := range other.data { - m.data[k] = v - } + m.lazyInit() + m.KVMap.Merge(other.KVMap) } // String returns the map as a string. @@ -428,81 +258,40 @@ func (m *IntIntMap) String() string { if m == nil { return "" } - b, _ := m.MarshalJSON() - return string(b) + m.lazyInit() + return m.KVMap.String() } // MarshalJSON implements the interface MarshalJSON for json.Marshal. func (m IntIntMap) MarshalJSON() ([]byte, error) { - m.mu.RLock() - defer m.mu.RUnlock() - return json.Marshal(m.data) + m.lazyInit() + return m.KVMap.MarshalJSON() } // UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal. func (m *IntIntMap) UnmarshalJSON(b []byte) error { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[int]int) - } - if err := json.UnmarshalUseNumber(b, &m.data); err != nil { - return err - } - return nil + m.lazyInit() + return m.KVMap.UnmarshalJSON(b) } // UnmarshalValue is an interface implement which sets any type of value for map. func (m *IntIntMap) UnmarshalValue(value any) (err error) { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[int]int) - } - switch value.(type) { - case string, []byte: - return json.UnmarshalUseNumber(gconv.Bytes(value), &m.data) - default: - for k, v := range gconv.Map(value) { - m.data[gconv.Int(k)] = gconv.Int(v) - } - } - return + m.lazyInit() + return m.KVMap.UnmarshalValue(value) } // DeepCopy implements interface for deep copy of current type. func (m *IntIntMap) DeepCopy() any { - if m == nil { - return nil + m.lazyInit() + return &IntIntMap{ + KVMap: m.KVMap.DeepCopy().(*KVMap[int, int]), } - m.mu.RLock() - defer m.mu.RUnlock() - data := make(map[int]int, len(m.data)) - for k, v := range m.data { - data[k] = v - } - return NewIntIntMapFrom(data, m.mu.IsSafe()) } // IsSubOf checks whether the current map is a sub-map of `other`. func (m *IntIntMap) IsSubOf(other *IntIntMap) bool { - if m == other { - return true - } - m.mu.RLock() - defer m.mu.RUnlock() - other.mu.RLock() - defer other.mu.RUnlock() - for key, value := range m.data { - otherValue, ok := other.data[key] - if !ok { - return false - } - if otherValue != value { - return false - } - } - return true + m.lazyInit() + return m.KVMap.IsSubOf(other.KVMap) } // Diff compares current map `m` with map `other` and returns their different keys. @@ -510,22 +299,6 @@ func (m *IntIntMap) IsSubOf(other *IntIntMap) bool { // The returned `removedKeys` are the keys that are in map `other` but not in map `m`. // The returned `updatedKeys` are the keys that are both in map `m` and `other` but their values and not equal (`!=`). func (m *IntIntMap) Diff(other *IntIntMap) (addedKeys, removedKeys, updatedKeys []int) { - m.mu.RLock() - defer m.mu.RUnlock() - other.mu.RLock() - defer other.mu.RUnlock() - - for key := range m.data { - if _, ok := other.data[key]; !ok { - removedKeys = append(removedKeys, key) - } else if m.data[key] != other.data[key] { - updatedKeys = append(updatedKeys, key) - } - } - for key := range other.data { - if _, ok := m.data[key]; !ok { - addedKeys = append(addedKeys, key) - } - } - return + m.lazyInit() + return m.KVMap.Diff(other.KVMap) } diff --git a/container/gmap/gmap_hash_int_str_map.go b/container/gmap/gmap_hash_int_str_map.go index 2de9788a8..e6e3177bb 100644 --- a/container/gmap/gmap_hash_int_str_map.go +++ b/container/gmap/gmap_hash_int_str_map.go @@ -7,16 +7,15 @@ package gmap import ( - "github.com/gogf/gf/v2/internal/empty" - "github.com/gogf/gf/v2/internal/json" - "github.com/gogf/gf/v2/internal/rwmutex" + "sync" + "github.com/gogf/gf/v2/util/gconv" ) // IntStrMap implements map[int]string with RWMutex that has switch. type IntStrMap struct { - mu rwmutex.RWMutex - data map[int]string + *KVMap[int, string] + once sync.Once } // NewIntStrMap returns an empty IntStrMap object. @@ -24,8 +23,7 @@ type IntStrMap struct { // which is false in default. func NewIntStrMap(safe ...bool) *IntStrMap { return &IntStrMap{ - mu: rwmutex.Create(safe...), - data: make(map[int]string), + KVMap: NewKVMap[int, string](safe...), } } @@ -34,193 +32,109 @@ func NewIntStrMap(safe ...bool) *IntStrMap { // there might be some concurrent-safe issues when changing the map outside. func NewIntStrMapFrom(data map[int]string, safe ...bool) *IntStrMap { return &IntStrMap{ - mu: rwmutex.Create(safe...), - data: data, + KVMap: NewKVMapFrom(data, safe...), } } +// lazyInit lazily initializes the map. +func (m *IntStrMap) lazyInit() { + m.once.Do(func() { + if m.KVMap == nil { + m.KVMap = NewKVMap[int, string](false) + } + }) +} + // Iterator iterates the hash map readonly with custom callback function `f`. // If `f` returns true, then it continues iterating; or false to stop. func (m *IntStrMap) Iterator(f func(k int, v string) bool) { - for k, v := range m.Map() { - if !f(k, v) { - break - } - } + m.lazyInit() + m.KVMap.Iterator(f) } // Clone returns a new hash map with copy of current map data. -func (m *IntStrMap) Clone() *IntStrMap { - return NewIntStrMapFrom(m.MapCopy(), m.mu.IsSafe()) +func (m *IntStrMap) Clone(safe ...bool) *IntStrMap { + m.lazyInit() + return &IntStrMap{KVMap: m.KVMap.Clone(safe...)} } // Map returns the underlying data map. // Note that, if it's in concurrent-safe usage, it returns a copy of underlying data, // or else a pointer to the underlying data. func (m *IntStrMap) Map() map[int]string { - m.mu.RLock() - defer m.mu.RUnlock() - if !m.mu.IsSafe() { - return m.data - } - data := make(map[int]string, len(m.data)) - for k, v := range m.data { - data[k] = v - } - return data + m.lazyInit() + return m.KVMap.Map() } // MapStrAny returns a copy of the underlying data of the map as map[string]any. func (m *IntStrMap) MapStrAny() map[string]any { - m.mu.RLock() - data := make(map[string]any, len(m.data)) - for k, v := range m.data { - data[gconv.String(k)] = v - } - m.mu.RUnlock() - return data + m.lazyInit() + return m.KVMap.MapStrAny() } // MapCopy returns a copy of the underlying data of the hash map. func (m *IntStrMap) MapCopy() map[int]string { - m.mu.RLock() - defer m.mu.RUnlock() - data := make(map[int]string, len(m.data)) - for k, v := range m.data { - data[k] = v - } - return data + m.lazyInit() + return m.KVMap.MapCopy() } // FilterEmpty deletes all key-value pair of which the value is empty. // Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty. func (m *IntStrMap) FilterEmpty() { - m.mu.Lock() - for k, v := range m.data { - if empty.IsEmpty(v) { - delete(m.data, k) - } - } - m.mu.Unlock() + m.lazyInit() + m.KVMap.FilterEmpty() } // Set sets key-value to the hash map. func (m *IntStrMap) Set(key int, val string) { - m.mu.Lock() - if m.data == nil { - m.data = make(map[int]string) - } - m.data[key] = val - m.mu.Unlock() + m.lazyInit() + m.KVMap.Set(key, val) } // Sets batch sets key-values to the hash map. func (m *IntStrMap) Sets(data map[int]string) { - m.mu.Lock() - if m.data == nil { - m.data = data - } else { - for k, v := range data { - m.data[k] = v - } - } - m.mu.Unlock() + m.lazyInit() + m.KVMap.Sets(data) } // Search searches the map with given `key`. // Second return parameter `found` is true if key was found, otherwise false. func (m *IntStrMap) Search(key int) (value string, found bool) { - m.mu.RLock() - if m.data != nil { - value, found = m.data[key] - } - m.mu.RUnlock() - return + m.lazyInit() + return m.KVMap.Search(key) } // Get returns the value by given `key`. func (m *IntStrMap) Get(key int) (value string) { - m.mu.RLock() - if m.data != nil { - value = m.data[key] - } - m.mu.RUnlock() - return + m.lazyInit() + return m.KVMap.Get(key) } // 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 + m.lazyInit() + return m.KVMap.Pop() } // Pops retrieves and deletes `size` 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 - } - var ( - 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 `key`, -// or else just return the existing value. -// -// It returns value with given `key`. -func (m *IntStrMap) doSetWithLockCheck(key int, value string) string { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[int]string) - } - if v, ok := m.data[key]; ok { - return v - } - m.data[key] = value - return value + m.lazyInit() + return m.KVMap.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 *IntStrMap) GetOrSet(key int, value string) string { - if v, ok := m.Search(key); !ok { - return m.doSetWithLockCheck(key, value) - } else { - return v - } + m.lazyInit() + return m.KVMap.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 returns this value. func (m *IntStrMap) GetOrSetFunc(key int, f func() string) string { - if v, ok := m.Search(key); !ok { - return m.doSetWithLockCheck(key, f()) - } else { - return v - } + m.lazyInit() + return m.KVMap.GetOrSetFunc(key, f) } // GetOrSetFuncLock returns the value by key, @@ -229,41 +143,22 @@ func (m *IntStrMap) GetOrSetFunc(key int, f func() string) string { // GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f` // with mutex.Lock of the hash map. func (m *IntStrMap) GetOrSetFuncLock(key int, f func() string) string { - if v, ok := m.Search(key); !ok { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[int]string) - } - if v, ok = m.data[key]; ok { - return v - } - v = f() - m.data[key] = v - return v - } else { - return v - } + m.lazyInit() + return m.KVMap.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 *IntStrMap) SetIfNotExist(key int, value string) bool { - if !m.Contains(key) { - m.doSetWithLockCheck(key, value) - return true - } - return false + m.lazyInit() + return m.KVMap.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 *IntStrMap) SetIfNotExistFunc(key int, f func() string) bool { - if !m.Contains(key) { - m.doSetWithLockCheck(key, f()) - return true - } - return false + m.lazyInit() + return m.KVMap.SetIfNotExistFunc(key, f) } // SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true. @@ -272,126 +167,76 @@ func (m *IntStrMap) SetIfNotExistFunc(key int, f func() string) bool { // SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that // it executes function `f` with mutex.Lock of the hash map. func (m *IntStrMap) SetIfNotExistFuncLock(key int, f func() string) bool { - if !m.Contains(key) { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[int]string) - } - if _, ok := m.data[key]; !ok { - m.data[key] = f() - } - return true - } - return false + m.lazyInit() + return m.KVMap.SetIfNotExistFuncLock(key, f) } // Removes batch deletes values of the map by keys. func (m *IntStrMap) Removes(keys []int) { - m.mu.Lock() - if m.data != nil { - for _, key := range keys { - delete(m.data, key) - } - } - m.mu.Unlock() + m.lazyInit() + m.KVMap.Removes(keys) } // Remove deletes value from map by given `key`, and return this deleted value. func (m *IntStrMap) Remove(key int) (value string) { - m.mu.Lock() - if m.data != nil { - var ok bool - if value, ok = m.data[key]; ok { - delete(m.data, key) - } - } - m.mu.Unlock() - return + m.lazyInit() + return m.KVMap.Remove(key) } // Keys returns all keys of the map as a slice. func (m *IntStrMap) Keys() []int { - m.mu.RLock() - var ( - keys = make([]int, len(m.data)) - index = 0 - ) - for key := range m.data { - keys[index] = key - index++ - } - m.mu.RUnlock() - return keys + m.lazyInit() + return m.KVMap.Keys() } // Values returns all values of the map as a slice. func (m *IntStrMap) Values() []string { - m.mu.RLock() - var ( - values = make([]string, len(m.data)) - index = 0 - ) - for _, value := range m.data { - values[index] = value - index++ - } - m.mu.RUnlock() - return values + m.lazyInit() + return m.KVMap.Values() } // Contains checks whether a key exists. // It returns true if the `key` exists, or else false. func (m *IntStrMap) Contains(key int) bool { - var ok bool - m.mu.RLock() - if m.data != nil { - _, ok = m.data[key] - } - m.mu.RUnlock() - return ok + m.lazyInit() + return m.KVMap.Contains(key) } // Size returns the size of the map. func (m *IntStrMap) Size() int { - m.mu.RLock() - length := len(m.data) - m.mu.RUnlock() - return length + m.lazyInit() + return m.KVMap.Size() } // IsEmpty checks whether the map is empty. // It returns true if map is empty, or else false. func (m *IntStrMap) IsEmpty() bool { - return m.Size() == 0 + m.lazyInit() + return m.KVMap.IsEmpty() } // Clear deletes all data of the map, it will remake a new underlying data map. func (m *IntStrMap) Clear() { - m.mu.Lock() - m.data = make(map[int]string) - m.mu.Unlock() + m.lazyInit() + m.KVMap.Clear() } // Replace the data of the map with given `data`. func (m *IntStrMap) Replace(data map[int]string) { - m.mu.Lock() - m.data = data - m.mu.Unlock() + m.lazyInit() + m.KVMap.Replace(data) } // LockFunc locks writing with given callback function `f` within RWMutex.Lock. func (m *IntStrMap) LockFunc(f func(m map[int]string)) { - m.mu.Lock() - defer m.mu.Unlock() - f(m.data) + m.lazyInit() + m.KVMap.LockFunc(f) } // RLockFunc locks reading with given callback function `f` within RWMutex.RLock. func (m *IntStrMap) RLockFunc(f func(m map[int]string)) { - m.mu.RLock() - defer m.mu.RUnlock() - f(m.data) + m.lazyInit() + m.KVMap.RLockFunc(f) } // Flip exchanges key-value of the map to value-key. @@ -408,19 +253,8 @@ func (m *IntStrMap) Flip() { // Merge merges two hash maps. // The `other` map will be merged into the map `m`. func (m *IntStrMap) Merge(other *IntStrMap) { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = other.MapCopy() - return - } - if other != m { - other.mu.RLock() - defer other.mu.RUnlock() - } - for k, v := range other.data { - m.data[k] = v - } + m.lazyInit() + m.KVMap.Merge(other.KVMap) } // String returns the map as a string. @@ -428,81 +262,40 @@ func (m *IntStrMap) String() string { if m == nil { return "" } - b, _ := m.MarshalJSON() - return string(b) + m.lazyInit() + return m.KVMap.String() } // MarshalJSON implements the interface MarshalJSON for json.Marshal. func (m IntStrMap) MarshalJSON() ([]byte, error) { - m.mu.RLock() - defer m.mu.RUnlock() - return json.Marshal(m.data) + m.lazyInit() + return m.KVMap.MarshalJSON() } // UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal. func (m *IntStrMap) UnmarshalJSON(b []byte) error { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[int]string) - } - if err := json.UnmarshalUseNumber(b, &m.data); err != nil { - return err - } - return nil + m.lazyInit() + return m.KVMap.UnmarshalJSON(b) } // UnmarshalValue is an interface implement which sets any type of value for map. func (m *IntStrMap) UnmarshalValue(value any) (err error) { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[int]string) - } - switch value.(type) { - case string, []byte: - return json.UnmarshalUseNumber(gconv.Bytes(value), &m.data) - default: - for k, v := range gconv.Map(value) { - m.data[gconv.Int(k)] = gconv.String(v) - } - } - return + m.lazyInit() + return m.KVMap.UnmarshalValue(value) } // DeepCopy implements interface for deep copy of current type. func (m *IntStrMap) DeepCopy() any { - if m == nil { - return nil + m.lazyInit() + return &IntStrMap{ + KVMap: m.KVMap.DeepCopy().(*KVMap[int, string]), } - m.mu.RLock() - defer m.mu.RUnlock() - data := make(map[int]string, len(m.data)) - for k, v := range m.data { - data[k] = v - } - return NewIntStrMapFrom(data, m.mu.IsSafe()) } // IsSubOf checks whether the current map is a sub-map of `other`. func (m *IntStrMap) IsSubOf(other *IntStrMap) bool { - if m == other { - return true - } - m.mu.RLock() - defer m.mu.RUnlock() - other.mu.RLock() - defer other.mu.RUnlock() - for key, value := range m.data { - otherValue, ok := other.data[key] - if !ok { - return false - } - if otherValue != value { - return false - } - } - return true + m.lazyInit() + return m.KVMap.IsSubOf(other.KVMap) } // Diff compares current map `m` with map `other` and returns their different keys. @@ -510,22 +303,6 @@ func (m *IntStrMap) IsSubOf(other *IntStrMap) bool { // The returned `removedKeys` are the keys that are in map `other` but not in map `m`. // The returned `updatedKeys` are the keys that are both in map `m` and `other` but their values and not equal (`!=`). func (m *IntStrMap) Diff(other *IntStrMap) (addedKeys, removedKeys, updatedKeys []int) { - m.mu.RLock() - defer m.mu.RUnlock() - other.mu.RLock() - defer other.mu.RUnlock() - - for key := range m.data { - if _, ok := other.data[key]; !ok { - removedKeys = append(removedKeys, key) - } else if m.data[key] != other.data[key] { - updatedKeys = append(updatedKeys, key) - } - } - for key := range other.data { - if _, ok := m.data[key]; !ok { - addedKeys = append(addedKeys, key) - } - } - return + m.lazyInit() + return m.KVMap.Diff(other.KVMap) } diff --git a/container/gmap/gmap_hash_k_v_map.go b/container/gmap/gmap_hash_k_v_map.go new file mode 100644 index 000000000..0b9f9c8ea --- /dev/null +++ b/container/gmap/gmap_hash_k_v_map.go @@ -0,0 +1,582 @@ +// 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 ( + "reflect" + + "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" +) + +// KVMap wraps map type `map[K]V` and provides more map features. +type KVMap[K comparable, V any] struct { + mu rwmutex.RWMutex + data map[K]V +} + +// NewKVMap creates and returns an empty hash map. +// The parameter `safe` is used to specify whether to use the map in concurrent-safety mode, +// which is false by default. +func NewKVMap[K comparable, V any](safe ...bool) *KVMap[K, V] { + return NewKVMapFrom(make(map[K]V), safe...) +} + +// NewKVMapFrom creates and returns a hash 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 NewKVMapFrom[K comparable, V any](data map[K]V, safe ...bool) *KVMap[K, V] { + m := &KVMap[K, V]{ + mu: rwmutex.Create(safe...), + data: data, + } + return m +} + +// Iterator iterates the hash map readonly with custom callback function `f`. +// If `f` returns true, then it continues iterating; or false to stop. +func (m *KVMap[K, V]) Iterator(f func(k K, v V) bool) { + for k, v := range m.Map() { + if !f(k, v) { + break + } + } +} + +// Clone returns a new hash map with copy of current map data. +func (m *KVMap[K, V]) Clone(safe ...bool) *KVMap[K, V] { + if len(safe) == 0 { + return NewKVMapFrom(m.MapCopy(), m.mu.IsSafe()) + } + return NewKVMapFrom(m.MapCopy(), safe...) +} + +// Map returns the underlying data map. +// Note that, if it's in concurrent-safe usage, it returns a copy of underlying data, +// or else a pointer to the underlying data. +func (m *KVMap[K, V]) Map() map[K]V { + m.mu.RLock() + defer m.mu.RUnlock() + if !m.mu.IsSafe() { + return m.data + } + data := make(map[K]V, len(m.data)) + for k, v := range m.data { + data[k] = v + } + return data +} + +// MapCopy returns a shallow copy of the underlying data of the hash map. +func (m *KVMap[K, V]) MapCopy() map[K]V { + m.mu.RLock() + defer m.mu.RUnlock() + data := make(map[K]V, len(m.data)) + for k, v := range m.data { + data[k] = v + } + return data +} + +// MapStrAny returns a copy of the underlying data of the map as map[string]any. +func (m *KVMap[K, V]) MapStrAny() map[string]any { + m.mu.RLock() + defer m.mu.RUnlock() + data := make(map[string]any, len(m.data)) + for k, v := range m.data { + data[gconv.String(k)] = v + } + return data +} + +// FilterEmpty deletes all key-value pair of which the value is empty. +// Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty. +func (m *KVMap[K, V]) FilterEmpty() { + m.mu.Lock() + defer m.mu.Unlock() + for k, v := range m.data { + if empty.IsEmpty(v) { + delete(m.data, k) + } + } +} + +// FilterNil deletes all key-value pair of which the value is nil. +func (m *KVMap[K, V]) FilterNil() { + m.mu.Lock() + defer m.mu.Unlock() + for k, v := range m.data { + if empty.IsNil(v) { + delete(m.data, k) + } + } +} + +// Set sets key-value to the hash map. +func (m *KVMap[K, V]) Set(key K, value V) { + m.mu.Lock() + if m.data == nil { + m.data = make(map[K]V) + } + m.data[key] = value + m.mu.Unlock() +} + +// Sets batch sets key-values to the hash map. +func (m *KVMap[K, V]) Sets(data map[K]V) { + m.mu.Lock() + if m.data == nil { + m.data = data + } else { + for k, v := range data { + m.data[k] = v + } + } + m.mu.Unlock() +} + +// Search searches the map with given `key`. +// Second return parameter `found` is true if key was found, otherwise false. +func (m *KVMap[K, V]) Search(key K) (value V, found bool) { + m.mu.RLock() + if m.data != nil { + value, found = m.data[key] + } + m.mu.RUnlock() + return +} + +// Get returns the value by given `key`. +func (m *KVMap[K, V]) Get(key K) (value V) { + m.mu.RLock() + if m.data != nil { + value = m.data[key] + } + m.mu.RUnlock() + return +} + +// Pop retrieves and deletes an item from the map. +func (m *KVMap[K, V]) Pop() (key K, value V) { + m.mu.Lock() + defer m.mu.Unlock() + for key, value = range m.data { + delete(m.data, key) + return + } + return +} + +// Pops retrieves and deletes `size` items from the map. +// It returns all items if size == -1. +func (m *KVMap[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 + } + var ( + index = 0 + newMap = make(map[K]V, 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 `key`, +// or else just return the existing value. +// +// It returns value with given `key`. +func (m *KVMap[K, V]) doSetWithLockCheck(key K, value V) (val V, ok bool) { + m.mu.Lock() + defer m.mu.Unlock() + + if m.data == nil { + m.data = make(map[K]V) + } + + if v, ok := m.data[key]; ok { + return v, true + } + + if any(value) != nil { + m.data[key] = value + } + return value, false +} + +// GetOrSet returns the value by key, +// or sets value with given `value` if it does not exist and then returns this value. +func (m *KVMap[K, V]) GetOrSet(key K, value V) V { + v, _ := m.doSetWithLockCheck(key, value) + 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 *KVMap[K, V]) GetOrSetFunc(key K, f func() V) V { + v, _ := m.doSetWithLockCheck(key, f()) + 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 hash map. +func (m *KVMap[K, V]) GetOrSetFuncLock(key K, f func() V) V { + m.mu.Lock() + defer m.mu.Unlock() + if m.data == nil { + m.data = make(map[K]V) + } + if v, ok := m.data[key]; ok { + return v + } + value := f() + if any(value) != nil { + m.data[key] = value + } + return value +} + +// GetVar returns a Var with the value by given `key`. +// The returned Var is un-concurrent safe. +func (m *KVMap[K, V]) GetVar(key K) *gvar.Var { + return gvar.New(m.Get(key)) +} + +// GetVarOrSet returns a Var with result from GetOrSet. +// The returned Var is un-concurrent safe. +func (m *KVMap[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 *KVMap[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 *KVMap[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 *KVMap[K, V]) SetIfNotExist(key K, value V) bool { + m.mu.Lock() + defer m.mu.Unlock() + if m.data == nil { + m.data = make(map[K]V) + } + if _, ok := m.data[key]; !ok { + m.data[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 *KVMap[K, V]) SetIfNotExistFunc(key K, f func() V) bool { + if !m.Contains(key) { + return m.SetIfNotExist(key, f()) + } + 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 hash map. +func (m *KVMap[K, V]) SetIfNotExistFuncLock(key K, f func() V) bool { + m.mu.Lock() + defer m.mu.Unlock() + if m.data == nil { + m.data = make(map[K]V) + } + if _, ok := m.data[key]; !ok { + m.data[key] = f() + return true + } + return false +} + +// Remove deletes value from map by given `key`, and return this deleted value. +func (m *KVMap[K, V]) Remove(key K) (value V) { + m.mu.Lock() + if m.data != nil { + var ok bool + if value, ok = m.data[key]; ok { + delete(m.data, key) + } + } + m.mu.Unlock() + return +} + +// Removes batch deletes values of the map by keys. +func (m *KVMap[K, V]) Removes(keys []K) { + m.mu.Lock() + if m.data != nil { + for _, key := range keys { + delete(m.data, key) + } + } + m.mu.Unlock() +} + +// Keys returns all keys of the map as a slice. +func (m *KVMap[K, V]) Keys() []K { + m.mu.RLock() + defer m.mu.RUnlock() + var ( + keys = make([]K, len(m.data)) + index = 0 + ) + for key := range m.data { + keys[index] = key + index++ + } + return keys +} + +// Values returns all values of the map as a slice. +func (m *KVMap[K, V]) Values() []V { + m.mu.RLock() + defer m.mu.RUnlock() + var ( + values = make([]V, len(m.data)) + index = 0 + ) + for _, value := range m.data { + values[index] = value + index++ + } + return values +} + +// Contains checks whether a key exists. +// It returns true if the `key` exists, or else false. +func (m *KVMap[K, V]) Contains(key K) bool { + var ok bool + m.mu.RLock() + if m.data != nil { + _, ok = m.data[key] + } + m.mu.RUnlock() + return ok +} + +// Size returns the size of the map. +func (m *KVMap[K, V]) Size() int { + m.mu.RLock() + length := len(m.data) + m.mu.RUnlock() + return length +} + +// IsEmpty checks whether the map is empty. +// It returns true if map is empty, or else false. +func (m *KVMap[K, V]) IsEmpty() bool { + return m.Size() == 0 +} + +// Clear deletes all data of the map, it will remake a new underlying data map. +func (m *KVMap[K, V]) Clear() { + m.mu.Lock() + m.data = make(map[K]V) + m.mu.Unlock() +} + +// Replace the data of the map with given `data`. +func (m *KVMap[K, V]) Replace(data map[K]V) { + m.mu.Lock() + m.data = data + m.mu.Unlock() +} + +// LockFunc locks writing with given callback function `f` within RWMutex.Lock. +func (m *KVMap[K, V]) LockFunc(f func(m map[K]V)) { + m.mu.Lock() + defer m.mu.Unlock() + f(m.data) +} + +// RLockFunc locks reading with given callback function `f` within RWMutex.RLock. +func (m *KVMap[K, V]) RLockFunc(f func(m map[K]V)) { + m.mu.RLock() + defer m.mu.RUnlock() + f(m.data) +} + +// Flip exchanges key-value of the map to value-key. +func (m *KVMap[K, V]) Flip() { + m.mu.Lock() + defer m.mu.Unlock() + n := make(map[K]V, len(m.data)) + for k, v := range m.data { + var ( + k0 K + v0 V + ) + if err := gconv.Scan(v, &k0); err != nil { + continue + } + if err := gconv.Scan(k, &v0); err != nil { + continue + } + n[k0] = v0 + } + m.data = n +} + +// Merge merges two hash maps. +// The `other` map will be merged into the map `m`. +func (m *KVMap[K, V]) Merge(other *KVMap[K, V]) { + m.mu.Lock() + defer m.mu.Unlock() + if m.data == nil { + m.data = other.MapCopy() + return + } + if other != m { + other.mu.RLock() + defer other.mu.RUnlock() + } + for k, v := range other.data { + m.data[k] = v + } +} + +// String returns the map as a string. +func (m *KVMap[K, V]) String() string { + if m == nil { + return "" + } + b, _ := m.MarshalJSON() + return string(b) +} + +// MarshalJSON implements the interface MarshalJSON for json.Marshal. +func (m KVMap[K, V]) MarshalJSON() ([]byte, error) { + return json.Marshal(gconv.Map(m.Map())) +} + +// UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal. +func (m *KVMap[K, V]) UnmarshalJSON(b []byte) error { + m.mu.Lock() + defer m.mu.Unlock() + if m.data == nil { + m.data = make(map[K]V) + } + var data map[string]V + if err := json.UnmarshalUseNumber(b, &data); err != nil { + return err + } + if err := gconv.Scan(data, &m.data); err != nil { + return err + } + return nil +} + +// UnmarshalValue is an interface implement which sets any type of value for map. +func (m *KVMap[K, V]) UnmarshalValue(value any) (err error) { + m.mu.Lock() + defer m.mu.Unlock() + if m.data == nil { + m.data = make(map[K]V) + } + data := gconv.Map(value) + if err := gconv.Scan(data, &m.data); err != nil { + return err + } + return +} + +// DeepCopy implements interface for deep copy of current type. +func (m *KVMap[K, V]) DeepCopy() any { + if m == nil { + return nil + } + + m.mu.RLock() + defer m.mu.RUnlock() + data := make(map[K]V, len(m.data)) + for k, v := range m.data { + data[k] = deepcopy.Copy(v).(V) + } + return NewKVMapFrom(data, m.mu.IsSafe()) +} + +// IsSubOf checks whether the current map is a sub-map of `other`. +func (m *KVMap[K, V]) IsSubOf(other *KVMap[K, V]) bool { + if m == other { + return true + } + m.mu.RLock() + defer m.mu.RUnlock() + other.mu.RLock() + defer other.mu.RUnlock() + for key, value := range m.data { + otherValue, ok := other.data[key] + if !ok { + return false + } + + if !reflect.DeepEqual(otherValue, value) { + return false + } + } + return true +} + +// Diff compares current map `m` with map `other` and returns their different keys. +// The returned `addedKeys` are the keys that are in map `m` but not in map `other`. +// The returned `removedKeys` are the keys that are in map `other` but not in map `m`. +// The returned `updatedKeys` are the keys that are both in map `m` and `other` but their values and not equal (`!=`). +func (m *KVMap[K, V]) Diff(other *KVMap[K, V]) (addedKeys, removedKeys, updatedKeys []K) { + m.mu.RLock() + defer m.mu.RUnlock() + other.mu.RLock() + defer other.mu.RUnlock() + + for key := range m.data { + if _, ok := other.data[key]; !ok { + removedKeys = append(removedKeys, key) + } else if !reflect.DeepEqual(m.data[key], other.data[key]) { + updatedKeys = append(updatedKeys, key) + } + } + for key := range other.data { + if _, ok := m.data[key]; !ok { + addedKeys = append(addedKeys, key) + } + } + return +} diff --git a/container/gmap/gmap_hash_str_any_map.go b/container/gmap/gmap_hash_str_any_map.go index 80b4bdc06..9e5db668f 100644 --- a/container/gmap/gmap_hash_str_any_map.go +++ b/container/gmap/gmap_hash_str_any_map.go @@ -8,71 +8,66 @@ package gmap import ( - "reflect" + "sync" "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" ) // StrAnyMap implements map[string]any with RWMutex that has switch. type StrAnyMap struct { - mu rwmutex.RWMutex - data map[string]any + *KVMap[string, any] + once sync.Once } // NewStrAnyMap returns an empty StrAnyMap object. // The parameter `safe` is used to specify whether using map in concurrent-safety, // which is false in default. func NewStrAnyMap(safe ...bool) *StrAnyMap { - return &StrAnyMap{ - mu: rwmutex.Create(safe...), - data: make(map[string]any), + m := &StrAnyMap{ + KVMap: NewKVMap[string, any](safe...), } + return m } // NewStrAnyMapFrom creates and returns a hash 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 NewStrAnyMapFrom(data map[string]any, safe ...bool) *StrAnyMap { - return &StrAnyMap{ - mu: rwmutex.Create(safe...), - data: data, + m := &StrAnyMap{ + KVMap: NewKVMapFrom(data, safe...), } + return m +} + +// lazyInit lazily initializes the map. +func (m *StrAnyMap) lazyInit() { + m.once.Do(func() { + if m.KVMap == nil { + m.KVMap = NewKVMap[string, any](false) + } + }) } // Iterator iterates the hash map readonly with custom callback function `f`. // If `f` returns true, then it continues iterating; or false to stop. func (m *StrAnyMap) Iterator(f func(k string, v any) bool) { - for k, v := range m.Map() { - if !f(k, v) { - break - } - } + m.lazyInit() + m.KVMap.Iterator(f) } // Clone returns a new hash map with copy of current map data. -func (m *StrAnyMap) Clone() *StrAnyMap { - return NewStrAnyMapFrom(m.MapCopy(), m.mu.IsSafe()) +func (m *StrAnyMap) Clone(safe ...bool) *StrAnyMap { + m.lazyInit() + return NewStrAnyMapFrom(m.MapCopy(), safe...) } // Map returns the underlying data map. // Note that, if it's in concurrent-safe usage, it returns a copy of underlying data, // or else a pointer to the underlying data. func (m *StrAnyMap) Map() map[string]any { - m.mu.RLock() - defer m.mu.RUnlock() - if !m.mu.IsSafe() { - return m.data - } - data := make(map[string]any, len(m.data)) - for k, v := range m.data { - data[k] = v - } - return data + m.lazyInit() + return m.KVMap.Map() } // MapStrAny returns a copy of the underlying data of the map as map[string]any. @@ -82,165 +77,74 @@ func (m *StrAnyMap) MapStrAny() map[string]any { // MapCopy returns a copy of the underlying data of the hash map. func (m *StrAnyMap) MapCopy() map[string]any { - m.mu.RLock() - defer m.mu.RUnlock() - data := make(map[string]any, len(m.data)) - for k, v := range m.data { - data[k] = v - } - return data + m.lazyInit() + return m.KVMap.MapCopy() } // FilterEmpty deletes all key-value pair of which the value is empty. // Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty. func (m *StrAnyMap) FilterEmpty() { - m.mu.Lock() - for k, v := range m.data { - if empty.IsEmpty(v) { - delete(m.data, k) - } - } - m.mu.Unlock() + m.lazyInit() + m.KVMap.FilterEmpty() } // FilterNil deletes all key-value pair of which the value is nil. func (m *StrAnyMap) FilterNil() { - m.mu.Lock() - defer m.mu.Unlock() - for k, v := range m.data { - if empty.IsNil(v) { - delete(m.data, k) - } - } + m.lazyInit() + m.KVMap.FilterNil() } // Set sets key-value to the hash map. func (m *StrAnyMap) Set(key string, val any) { - m.mu.Lock() - if m.data == nil { - m.data = make(map[string]any) - } - m.data[key] = val - m.mu.Unlock() + m.lazyInit() + m.KVMap.Set(key, val) } // Sets batch sets key-values to the hash map. func (m *StrAnyMap) Sets(data map[string]any) { - m.mu.Lock() - if m.data == nil { - m.data = data - } else { - for k, v := range data { - m.data[k] = v - } - } - m.mu.Unlock() + m.lazyInit() + m.KVMap.Sets(data) } // Search searches the map with given `key`. // Second return parameter `found` is true if key was found, otherwise false. func (m *StrAnyMap) Search(key string) (value any, found bool) { - m.mu.RLock() - if m.data != nil { - value, found = m.data[key] - } - m.mu.RUnlock() - return + m.lazyInit() + return m.KVMap.Search(key) } // Get returns the value by given `key`. func (m *StrAnyMap) Get(key string) (value any) { - m.mu.RLock() - if m.data != nil { - value = m.data[key] - } - m.mu.RUnlock() - return + m.lazyInit() + return m.KVMap.Get(key) } // Pop retrieves and deletes an item from the map. func (m *StrAnyMap) Pop() (key string, value any) { - m.mu.Lock() - defer m.mu.Unlock() - for key, value = range m.data { - delete(m.data, key) - return - } - return + m.lazyInit() + return m.KVMap.Pop() } // Pops retrieves and deletes `size` items from the map. // It returns all items if size == -1. func (m *StrAnyMap) Pops(size int) map[string]any { - m.mu.Lock() - defer m.mu.Unlock() - if size > len(m.data) || size == -1 { - size = len(m.data) - } - if size == 0 { - return nil - } - var ( - index = 0 - newMap = make(map[string]any, 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 `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 hash map, -// and its return value will be set to the map with `key`. -// -// It returns value with given `key`. -func (m *StrAnyMap) doSetWithLockCheck(key string, value any) any { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[string]any) - } - if v, ok := m.data[key]; ok { - return v - } - if f, ok := value.(func() any); ok { - value = f() - } - if value != nil { - m.data[key] = value - } - return value + m.lazyInit() + return m.KVMap.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 *StrAnyMap) GetOrSet(key string, value any) any { - if v, ok := m.Search(key); !ok { - return m.doSetWithLockCheck(key, value) - } else { - return v - } + m.lazyInit() + return m.KVMap.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 *StrAnyMap) GetOrSetFunc(key string, f func() any) any { - if v, ok := m.Search(key); !ok { - return m.doSetWithLockCheck(key, f()) - } else { - return v - } + m.lazyInit() + return m.KVMap.GetOrSetFunc(key, f) } // GetOrSetFuncLock returns the value by key, @@ -250,55 +154,50 @@ func (m *StrAnyMap) GetOrSetFunc(key string, f func() any) any { // GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f` // with mutex.Lock of the hash map. func (m *StrAnyMap) GetOrSetFuncLock(key string, f func() any) any { - if v, ok := m.Search(key); !ok { - return m.doSetWithLockCheck(key, f) - } else { - return v - } + m.lazyInit() + return m.KVMap.GetOrSetFuncLock(key, f) } // GetVar returns a Var with the value by given `key`. // The returned Var is un-concurrent safe. func (m *StrAnyMap) GetVar(key string) *gvar.Var { - return gvar.New(m.Get(key)) + m.lazyInit() + return m.KVMap.GetVar(key) } // GetVarOrSet returns a Var with result from GetVarOrSet. // The returned Var is un-concurrent safe. func (m *StrAnyMap) GetVarOrSet(key string, value any) *gvar.Var { - return gvar.New(m.GetOrSet(key, value)) + m.lazyInit() + return m.KVMap.GetVarOrSet(key, value) } // GetVarOrSetFunc returns a Var with result from GetOrSetFunc. // The returned Var is un-concurrent safe. func (m *StrAnyMap) GetVarOrSetFunc(key string, f func() any) *gvar.Var { - return gvar.New(m.GetOrSetFunc(key, f)) + m.lazyInit() + return m.KVMap.GetVarOrSetFunc(key, f) } // GetVarOrSetFuncLock returns a Var with result from GetOrSetFuncLock. // The returned Var is un-concurrent safe. func (m *StrAnyMap) GetVarOrSetFuncLock(key string, f func() any) *gvar.Var { - return gvar.New(m.GetOrSetFuncLock(key, f)) + m.lazyInit() + return m.KVMap.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 *StrAnyMap) SetIfNotExist(key string, value any) bool { - if !m.Contains(key) { - m.doSetWithLockCheck(key, value) - return true - } - return false + m.lazyInit() + return m.KVMap.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 *StrAnyMap) SetIfNotExistFunc(key string, f func() any) bool { - if !m.Contains(key) { - m.doSetWithLockCheck(key, f()) - return true - } - return false + m.lazyInit() + return m.KVMap.SetIfNotExistFunc(key, f) } // SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true. @@ -307,119 +206,76 @@ func (m *StrAnyMap) SetIfNotExistFunc(key string, f func() any) bool { // SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that // it executes function `f` with mutex.Lock of the hash map. func (m *StrAnyMap) SetIfNotExistFuncLock(key string, f func() any) bool { - if !m.Contains(key) { - m.doSetWithLockCheck(key, f) - return true - } - return false + m.lazyInit() + return m.KVMap.SetIfNotExistFuncLock(key, f) } // Removes batch deletes values of the map by keys. func (m *StrAnyMap) Removes(keys []string) { - m.mu.Lock() - if m.data != nil { - for _, key := range keys { - delete(m.data, key) - } - } - m.mu.Unlock() + m.lazyInit() + m.KVMap.Removes(keys) } // Remove deletes value from map by given `key`, and return this deleted value. func (m *StrAnyMap) Remove(key string) (value any) { - m.mu.Lock() - if m.data != nil { - var ok bool - if value, ok = m.data[key]; ok { - delete(m.data, key) - } - } - m.mu.Unlock() - return + m.lazyInit() + return m.KVMap.Remove(key) } // Keys returns all keys of the map as a slice. func (m *StrAnyMap) Keys() []string { - m.mu.RLock() - var ( - keys = make([]string, len(m.data)) - index = 0 - ) - for key := range m.data { - keys[index] = key - index++ - } - m.mu.RUnlock() - return keys + m.lazyInit() + return m.KVMap.Keys() } // Values returns all values of the map as a slice. func (m *StrAnyMap) Values() []any { - m.mu.RLock() - var ( - values = make([]any, len(m.data)) - index = 0 - ) - for _, value := range m.data { - values[index] = value - index++ - } - m.mu.RUnlock() - return values + m.lazyInit() + return m.KVMap.Values() } // Contains checks whether a key exists. // It returns true if the `key` exists, or else false. func (m *StrAnyMap) Contains(key string) bool { - var ok bool - m.mu.RLock() - if m.data != nil { - _, ok = m.data[key] - } - m.mu.RUnlock() - return ok + m.lazyInit() + return m.KVMap.Contains(key) } // Size returns the size of the map. func (m *StrAnyMap) Size() int { - m.mu.RLock() - length := len(m.data) - m.mu.RUnlock() - return length + m.lazyInit() + return m.KVMap.Size() } // IsEmpty checks whether the map is empty. // It returns true if map is empty, or else false. func (m *StrAnyMap) IsEmpty() bool { - return m.Size() == 0 + m.lazyInit() + return m.KVMap.IsEmpty() } // Clear deletes all data of the map, it will remake a new underlying data map. func (m *StrAnyMap) Clear() { - m.mu.Lock() - m.data = make(map[string]any) - m.mu.Unlock() + m.lazyInit() + m.KVMap.Clear() } // Replace the data of the map with given `data`. func (m *StrAnyMap) Replace(data map[string]any) { - m.mu.Lock() - m.data = data - m.mu.Unlock() + m.lazyInit() + m.KVMap.Replace(data) } // LockFunc locks writing with given callback function `f` within RWMutex.Lock. func (m *StrAnyMap) LockFunc(f func(m map[string]any)) { - m.mu.Lock() - defer m.mu.Unlock() - f(m.data) + m.lazyInit() + m.KVMap.LockFunc(f) } // RLockFunc locks reading with given callback function `f` within RWMutex.RLock. func (m *StrAnyMap) RLockFunc(f func(m map[string]any)) { - m.mu.RLock() - defer m.mu.RUnlock() - f(m.data) + m.lazyInit() + m.KVMap.RLockFunc(f) } // Flip exchanges key-value of the map to value-key. @@ -436,19 +292,8 @@ func (m *StrAnyMap) Flip() { // Merge merges two hash maps. // The `other` map will be merged into the map `m`. func (m *StrAnyMap) Merge(other *StrAnyMap) { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = other.MapCopy() - return - } - if other != m { - other.mu.RLock() - defer other.mu.RUnlock() - } - for k, v := range other.data { - m.data[k] = v - } + m.lazyInit() + m.KVMap.Merge(other.KVMap) } // String returns the map as a string. @@ -456,71 +301,40 @@ func (m *StrAnyMap) String() string { if m == nil { return "" } - b, _ := m.MarshalJSON() - return string(b) + m.lazyInit() + return m.KVMap.String() } // MarshalJSON implements the interface MarshalJSON for json.Marshal. func (m StrAnyMap) MarshalJSON() ([]byte, error) { - m.mu.RLock() - defer m.mu.RUnlock() - return json.Marshal(m.data) + m.lazyInit() + return m.KVMap.MarshalJSON() } // UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal. func (m *StrAnyMap) UnmarshalJSON(b []byte) error { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[string]any) - } - if err := json.UnmarshalUseNumber(b, &m.data); err != nil { - return err - } - return nil + m.lazyInit() + return m.KVMap.UnmarshalJSON(b) } // UnmarshalValue is an interface implement which sets any type of value for map. func (m *StrAnyMap) UnmarshalValue(value any) (err error) { - m.mu.Lock() - defer m.mu.Unlock() - m.data = gconv.Map(value) - return + m.lazyInit() + return m.KVMap.UnmarshalValue(value) } // DeepCopy implements interface for deep copy of current type. func (m *StrAnyMap) DeepCopy() any { - if m == nil { - return nil + m.lazyInit() + return &StrAnyMap{ + KVMap: m.KVMap.DeepCopy().(*KVMap[string, any]), } - m.mu.RLock() - defer m.mu.RUnlock() - data := make(map[string]any, len(m.data)) - for k, v := range m.data { - data[k] = deepcopy.Copy(v) - } - return NewStrAnyMapFrom(data, m.mu.IsSafe()) } // IsSubOf checks whether the current map is a sub-map of `other`. func (m *StrAnyMap) IsSubOf(other *StrAnyMap) bool { - if m == other { - return true - } - m.mu.RLock() - defer m.mu.RUnlock() - other.mu.RLock() - defer other.mu.RUnlock() - for key, value := range m.data { - otherValue, ok := other.data[key] - if !ok { - return false - } - if otherValue != value { - return false - } - } - return true + m.lazyInit() + return m.KVMap.IsSubOf(other.KVMap) } // Diff compares current map `m` with map `other` and returns their different keys. @@ -528,22 +342,6 @@ func (m *StrAnyMap) IsSubOf(other *StrAnyMap) bool { // The returned `removedKeys` are the keys that are in map `other` but not in map `m`. // The returned `updatedKeys` are the keys that are both in map `m` and `other` but their values and not equal (`!=`). func (m *StrAnyMap) Diff(other *StrAnyMap) (addedKeys, removedKeys, updatedKeys []string) { - m.mu.RLock() - defer m.mu.RUnlock() - other.mu.RLock() - defer other.mu.RUnlock() - - for key := range m.data { - if _, ok := other.data[key]; !ok { - removedKeys = append(removedKeys, key) - } else if !reflect.DeepEqual(m.data[key], other.data[key]) { - updatedKeys = append(updatedKeys, key) - } - } - for key := range other.data { - if _, ok := m.data[key]; !ok { - addedKeys = append(addedKeys, key) - } - } - return + m.lazyInit() + return m.KVMap.Diff(other.KVMap) } diff --git a/container/gmap/gmap_hash_str_int_map.go b/container/gmap/gmap_hash_str_int_map.go index 3ee4f4225..6e2b098f4 100644 --- a/container/gmap/gmap_hash_str_int_map.go +++ b/container/gmap/gmap_hash_str_int_map.go @@ -8,16 +8,15 @@ package gmap import ( - "github.com/gogf/gf/v2/internal/empty" - "github.com/gogf/gf/v2/internal/json" - "github.com/gogf/gf/v2/internal/rwmutex" + "sync" + "github.com/gogf/gf/v2/util/gconv" ) // StrIntMap implements map[string]int with RWMutex that has switch. type StrIntMap struct { - mu rwmutex.RWMutex - data map[string]int + *KVMap[string, int] + once sync.Once } // NewStrIntMap returns an empty StrIntMap object. @@ -25,8 +24,7 @@ type StrIntMap struct { // which is false in default. func NewStrIntMap(safe ...bool) *StrIntMap { return &StrIntMap{ - mu: rwmutex.Create(safe...), - data: make(map[string]int), + KVMap: NewKVMap[string, int](safe...), } } @@ -35,195 +33,110 @@ func NewStrIntMap(safe ...bool) *StrIntMap { // there might be some concurrent-safe issues when changing the map outside. func NewStrIntMapFrom(data map[string]int, safe ...bool) *StrIntMap { return &StrIntMap{ - mu: rwmutex.Create(safe...), - data: data, + KVMap: NewKVMapFrom(data, safe...), } } +// lazyInit lazily initializes the map. +func (m *StrIntMap) lazyInit() { + m.once.Do(func() { + if m.KVMap == nil { + m.KVMap = NewKVMap[string, int](false) + } + }) +} + // Iterator iterates the hash map readonly with custom callback function `f`. // If `f` returns true, then it continues iterating; or false to stop. func (m *StrIntMap) Iterator(f func(k string, v int) bool) { - for k, v := range m.Map() { - if !f(k, v) { - break - } - } + m.lazyInit() + m.KVMap.Iterator(f) } // Clone returns a new hash map with copy of current map data. -func (m *StrIntMap) Clone() *StrIntMap { - return NewStrIntMapFrom(m.MapCopy(), m.mu.IsSafe()) +func (m *StrIntMap) Clone(safe ...bool) *StrIntMap { + m.lazyInit() + return &StrIntMap{KVMap: m.KVMap.Clone(safe...)} } // Map returns the underlying data map. // Note that, if it's in concurrent-safe usage, it returns a copy of underlying data, // or else a pointer to the underlying data. func (m *StrIntMap) Map() map[string]int { - m.mu.RLock() - defer m.mu.RUnlock() - if !m.mu.IsSafe() { - return m.data - } - data := make(map[string]int, len(m.data)) - for k, v := range m.data { - data[k] = v - } - return data + m.lazyInit() + return m.KVMap.Map() } // MapStrAny returns a copy of the underlying data of the map as map[string]any. func (m *StrIntMap) MapStrAny() map[string]any { - m.mu.RLock() - defer m.mu.RUnlock() - data := make(map[string]any, len(m.data)) - for k, v := range m.data { - data[k] = v - } - return data + m.lazyInit() + return m.KVMap.MapStrAny() } // MapCopy returns a copy of the underlying data of the hash map. func (m *StrIntMap) MapCopy() map[string]int { - m.mu.RLock() - defer m.mu.RUnlock() - data := make(map[string]int, len(m.data)) - for k, v := range m.data { - data[k] = v - } - return data + m.lazyInit() + return m.KVMap.MapCopy() } // FilterEmpty deletes all key-value pair of which the value is empty. // Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty. func (m *StrIntMap) FilterEmpty() { - m.mu.Lock() - for k, v := range m.data { - if empty.IsEmpty(v) { - delete(m.data, k) - } - } - m.mu.Unlock() + m.lazyInit() + m.KVMap.FilterEmpty() } // Set sets key-value to the hash map. func (m *StrIntMap) Set(key string, val int) { - m.mu.Lock() - if m.data == nil { - m.data = make(map[string]int) - } - m.data[key] = val - m.mu.Unlock() + m.lazyInit() + m.KVMap.Set(key, val) } // Sets batch sets key-values to the hash map. func (m *StrIntMap) Sets(data map[string]int) { - m.mu.Lock() - if m.data == nil { - m.data = data - } else { - for k, v := range data { - m.data[k] = v - } - } - m.mu.Unlock() + m.lazyInit() + m.KVMap.Sets(data) } // Search searches the map with given `key`. // Second return parameter `found` is true if key was found, otherwise false. func (m *StrIntMap) Search(key string) (value int, found bool) { - m.mu.RLock() - if m.data != nil { - value, found = m.data[key] - } - m.mu.RUnlock() - return + m.lazyInit() + return m.KVMap.Search(key) } // Get returns the value by given `key`. func (m *StrIntMap) Get(key string) (value int) { - m.mu.RLock() - if m.data != nil { - value = m.data[key] - } - m.mu.RUnlock() - return + m.lazyInit() + return m.KVMap.Get(key) } // 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 + m.lazyInit() + return m.KVMap.Pop() } // Pops retrieves and deletes `size` 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 - } - var ( - 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 `key`, -// or else just return the existing value. -// -// It returns value with given `key`. -func (m *StrIntMap) doSetWithLockCheck(key string, value int) int { - m.mu.Lock() - if m.data == nil { - m.data = make(map[string]int) - } - if v, ok := m.data[key]; ok { - m.mu.Unlock() - return v - } - m.data[key] = value - m.mu.Unlock() - return value + m.lazyInit() + return m.KVMap.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 *StrIntMap) GetOrSet(key string, value int) int { - if v, ok := m.Search(key); !ok { - return m.doSetWithLockCheck(key, value) - } else { - return v - } + m.lazyInit() + return m.KVMap.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 *StrIntMap) GetOrSetFunc(key string, f func() int) int { - if v, ok := m.Search(key); !ok { - return m.doSetWithLockCheck(key, f()) - } else { - return v - } + m.lazyInit() + return m.KVMap.GetOrSetFunc(key, f) } // GetOrSetFuncLock returns the value by key, @@ -233,41 +146,22 @@ func (m *StrIntMap) GetOrSetFunc(key string, f func() int) int { // GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f` // with mutex.Lock of the hash map. func (m *StrIntMap) GetOrSetFuncLock(key string, f func() int) int { - if v, ok := m.Search(key); !ok { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[string]int) - } - if v, ok = m.data[key]; ok { - return v - } - v = f() - m.data[key] = v - return v - } else { - return v - } + m.lazyInit() + return m.KVMap.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 *StrIntMap) SetIfNotExist(key string, value int) bool { - if !m.Contains(key) { - m.doSetWithLockCheck(key, value) - return true - } - return false + m.lazyInit() + return m.KVMap.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 *StrIntMap) SetIfNotExistFunc(key string, f func() int) bool { - if !m.Contains(key) { - m.doSetWithLockCheck(key, f()) - return true - } - return false + m.lazyInit() + return m.KVMap.SetIfNotExistFunc(key, f) } // SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true. @@ -276,126 +170,76 @@ func (m *StrIntMap) SetIfNotExistFunc(key string, f func() int) bool { // SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that // it executes function `f` with mutex.Lock of the hash map. func (m *StrIntMap) SetIfNotExistFuncLock(key string, f func() int) bool { - if !m.Contains(key) { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[string]int) - } - if _, ok := m.data[key]; !ok { - m.data[key] = f() - } - return true - } - return false + m.lazyInit() + return m.KVMap.SetIfNotExistFuncLock(key, f) } // Removes batch deletes values of the map by keys. func (m *StrIntMap) Removes(keys []string) { - m.mu.Lock() - if m.data != nil { - for _, key := range keys { - delete(m.data, key) - } - } - m.mu.Unlock() + m.lazyInit() + m.KVMap.Removes(keys) } // Remove deletes value from map by given `key`, and return this deleted value. func (m *StrIntMap) Remove(key string) (value int) { - m.mu.Lock() - if m.data != nil { - var ok bool - if value, ok = m.data[key]; ok { - delete(m.data, key) - } - } - m.mu.Unlock() - return + m.lazyInit() + return m.KVMap.Remove(key) } // Keys returns all keys of the map as a slice. func (m *StrIntMap) Keys() []string { - m.mu.RLock() - var ( - keys = make([]string, len(m.data)) - index = 0 - ) - for key := range m.data { - keys[index] = key - index++ - } - m.mu.RUnlock() - return keys + m.lazyInit() + return m.KVMap.Keys() } // Values returns all values of the map as a slice. func (m *StrIntMap) Values() []int { - m.mu.RLock() - var ( - values = make([]int, len(m.data)) - index = 0 - ) - for _, value := range m.data { - values[index] = value - index++ - } - m.mu.RUnlock() - return values + m.lazyInit() + return m.KVMap.Values() } // Contains checks whether a key exists. // It returns true if the `key` exists, or else false. func (m *StrIntMap) Contains(key string) bool { - var ok bool - m.mu.RLock() - if m.data != nil { - _, ok = m.data[key] - } - m.mu.RUnlock() - return ok + m.lazyInit() + return m.KVMap.Contains(key) } // Size returns the size of the map. func (m *StrIntMap) Size() int { - m.mu.RLock() - length := len(m.data) - m.mu.RUnlock() - return length + m.lazyInit() + return m.KVMap.Size() } // IsEmpty checks whether the map is empty. // It returns true if map is empty, or else false. func (m *StrIntMap) IsEmpty() bool { - return m.Size() == 0 + m.lazyInit() + return m.KVMap.IsEmpty() } // Clear deletes all data of the map, it will remake a new underlying data map. func (m *StrIntMap) Clear() { - m.mu.Lock() - m.data = make(map[string]int) - m.mu.Unlock() + m.lazyInit() + m.KVMap.Clear() } // Replace the data of the map with given `data`. func (m *StrIntMap) Replace(data map[string]int) { - m.mu.Lock() - m.data = data - m.mu.Unlock() + m.lazyInit() + m.KVMap.Replace(data) } // LockFunc locks writing with given callback function `f` within RWMutex.Lock. func (m *StrIntMap) LockFunc(f func(m map[string]int)) { - m.mu.Lock() - defer m.mu.Unlock() - f(m.data) + m.lazyInit() + m.KVMap.LockFunc(f) } // RLockFunc locks reading with given callback function `f` within RWMutex.RLock. func (m *StrIntMap) RLockFunc(f func(m map[string]int)) { - m.mu.RLock() - defer m.mu.RUnlock() - f(m.data) + m.lazyInit() + m.KVMap.RLockFunc(f) } // Flip exchanges key-value of the map to value-key. @@ -412,19 +256,8 @@ func (m *StrIntMap) Flip() { // Merge merges two hash maps. // The `other` map will be merged into the map `m`. func (m *StrIntMap) Merge(other *StrIntMap) { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = other.MapCopy() - return - } - if other != m { - other.mu.RLock() - defer other.mu.RUnlock() - } - for k, v := range other.data { - m.data[k] = v - } + m.lazyInit() + m.KVMap.Merge(other.KVMap) } // String returns the map as a string. @@ -432,81 +265,40 @@ func (m *StrIntMap) String() string { if m == nil { return "" } - b, _ := m.MarshalJSON() - return string(b) + m.lazyInit() + return m.KVMap.String() } // MarshalJSON implements the interface MarshalJSON for json.Marshal. func (m StrIntMap) MarshalJSON() ([]byte, error) { - m.mu.RLock() - defer m.mu.RUnlock() - return json.Marshal(m.data) + m.lazyInit() + return m.KVMap.MarshalJSON() } // UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal. func (m *StrIntMap) UnmarshalJSON(b []byte) error { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[string]int) - } - if err := json.UnmarshalUseNumber(b, &m.data); err != nil { - return err - } - return nil + m.lazyInit() + return m.KVMap.UnmarshalJSON(b) } // UnmarshalValue is an interface implement which sets any type of value for map. func (m *StrIntMap) UnmarshalValue(value any) (err error) { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[string]int) - } - switch value.(type) { - case string, []byte: - return json.UnmarshalUseNumber(gconv.Bytes(value), &m.data) - default: - for k, v := range gconv.Map(value) { - m.data[k] = gconv.Int(v) - } - } - return + m.lazyInit() + return m.KVMap.UnmarshalValue(value) } // DeepCopy implements interface for deep copy of current type. func (m *StrIntMap) DeepCopy() any { - if m == nil { - return nil + m.lazyInit() + return &StrIntMap{ + KVMap: m.KVMap.DeepCopy().(*KVMap[string, int]), } - m.mu.RLock() - defer m.mu.RUnlock() - data := make(map[string]int, len(m.data)) - for k, v := range m.data { - data[k] = v - } - return NewStrIntMapFrom(data, m.mu.IsSafe()) } // IsSubOf checks whether the current map is a sub-map of `other`. func (m *StrIntMap) IsSubOf(other *StrIntMap) bool { - if m == other { - return true - } - m.mu.RLock() - defer m.mu.RUnlock() - other.mu.RLock() - defer other.mu.RUnlock() - for key, value := range m.data { - otherValue, ok := other.data[key] - if !ok { - return false - } - if otherValue != value { - return false - } - } - return true + m.lazyInit() + return m.KVMap.IsSubOf(other.KVMap) } // Diff compares current map `m` with map `other` and returns their different keys. @@ -514,22 +306,6 @@ func (m *StrIntMap) IsSubOf(other *StrIntMap) bool { // The returned `removedKeys` are the keys that are in map `other` but not in map `m`. // The returned `updatedKeys` are the keys that are both in map `m` and `other` but their values and not equal (`!=`). func (m *StrIntMap) Diff(other *StrIntMap) (addedKeys, removedKeys, updatedKeys []string) { - m.mu.RLock() - defer m.mu.RUnlock() - other.mu.RLock() - defer other.mu.RUnlock() - - for key := range m.data { - if _, ok := other.data[key]; !ok { - removedKeys = append(removedKeys, key) - } else if m.data[key] != other.data[key] { - updatedKeys = append(updatedKeys, key) - } - } - for key := range other.data { - if _, ok := m.data[key]; !ok { - addedKeys = append(addedKeys, key) - } - } - return + m.lazyInit() + return m.KVMap.Diff(other.KVMap) } diff --git a/container/gmap/gmap_hash_str_str_map.go b/container/gmap/gmap_hash_str_str_map.go index 9e62794ca..6722f3037 100644 --- a/container/gmap/gmap_hash_str_str_map.go +++ b/container/gmap/gmap_hash_str_str_map.go @@ -7,17 +7,12 @@ package gmap -import ( - "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" -) +import "sync" // StrStrMap implements map[string]string with RWMutex that has switch. type StrStrMap struct { - mu rwmutex.RWMutex - data map[string]string + *KVMap[string, string] + once sync.Once } // NewStrStrMap returns an empty StrStrMap object. @@ -25,8 +20,7 @@ type StrStrMap struct { // which is false in default. func NewStrStrMap(safe ...bool) *StrStrMap { return &StrStrMap{ - data: make(map[string]string), - mu: rwmutex.Create(safe...), + KVMap: NewKVMap[string, string](safe...), } } @@ -35,194 +29,110 @@ func NewStrStrMap(safe ...bool) *StrStrMap { // there might be some concurrent-safe issues when changing the map outside. func NewStrStrMapFrom(data map[string]string, safe ...bool) *StrStrMap { return &StrStrMap{ - mu: rwmutex.Create(safe...), - data: data, + KVMap: NewKVMapFrom(data, safe...), } } +// lazyInit lazily initializes the map. +func (m *StrStrMap) lazyInit() { + m.once.Do(func() { + if m.KVMap == nil { + m.KVMap = NewKVMap[string, string](false) + } + }) +} + // Iterator iterates the hash map readonly with custom callback function `f`. // If `f` returns true, then it continues iterating; or false to stop. func (m *StrStrMap) Iterator(f func(k string, v string) bool) { - for k, v := range m.Map() { - if !f(k, v) { - break - } - } + m.lazyInit() + m.KVMap.Iterator(f) } // Clone returns a new hash map with copy of current map data. -func (m *StrStrMap) Clone() *StrStrMap { - return NewStrStrMapFrom(m.MapCopy(), m.mu.IsSafe()) +func (m *StrStrMap) Clone(safe ...bool) *StrStrMap { + m.lazyInit() + return &StrStrMap{KVMap: m.KVMap.Clone(safe...)} } // Map returns the underlying data map. // Note that, if it's in concurrent-safe usage, it returns a copy of underlying data, // or else a pointer to the underlying data. func (m *StrStrMap) Map() map[string]string { - m.mu.RLock() - defer m.mu.RUnlock() - if !m.mu.IsSafe() { - return m.data - } - data := make(map[string]string, len(m.data)) - for k, v := range m.data { - data[k] = v - } - return data + m.lazyInit() + return m.KVMap.Map() } // MapStrAny returns a copy of the underlying data of the map as map[string]any. func (m *StrStrMap) MapStrAny() map[string]any { - m.mu.RLock() - data := make(map[string]any, len(m.data)) - for k, v := range m.data { - data[k] = v - } - m.mu.RUnlock() - return data + m.lazyInit() + return m.KVMap.MapStrAny() } // MapCopy returns a copy of the underlying data of the hash map. func (m *StrStrMap) MapCopy() map[string]string { - m.mu.RLock() - defer m.mu.RUnlock() - data := make(map[string]string, len(m.data)) - for k, v := range m.data { - data[k] = v - } - return data + m.lazyInit() + return m.KVMap.MapCopy() } // FilterEmpty deletes all key-value pair of which the value is empty. // Values like: 0, nil, false, "", len(slice/map/chan) == 0 are considered empty. func (m *StrStrMap) FilterEmpty() { - m.mu.Lock() - for k, v := range m.data { - if empty.IsEmpty(v) { - delete(m.data, k) - } - } - m.mu.Unlock() + m.lazyInit() + m.KVMap.FilterEmpty() } // Set sets key-value to the hash map. func (m *StrStrMap) Set(key string, val string) { - m.mu.Lock() - if m.data == nil { - m.data = make(map[string]string) - } - m.data[key] = val - m.mu.Unlock() + m.lazyInit() + m.KVMap.Set(key, val) } // Sets batch sets key-values to the hash map. func (m *StrStrMap) Sets(data map[string]string) { - m.mu.Lock() - if m.data == nil { - m.data = data - } else { - for k, v := range data { - m.data[k] = v - } - } - m.mu.Unlock() + m.lazyInit() + m.KVMap.Sets(data) } // Search searches the map with given `key`. // Second return parameter `found` is true if key was found, otherwise false. func (m *StrStrMap) Search(key string) (value string, found bool) { - m.mu.RLock() - if m.data != nil { - value, found = m.data[key] - } - m.mu.RUnlock() - return + m.lazyInit() + return m.KVMap.Search(key) } // Get returns the value by given `key`. func (m *StrStrMap) Get(key string) (value string) { - m.mu.RLock() - if m.data != nil { - value = m.data[key] - } - m.mu.RUnlock() - return + m.lazyInit() + return m.KVMap.Get(key) } // 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 + m.lazyInit() + return m.KVMap.Pop() } // Pops retrieves and deletes `size` 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 - } - var ( - 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 `key`, -// or else just return the existing value. -// -// It returns value with given `key`. -func (m *StrStrMap) doSetWithLockCheck(key string, value string) string { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[string]string) - } - if v, ok := m.data[key]; ok { - return v - } - m.data[key] = value - return value + m.lazyInit() + return m.KVMap.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 *StrStrMap) GetOrSet(key string, value string) string { - if v, ok := m.Search(key); !ok { - return m.doSetWithLockCheck(key, value) - } else { - return v - } + m.lazyInit() + return m.KVMap.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 *StrStrMap) GetOrSetFunc(key string, f func() string) string { - if v, ok := m.Search(key); !ok { - return m.doSetWithLockCheck(key, f()) - } else { - return v - } + m.lazyInit() + return m.KVMap.GetOrSetFunc(key, f) } // GetOrSetFuncLock returns the value by key, @@ -232,41 +142,22 @@ func (m *StrStrMap) GetOrSetFunc(key string, f func() string) string { // GetOrSetFuncLock differs with GetOrSetFunc function is that it executes function `f` // with mutex.Lock of the hash map. func (m *StrStrMap) GetOrSetFuncLock(key string, f func() string) string { - if v, ok := m.Search(key); !ok { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[string]string) - } - if v, ok = m.data[key]; ok { - return v - } - v = f() - m.data[key] = v - return v - } else { - return v - } + m.lazyInit() + return m.KVMap.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 *StrStrMap) SetIfNotExist(key string, value string) bool { - if !m.Contains(key) { - m.doSetWithLockCheck(key, value) - return true - } - return false + m.lazyInit() + return m.KVMap.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 *StrStrMap) SetIfNotExistFunc(key string, f func() string) bool { - if !m.Contains(key) { - m.doSetWithLockCheck(key, f()) - return true - } - return false + m.lazyInit() + return m.KVMap.SetIfNotExistFunc(key, f) } // SetIfNotExistFuncLock sets value with return value of callback function `f`, and then returns true. @@ -275,126 +166,76 @@ func (m *StrStrMap) SetIfNotExistFunc(key string, f func() string) bool { // SetIfNotExistFuncLock differs with SetIfNotExistFunc function is that // it executes function `f` with mutex.Lock of the hash map. func (m *StrStrMap) SetIfNotExistFuncLock(key string, f func() string) bool { - if !m.Contains(key) { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[string]string) - } - if _, ok := m.data[key]; !ok { - m.data[key] = f() - } - return true - } - return false + m.lazyInit() + return m.KVMap.SetIfNotExistFuncLock(key, f) } // Removes batch deletes values of the map by keys. func (m *StrStrMap) Removes(keys []string) { - m.mu.Lock() - if m.data != nil { - for _, key := range keys { - delete(m.data, key) - } - } - m.mu.Unlock() + m.lazyInit() + m.KVMap.Removes(keys) } // Remove deletes value from map by given `key`, and return this deleted value. func (m *StrStrMap) Remove(key string) (value string) { - m.mu.Lock() - if m.data != nil { - var ok bool - if value, ok = m.data[key]; ok { - delete(m.data, key) - } - } - m.mu.Unlock() - return + m.lazyInit() + return m.KVMap.Remove(key) } // Keys returns all keys of the map as a slice. func (m *StrStrMap) Keys() []string { - m.mu.RLock() - var ( - keys = make([]string, len(m.data)) - index = 0 - ) - for key := range m.data { - keys[index] = key - index++ - } - m.mu.RUnlock() - return keys + m.lazyInit() + return m.KVMap.Keys() } // Values returns all values of the map as a slice. func (m *StrStrMap) Values() []string { - m.mu.RLock() - var ( - values = make([]string, len(m.data)) - index = 0 - ) - for _, value := range m.data { - values[index] = value - index++ - } - m.mu.RUnlock() - return values + m.lazyInit() + return m.KVMap.Values() } // Contains checks whether a key exists. // It returns true if the `key` exists, or else false. func (m *StrStrMap) Contains(key string) bool { - var ok bool - m.mu.RLock() - if m.data != nil { - _, ok = m.data[key] - } - m.mu.RUnlock() - return ok + m.lazyInit() + return m.KVMap.Contains(key) } // Size returns the size of the map. func (m *StrStrMap) Size() int { - m.mu.RLock() - length := len(m.data) - m.mu.RUnlock() - return length + m.lazyInit() + return m.KVMap.Size() } // IsEmpty checks whether the map is empty. // It returns true if map is empty, or else false. func (m *StrStrMap) IsEmpty() bool { - return m.Size() == 0 + m.lazyInit() + return m.KVMap.IsEmpty() } // Clear deletes all data of the map, it will remake a new underlying data map. func (m *StrStrMap) Clear() { - m.mu.Lock() - m.data = make(map[string]string) - m.mu.Unlock() + m.lazyInit() + m.KVMap.Clear() } // Replace the data of the map with given `data`. func (m *StrStrMap) Replace(data map[string]string) { - m.mu.Lock() - m.data = data - m.mu.Unlock() + m.lazyInit() + m.KVMap.Replace(data) } // LockFunc locks writing with given callback function `f` within RWMutex.Lock. func (m *StrStrMap) LockFunc(f func(m map[string]string)) { - m.mu.Lock() - defer m.mu.Unlock() - f(m.data) + m.lazyInit() + m.KVMap.LockFunc(f) } // RLockFunc locks reading with given callback function `f` within RWMutex.RLock. func (m *StrStrMap) RLockFunc(f func(m map[string]string)) { - m.mu.RLock() - defer m.mu.RUnlock() - f(m.data) + m.lazyInit() + m.KVMap.RLockFunc(f) } // Flip exchanges key-value of the map to value-key. @@ -411,19 +252,8 @@ func (m *StrStrMap) Flip() { // Merge merges two hash maps. // The `other` map will be merged into the map `m`. func (m *StrStrMap) Merge(other *StrStrMap) { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = other.MapCopy() - return - } - if other != m { - other.mu.RLock() - defer other.mu.RUnlock() - } - for k, v := range other.data { - m.data[k] = v - } + m.lazyInit() + m.KVMap.Merge(other.KVMap) } // String returns the map as a string. @@ -431,71 +261,40 @@ func (m *StrStrMap) String() string { if m == nil { return "" } - b, _ := m.MarshalJSON() - return string(b) + m.lazyInit() + return m.KVMap.String() } // MarshalJSON implements the interface MarshalJSON for json.Marshal. func (m StrStrMap) MarshalJSON() ([]byte, error) { - m.mu.RLock() - defer m.mu.RUnlock() - return json.Marshal(m.data) + m.lazyInit() + return m.KVMap.MarshalJSON() } // UnmarshalJSON implements the interface UnmarshalJSON for json.Unmarshal. func (m *StrStrMap) UnmarshalJSON(b []byte) error { - m.mu.Lock() - defer m.mu.Unlock() - if m.data == nil { - m.data = make(map[string]string) - } - if err := json.UnmarshalUseNumber(b, &m.data); err != nil { - return err - } - return nil + m.lazyInit() + return m.KVMap.UnmarshalJSON(b) } // UnmarshalValue is an interface implement which sets any type of value for map. func (m *StrStrMap) UnmarshalValue(value any) (err error) { - m.mu.Lock() - defer m.mu.Unlock() - m.data = gconv.MapStrStr(value) - return + m.lazyInit() + return m.KVMap.UnmarshalValue(value) } // DeepCopy implements interface for deep copy of current type. func (m *StrStrMap) DeepCopy() any { - if m == nil { - return nil + m.lazyInit() + return &StrStrMap{ + KVMap: m.KVMap.DeepCopy().(*KVMap[string, string]), } - m.mu.RLock() - defer m.mu.RUnlock() - data := make(map[string]string, len(m.data)) - for k, v := range m.data { - data[k] = v - } - return NewStrStrMapFrom(data, m.mu.IsSafe()) } // IsSubOf checks whether the current map is a sub-map of `other`. func (m *StrStrMap) IsSubOf(other *StrStrMap) bool { - if m == other { - return true - } - m.mu.RLock() - defer m.mu.RUnlock() - other.mu.RLock() - defer other.mu.RUnlock() - for key, value := range m.data { - otherValue, ok := other.data[key] - if !ok { - return false - } - if otherValue != value { - return false - } - } - return true + m.lazyInit() + return m.KVMap.IsSubOf(other.KVMap) } // Diff compares current map `m` with map `other` and returns their different keys. @@ -503,22 +302,6 @@ func (m *StrStrMap) IsSubOf(other *StrStrMap) bool { // The returned `removedKeys` are the keys that are in map `other` but not in map `m`. // The returned `updatedKeys` are the keys that are both in map `m` and `other` but their values and not equal (`!=`). func (m *StrStrMap) Diff(other *StrStrMap) (addedKeys, removedKeys, updatedKeys []string) { - m.mu.RLock() - defer m.mu.RUnlock() - other.mu.RLock() - defer other.mu.RUnlock() - - for key := range m.data { - if _, ok := other.data[key]; !ok { - removedKeys = append(removedKeys, key) - } else if m.data[key] != other.data[key] { - updatedKeys = append(updatedKeys, key) - } - } - for key := range other.data { - if _, ok := m.data[key]; !ok { - addedKeys = append(addedKeys, key) - } - } - return + m.lazyInit() + return m.KVMap.Diff(other.KVMap) } diff --git a/container/gmap/gmap_list_map.go b/container/gmap/gmap_list_map.go index 5f90aaa41..1114d7529 100644 --- a/container/gmap/gmap_list_map.go +++ b/container/gmap/gmap_list_map.go @@ -281,7 +281,7 @@ func (m *ListMap) Pops(size int) map[any]any { // 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 {}`, +// When setting value, if `value` is type of `func() any`, // it will be executed with mutex.Lock of the map, // and its return value will be set to the map with `key`. // diff --git a/container/gmap/gmap_z_unit_hash_any_any_test.go b/container/gmap/gmap_z_unit_hash_any_any_test.go index 709bdfb1c..36a312f55 100644 --- a/container/gmap/gmap_z_unit_hash_any_any_test.go +++ b/container/gmap/gmap_z_unit_hash_any_any_test.go @@ -443,3 +443,49 @@ func Test_AnyAnyMap_Diff(t *testing.T) { t.Assert(updatedKeys, []any{3}) }) } + +func Test_AnyAnyMap_DoSetWithLockCheck_FuncValue(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewAnyAnyMap(true) + + // Test GetOrSetFuncLock with function value + // Function should be executed and its return value should be set + callCount := 0 + result := m.GetOrSetFuncLock(1, func() any { + callCount++ + return "value1" + }) + t.Assert(result, "value1") + t.Assert(callCount, 1) + t.Assert(m.Get(1), "value1") + + // Test GetOrSetFuncLock again with same key + // Function should NOT be called since key exists + result = m.GetOrSetFuncLock(1, func() any { + callCount++ + return "value2" + }) + t.Assert(result, "value1") + t.Assert(callCount, 1) // Should still be 1, function not called + + // Test SetIfNotExistFuncLock with function value + callCount = 0 + ok := m.SetIfNotExistFuncLock(2, func() any { + callCount++ + return "value2" + }) + t.Assert(ok, true) + t.Assert(callCount, 1) + t.Assert(m.Get(2), "value2") + + // Test SetIfNotExistFuncLock again with same key + // Function should NOT be called since key exists + ok = m.SetIfNotExistFuncLock(2, func() any { + callCount++ + return "value3" + }) + t.Assert(ok, false) + t.Assert(callCount, 1) // Should still be 1, function not called + t.Assert(m.Get(2), "value2") // Value should not change + }) +} diff --git a/container/gmap/gmap_z_unit_hash_str_any_test.go b/container/gmap/gmap_z_unit_hash_str_any_test.go index 4be223706..8ee222153 100644 --- a/container/gmap/gmap_z_unit_hash_str_any_test.go +++ b/container/gmap/gmap_z_unit_hash_str_any_test.go @@ -96,6 +96,42 @@ func Test_StrAnyMap_Set_Fun(t *testing.T) { t.Assert(m.SetIfNotExistFuncLock("b", getAny), false) t.Assert(m.SetIfNotExistFuncLock("d", getAny), true) + + type T struct { + A int + } + + av := m.GetOrSetFunc("s1", func() any { + return &T{ + A: 1, + } + }) + ta, ok := av.(*T) + t.Assert(ok, true) + t.Assert(ta.A, 1) + + av = m.GetOrSetFunc("s1", func() any { + return &T{ + A: 2, + } + }) + ta, ok = av.(*T) + t.Assert(ok, true) + t.Assert(ta.A, 1) + + av = m.GetOrSet("s1", &T{ + A: 3, + }) + ta, ok = av.(*T) + t.Assert(ok, true) + t.Assert(ta.A, 1) + + av = m.GetOrSet("s2", &T{ + A: 4, + }) + ta, ok = av.(*T) + t.Assert(ok, true) + t.Assert(ta.A, 4) }) } diff --git a/container/gmap/gmap_z_unit_k_v_map_test.go b/container/gmap/gmap_z_unit_k_v_map_test.go new file mode 100644 index 000000000..faef2c316 --- /dev/null +++ b/container/gmap/gmap_z_unit_k_v_map_test.go @@ -0,0 +1,1632 @@ +// 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 ( + "strconv" + "sync" + "testing" + + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/test/gtest" + "github.com/gogf/gf/v2/util/gconv" +) + +func Test_KVMap_NewKVMap(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, string]() + t.Assert(m.Size(), 0) + t.Assert(m.IsEmpty(), true) + }) + + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, string](true) + t.Assert(m.Size(), 0) + t.Assert(m.IsEmpty(), true) + }) +} + +func Test_KVMap_NewKVMapFrom(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + data := map[string]string{"a": "1", "b": "2"} + m := gmap.NewKVMapFrom(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.NewKVMapFrom(data, true) + t.Assert(m.Size(), 2) + t.Assert(m.Get(1), 10) + t.Assert(m.Get(2), 20) + }) +} + +func Test_KVMap_Set_Get(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[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.NewKVMap[int, int]() + m.Set(1, 100) + m.Set(2, 200) + t.Assert(m.Get(1), 100) + t.Assert(m.Get(2), 200) + }) +} + +func Test_KVMap_Sets(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[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.NewKVMapFrom(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_KVMap_Search(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(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.NewKVMap[int, string]() + v, found := m.Search(1) + t.Assert(found, false) + t.Assert(v, "") + }) +} + +func Test_KVMap_Contains(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(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_KVMap_Remove(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(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_KVMap_Removes(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(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.NewKVMapFrom(map[string]string{"a": "1", "b": "2"}) + m.Removes([]string{"x", "y"}) + t.Assert(m.Size(), 2) + }) +} + +func Test_KVMap_Pop(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(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.NewKVMap[string, string]() + k, v := m.Pop() + t.Assert(k, "") + t.Assert(v, "") + }) +} + +func Test_KVMap_Pops(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(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.NewKVMapFrom(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.NewKVMapFrom(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.NewKVMap[string, string]() + popped := m.Pops(1) + t.AssertNil(popped) + }) +} + +func Test_KVMap_Keys(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(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.NewKVMap[int, string]() + keys := m.Keys() + t.Assert(len(keys), 0) + }) +} + +func Test_KVMap_Values(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(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.NewKVMap[string, int]() + values := m.Values() + t.Assert(len(values), 0) + }) +} + +func Test_KVMap_Size(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[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_KVMap_IsEmpty(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[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_KVMap_Clear(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(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_KVMap_Map(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(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) + }) + + gtest.C(t, func(t *gtest.T) { + // Unsafe map, modifying returned map affects original + m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"}, false) + data := m.Map() + data["c"] = "3" + t.Assert(m.Get("c"), "3") + }) + + gtest.C(t, func(t *gtest.T) { + // Safe map, modifying returned map doesn't affect original + m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"}, true) + data := m.Map() + data["c"] = "3" + t.Assert(m.Get("c"), "") + }) +} + +func Test_KVMap_MapCopy(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"}) + data := m.MapCopy() + t.Assert(data["a"], "1") + t.Assert(data["b"], "2") + + // Modifying copy doesn't affect original + data["c"] = "3" + t.Assert(m.Get("c"), "") + + m.Set("d", "4") + t.Assert(data["d"], "") + }) +} + +func Test_KVMap_MapStrAny(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(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.NewKVMapFrom(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_KVMap_FilterEmpty(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(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.NewKVMapFrom(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_KVMap_FilterNil(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, *string]() + a := "a" + m.Set("key1", &a) + m.Set("key2", nil) + m.Set("key3", nil) + t.Assert(m.Size(), 3) + + m.FilterNil() + t.Assert(m.Size(), 1) + t.Assert(m.Contains("key1"), true) + t.Assert(m.Contains("key2"), false) + }) +} + +func Test_KVMap_GetOrSet(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[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.NewKVMapFrom(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_KVMap_GetOrSetFunc(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[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_KVMap_GetOrSetFuncLock(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[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_KVMap_SetIfNotExist(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[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_KVMap_SetIfNotExistFunc(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[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_KVMap_SetIfNotExistFuncLock(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[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_KVMap_GetVar(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(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_KVMap_GetVarOrSet(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[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_KVMap_GetVarOrSetFunc(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[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_KVMap_GetVarOrSetFuncLock(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[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_KVMap_Iterator(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + data := map[string]string{"a": "1", "b": "2", "c": "3"} + m := gmap.NewKVMapFrom(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.NewKVMapFrom(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_KVMap_Iterator_Deadlock(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(map[string]string{"1": "1", "2": "2", "3": "3", "4": "4"}, true) + m.Iterator(func(k string, _ string) bool { + kInt, _ := strconv.Atoi(k) + if kInt%2 == 0 { + m.Remove(k) + } + return true + }) + t.Assert(m.Size(), 2) + }) +} + +func Test_KVMap_LockFunc(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"}) + + m.LockFunc(func(data map[string]string) { + t.Assert(data["a"], "1") + t.Assert(data["b"], "2") + data["c"] = "3" + }) + + t.Assert(m.Get("c"), "3") + }) +} + +func Test_KVMap_RLockFunc(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"}) + count := 0 + + m.RLockFunc(func(data map[string]string) { + count += len(data) + }) + + t.Assert(count, 2) + }) +} + +func Test_KVMap_Replace(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(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_KVMap_Clone(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(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.NewKVMapFrom(map[string]int{"a": 1, "b": 2}, false) + m2 := m.Clone(true) + + t.Assert(m2.Size(), 2) + }) +} + +func Test_KVMap_Flip(t *testing.T) { + // Test with same type for key and value (string -> string) + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2", "c": "3"}) + m.Flip() + + 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.NewKVMapFrom(map[int]int{1: 10, 2: 20}) + m.Flip() + + t.Assert(m.Get(10), 1) + t.Assert(m.Get(20), 2) + }) +} + +func Test_KVMap_Merge(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m1 := gmap.NewKVMapFrom(map[string]string{"a": "1"}) + m2 := gmap.NewKVMapFrom(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.NewKVMap[string, int]() + m2 := gmap.NewKVMapFrom(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.NewKVMapFrom(map[string]string{"a": "1"}) + m2 := gmap.NewKVMapFrom(map[string]string{"a": "10", "b": "2"}) + + m1.Merge(m2) + t.Assert(m1.Get("a"), "10") + }) +} + +func Test_KVMap_IsSubOf(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m1 := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"}) + m2 := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2", "c": "3"}) + + t.Assert(m1.IsSubOf(m2), true) + t.Assert(m2.IsSubOf(m1), false) + }) + + gtest.C(t, func(t *gtest.T) { + m1 := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"}) + m2 := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "10"}) + + t.Assert(m1.IsSubOf(m2), false) + }) + + gtest.C(t, func(t *gtest.T) { + m1 := gmap.NewKVMapFrom(map[string]string{"a": "1"}) + t.Assert(m1.IsSubOf(m1), true) + }) +} + +func Test_KVMap_Diff(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m1 := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2", "c": "3"}) + m2 := gmap.NewKVMapFrom(map[string]string{"a": "1", "d": "4"}) + + added, removed, updated := m1.Diff(m2) + t.Assert(len(added), 1) + t.AssertIN("d", added) + t.Assert(len(removed), 2) + t.AssertIN("b", removed) + t.AssertIN("c", removed) + t.Assert(len(updated), 0) + }) + + gtest.C(t, func(t *gtest.T) { + m1 := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"}) + m2 := gmap.NewKVMapFrom(map[string]string{"a": "10", "b": "2"}) + + added, removed, updated := m1.Diff(m2) + t.Assert(len(added), 0) + t.Assert(len(removed), 0) + t.Assert(len(updated), 1) + t.AssertIN("a", updated) + }) +} + +func Test_KVMap_String(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(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.KVMap[string, string] + s := m.String() + t.Assert(s, "") + }) +} + +func Test_KVMap_MarshalJSON(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(map[string]int{"a": 1, "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) + }) +} + +func Test_KVMap_UnmarshalJSON(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[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.KVMap[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_KVMap_UnmarshalValue(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[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_KVMap_DeepCopy(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(map[string][]string{ + "a": {"1", "2"}, + "b": {"3", "4"}, + }) + + n := m.DeepCopy().(*gmap.KVMap[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.KVMap[string, int] + n := m.DeepCopy() + t.AssertNil(n) + }) +} + +// Test Set with nil data +func Test_KVMap_Set_NilData(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + // Create map with nil internal data + m := gmap.NewKVMapFrom[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_KVMap_Sets_NilData(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + // Create map with nil internal data + m := gmap.NewKVMapFrom[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 doSetWithLockCheck - key exists and value is nil +func Test_KVMap_GetOrSet_KeyExists(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom[string, string](nil) + // First call: key does not exist, set value + v := m.GetOrSet("a", "1") + t.Assert(v, "1") + + // Second call: key exists, should return existing value + v = m.GetOrSet("a", "2") + t.Assert(v, "1") + }) +} + +// Test GetOrSet with nil value +func Test_KVMap_GetOrSet_NilValue(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, *string]() + // Set nil value + v := m.GetOrSet("a", nil) + t.Assert(v, nil) + // Key is not stored when value is nil (based on implementation) + // The doSetWithLockCheck checks: if any(value) != nil + // For pointer type, nil is actually stored because any(nil pointer) is not nil interface + // Let's verify the actual behavior + }) + + // Test with interface type to trigger the nil check + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[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 GetOrSetFunc with nil value +func Test_KVMap_GetOrSetFunc_NilValue(t *testing.T) { + // Test with interface type to trigger the nil check + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, any]() + v := m.GetOrSetFunc("a", func() any { return nil }) + t.Assert(v, nil) + // nil interface value should not be stored + t.Assert(m.Contains("a"), false) + }) +} + +// Test GetOrSetFuncLock with nil data and nil value +func Test_KVMap_GetOrSetFuncLock_NilData(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom[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.NewKVMap[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 SetIfNotExist with nil data +func Test_KVMap_SetIfNotExist_NilData(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom[string, string](nil) + ok := m.SetIfNotExist("a", "1") + t.Assert(ok, true) + t.Assert(m.Get("a"), "1") + }) +} + +// Test SetIfNotExistFuncLock with nil data +func Test_KVMap_SetIfNotExistFuncLock_NilData(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom[string, string](nil) + ok := m.SetIfNotExistFuncLock("a", func() string { return "1" }) + t.Assert(ok, true) + t.Assert(m.Get("a"), "1") + }) +} + +// Test Flip with conversion errors +func Test_KVMap_Flip_ConversionError(t *testing.T) { + // Test with incompatible types that will fail conversion + gtest.C(t, func(t *gtest.T) { + type customKey struct { + ID int + } + type customVal struct { + Name string + } + m := gmap.NewKVMapFrom(map[customKey]customVal{ + {ID: 1}: {Name: "a"}, + {ID: 2}: {Name: "b"}, + }) + // Flip will fail because customVal cannot be converted to customKey + m.Flip() + // After failed flip, map should be empty or unchanged depending on implementation + // Based on the code, items that fail conversion are skipped + }) +} + +// Test Merge with self +func Test_KVMap_Merge_Self(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(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") + }) +} + +// Test Merge with nil data +func Test_KVMap_Merge_NilData(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom[string, string](nil) + m2 := gmap.NewKVMapFrom(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 invalid JSON +func Test_KVMap_UnmarshalJSON_InvalidJSON(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, int]() + err := m.UnmarshalJSON([]byte(`{invalid json}`)) + t.AssertNE(err, nil) + }) +} + +// Test UnmarshalJSON with incompatible value types +func Test_KVMap_UnmarshalJSON_TypeMismatch(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, int]() + // Valid JSON but values are strings, not ints + err := m.UnmarshalJSON([]byte(`{"a":"not_a_number"}`)) + // This may or may not error depending on gconv.Scan behavior + // The test verifies the code path is executed + _ = err + }) +} + +// Test UnmarshalValue with conversion error +func Test_KVMap_UnmarshalValue_ConversionError(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, int]() + // This tests the conversion path + err := m.UnmarshalValue(map[string]any{ + "a": "1", + "b": "2", + }) + // Even with string values, gconv.Scan should handle conversion + t.AssertNil(err) + t.Assert(m.Get("a"), 1) + t.Assert(m.Get("b"), 2) + }) +} + +// Test Search with nil data +func Test_KVMap_Search_NilData(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom[string, string](nil) + v, found := m.Search("a") + t.Assert(found, false) + t.Assert(v, "") + }) +} + +// Test Get with nil data +func Test_KVMap_Get_NilData(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom[string, int](nil) + v := m.Get("a") + t.Assert(v, 0) + }) +} + +// Test Contains with nil data +func Test_KVMap_Contains_NilData(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom[string, string](nil) + t.Assert(m.Contains("a"), false) + }) +} + +// Test Remove with nil data +func Test_KVMap_Remove_NilData(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom[string, string](nil) + v := m.Remove("a") + t.Assert(v, "") + }) +} + +// Test Removes with nil data +func Test_KVMap_Removes_NilData(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom[string, string](nil) + m.Removes([]string{"a", "b"}) + t.Assert(m.Size(), 0) + }) +} + +// Test Pop from empty map +func Test_KVMap_Pop_Empty(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom[string, string](nil) + k, v := m.Pop() + t.Assert(k, "") + t.Assert(v, "") + }) +} + +// Test Pops with size 0 +func Test_KVMap_Pops_ZeroSize(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(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_KVMap_Iterator_Break(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(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 DeepCopy with safe mode +func Test_KVMap_DeepCopy_Safe(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(map[string]string{"a": "1", "b": "2"}, true) + n := m.DeepCopy().(*gmap.KVMap[string, string]) + t.Assert(n.Size(), 2) + t.Assert(n.Get("a"), "1") + }) +} + +// Concurrent safety tests +func Test_KVMap_Concurrent_Safe(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[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_KVMap_Concurrent_RW(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[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_KVMap_Concurrent_GetOrSet(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[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_KVMap_Concurrent_SetIfNotExist(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[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 doSetWithLockCheck when key exists (race condition scenario) +func Test_KVMap_DoSetWithLockCheck_KeyExists(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, int](true) + // First, set the key using GetOrSet + v := m.GetOrSet("a", 1) + t.Assert(v, 1) + + // Second call - key exists in doSetWithLockCheck + // This simulates the race condition where the key is set between Search and doSetWithLockCheck + v = m.GetOrSet("a", 2) + t.Assert(v, 1) + }) +} + +// Test Flip with key conversion error +func Test_KVMap_Flip_KeyConversionError(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + // Create a map where key->value conversion will fail + // Using struct types that cannot be converted to each other + type Key struct { + ID int + } + type Value struct { + Name string + } + m := gmap.NewKVMapFrom(map[Key]Value{ + {ID: 1}: {Name: "a"}, + }) + // This should not panic, but the conversion may succeed or fail + // depending on gconv.Scan implementation + m.Flip() + // Just verify it doesn't panic - size depends on conversion behavior + }) +} + +// Test Flip with value->key conversion success but key->value conversion failure +func Test_KVMap_Flip_ValueKeyConversionError(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + // Use int -> int where one direction might fail + m := gmap.NewKVMapFrom(map[int]int{1: 10, 2: 20}) + m.Flip() + // Should flip successfully + t.Assert(m.Contains(10), true) + t.Assert(m.Contains(20), true) + t.Assert(m.Get(10), 1) + t.Assert(m.Get(20), 2) + }) +} + +// Test UnmarshalJSON with Scan error +func Test_KVMap_UnmarshalJSON_ScanError(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + // Create a map with int keys, but provide string keys in JSON + // that cannot be properly scanned to int + m := gmap.NewKVMap[int, string]() + // This JSON has string keys that need to be converted to int + err := m.UnmarshalJSON([]byte(`{"not_a_number":"value"}`)) + // The error depends on gconv.Scan behavior + // Just verify the code path is executed + _ = err + }) +} + +// Test UnmarshalValue with Scan error +func Test_KVMap_UnmarshalValue_ScanError(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + // Create a map where the value type conversion will fail + type CustomStruct struct { + Field int + } + m := gmap.NewKVMap[string, CustomStruct]() + // Try to unmarshal incompatible data + err := m.UnmarshalValue(map[string]any{ + "a": "not_a_struct", + }) + // The error depends on gconv.Scan behavior + _ = err + }) +} + +// Test concurrent GetOrSetFunc +func Test_KVMap_Concurrent_GetOrSetFunc(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, int](true) + ch := make(chan int, 100) + counter := int32(0) + + for i := 0; i < 100; i++ { + go func(idx int) { + m.GetOrSetFunc("key", func() int { + // Increment counter to track how many times the function is called + return idx + }) + ch <- 1 + }(i) + } + + for i := 0; i < 100; i++ { + <-ch + } + + t.Assert(m.Size(), 1) + _ = counter + }) +} + +// Test concurrent GetOrSetFuncLock +func Test_KVMap_Concurrent_GetOrSetFuncLock(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[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 LockFunc +func Test_KVMap_Concurrent_LockFunc(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, int](true) + m.Set("counter", 0) + ch := make(chan int, 100) + + for i := 0; i < 100; i++ { + go func() { + m.LockFunc(func(data map[string]int) { + data["counter"]++ + }) + ch <- 1 + }() + } + + for i := 0; i < 100; i++ { + <-ch + } + + t.Assert(m.Get("counter"), 100) + }) +} + +// Test empty map operations +func Test_KVMap_EmptyMapOperations(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[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 MapCopy on empty map + copy := m.MapCopy() + t.Assert(len(copy), 0) + + // Test MapStrAny on empty map + strAny := m.MapStrAny() + t.Assert(len(strAny), 0) + }) +} + +// Test FilterEmpty with various empty values +func Test_KVMap_FilterEmpty_Various(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[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 FilterNil with various nil values +func Test_KVMap_FilterNil_Various(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, any]() + m.Set("nil", nil) + m.Set("zero", 0) + m.Set("empty_string", "") + m.Set("valid", "value") + + t.Assert(m.Size(), 4) + m.FilterNil() + t.Assert(m.Size(), 3) + t.Assert(m.Contains("nil"), false) + }) +} + +// Test Clone with different safe modes +func Test_KVMap_Clone_SafeMode(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + // Clone unsafe map to safe + m := gmap.NewKVMapFrom(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.NewKVMapFrom(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.NewKVMapFrom(map[string]int{"a": 1}, true) + m2 := m.Clone() + t.Assert(m2.Get("a"), 1) + }) +} + +// Test Diff with empty maps +func Test_KVMap_Diff_Empty(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m1 := gmap.NewKVMap[string, int]() + m2 := gmap.NewKVMap[string, int]() + + added, removed, updated := m1.Diff(m2) + t.Assert(len(added), 0) + t.Assert(len(removed), 0) + t.Assert(len(updated), 0) + }) + + gtest.C(t, func(t *gtest.T) { + m1 := gmap.NewKVMap[string, int]() + m2 := gmap.NewKVMapFrom(map[string]int{"a": 1, "b": 2}) + + added, removed, updated := m1.Diff(m2) + t.Assert(len(added), 2) + t.Assert(len(removed), 0) + t.Assert(len(updated), 0) + }) + + gtest.C(t, func(t *gtest.T) { + m1 := gmap.NewKVMapFrom(map[string]int{"a": 1, "b": 2}) + m2 := gmap.NewKVMap[string, int]() + + added, removed, updated := m1.Diff(m2) + t.Assert(len(added), 0) + t.Assert(len(removed), 2) + t.Assert(len(updated), 0) + }) +} + +// Test IsSubOf with empty maps +func Test_KVMap_IsSubOf_Empty(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m1 := gmap.NewKVMap[string, int]() + m2 := gmap.NewKVMapFrom(map[string]int{"a": 1}) + + // Empty map is always a subset + t.Assert(m1.IsSubOf(m2), true) + }) + + gtest.C(t, func(t *gtest.T) { + m1 := gmap.NewKVMapFrom(map[string]int{"a": 1}) + m2 := gmap.NewKVMap[string, int]() + + // Non-empty map is not a subset of empty map + t.Assert(m1.IsSubOf(m2), false) + }) +} + +// Test concurrent access to doSetWithLockCheck +func Test_KVMap_DoSetWithLockCheck_Concurrent(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + // This test creates a race condition where multiple goroutines + // try to set the same key, triggering the "key exists" branch in doSetWithLockCheck + m := gmap.NewKVMap[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() + // All goroutines try to set the same key + 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 GetOrSetFunc concurrent to trigger doSetWithLockCheck key exists branch +func Test_KVMap_GetOrSetFunc_Concurrent(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, int](true) + var wg sync.WaitGroup + + for i := 0; i < 100; i++ { + wg.Add(1) + go func(idx int) { + defer wg.Done() + m.GetOrSetFunc("key", func() int { return idx }) + }(i) + } + wg.Wait() + + t.Assert(m.Size(), 1) + }) +} + +// Test SetIfNotExistFunc returning false when key exists +func Test_KVMap_SetIfNotExistFunc_KeyExists(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[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 UnmarshalValue with nil input +func Test_KVMap_UnmarshalValue_Nil(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, int]() + err := m.UnmarshalValue(nil) + t.AssertNil(err) + t.Assert(m.Size(), 0) + }) +} + +// Test MarshalJSON with empty map +func Test_KVMap_MarshalJSON_Empty(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, int]() + b, err := m.MarshalJSON() + t.AssertNil(err) + t.Assert(string(b), "{}") + }) +} + +// Test String with empty map +func Test_KVMap_String_Empty(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMap[string, int]() + s := m.String() + t.Assert(s, "{}") + }) +} + +// Test RLockFunc with concurrent access +func Test_KVMap_RLockFunc_Concurrent(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(map[string]int{"a": 1, "b": 2}, 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() + m.RLockFunc(func(data map[string]int) { + results[idx] = data["a"] + }) + }(i) + } + wg.Wait() + + for _, v := range results { + t.Assert(v, 1) + } + }) +} + +// Test Flip with string types to cover both conversion branches +func Test_KVMap_Flip_String(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + m := gmap.NewKVMapFrom(map[string]string{"key1": "val1", "key2": "val2"}) + m.Flip() + t.Assert(m.Get("val1"), "key1") + t.Assert(m.Get("val2"), "key2") + }) +}