«1. Обзор

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

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

2. Коллекции Eclipse

Коллекции Eclipse — это высокопроизводительная среда коллекций для Java. Он предоставляет улучшенные реализации, а также некоторые дополнительные структуры данных, включая несколько примитивных коллекций.

2.1. Изменяемые и неизменяемые карты

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

MutableIntIntMap mutableIntIntMap = IntIntMaps.mutable.empty();

Фабричный класс IntIntMaps — наиболее удобный способ создания примитивных карт. Это позволяет нам создавать как изменяемые, так и неизменяемые экземпляры желаемого типа карты. В нашем примере мы создали изменяемый экземпляр IntIntMap. Точно так же мы можем создать неизменяемый экземпляр, просто заменив вызов статической фабрики IntIntMaps.mutable на IntIntMaps.immutable:

ImmutableIntIntMap immutableIntIntMap = IntIntMaps.immutable.empty();

Итак, давайте добавим пару ключ-значение в нашу изменяемую карту:

mutableIntIntMap.addToValue(1, 1);

Аналогично , мы можем создавать смешанные карты с парами ключ-значение эталонного и примитивного типа. Давайте создадим карту со строковыми ключами и двойными значениями:

MutableObjectDoubleMap dObject = ObjectDoubleMaps.mutable.empty();

Здесь мы использовали класс фабрики ObjectDoubleMaps для создания изменяемого экземпляра для MutableObjectDoubleMap.

Теперь добавим несколько записей:

dObject.addToValue("price", 150.5);
dObject.addToValue("quality", 4.4);
dObject.addToValue("stability", 0.8);

2.2. Примитивное дерево API

В Eclipse Collections есть базовый интерфейс под названием PrimitiveIterable. Это базовый интерфейс для каждого из примитивных контейнеров библиотеки. Все они называются PrimitiveTypeIterable, где PrimitiveType может быть Int, Long, Short, Byte, Char, Float, Double или Boolean.

Все эти базовые интерфейсы, в свою очередь, имеют свое дерево реализаций XYMap, которое разделено по тому, является ли карта изменяемой или неизменяемой. Например, для IntIntMap у нас есть MutableIntIntMap и ImmutableIntIntMap.

Наконец, как мы видели выше, у нас есть интерфейсы, охватывающие все виды комбинаций типов для ключей и значений как для примитивных, так и для объектных значений. Так, например, у нас может быть IntObjectMap\u003cK\u003e для примитивного ключа со значением Object или ObjectIntMap\u003cK\u003e для противоположного случая.

3. HPPC

HPPC — это библиотека, ориентированная на высокую производительность и эффективность использования памяти. Это означает, что в библиотеке меньше абстракций, чем в других. Тем не менее, это имеет преимущество, заключающееся в том, что внутренние компоненты подвергаются полезным низкоуровневым манипуляциям. Он предоставляет как карты, так и наборы.

3.1. Простой пример

Начнем с создания карты с ключом int и значением long. Использование этого довольно знакомо:

IntLongHashMap intLongHashMap = new IntLongHashMap();
intLongHashMap.put(25, 1L);
intLongHashMap.put(150, Long.MAX_VALUE);
intLongHashMap.put(1, 0L);
        
intLongHashMap.get(150);

HPPC предоставляет карты для всех комбинаций ключей и значений:

    Примитивный ключ и примитивное значение Примитивный ключ и значение объектного типа Ключ объектного типа и примитивное значение Оба Ключ объектного типа и значение

Карты объектного типа поддерживают дженерики:

IntObjectOpenHashMap<BigDecimal>
ObjectIntOpenHashMap<LocalDate>

Первая карта имеет примитивный ключ int и значение BigDecimal. Вторая карта имеет LocalDate в качестве ключей и int в качестве значений

3.2. Hash Maps vs Scatter Maps

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

Однако место для карт, имеющих более простую функцию распределения, все же остается. Это полезно, если карты используются в качестве таблиц поиска или для подсчета, или если они не требуют большого количества операций записи после загрузки. HHPC предоставляет точечные карты для еще большего повышения производительности.

Все классы точечных карт поддерживают то же соглашение об именах, что и карты, но вместо этого используют слово Scatter:

    IntScatterSet IntIntScatterMap IntObjectScatterMap\u003cBigDecimal\u003e

4. Fastutil

Fastutil — это быстрая и компактная среда, которая предоставляет коллекции для конкретных типов, включая карты примитивных типов.

4.1. Быстрый пример

«Аналогично Eclipse Collections и HPPC. Fastutil также предоставляет ассоциативные карты типа «примитив-примитив» и «примитив-объект».

Давайте создадим карту int to boolean:

Int2BooleanMap int2BooleanMap = new Int2BooleanOpenHashMap();

А теперь добавим несколько записей:

int2BooleanMap.put(1, true);
int2BooleanMap.put(7, false);
int2BooleanMap.put(4, true);

Затем мы сможем получить из нее значения:

boolean value = int2BooleanMap.get(1);

4.2. Итерация на месте

Стандартные коллекции JVM, которые реализуют интерфейс Iterable, обычно создают новый временный объект итератора на каждом шаге итерации. С огромными коллекциями это может создать проблему со сборкой мусора.

Fastutil предоставляет альтернативу, которая значительно смягчает это:

Int2FloatMap map = new Int2FloatMap();
//Add keys here
for(Int2FloatMap.Entry e : Fastutil.fastIterable(map)) {
    //e will be reused on each iteration, so it will be only one object
}

Fastutil также предоставляет метод fastForeach. Это возьмет функциональный интерфейс Consumer и выполнит лямбда-выражение для каждого цикла:

Int2FloatMap map = new Int2FloatMap();
//Add keys here
Int2FloatMaps.fastForEach(map , e ->  {
    // e is also reused across iterations
});

Это очень похоже на стандартную конструкцию foreach в Java:

Int2FloatMap map = new Int2FloatMap();
//Add keys here
map.forEach((key,value) -> {
    // use each key/value entry   
});

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

В этой статье мы научились создавать примитивные карты на Java с помощью Eclipse Collections, HPPC и Fastutil.

Как всегда, пример кода для этой статьи доступен на GitHub.