diff --git a/README.md b/README.md index 6fe0497..dac831b 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,14 @@ m.Store(Point{42, 42}, 42) v, ok := m.Load(point{42, 42}) ``` -Both maps use the built-in Golang's hash function which has DDOS protection. This means that each map instance gets its own seed number and the hash function uses that seed for hash code calculation. However, for smaller keys this hash function has some overhead. So, if you don't need DDOS protection, you may provide a custom hash function when creating a `MapOf`. For instance, Murmur3 finalizer does a decent job when it comes to integers: +Apart from `Range` method available for map iteration, there are also `ToPlainMap`/`ToPlainMapOf` utility functions to convert a `Map`/`MapOf` to a built-in Go's `map`: +```go +m := xsync.NewMapOf[int, int]() +m.Store(42, 42) +pm := xsync.ToPlainMapOf(m) +``` + +Both `Map` and `MapOf` use the built-in Golang's hash function which has DDOS protection. This means that each map instance gets its own seed number and the hash function uses that seed for hash code calculation. However, for smaller keys this hash function has some overhead. So, if you don't need DDOS protection, you may provide a custom hash function when creating a `MapOf`. For instance, Murmur3 finalizer does a decent job when it comes to integers: ```go m := NewMapOfWithHasher[int, int](func(i int, _ uint64) uint64 { diff --git a/map.go b/map.go index 6c5b6eb..092aa0b 100644 --- a/map.go +++ b/map.go @@ -200,6 +200,21 @@ func newMapTable(minTableLen int) *mapTable { return t } +// ToPlainMap returns a native map with a copy of xsync Map's +// contents. The copied xsync Map should not be modified while +// this call is made. If the copied Map is modified, the copying +// behavior is the same as in the Range method. +func ToPlainMap(m *Map) map[string]interface{} { + pm := make(map[string]interface{}) + if m != nil { + m.Range(func(key string, value interface{}) bool { + pm[key] = value + return true + }) + } + return pm +} + // Load returns the value stored in the map for a key, or nil if no // value is present. // The ok result indicates whether value was found in the map. diff --git a/map_test.go b/map_test.go index 74a3885..364d3b8 100644 --- a/map_test.go +++ b/map_test.go @@ -1265,6 +1265,30 @@ func TestMapStats(t *testing.T) { } } +func TestToPlainMap_NilPointer(t *testing.T) { + pm := ToPlainMap(nil) + if len(pm) != 0 { + t.Fatalf("got unexpected size of nil map copy: %d", len(pm)) + } +} + +func TestToPlainMap(t *testing.T) { + const numEntries = 1000 + m := NewMap() + for i := 0; i < numEntries; i++ { + m.Store(strconv.Itoa(i), i) + } + pm := ToPlainMap(m) + if len(pm) != numEntries { + t.Fatalf("got unexpected size of nil map copy: %d", len(pm)) + } + for i := 0; i < numEntries; i++ { + if v := pm[strconv.Itoa(i)]; v != i { + t.Fatalf("unexpected value for key %d: %d", i, v) + } + } +} + func BenchmarkMap_NoWarmUp(b *testing.B) { for _, bc := range benchmarkCases { if bc.readPercentage == 100 { diff --git a/mapof.go b/mapof.go index 4c4ad08..9d8105e 100644 --- a/mapof.go +++ b/mapof.go @@ -149,6 +149,21 @@ func newMapOfTable[K comparable, V any](minTableLen int) *mapOfTable[K, V] { return t } +// ToPlainMapOf returns a native map with a copy of xsync Map's +// contents. The copied xsync Map should not be modified while +// this call is made. If the copied Map is modified, the copying +// behavior is the same as in the Range method. +func ToPlainMapOf[K comparable, V any](m *MapOf[K, V]) map[K]V { + pm := make(map[K]V) + if m != nil { + m.Range(func(key K, value V) bool { + pm[key] = value + return true + }) + } + return pm +} + // Load returns the value stored in the map for a key, or zero value // of type V if no value is present. // The ok result indicates whether value was found in the map. diff --git a/mapof_test.go b/mapof_test.go index e0433a1..e707909 100644 --- a/mapof_test.go +++ b/mapof_test.go @@ -1205,6 +1205,30 @@ func TestMapOfStats(t *testing.T) { } } +func TestToPlainMapOf_NilPointer(t *testing.T) { + pm := ToPlainMapOf[int, int](nil) + if len(pm) != 0 { + t.Fatalf("got unexpected size of nil map copy: %d", len(pm)) + } +} + +func TestToPlainMapOf(t *testing.T) { + const numEntries = 1000 + m := NewMapOf[int, int]() + for i := 0; i < numEntries; i++ { + m.Store(i, i) + } + pm := ToPlainMapOf[int, int](m) + if len(pm) != numEntries { + t.Fatalf("got unexpected size of nil map copy: %d", len(pm)) + } + for i := 0; i < numEntries; i++ { + if v := pm[i]; v != i { + t.Fatalf("unexpected value for key %d: %d", i, v) + } + } +} + func BenchmarkMapOf_NoWarmUp(b *testing.B) { for _, bc := range benchmarkCases { if bc.readPercentage == 100 {