Ciąg niewrażliwy na wielkość liter jako klucz HashMap

178

Chciałbym użyć łańcucha niewrażliwego na wielkość liter jako klucza HashMap z następujących powodów.

  • Podczas inicjalizacji mój program tworzy HashMap ze zdefiniowanym przez użytkownika ciągiem znaków
  • Podczas przetwarzania zdarzenia (ruchu sieciowego w moim przypadku), mogłem otrzymać String w innym przypadku, ale powinienem być w stanie zlokalizować <key, value>z HashMap ignorując przypadek, który otrzymałem z ruchu.

Postępowałem zgodnie z tym podejściem

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()));

Z tego powodu tworzę nowy obiekt CaseInsensitiveString dla każdego zdarzenia. Może więc uderzyć w wydajność.

Czy istnieje inny sposób rozwiązania tego problemu?

rs
źródło
3
[Czy jest dobry sposób, aby Map <String,?> Get and put ignore case?] [1] [1]: stackoverflow.com/questions/212562/…
Beau Grantham
Skomentowałem poniższe problemy, ale są one poniżej progu, więc ludzie mogą ich nie widzieć. Uważaj na podklasy HashMap. JDK8 zmienił implementację i teraz musisz nadpisać putAll (przynajmniej), aby te sugestie działały.
Steve N,
To powinno działać dobrze. Możesz użyć flyweight, aby pozbyć się nowej instancji obiektu.
topkara

Odpowiedzi:

331
Map<String, String> nodeMap = 
    new TreeMap<>(String.CASE_INSENSITIVE_ORDER);

To naprawdę wszystko, czego potrzebujesz.

Roel Spilker
źródło
6
Jest to zdecydowanie najprostsze rozwiązanie, a także zachowuje wielkość liter podczas ich iteracji.
Ralf,
To jest piękne! To był ostatni element układanki przy tworzeniu uporządkowanej struktury w ColdFusion, która zachowuje możliwość używania notacji kropkowej. Zamiast var struct = {} lub var struct = structnew () możesz użyć var ​​struct = createObject ('java', 'java.util.TreeMap'). Init (createObject ('java', 'java.lang.String' ) .CASE_INSENSITIVE_ORDER); FUGLY, ale działa;)
Eric Fuller
public static <K extends String, V> Map<K, V> caseInsensitiveMap() { return new TreeMap<K, V>(String.CASE_INSENSITIVE_ORDER); }
pllee
5
Nie ma potrzeby <K extends String> ponieważ Stringjest ostateczny: public static <V> Map<String, V> caseInsensitiveMap() { return new TreeMap<String, V>(String.CASE_INSENSITIVE_ORDER); }
Roel Spilker
19
Należy pamiętać, że TreeMap nie jest stałym czasem wykonywania podstawowych operacji. Nie jest to problem dla większości aplikacji, ale warto o tym pamiętać. Z JavaDoc: „Ta implementacja zapewnia gwarantowany koszt czasu logowania (n) dla operacji includeKey, get, put i remove. Algorytmy są adaptacjami algorytmów z Cormena, Leisersona i wprowadzenia do algorytmów Rivesta”.
James Schek,
57

Jak zasugerował Guido García w odpowiedzi tutaj :

import java.util.HashMap;

public class CaseInsensitiveMap extends HashMap<String, String> {

    @Override
    public String put(String key, String value) {
       return super.put(key.toLowerCase(), value);
    }

    // not @Override because that would require the key parameter to be of type Object
    public String get(String key) {
       return super.get(key.toLowerCase());
    }
}

Lub

https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/map/CaseInsensitiveMap.html

Vishal
źródło
28
A może zawiera, putAll itp.?
assylias
14
To nie działa w niektórych językach, takich jak turecki. Google „The turkey test”
Hugo,
5
@assylias: true containsKey()i remove()powinno zostać zastąpione w taki sam sposób, jak get(). z HashMap.putAll()zastosowania wdrożeniowe put(), więc nie powinno być problemu - tak długo, jak pobytów realizacji HashMap samo. ;) również get()sygnatura metody przyjmuje Objectjako argument, a nie String. kod również nie sprawdza klucza pustego:super.get(key == null ? null : key.toString().toLowercase());
sfera
zwróć uwagę, że jeśli potrzebujesz konstruktora kopiującego HashMap(<? extends String, ? extends String> anotherMap), nie powinieneś wywoływać super implementacji tego samego konstruktora, ponieważ ta operacja nie zagwarantuje, że twoje klucze będą pisane małymi literami. możesz użyć: super(anotherMap.size()); putAll(anotherMap);zamiast.
sfera
A jeśli chcesz, aby wartości mapy nie były ciągami? (tj. CaseInsensitiveMap<String, Integer>)
Adam Parkin
16

