Java 不区分大小写的字符串作为HashMap键

Java 不区分大小写的字符串作为HashMap键,java,dictionary,case-insensitive,Java,Dictionary,Case Insensitive,出于以下原因,我想使用不区分大小写的字符串作为HashMap键 在初始化期间,我的程序使用用户定义的字符串创建HashMap 在处理一个事件(在我的案例中是网络流量)时,我可能会收到另一个案例中的字符串,但我应该能够从HashMap中找到,忽略我从流量中收到的案例 我采用了这种方法 CaseInsensitiveString.java public final class CaseInsensitiveString { private String s;

出于以下原因,我想使用不区分大小写的字符串作为HashMap键

  • 在初始化期间,我的程序使用用户定义的字符串创建HashMap
  • 在处理一个事件(在我的案例中是网络流量)时,我可能会收到另一个案例中的字符串,但我应该能够从HashMap中找到
    ,忽略我从流量中收到的案例
我采用了这种方法

CaseInsensitiveString.java

    public final class CaseInsensitiveString {
            private String s;

            public CaseInsensitiveString(String s) {
                            if (s == null)
                            throw new NullPointerException();
                            this.s = s;
            }

            public boolean equals(Object o) {
                            return o instanceof CaseInsensitiveString &&
                            ((CaseInsensitiveString)o).s.equalsIgnoreCase(s);
            }

            private volatile int hashCode = 0;

            public int hashCode() {
                            if (hashCode == 0)
                            hashCode = s.toUpperCase().hashCode();

                            return hashCode;
            }

            public String toString() {
                            return s;
            }
    }
LookupCode.java

    node = nodeMap.get(new CaseInsensitiveString(stringFromEvent.toString()));
正因为如此,我为每一个事件创建了一个新的不区分案件的预算对象。因此,它可能会影响性能


还有别的办法解决这个问题吗

子类
HashMap
,并创建一个版本,该版本将
put
get
上的键小写(可能还有其他面向键的方法)

或者将一个
HashMap
组合到新类中,并将所有内容委托给该映射,但转换键


如果需要保留原始密钥,可以维护双映射,或者将原始密钥与值一起存储。

我想到了两个选择:

  • 您可以直接使用
    s.toUpperCase().hashCode()
    作为
    映射的键
  • 您可以将
    TreeMap
    与忽略大小写的自定义
    比较器一起使用

  • 否则,如果您更喜欢您的解决方案,我宁愿使用所需的不区分大小写功能实现一个新映射,而不是定义一种新的字符串。

    一种方法是创建Apache Commons类的自定义子类,重写
    散列
    isEqualKeys
    方法以执行不区分大小写的散列和键比较。(注意——我自己从未尝试过……)

    这避免了每次需要执行地图查找或更新时创建新对象的开销。常见的
    Map
    操作应该是O(1)。。。就像一个普通的
    HashMap

    如果您准备接受他们所做的实现选择,apachecommons将为您定制/专门化
    AbstractHashedMap


    但是如果O(logN)
    get
    put
    操作是可以接受的,那么带有不区分大小写的字符串比较器的
    TreeMap
    是一个选项;e、 g.使用


    如果您不介意每次执行
    put
    get
    ,都创建一个新的临时字符串对象,那么Vishal的答案就很好。(尽管如此,我注意到,如果您这样做,就不会保留密钥的原始大小写…

    正如Guido García在:

    import java.util.HashMap;
    公共类CaseInsensitiveMap扩展了HashMap{
    @凌驾
    公共字符串put(字符串键、字符串值){
    返回super.put(key.toLowerCase(),value);
    }
    //不是@Override,因为这将要求键参数的类型为Object
    公共字符串获取(字符串键){
    返回super.get(key.toLowerCase());
    }
    }
    

    为了记住哈希代码,最好是“包装”字符串。在普通的字符串类中,hashCode()第一次是O(N),然后是O(1),因为它是为将来使用而保留的

    public class HashWrap {
        private final String value;
        private final int hash;
    
        public String get() {
            return value;
        }
    
        public HashWrap(String value) {
            this.value = value;
            String lc = value.toLowerCase();
            this.hash = lc.hashCode();
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o instanceof HashWrap) {
                HashWrap that = (HashWrap) o;
                return value.equalsIgnoreCase(that.value);
            } else {
                return false;
            }
        }
    
        @Override
        public int hashCode() {
            return this.hash;
        }
    
        //might want to implement compare too if you want to use with SortedMaps/Sets.
    }
    

    这将允许您使用java中Hashtable的任何实现,并拥有O(1)hasCode()。

    根据其他答案,基本上有两种方法:子类化
    HashMap
    或包装
    字符串。第一个需要更多的工作。事实上,如果您想正确地执行此操作,您必须覆盖几乎所有的方法(
    containsKey、entrySet、get、put、putAll和remove

    无论如何,它有一个问题。如果要避免将来出现问题,必须在
    String
    case操作中指定
    Locale
    。因此,您将创建新方法(
    get(String,Locale)
    ,…)。一切都更简单、更清晰,包装线:

    public final class CaseInsensitiveString {
    
        private final String s;
    
        public CaseInsensitiveString(String s, Locale locale) {
            this.s = s.toUpperCase(locale);
        }
    
        // equals, hashCode & toString, no need for memoizing hashCode
    }
    

    还有,关于您对性能的担忧:过早优化是万恶之源:)

    Map nodeMap=
    新的树映射(字符串.不区分大小写的顺序);
    
    这就是您真正需要的。

    您可以使用来自

    HashingStrategy HashingStrategy=
    fromFunction(String::toUpperCase);
    MutableMap节点=HashingStrategyMaps.mutable.of(hashingStrategy);
    

    注意:我是Eclipse集合的贡献者。

    这是我为最近的项目实现的HashMaps适配器。其工作方式与@SandyR类似,但封装了转换逻辑,因此您不会手动将字符串转换为包装器对象

    我使用了Java8的特性,但只做了一些更改,就可以将其应用到以前的版本中。除了新的Java8流函数之外,我在最常见的场景中测试了它

    基本上,它包装一个HashMap,将所有函数指向它,同时将字符串转换为包装器对象或从包装器对象转换为字符串。但我还必须调整KeySet和EntrySet,因为它们将一些函数转发给映射本身。因此,我返回两个新的键集和条目集,它们实际上包装了原始的keySet()和entrySet()

    注意:Java8改变了putAll方法的实现,我找不到一种简单的方法来覆盖它。因此,当前的实现可能会降低性能,特别是在对大型数据集使用putAll()时

    请让我知道,如果你发现一个错误或有改进代码的建议

    包装:it.collections

    import java.util.*;
    import java.util.function.*;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;
    import java.util.stream.StreamSupport;
    
    
    public class CaseInsensitiveMapAdapter<T> implements Map<String,T>
    {
        private Map<CaseInsensitiveMapKey,T> map;
        private KeySet keySet;
        private EntrySet entrySet;
    
    
        public CaseInsensitiveMapAdapter()
        {
        }
    
        public CaseInsensitiveMapAdapter(Map<String, T> map)
        {
            this.map = getMapImplementation();
            this.putAll(map);
        }
    
        @Override
        public int size()
        {
            return getMap().size();
        }
    
        @Override
        public boolean isEmpty()
        {
            return getMap().isEmpty();
        }
    
        @Override
        public boolean containsKey(Object key)
        {
            return getMap().containsKey(lookupKey(key));
        }
    
        @Override
        public boolean containsValue(Object value)
        {
            return getMap().containsValue(value);
        }
    
        @Override
        public T get(Object key)
        {
            return getMap().get(lookupKey(key));
        }
    
        @Override
        public T put(String key, T value)
        {
            return getMap().put(lookupKey(key), value);
        }
    
        @Override
        public T remove(Object key)
        {
            return getMap().remove(lookupKey(key));
        }
    
        /***
         * I completely ignore Java 8 implementation and put one by one.This will be slower.
         */
        @Override
        public void putAll(Map<? extends String, ? extends T> m)
        {
            for (String key : m.keySet()) {
                getMap().put(lookupKey(key),m.get(key));
            }
        }
    
        @Override
        public void clear()
        {
            getMap().clear();
        }
    
        @Override
        public Set<String> keySet()
        {
            if (keySet == null)
                keySet = new KeySet(getMap().keySet());
            return keySet;
        }
    
        @Override
        public Collection<T> values()
        {
            return getMap().values();
        }
    
        @Override
        public Set<Entry<String, T>> entrySet()
        {
            if (entrySet == null)
                entrySet = new EntrySet(getMap().entrySet());
            return entrySet;
        }
    
        @Override
        public boolean equals(Object o)
        {
            return getMap().equals(o);
        }
    
        @Override
        public int hashCode()
        {
            return getMap().hashCode();
        }
    
        @Override
        public T getOrDefault(Object key, T defaultValue)
        {
            return getMap().getOrDefault(lookupKey(key), defaultValue);
        }
    
        @Override
        public void forEach(final BiConsumer<? super String, ? super T> action)
        {
            getMap().forEach(new BiConsumer<CaseInsensitiveMapKey, T>()
            {
                @Override
                public void accept(CaseInsensitiveMapKey lookupKey, T t)
                {
                    action.accept(lookupKey.key,t);
                }
            });
        }
    
        @Override
        public void replaceAll(final BiFunction<? super String, ? super T, ? extends T> function)
        {
            getMap().replaceAll(new BiFunction<CaseInsensitiveMapKey, T, T>()
            {
                @Override
                public T apply(CaseInsensitiveMapKey lookupKey, T t)
                {
                    return function.apply(lookupKey.key,t);
                }
            });
        }
    
        @Override
        public T putIfAbsent(String key, T value)
        {
            return getMap().putIfAbsent(lookupKey(key), value);
        }
    
        @Override
        public boolean remove(Object key, Object value)
        {
            return getMap().remove(lookupKey(key), value);
        }
    
        @Override
        public boolean replace(String key, T oldValue, T newValue)
        {
            return getMap().replace(lookupKey(key), oldValue, newValue);
        }
    
        @Override
        public T replace(String key, T value)
        {
            return getMap().replace(lookupKey(key), value);
        }
    
        @Override
        public T computeIfAbsent(String key, final Function<? super String, ? extends T> mappingFunction)
        {
            return getMap().computeIfAbsent(lookupKey(key), new Function<CaseInsensitiveMapKey, T>()
            {
                @Override
                public T apply(CaseInsensitiveMapKey lookupKey)
                {
                    return mappingFunction.apply(lookupKey.key);
                }
            });
        }
    
        @Override
        public T computeIfPresent(String key, final BiFunction<? super String, ? super T, ? extends T> remappingFunction)
        {
            return getMap().computeIfPresent(lookupKey(key), new BiFunction<CaseInsensitiveMapKey, T, T>()
            {
                @Override
                public T apply(CaseInsensitiveMapKey lookupKey, T t)
                {
                    return remappingFunction.apply(lookupKey.key, t);
                }
            });
        }
    
        @Override
        public T compute(String key, final BiFunction<? super String, ? super T, ? extends T> remappingFunction)
        {
            return getMap().compute(lookupKey(key), new BiFunction<CaseInsensitiveMapKey, T, T>()
            {
                @Override
                public T apply(CaseInsensitiveMapKey lookupKey, T t)
                {
                    return remappingFunction.apply(lookupKey.key,t);
                }
            });
        }
    
        @Override
        public T merge(String key, T value, BiFunction<? super T, ? super T, ? extends T> remappingFunction)
        {
            return getMap().merge(lookupKey(key), value, remappingFunction);
        }
    
        protected  Map<CaseInsensitiveMapKey,T> getMapImplementation() {
            return new HashMap<>();
        }
    
        private Map<CaseInsensitiveMapKey,T> getMap() {
            if (map == null)
                map = getMapImplementation();
            return map;
        }
    
        private CaseInsensitiveMapKey lookupKey(Object key)
        {
            return new CaseInsensitiveMapKey((String)key);
        }
    
        public class CaseInsensitiveMapKey {
            private String key;
            private String lookupKey;
    
            public CaseInsensitiveMapKey(String key)
            {
                this.key = key;
                this.lookupKey = key.toUpperCase();
            }
    
            @Override
            public boolean equals(Object o)
            {
                if (this == o) return true;
                if (o == null || getClass() != o.getClass()) return false;
    
                CaseInsensitiveMapKey that = (CaseInsensitiveMapKey) o;
    
                return lookupKey.equals(that.lookupKey);
    
            }
    
            @Override
            public int hashCode()
            {
                return lookupKey.hashCode();
            }
        }
    
        private class KeySet implements Set<String> {
    
            private Set<CaseInsensitiveMapKey> wrapped;
    
            public KeySet(Set<CaseInsensitiveMapKey> wrapped)
            {
                this.wrapped = wrapped;
            }
    
    
            private List<String> keyList() {
                return stream().collect(Collectors.toList());
            }
    
            private Collection<CaseInsensitiveMapKey> mapCollection(Collection<?> c) {
                return c.stream().map(it -> lookupKey(it)).collect(Collectors.toList());
            }
    
            @Override
            public int size()
            {
                return wrapped.size();
            }
    
            @Override
            public boolean isEmpty()
            {
                return wrapped.isEmpty();
            }
    
            @Override
            public boolean contains(Object o)
            {
                return wrapped.contains(lookupKey(o));
            }
    
            @Override
            public Iterator<String> iterator()
            {
                return keyList().iterator();
            }
    
            @Override
            public Object[] toArray()
            {
                return keyList().toArray();
            }
    
            @Override
            public <T> T[] toArray(T[] a)
            {
                return keyList().toArray(a);
            }
    
            @Override
            public boolean add(String s)
            {
                return wrapped.add(lookupKey(s));
            }
    
            @Override
            public boolean remove(Object o)
            {
                return wrapped.remove(lookupKey(o));
            }
    
            @Override
            public boolean containsAll(Collection<?> c)
            {
                return keyList().containsAll(c);
            }
    
            @Override
            public boolean addAll(Collection<? extends String> c)
            {
                return wrapped.addAll(mapCollection(c));
            }
    
            @Override
            public boolean retainAll(Collection<?> c)
            {
                return wrapped.retainAll(mapCollection(c));
            }
    
            @Override
            public boolean removeAll(Collection<?> c)
            {
                return wrapped.removeAll(mapCollection(c));
            }
    
            @Override
            public void clear()
            {
                wrapped.clear();
            }
    
            @Override
            public boolean equals(Object o)
            {
                return wrapped.equals(lookupKey(o));
            }
    
            @Override
            public int hashCode()
            {
                return wrapped.hashCode();
            }
    
            @Override
            public Spliterator<String> spliterator()
            {
                return keyList().spliterator();
            }
    
            @Override
            public boolean removeIf(Predicate<? super String> filter)
            {
                return wrapped.removeIf(new Predicate<CaseInsensitiveMapKey>()
                {
                    @Override
                    public boolean test(CaseInsensitiveMapKey lookupKey)
                    {
                        return filter.test(lookupKey.key);
                    }
                });
            }
    
            @Override
            public Stream<String> stream()
            {
                return wrapped.stream().map(it -> it.key);
            }
    
            @Override
            public Stream<String> parallelStream()
            {
                return wrapped.stream().map(it -> it.key).parallel();
            }
    
            @Override
            public void forEach(Consumer<? super String> action)
            {
                wrapped.forEach(new Consumer<CaseInsensitiveMapKey>()
                {
                    @Override
                    public void accept(CaseInsensitiveMapKey lookupKey)
                    {
                        action.accept(lookupKey.key);
                    }
                });
            }
        }
    
        private class EntrySet implements Set<Map.Entry<String,T>> {
    
            private Set<Entry<CaseInsensitiveMapKey,T>> wrapped;
    
            public EntrySet(Set<Entry<CaseInsensitiveMapKey,T>> wrapped)
            {
                this.wrapped = wrapped;
            }
    
    
            private List<Map.Entry<String,T>> keyList() {
                return stream().collect(Collectors.toList());
            }
    
            private Collection<Entry<CaseInsensitiveMapKey,T>> mapCollection(Collection<?> c) {
                return c.stream().map(it -> new CaseInsensitiveEntryAdapter((Entry<String,T>)it)).collect(Collectors.toList());
            }
    
            @Override
            public int size()
            {
                return wrapped.size();
            }
    
            @Override
            public boolean isEmpty()
            {
                return wrapped.isEmpty();
            }
    
            @Override
            public boolean contains(Object o)
            {
                return wrapped.contains(lookupKey(o));
            }
    
            @Override
            public Iterator<Map.Entry<String,T>> iterator()
            {
                return keyList().iterator();
            }
    
            @Override
            public Object[] toArray()
            {
                return keyList().toArray();
            }
    
            @Override
            public <T> T[] toArray(T[] a)
            {
                return keyList().toArray(a);
            }
    
            @Override
            public boolean add(Entry<String,T> s)
            {
                return wrapped.add(null );
            }
    
            @Override
            public boolean remove(Object o)
            {
                return wrapped.remove(lookupKey(o));
            }
    
            @Override
            public boolean containsAll(Collection<?> c)
            {
                return keyList().containsAll(c);
            }
    
            @Override
            public boolean addAll(Collection<? extends Entry<String,T>> c)
            {
                return wrapped.addAll(mapCollection(c));
            }
    
            @Override
            public boolean retainAll(Collection<?> c)
            {
                return wrapped.retainAll(mapCollection(c));
            }
    
            @Override
            public boolean removeAll(Collection<?> c)
            {
                return wrapped.removeAll(mapCollection(c));
            }
    
            @Override
            public void clear()
            {
                wrapped.clear();
            }
    
            @Override
            public boolean equals(Object o)
            {
                return wrapped.equals(lookupKey(o));
            }
    
            @Override
            public int hashCode()
            {
                return wrapped.hashCode();
            }
    
            @Override
            public Spliterator<Entry<String,T>> spliterator()
            {
                return keyList().spliterator();
            }
    
            @Override
            public boolean removeIf(Predicate<? super Entry<String, T>> filter)
            {
                return wrapped.removeIf(new Predicate<Entry<CaseInsensitiveMapKey, T>>()
                {
                    @Override
                    public boolean test(Entry<CaseInsensitiveMapKey, T> entry)
                    {
                        return filter.test(new FromCaseInsensitiveEntryAdapter(entry));
                    }
                });
            }
    
            @Override
            public Stream<Entry<String,T>> stream()
            {
                return wrapped.stream().map(it -> new Entry<String, T>()
                {
                    @Override
                    public String getKey()
                    {
                        return it.getKey().key;
                    }
    
                    @Override
                    public T getValue()
                    {
                        return it.getValue();
                    }
    
                    @Override
                    public T setValue(T value)
                    {
                        return it.setValue(value);
                    }
                });
            }
    
            @Override
            public Stream<Map.Entry<String,T>> parallelStream()
            {
                return StreamSupport.stream(spliterator(), true);
            }
    
            @Override
            public void forEach(Consumer<? super Entry<String, T>> action)
            {
                wrapped.forEach(new Consumer<Entry<CaseInsensitiveMapKey, T>>()
                {
                    @Override
                    public void accept(Entry<CaseInsensitiveMapKey, T> entry)
                    {
                        action.accept(new FromCaseInsensitiveEntryAdapter(entry));
                    }
                });
            }
        }
    
        private class EntryAdapter implements Map.Entry<String,T> {
            private Entry<String,T> wrapped;
    
            public EntryAdapter(Entry<String, T> wrapped)
            {
                this.wrapped = wrapped;
            }
    
            @Override
            public String getKey()
            {
                return wrapped.getKey();
            }
    
            @Override
            public T getValue()
            {
                return wrapped.getValue();
            }
    
            @Override
            public T setValue(T value)
            {
                return wrapped.setValue(value);
            }
    
            @Override
            public boolean equals(Object o)
            {
                return wrapped.equals(o);
            }
    
            @Override
            public int hashCode()
            {
                return wrapped.hashCode();
            }
    
    
        }
    
        private class CaseInsensitiveEntryAdapter implements Map.Entry<CaseInsensitiveMapKey,T> {
    
            private Entry<String,T> wrapped;
    
            public CaseInsensitiveEntryAdapter(Entry<String, T> wrapped)
            {
                this.wrapped = wrapped;
            }
    
            @Override
            public CaseInsensitiveMapKey getKey()
            {
                return lookupKey(wrapped.getKey());
            }
    
            @Override
            public T getValue()
            {
                return wrapped.getValue();
            }
    
            @Override
            public T setValue(T value)
            {
                return wrapped.setValue(value);
            }
        }
    
        private class FromCaseInsensitiveEntryAdapter implements Map.Entry<String,T> {
    
            private Entry<CaseInsensitiveMapKey,T> wrapped;
    
            public FromCaseInsensitiveEntryAdapter(Entry<CaseInsensitiveMapKey, T> wrapped)
            {
                this.wrapped = wrapped;
            }
    
            @Override
            public String getKey()
            {
                return wrapped.getKey().key;
            }
    
            @Override
            public T getValue()
            {
                return wrapped.getValue();
            }
    
            @Override
            public T setValue(T value)
            {
                return wrapped.setValue(value);
            }
        }
    
    
    }
    
    import java.util.*;
    导入java.util.function.*;
    导入java.util.stream.collector;
    导入java.util.stream.stream;
    导入java.util.stream.StreamSupport;
    公共类CaseInsensitiveMapAdapter实现Map
    {
    私人地图;
    专用密钥集密钥集;
    私有入口集入口集;
    公共案件不敏感Padapter()
    {
    }
    公共案件不敏感PADAPTER(地图)
    {
    this.map=getMapImplementation();
    这是普
    
    Map<String, String> nodeMap = 
        new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
    
    HashingStrategy<String> hashingStrategy =
        HashingStrategies.fromFunction(String::toUpperCase);
    MutableMap<String, String> node = HashingStrategyMaps.mutable.of(hashingStrategy);
    
    import java.util.*;
    import java.util.function.*;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;
    import java.util.stream.StreamSupport;
    
    
    public class CaseInsensitiveMapAdapter<T> implements Map<String,T>
    {
        private Map<CaseInsensitiveMapKey,T> map;
        private KeySet keySet;
        private EntrySet entrySet;
    
    
        public CaseInsensitiveMapAdapter()
        {
        }
    
        public CaseInsensitiveMapAdapter(Map<String, T> map)
        {
            this.map = getMapImplementation();
            this.putAll(map);
        }
    
        @Override
        public int size()
        {
            return getMap().size();
        }
    
        @Override
        public boolean isEmpty()
        {
            return getMap().isEmpty();
        }
    
        @Override
        public boolean containsKey(Object key)
        {
            return getMap().containsKey(lookupKey(key));
        }
    
        @Override
        public boolean containsValue(Object value)
        {
            return getMap().containsValue(value);
        }
    
        @Override
        public T get(Object key)
        {
            return getMap().get(lookupKey(key));
        }
    
        @Override
        public T put(String key, T value)
        {
            return getMap().put(lookupKey(key), value);
        }
    
        @Override
        public T remove(Object key)
        {
            return getMap().remove(lookupKey(key));
        }
    
        /***
         * I completely ignore Java 8 implementation and put one by one.This will be slower.
         */
        @Override
        public void putAll(Map<? extends String, ? extends T> m)
        {
            for (String key : m.keySet()) {
                getMap().put(lookupKey(key),m.get(key));
            }
        }
    
        @Override
        public void clear()
        {
            getMap().clear();
        }
    
        @Override
        public Set<String> keySet()
        {
            if (keySet == null)
                keySet = new KeySet(getMap().keySet());
            return keySet;
        }
    
        @Override
        public Collection<T> values()
        {
            return getMap().values();
        }
    
        @Override
        public Set<Entry<String, T>> entrySet()
        {
            if (entrySet == null)
                entrySet = new EntrySet(getMap().entrySet());
            return entrySet;
        }
    
        @Override
        public boolean equals(Object o)
        {
            return getMap().equals(o);
        }
    
        @Override
        public int hashCode()
        {
            return getMap().hashCode();
        }
    
        @Override
        public T getOrDefault(Object key, T defaultValue)
        {
            return getMap().getOrDefault(lookupKey(key), defaultValue);
        }
    
        @Override
        public void forEach(final BiConsumer<? super String, ? super T> action)
        {
            getMap().forEach(new BiConsumer<CaseInsensitiveMapKey, T>()
            {
                @Override
                public void accept(CaseInsensitiveMapKey lookupKey, T t)
                {
                    action.accept(lookupKey.key,t);
                }
            });
        }
    
        @Override
        public void replaceAll(final BiFunction<? super String, ? super T, ? extends T> function)
        {
            getMap().replaceAll(new BiFunction<CaseInsensitiveMapKey, T, T>()
            {
                @Override
                public T apply(CaseInsensitiveMapKey lookupKey, T t)
                {
                    return function.apply(lookupKey.key,t);
                }
            });
        }
    
        @Override
        public T putIfAbsent(String key, T value)
        {
            return getMap().putIfAbsent(lookupKey(key), value);
        }
    
        @Override
        public boolean remove(Object key, Object value)
        {
            return getMap().remove(lookupKey(key), value);
        }
    
        @Override
        public boolean replace(String key, T oldValue, T newValue)
        {
            return getMap().replace(lookupKey(key), oldValue, newValue);
        }
    
        @Override
        public T replace(String key, T value)
        {
            return getMap().replace(lookupKey(key), value);
        }
    
        @Override
        public T computeIfAbsent(String key, final Function<? super String, ? extends T> mappingFunction)
        {
            return getMap().computeIfAbsent(lookupKey(key), new Function<CaseInsensitiveMapKey, T>()
            {
                @Override
                public T apply(CaseInsensitiveMapKey lookupKey)
                {
                    return mappingFunction.apply(lookupKey.key);
                }
            });
        }
    
        @Override
        public T computeIfPresent(String key, final BiFunction<? super String, ? super T, ? extends T> remappingFunction)
        {
            return getMap().computeIfPresent(lookupKey(key), new BiFunction<CaseInsensitiveMapKey, T, T>()
            {
                @Override
                public T apply(CaseInsensitiveMapKey lookupKey, T t)
                {
                    return remappingFunction.apply(lookupKey.key, t);
                }
            });
        }
    
        @Override
        public T compute(String key, final BiFunction<? super String, ? super T, ? extends T> remappingFunction)
        {
            return getMap().compute(lookupKey(key), new BiFunction<CaseInsensitiveMapKey, T, T>()
            {
                @Override
                public T apply(CaseInsensitiveMapKey lookupKey, T t)
                {
                    return remappingFunction.apply(lookupKey.key,t);
                }
            });
        }
    
        @Override
        public T merge(String key, T value, BiFunction<? super T, ? super T, ? extends T> remappingFunction)
        {
            return getMap().merge(lookupKey(key), value, remappingFunction);
        }
    
        protected  Map<CaseInsensitiveMapKey,T> getMapImplementation() {
            return new HashMap<>();
        }
    
        private Map<CaseInsensitiveMapKey,T> getMap() {
            if (map == null)
                map = getMapImplementation();
            return map;
        }
    
        private CaseInsensitiveMapKey lookupKey(Object key)
        {
            return new CaseInsensitiveMapKey((String)key);
        }
    
        public class CaseInsensitiveMapKey {
            private String key;
            private String lookupKey;
    
            public CaseInsensitiveMapKey(String key)
            {
                this.key = key;
                this.lookupKey = key.toUpperCase();
            }
    
            @Override
            public boolean equals(Object o)
            {
                if (this == o) return true;
                if (o == null || getClass() != o.getClass()) return false;
    
                CaseInsensitiveMapKey that = (CaseInsensitiveMapKey) o;
    
                return lookupKey.equals(that.lookupKey);
    
            }
    
            @Override
            public int hashCode()
            {
                return lookupKey.hashCode();
            }
        }
    
        private class KeySet implements Set<String> {
    
            private Set<CaseInsensitiveMapKey> wrapped;
    
            public KeySet(Set<CaseInsensitiveMapKey> wrapped)
            {
                this.wrapped = wrapped;
            }
    
    
            private List<String> keyList() {
                return stream().collect(Collectors.toList());
            }
    
            private Collection<CaseInsensitiveMapKey> mapCollection(Collection<?> c) {
                return c.stream().map(it -> lookupKey(it)).collect(Collectors.toList());
            }
    
            @Override
            public int size()
            {
                return wrapped.size();
            }
    
            @Override
            public boolean isEmpty()
            {
                return wrapped.isEmpty();
            }
    
            @Override
            public boolean contains(Object o)
            {
                return wrapped.contains(lookupKey(o));
            }
    
            @Override
            public Iterator<String> iterator()
            {
                return keyList().iterator();
            }
    
            @Override
            public Object[] toArray()
            {
                return keyList().toArray();
            }
    
            @Override
            public <T> T[] toArray(T[] a)
            {
                return keyList().toArray(a);
            }
    
            @Override
            public boolean add(String s)
            {
                return wrapped.add(lookupKey(s));
            }
    
            @Override
            public boolean remove(Object o)
            {
                return wrapped.remove(lookupKey(o));
            }
    
            @Override
            public boolean containsAll(Collection<?> c)
            {
                return keyList().containsAll(c);
            }
    
            @Override
            public boolean addAll(Collection<? extends String> c)
            {
                return wrapped.addAll(mapCollection(c));
            }
    
            @Override
            public boolean retainAll(Collection<?> c)
            {
                return wrapped.retainAll(mapCollection(c));
            }
    
            @Override
            public boolean removeAll(Collection<?> c)
            {
                return wrapped.removeAll(mapCollection(c));
            }
    
            @Override
            public void clear()
            {
                wrapped.clear();
            }
    
            @Override
            public boolean equals(Object o)
            {
                return wrapped.equals(lookupKey(o));
            }
    
            @Override
            public int hashCode()
            {
                return wrapped.hashCode();
            }
    
            @Override
            public Spliterator<String> spliterator()
            {
                return keyList().spliterator();
            }
    
            @Override
            public boolean removeIf(Predicate<? super String> filter)
            {
                return wrapped.removeIf(new Predicate<CaseInsensitiveMapKey>()
                {
                    @Override
                    public boolean test(CaseInsensitiveMapKey lookupKey)
                    {
                        return filter.test(lookupKey.key);
                    }
                });
            }
    
            @Override
            public Stream<String> stream()
            {
                return wrapped.stream().map(it -> it.key);
            }
    
            @Override
            public Stream<String> parallelStream()
            {
                return wrapped.stream().map(it -> it.key).parallel();
            }
    
            @Override
            public void forEach(Consumer<? super String> action)
            {
                wrapped.forEach(new Consumer<CaseInsensitiveMapKey>()
                {
                    @Override
                    public void accept(CaseInsensitiveMapKey lookupKey)
                    {
                        action.accept(lookupKey.key);
                    }
                });
            }
        }
    
        private class EntrySet implements Set<Map.Entry<String,T>> {
    
            private Set<Entry<CaseInsensitiveMapKey,T>> wrapped;
    
            public EntrySet(Set<Entry<CaseInsensitiveMapKey,T>> wrapped)
            {
                this.wrapped = wrapped;
            }
    
    
            private List<Map.Entry<String,T>> keyList() {
                return stream().collect(Collectors.toList());
            }
    
            private Collection<Entry<CaseInsensitiveMapKey,T>> mapCollection(Collection<?> c) {
                return c.stream().map(it -> new CaseInsensitiveEntryAdapter((Entry<String,T>)it)).collect(Collectors.toList());
            }
    
            @Override
            public int size()
            {
                return wrapped.size();
            }
    
            @Override
            public boolean isEmpty()
            {
                return wrapped.isEmpty();
            }
    
            @Override
            public boolean contains(Object o)
            {
                return wrapped.contains(lookupKey(o));
            }
    
            @Override
            public Iterator<Map.Entry<String,T>> iterator()
            {
                return keyList().iterator();
            }
    
            @Override
            public Object[] toArray()
            {
                return keyList().toArray();
            }
    
            @Override
            public <T> T[] toArray(T[] a)
            {
                return keyList().toArray(a);
            }
    
            @Override
            public boolean add(Entry<String,T> s)
            {
                return wrapped.add(null );
            }
    
            @Override
            public boolean remove(Object o)
            {
                return wrapped.remove(lookupKey(o));
            }
    
            @Override
            public boolean containsAll(Collection<?> c)
            {
                return keyList().containsAll(c);
            }
    
            @Override
            public boolean addAll(Collection<? extends Entry<String,T>> c)
            {
                return wrapped.addAll(mapCollection(c));
            }
    
            @Override
            public boolean retainAll(Collection<?> c)
            {
                return wrapped.retainAll(mapCollection(c));
            }
    
            @Override
            public boolean removeAll(Collection<?> c)
            {
                return wrapped.removeAll(mapCollection(c));
            }
    
            @Override
            public void clear()
            {
                wrapped.clear();
            }
    
            @Override
            public boolean equals(Object o)
            {
                return wrapped.equals(lookupKey(o));
            }
    
            @Override
            public int hashCode()
            {
                return wrapped.hashCode();
            }
    
            @Override
            public Spliterator<Entry<String,T>> spliterator()
            {
                return keyList().spliterator();
            }
    
            @Override
            public boolean removeIf(Predicate<? super Entry<String, T>> filter)
            {
                return wrapped.removeIf(new Predicate<Entry<CaseInsensitiveMapKey, T>>()
                {
                    @Override
                    public boolean test(Entry<CaseInsensitiveMapKey, T> entry)
                    {
                        return filter.test(new FromCaseInsensitiveEntryAdapter(entry));
                    }
                });
            }
    
            @Override
            public Stream<Entry<String,T>> stream()
            {
                return wrapped.stream().map(it -> new Entry<String, T>()
                {
                    @Override
                    public String getKey()
                    {
                        return it.getKey().key;
                    }
    
                    @Override
                    public T getValue()
                    {
                        return it.getValue();
                    }
    
                    @Override
                    public T setValue(T value)
                    {
                        return it.setValue(value);
                    }
                });
            }
    
            @Override
            public Stream<Map.Entry<String,T>> parallelStream()
            {
                return StreamSupport.stream(spliterator(), true);
            }
    
            @Override
            public void forEach(Consumer<? super Entry<String, T>> action)
            {
                wrapped.forEach(new Consumer<Entry<CaseInsensitiveMapKey, T>>()
                {
                    @Override
                    public void accept(Entry<CaseInsensitiveMapKey, T> entry)
                    {
                        action.accept(new FromCaseInsensitiveEntryAdapter(entry));
                    }
                });
            }
        }
    
        private class EntryAdapter implements Map.Entry<String,T> {
            private Entry<String,T> wrapped;
    
            public EntryAdapter(Entry<String, T> wrapped)
            {
                this.wrapped = wrapped;
            }
    
            @Override
            public String getKey()
            {
                return wrapped.getKey();
            }
    
            @Override
            public T getValue()
            {
                return wrapped.getValue();
            }
    
            @Override
            public T setValue(T value)
            {
                return wrapped.setValue(value);
            }
    
            @Override
            public boolean equals(Object o)
            {
                return wrapped.equals(o);
            }
    
            @Override
            public int hashCode()
            {
                return wrapped.hashCode();
            }
    
    
        }
    
        private class CaseInsensitiveEntryAdapter implements Map.Entry<CaseInsensitiveMapKey,T> {
    
            private Entry<String,T> wrapped;
    
            public CaseInsensitiveEntryAdapter(Entry<String, T> wrapped)
            {
                this.wrapped = wrapped;
            }
    
            @Override
            public CaseInsensitiveMapKey getKey()
            {
                return lookupKey(wrapped.getKey());
            }
    
            @Override
            public T getValue()
            {
                return wrapped.getValue();
            }
    
            @Override
            public T setValue(T value)
            {
                return wrapped.setValue(value);
            }
        }
    
        private class FromCaseInsensitiveEntryAdapter implements Map.Entry<String,T> {
    
            private Entry<CaseInsensitiveMapKey,T> wrapped;
    
            public FromCaseInsensitiveEntryAdapter(Entry<CaseInsensitiveMapKey, T> wrapped)
            {
                this.wrapped = wrapped;
            }
    
            @Override
            public String getKey()
            {
                return wrapped.getKey().key;
            }
    
            @Override
            public T getValue()
            {
                return wrapped.getValue();
            }
    
            @Override
            public T setValue(T value)
            {
                return wrapped.setValue(value);
            }
        }
    
    
    }
    
    static int ciHashCode(String string)
    {
        // length and the low 5 bits of hashCode() are case insensitive
        return (string.hashCode() & 0x1f)*33 + string.length();
    }
    
    HashMap<CaseInsensitiveString, String> caseInsensitiveMap = new HashMap<>();
    caseInsensitiveMap.put("tschüß", "bye");
    caseInsensitiveMap.containsKey("TSCHÜSS"); # true
    
    
    Map<String, Integer> linkedHashMap = new LinkedCaseInsensitiveMap<>();
    linkedHashMap.put("abc", 1);
    linkedHashMap.put("AbC", 2);
    
    System.out.println(linkedHashMap);
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>5.2.5.RELEASE</version>
    </dependency>
    
    Map<String, Integer> commonsHashMap = new CaseInsensitiveMap<>();
    commonsHashMap.put("ABC", 1);
    commonsHashMap.put("abc", 2);
    commonsHashMap.put("aBc", 3);
    
    System.out.println(commonsHashMap);
    
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-collections4</artifactId>
        <version>4.4</version>
    </dependency>
    
    Map<String, Integer> treeMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
    treeMap.put("ABC", 1);
    treeMap.put("ABc", 2);
    treeMap.put("cde", 1);
            
    System.out.println(treeMap);
    
    Locale locale = ...;
    Collator collator = Collator.getInstance(locale);
    collator.setStrength(Collator.SECONDARY); // Case-insensitive.
    collator.setDecomposition(Collator.FULL_DECOMPOSITION);
    
    CollationKey collationKey = collator.getCollationKey(stringKey);
    hashMap.put(collationKey, value);
    hashMap.get(collationKey);