Строка без учета регистра в качестве ключа HashMap - PullRequest
148 голосов
/ 23 ноября 2011

Я хотел бы использовать строку без учета регистра в качестве ключа HashMap по следующим причинам.

  • Во время инициализации моя программа создает HashMap с пользовательской строкой
  • При обработке события (сетевой трафик в моем случае) я мог бы получить String в другом случае, но я мог бы найти <key, value> из 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()));

Из-за этого я создаю новый объект CaseInsensitiveString для каждого события. Таким образом, это может повлиять на производительность.

Есть ли другой способ решить эту проблему?

Ответы [ 12 ]

267 голосов
/ 12 марта 2014
Map<String, String> nodeMap = 
    new TreeMap<>(String.CASE_INSENSITIVE_ORDER);

Это действительно все, что тебе нужно.

55 голосов
/ 23 ноября 2011

Как предполагает Гвидо Гарсия в их ответ здесь :

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

Или

http://commons.apache.org/proper/commons-collections/javadocs/api-release/org/apache/commons/collections4/map/CaseInsensitiveMap.html

13 голосов
/ 23 ноября 2011

Один из подходов заключается в создании пользовательского подкласса класса Apache Commons AbstractHashedMap, переопределяющего методы hash и isEqualKeys для выполнения хеширования без учета регистра и сравнения ключей. (Примечание - я никогда не пробовал это сам ...)

Это позволяет избежать затрат на создание новых объектов каждый раз, когда вам нужно выполнить поиск или обновление карты. И обычные Map операции должны O (1) ... точно так же, как обычные HashMap.

И если вы готовы принять выбранный ими вариант реализации, Apache Commons CaseInsensitiveMap выполнит для вас настройку / специализацию AbstractHashedMap.


Но если допустимы операции O (logN) * ​​1019 * и put, вариант TreeMap с нечувствительным к регистру компаратором строк является опцией; например используя String.CASE_INSENSITIVE_ORDER.

И если вы не возражаете против создания нового временного объекта String каждый раз, когда вы делаете put или get, то ответ Вишала очень хорош. (Хотя я отмечаю, что вы бы не сохранили оригинальный регистр ключей, если бы сделали это ...)

6 голосов
/ 23 ноября 2011

Подкласс HashMap и создайте версию, которая в нижнем регистре вводит ключ на put и get (и, вероятно, другие ориентированные на ключ методы).

Или объединяет HashMap вновый класс и делегируйте все на карту, но переведите ключи.

Если вам нужно сохранить исходный ключ, вы можете сохранить двойные карты или сохранить оригинальный ключ вместе со значением.

4 голосов
/ 23 ноября 2011

На ум приходят два варианта:

  1. Вы можете напрямую использовать s.toUpperCase().hashCode(); в качестве ключа Map.
  2. Вы можете использовать TreeMap<String> спользовательский Comparator, который игнорирует регистр.

В противном случае, если вы предпочитаете свое решение, вместо определения нового типа String, я бы предпочел реализовать новую карту с требуемой функциональностью без учета регистра.

3 голосов
/ 26 марта 2016

Вы можете использовать HashingStrategy на основе Map из Коллекции Eclipse

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

Примечание. Я участвую в коллекциях Eclipse.

3 голосов
/ 21 августа 2013

Не лучше ли "обернуть" строку, чтобы запомнить хэш-код. В обычном классе String 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.
}

Это позволит вам использовать любую реализацию Hashtable в java и иметь O (1) hasCode ().

1 голос
/ 17 мая 2015

Для надежной реализации CaseInsensitiveMap / CaseInsensitiveSet, проверьте java-util (https://github.com/jdereg/java-util).

. Эти Карты работают в стандартное время поиска O (1), сохраняют регистр добавленных элементов, поддерживают все API-интерфейсы карты.как putAll (), retainAll (), removeAll () и позволяет размещать разнородные элементы в наборе ключей.

Кроме того, java.util.Set возвращается .keySet () и .entrySet ()соблюдать нечувствительность к регистру (многие реализации этого не делают). Наконец, если вы извлекаете ключ из набора ключ / запись во время итерации, вы получаете String, а не класс-оболочку CaseInsensitiveString.

1 голос
/ 21 августа 2013

Основываясь на других ответах, в основном существует два подхода: создание подклассов HashMap или перенос String.Первый требует немного больше работы.На самом деле, если вы хотите сделать это правильно, вы должны переопределить почти все методы (containsKey, entrySet, get, put, putAll and remove).

Во всяком случае, у него есть проблема.Если вы хотите избежать будущих проблем, вы должны указать операции Locale в String.Таким образом, вы бы создали новые методы (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
}

Ну и о ваших заботах о производительности: преждевременная оптимизация - корень всех зол :)

0 голосов
/ 01 марта 2018

Как насчет использования потоков Java 8.

nodeMap.entrySet().stream().filter(x->x.getKey().equalsIgnoreCase(stringfromEven.toString()).collect(Collectors.toList())
...