Jednym z podejść jest utworzenie niestandardowej podklasy AbstractHashedMapklasy Apache Commons , nadpisując metody hashi w isEqualKeyscelu wykonania mieszania i porównywania kluczy bez uwzględniania wielkości liter. (Uwaga - sam nigdy tego nie próbowałem ...)

Pozwala to uniknąć nakładu pracy związanego z tworzeniem nowych obiektów za każdym razem, gdy trzeba przeszukać mapę lub zaktualizować ją. A typowe Mapoperacje powinny O (1) ... tak jak zwykły HashMap.

A jeśli jesteś przygotowany na zaakceptowanie dokonanych przez nich wyborów dotyczących implementacji, Apache Commons CaseInsensitiveMapwykona AbstractHashedMapza Ciebie pracę dostosowywania / specjalizacji .


Ale jeśli O (logN) geti putoperacje są dopuszczalne, TreeMapopcją jest a z komparatorem bez rozróżniania wielkości liter; np String.CASE_INSENSITIVE_ORDER. używanie .

A jeśli nie masz nic przeciwko tworzeniu nowego tymczasowego obiektu String za każdym razem, gdy wykonujesz putlub get, odpowiedź Vishala jest w porządku. (Chociaż zauważam, że nie zachowałbyś oryginalnego pudełka kluczy, gdybyś to zrobił ...)

Stephen C.
źródło
6

Podklasę HashMapi utwórz wersję, w której klawisze putiget (i prawdopodobnie inne metody zorientowane na klucz).

Lub złożony a HashMap w nową klasę i deleguj wszystko do mapy, ale przetłumacz klucze.

Jeśli chcesz zachować oryginalny klucz, możesz zachować podwójne mapy lub przechowywać oryginalny klucz wraz z wartością.

Dave Newton
źródło
Czy masz na myśli wykonanie String.toLowerCase () podczas wyszukiwania?
rs
@ user710178 Nie tylko podczas wyszukiwania, ale także podczas przechowywania.
Dave Newton
@ user710178 Och, tak, jak wskazuje ta inna odpowiedź, to już istnieje, jeśli nie masz nic przeciwko dodatkowej zależności.
Dave Newton
@StephenC Jeśli spełnia Twoje potrzeby, jasne; OP określił a HashMap, więc właśnie to poszedłem :) Och, masz na myśli wspólny; Widzę. Myślę, że tak długo, jak nie potrzebujesz tego uogólnionego (czy w końcu mają teraz leki generyczne?)
Dave Newton
1
W przypadku JDK 8 i nowszych musisz również (przynajmniej) zastąpić metodę putAll, ponieważ implementacja uległa zmianie.
Steve N,
4

Przychodzą mi do głowy dwie możliwości:

  1. Możesz użyć bezpośrednio s.toUpperCase().hashCode();jako klucza Map.
  2. Możesz użyć TreeMap<String>z niestandardowym, Comparatorktóry ignoruje wielkość liter.

W przeciwnym razie, jeśli wolisz swoje rozwiązanie, zamiast definiować nowy rodzaj String, wolałbym raczej zaimplementować nową Mapę z wymaganą funkcją niewrażliwości na wielkość liter.

Gabriel Belingueres
źródło
3

Czy nie byłoby lepiej „zawinąć” String w celu zapamiętania hashCode. W normalnej klasie String hashCode () ma wartość O (N) za pierwszym razem, a następnie ma wartość O (1), ponieważ jest zachowana do wykorzystania w przyszłości.

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.
}

Umożliwiłoby to użycie dowolnej implementacji Hashtable w java i posiadanie O (1) hasCode ().

le-doude
źródło
3

Możesz użyć HashingStrategy opartej Mapna Eclipse Collections

HashingStrategy<String> hashingStrategy =
    HashingStrategies.fromFunction(String::toUpperCase);
MutableMap<String, String> node = HashingStrategyMaps.mutable.of(hashingStrategy);

Uwaga: współtworzę kolekcje Eclipse.

