«1. Обзор

Карта — одна из наиболее распространенных структур данных в Java, а String — один из наиболее распространенных типов ключа карты. По умолчанию карта такого типа имеет ключи с учетом регистра.

В этом кратком руководстве мы рассмотрим различные реализации Map, которые принимают все варианты регистра String как один и тот же ключ.

2. Пристальный взгляд на карту с ключами, нечувствительными к регистру

Давайте рассмотрим проблему, которую мы пытаемся решить, более подробно.

Предположим, у нас есть Map\u003cString, Integer\u003e с одной записью:

Добавим следующую запись:

map.put("ABC", 2);

При работе с Map с ключами, чувствительными к регистру, мы получим две записи:

Но при работе с Map с ключами, нечувствительными к регистру, содержимое будет:

В следующих примерах мы углубимся в нечувствительные к регистру реализации некоторых популярных реализаций Map: TreeMap, HashMap и LinkedHashMap.

3. TreeMap

TreeMap является реализацией NavigableMap, что означает, что он всегда сортирует записи после вставки на основе данного компаратора. Кроме того, TreeMap использует компаратор, чтобы определить, является ли вставленный ключ дубликатом или новым.

Поэтому, если мы предоставим компаратор строк, нечувствительный к регистру, мы получим TreeMap, нечувствительный к регистру.

К счастью, String уже предоставляет этот статический компаратор:

public static final Comparator <String> CASE_INSENSITIVE_ORDER

который мы можем указать в конструкторе:

Map<String, Integer> treeMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
treeMap.put("abc", 1);
treeMap.put("ABC", 2);

И теперь, когда мы запускаем тесты, мы видим, что размер карты one:

assertEquals(1, treeMap.size());

и значение обновляется до 2:

assertEquals(2, treeMap.get("aBc").intValue());
assertEquals(2, treeMap.get("ABc").intValue());

Теперь давайте удалим запись, используя ту же строку, но с другим регистром:

treeMap.remove("aBC");
assertEquals(0, treeMap.size());

Мы должны иметь в виду, что такие функции, как put и get, стоят в среднем O(log n) для TreeMap по сравнению с HashMap, который обеспечивает O(1) вставку и поиск.

Также стоит отметить, что TreeMap не поддерживает нулевые ключи.

4. Apache’s CaseInsensitiveMap

Apache’s Commons-Collections — очень популярная библиотека Java, предоставляющая большое количество полезных классов, среди которых есть CaseInsensitiveMap.

CaseInsensitiveMap — это карта на основе хэша, которая преобразует ключи в нижний регистр перед их добавлением или извлечением. В отличие от TreeMap, CaseInsensitiveMap допускает вставку нулевого ключа.

Во-первых, нам нужно добавить зависимость commons-collections4:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.4</version>
</dependency>

Теперь мы можем использовать CaseInsensitiveMap и добавить две записи:

Map<String, Integer> commonsHashMap = new CaseInsensitiveMap<>();
commonsHashMap.put("abc", 1);
commonsHashMap.put("ABC", 2);

При тестировании мы ожидаем тех же результатов, что и видел ранее:

assertEquals(1, commonsHashMap.size());
assertEquals(2, commonsHashMap.get("aBc").intValue());
assertEquals(2, commonsHashMap.get("ABc").intValue());

commonsHashMap.remove("aBC");
assertEquals(0, commonsHashMap.size());

5. Spring LinkedCaseInsensitiveMap

Spring Core — это модуль Spring Framework, который также предоставляет служебные классы, включая LinkedCaseInsensitiveMap.

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

Во-первых, давайте добавим зависимость от spring-core:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.2.5.RELEASE</version>
</dependency>

Теперь мы можем инициализировать новую LinkedCaseInsensitiveMap:

Map<String, Integer> linkedHashMap = new LinkedCaseInsensitiveMap<>();
linkedHashMap.put("abc", 1);
linkedHashMap.put("ABC", 2);

add test it:

assertEquals(1, linkedHashMap.size());
assertEquals(2, linkedHashMap.get("aBc").intValue());
assertEquals(2, linkedHashMap.get("ABc").intValue());

linkedHashMap.remove("aBC");
assertEquals(0, linkedHashMap.size());

6. Заключение

В этом руководстве мы рассмотрели различные способы создания карты Java с ключами, нечувствительными к регистру, и использовали для этого разные классы.

Как всегда, код доступен на GitHub.