This is an automated email from the ASF dual-hosted git repository.
gnodet pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 7ac20cbcafc9 CAMEL-23693: Optimize CaseInsensitiveMap hash for ASCII
keys
7ac20cbcafc9 is described below
commit 7ac20cbcafc92e7350f47a8c22158170f4209601
Author: Guillaume Nodet <[email protected]>
AuthorDate: Fri Jun 5 18:16:55 2026 +0200
CAMEL-23693: Optimize CaseInsensitiveMap hash for ASCII keys
Co-authored-by: Claude Opus 4.6 <[email protected]>
---
.../org/apache/camel/util/CaseInsensitiveMap.java | 63 ++++++++++++++++------
1 file changed, 46 insertions(+), 17 deletions(-)
diff --git
a/core/camel-util/src/main/java/org/apache/camel/util/CaseInsensitiveMap.java
b/core/camel-util/src/main/java/org/apache/camel/util/CaseInsensitiveMap.java
index 7f0cbe5f35e2..7dbbb49e1125 100644
---
a/core/camel-util/src/main/java/org/apache/camel/util/CaseInsensitiveMap.java
+++
b/core/camel-util/src/main/java/org/apache/camel/util/CaseInsensitiveMap.java
@@ -84,12 +84,12 @@ public class CaseInsensitiveMap extends AbstractMap<String,
Object> implements S
knownTable = tbl;
}
- private static String deduplicateKey(String key) {
+ private static String deduplicateKey(String key, int hash) {
int[] tbl = knownTable;
if (tbl == null) {
return key;
}
- int idx = tbl[caseInsensitiveHash(key) & knownMask];
+ int idx = tbl[hash & knownMask];
while (idx != EMPTY) {
if (knownEntries[idx].equalsIgnoreCase(key)) {
return knownEntries[idx];
@@ -142,18 +142,31 @@ public class CaseInsensitiveMap extends
AbstractMap<String, Object> implements S
static int caseInsensitiveHash(String key) {
int h = 0;
for (int i = 0, len = key.length(); i < len; i++) {
- // two-step fold matches String.equalsIgnoreCase /
CASE_INSENSITIVE_ORDER
- h = 31 * h +
Character.toLowerCase(Character.toUpperCase(key.charAt(i)));
+ char c = key.charAt(i);
+ if (c >= 'A' && c <= 'Z') {
+ c += 32; // fast ASCII upper-to-lower
+ } else if (c >= 128) {
+ // full Unicode two-step fold for non-ASCII
+ c = Character.toLowerCase(Character.toUpperCase(c));
+ }
+ h = 31 * h + c;
}
return h ^ (h >>> 16);
}
- private int bucketIndex(String key) {
- return caseInsensitiveHash(key) & (table.length - 1);
+ private int findIndex(String key) {
+ int idx = table[caseInsensitiveHash(key) & (table.length - 1)];
+ while (idx != EMPTY) {
+ if (keys[idx].equalsIgnoreCase(key)) {
+ return idx;
+ }
+ idx = chainNext[idx];
+ }
+ return EMPTY;
}
- private int findIndex(String key) {
- int idx = table[bucketIndex(key)];
+ private int findIndex(String key, int hash) {
+ int idx = table[hash & (table.length - 1)];
while (idx != EMPTY) {
if (keys[idx].equalsIgnoreCase(key)) {
return idx;
@@ -186,8 +199,9 @@ public class CaseInsensitiveMap extends AbstractMap<String,
Object> implements S
@Override
public Object put(String key, Object value) {
- key = deduplicateKey(key);
- int idx = findIndex(key);
+ int hash = caseInsensitiveHash(key);
+ key = deduplicateKey(key, hash);
+ int idx = findIndex(key, hash);
if (idx != EMPTY) {
Object old = values[idx];
values[idx] = value;
@@ -195,6 +209,7 @@ public class CaseInsensitiveMap extends AbstractMap<String,
Object> implements S
}
if (size >= threshold) {
resize(table.length * 2);
+ // table length changed, but hash is still valid
}
if (usedSlots >= keys.length) {
int newCap = keys.length + (keys.length >> 1);
@@ -205,7 +220,7 @@ public class CaseInsensitiveMap extends AbstractMap<String,
Object> implements S
int slot = usedSlots++;
keys[slot] = key;
values[slot] = value;
- int b = bucketIndex(key);
+ int b = hash & (table.length - 1);
chainNext[slot] = table[b];
table[b] = slot;
size++;
@@ -214,18 +229,32 @@ public class CaseInsensitiveMap extends
AbstractMap<String, Object> implements S
@Override
public Object remove(Object key) {
- int idx = findIndex((String) key);
- if (idx != EMPTY) {
- Object old = values[idx];
- removeByIndex(idx);
- return old;
+ int hash = caseInsensitiveHash((String) key);
+ int b = hash & (table.length - 1);
+ int prev = EMPTY;
+ int cur = table[b];
+ while (cur != EMPTY) {
+ if (keys[cur].equalsIgnoreCase((String) key)) {
+ Object old = values[cur];
+ if (prev == EMPTY) {
+ table[b] = chainNext[cur];
+ } else {
+ chainNext[prev] = chainNext[cur];
+ }
+ keys[cur] = null;
+ values[cur] = null;
+ size--;
+ return old;
+ }
+ prev = cur;
+ cur = chainNext[cur];
}
return null;
}
private void removeByIndex(int idx) {
String key = keys[idx];
- int b = bucketIndex(key);
+ int b = caseInsensitiveHash(key) & (table.length - 1);
int prev = EMPTY;
int cur = table[b];
while (cur != EMPTY) {