Nikhil Nanivadekar
źródło
2

Opierając się na innych odpowiedziach, istnieją zasadniczo dwa podejścia: tworzenie podklas HashMaplub pakowanie String. Pierwsza wymaga trochę więcej pracy. W rzeczywistości, jeśli chcesz to zrobić poprawnie, musisz przesłonić prawie wszystkie metody ( containsKey, entrySet, get, put, putAll and remove).

Zresztą ma problem. Jeśli chcesz uniknąć przyszłych problemów, musisz określić operacje Localew Stringprzypadku. Więc tworzyłbyś nowe metody ( get(String, Locale), ...). Wszystko jest łatwiejsze i jaśniejsze owijanie Sznurek:

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
}

No cóż, jeśli chodzi o Twoje obawy o wydajność: przedwczesna optymalizacja jest źródłem wszelkiego zła :)

sinuhepop
źródło
2
„No cóż, Twoje zmartwienia związane z wydajnością: przedwczesna optymalizacja jest źródłem wszelkiego zła :)” - Wręcz przeciwnie, używanie tego jako pretekstu do pisania nieefektywnego kodu jest tym, co jest złe.
Gordon
1
Właściwie @Gordon, oba są równie złe, w zależności od kontekstu. Etykieta „zło” jest oznaką czarno-białego myślenia, jak „najlepsza praktyka” i różne inne niepomocne zwroty, których używa wielu informatyków. Najlepiej całkowicie tego unikać.
Stephen C
Zauważyłem, że mówienie ludziom, że nie stosują się do „najlepszych praktyk”, zwykle powoduje mniej wbijania się w pięty niż mówienie im, że stosują złe praktyki.
Gordon
0

To jest adapter do HashMaps, który zaimplementowałem w ostatnim projekcie. Działa w sposób podobny do tego, co robi @SandyR, ale hermetyzuje logikę konwersji, więc nie można ręcznie konwertować ciągów znaków na obiekt opakowujący.

Korzystałem z funkcji Java 8, ale po kilku zmianach można ją dostosować do poprzednich wersji. Przetestowałem go w większości typowych scenariuszy, z wyjątkiem nowych funkcji strumieniowych Java 8.

Zasadniczo zawija HashMap, kieruje do niego wszystkie funkcje podczas konwersji łańcuchów do / z obiektu opakowującego. Ale musiałem również dostosować zestaw kluczy i zestaw pozycji, ponieważ przekazują one niektóre funkcje do samej mapy. Więc zwracam dwa nowe zestawy dla kluczy i wpisów, które faktycznie opakowują oryginalny zestaw keySet () i entrySet ().

Jedna uwaga: Java 8 zmieniła implementację metody putAll, której nie mogłem znaleźć w prosty sposób. Dlatego obecna implementacja może obniżyć wydajność, zwłaszcza jeśli używasz metody putAll () dla dużego zestawu danych.

Daj mi znać, jeśli znajdziesz błąd lub masz sugestie dotyczące ulepszenia kodu.

pakiet webbit.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);
        }
    }


}
Cagatay Kalan
źródło
0

Z tego powodu tworzę nowy obiekt CaseInsensitiveString dla każdego zdarzenia. Może więc uderzyć w wydajność.

Tworzenie opakowań lub konwersja klucza na małe litery przed wyszukiwaniem tworzą nowe obiekty. Napisanie własnej implementacji java.util.Map jest jedynym sposobem uniknięcia tego. Nie jest to zbyt trudne, a IMO jest tego warte. Zauważyłem, że następująca funkcja skrótu działa całkiem dobrze, do kilkuset kluczy.

static int ciHashCode(String string)
{
    // length and the low 5 bits of hashCode() are case insensitive
    return (string.hashCode() & 0x1f)*33 + string.length();
}
Zdeněk Pavlas
źródło
-3

Co powiesz na korzystanie ze strumieni Java 8.

nodeMap.entrySet().stream().filter(x->x.getKey().equalsIgnoreCase(stringfromEven.toString()).collect(Collectors.toList())
Amarendra Reddy
źródło
Nie umożliwia to wyszukiwania wartości na mapie bez rozróżniania wielkości liter.
Gili
equalsignorecase to zrobi, prawda?
Amarendra Reddy
Budujesz listę. OP poprosił o mapę.
Gili
To niweczy korzyść złożoności mapy O (1).
Paul Rooney