Skip to content

Commit 484bddf

Browse files
jderegclaude
andcommitted
Optimize keySet() and values() to avoid unnecessary entrySet() rebuild
values() now iterates buckets directly, skipping expensive reconstructKey() and SimpleEntry allocation entirely. keySet() iterates buckets directly, avoiding SimpleEntry wrapper allocation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 089216a commit 484bddf

2 files changed

Lines changed: 38 additions & 4 deletions

File tree

changelog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@
4646
* Added `spread(h) = h ^ (h >>> 16)` at all bucket selection points (same technique as `ConcurrentHashMap`)
4747
* When the table is small, only low-order bits select the bucket; spreading mixes in higher bits to reduce collisions
4848
* Applied at all 7 bucket index computation sites; stored hashes are unchanged
49+
* **PERFORMANCE**: `MultiKeyMap` - `keySet()` and `values()` no longer rebuild full `entrySet()`
50+
* `values()` now iterates buckets directly, skipping all key reconstruction (`reconstructKey()`) and `SimpleEntry` allocation
51+
* `keySet()` now iterates buckets directly, skipping `SimpleEntry` wrapper allocation
4952

5053
#### 4.90.0 2026-02-02
5154
* **BUG FIX**: `DeepEquals` - URL comparison now uses string representation instead of `URL.equals()`

src/main/java/com/cedarsoftware/util/MultiKeyMap.java

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3563,8 +3563,32 @@ public boolean containsValue(Object value) {
35633563
*/
35643564
public Set<Object> keySet() {
35653565
Set<Object> set = new HashSet<>();
3566-
for (Map.Entry<Object, V> e : entrySet()) {
3567-
set.add(e.getKey());
3566+
final AtomicReferenceArray<MultiKey<V>[]> snapshot = buckets;
3567+
final int len = snapshot.length();
3568+
for (int bucketIdx = 0; bucketIdx < len; bucketIdx++) {
3569+
MultiKey<V>[] chain = snapshot.get(bucketIdx);
3570+
if (chain != null) {
3571+
for (MultiKey<V> e : chain) {
3572+
Object keys = e.keys;
3573+
Object k;
3574+
if (keys == null || keys == NULL_SENTINEL) {
3575+
k = null;
3576+
} else if (keys instanceof Object[]) {
3577+
Object[] keysArray = (Object[]) keys;
3578+
k = keysArray.length == 1 ? (keysArray[0] == NULL_SENTINEL ? null : keysArray[0]) : reconstructKey(keysArray);
3579+
} else if (keys instanceof Collection) {
3580+
if (collectionKeyMode == CollectionKeyMode.COLLECTIONS_NOT_EXPANDED) {
3581+
k = keys;
3582+
} else {
3583+
Object[] keysArray = ((Collection<?>) keys).toArray();
3584+
k = reconstructKey(keysArray);
3585+
}
3586+
} else {
3587+
k = keys;
3588+
}
3589+
set.add(k);
3590+
}
3591+
}
35683592
}
35693593
return set;
35703594
}
@@ -3580,8 +3604,15 @@ public Set<Object> keySet() {
35803604
*/
35813605
public Collection<V> values() {
35823606
List<V> vals = new ArrayList<>();
3583-
for (Map.Entry<Object, V> e : entrySet()) {
3584-
vals.add(e.getValue());
3607+
final AtomicReferenceArray<MultiKey<V>[]> snapshot = buckets;
3608+
final int len = snapshot.length();
3609+
for (int bucketIdx = 0; bucketIdx < len; bucketIdx++) {
3610+
MultiKey<V>[] chain = snapshot.get(bucketIdx);
3611+
if (chain != null) {
3612+
for (MultiKey<V> e : chain) {
3613+
vals.add(e.value);
3614+
}
3615+
}
35853616
}
35863617
return vals;
35873618
}

0 commit comments

Comments
 (0)