Эффективный поиск HashMap с ключевым составным ключом (сборка из 2-х перечислений) - PullRequest
0 голосов
/ 16 сентября 2010

У меня есть 2 значения перечисления, представляющих отображение на объект, который я (в настоящее время) моделирую с помощью HashMap со значениями 2 перечисления, которые используются в качестве ключа, а объект является значением.

Это неэффективнопотому что я создаю новый CompositeKey (Enum1 enum1, Enum2 enum2) для каждой комбинации Enum1.values ​​() x Enum2.values ​​().

Я хотел бы пропустить новый CompositeKey () проблема.

Решение, о котором я сейчас думаю, - это вычисление числовых представлений из 2 перечислений, что-то вроде int numericKey = enum1.ordinal() * 0xFFFF + enum2.ordinal();, но потом, когда я сделаю map.get(numricKey), я все равно буду автоматически боксировать вЦелое число - отсюда и создание новых экземпляров.

Идеальным решением (IMO) было бы внедрение Map (не обязательно должно быть универсальным ...), но я не думаю, что такое существует для Java.

Возможно, есть и другой вариант mapping = new Object[Enum1.values().length][Enum2.values().length], который я затем сделаю для поиска Object = mapping[enum1.ordinal()][enum2.ordinal()], но это кажется слишком "C'ish".

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

Комментарии приветствуются.

Спасибо, Максим.

Ответы [ 4 ]

5 голосов
/ 17 сентября 2010

Использование порядкового номера enum - очень плохая идея, поскольку порядковый номер является внутренним представлением перечисления и никогда не должен использоваться во внешнем коде. Спецификация Enum имеет следующее выражение для порядкового номера:

Большинство программистов не будут использовать этот метод. Он предназначен для использования сложными структурами данных на основе перечислений, такими как EnumSet и EnumMap.

Я рекомендую использовать EnumMap, который специально разработан для таких целей, как у вас. Создайте EnumMap<Enum1,EnumMap<Enum2,V>> и заполните его, используя значения ваших перечислений:

for (Enum1 e1: Enum1.values()) {
    map.put(e1, new EnumMap<Enum2,V>());
    for (Enum2 e2 : Enum2.values()) {
        map.get(e1).put(e2, getSomeValue(e1, e2));
    }
}
1 голос
/ 01 сентября 2011

Я не согласен с мнением Саркара об использовании метода Enum.ordinal ().Это эффективно и действительно для использования.Конечно, это полностью зависит от сферы использования.Если вы можете применить в своем коде использование EnumMap или EnumSet, то любое разработанное вами решение будет иметь ту же область выполнения.Вам следует избегать использования метода ordinal () вне текущей работающей виртуальной машины, например, сериализации, externalize или постоянного значения.

Самым быстрым решением остается вычисленное смещение в массиве, но может использоваться потенциально большое количествообъем памяти.Но если у вас есть большие перечисления (которые действительно ставят под сомнение правильное использование Enum) с очень небольшим количеством комбинаций, что приводит к большому, но разреженному массиву, тогда ваш оригинальный «составной» ключ - очень близкая секунда.1004 * Наконец, исключительно для повышения производительности, если вы формируете составной ключ, приведенный в вашем примере, int numericKey = enum1.ordinal() * 0xFFFF + enum2.ordinal(); используйте более быстрый оператор сдвига и оператор ИЛИ, а не операторы умножения и добавления: int numericKey = (enum1.ordinal() << 16) | enum2.ordinal();

1 голос
/ 17 сентября 2010

Я думаю, что подход CompositeKey является лучшим с точки зрения обслуживания, поскольку он позволяет вам использовать существующие коллекции без изменений.Затраты на создание экземпляров этого объекта на самом деле не так велики - большинство экземпляров будут недолговечными и никогда не выйдут из пространства Эдема (которое распределяется и собирается очень быстро).Вы также можете реализовать Map<Enum1,Map<Enum2,?>>, с EnumMap в качестве типа реализации.Однако вам придется либо предварительно заполнить «внешние» карты, либо написать код для их ленивого заполнения.

Но если производительность среды выполнения является вашим главным приоритетом, тогда ваша идея использовать массив является наилучшей.Однако я бы не реализовал это как двумерный массив.Вместо этого используйте одномерный массив и индекс со следующим:

int index = enum1.ordinal() * _enum1Size + enum2.ordinal;

Оберните его в классе, и все готово.

0 голосов
/ 04 декабря 2013

У меня есть HashMap с именем label с ключом типа Key с двумя перечислениями в качестве членов.Я заметил, что два экземпляра Key, созданные с одинаковыми значениями, имеют разные хеш-коды.Переопределение hashCode () Я могу получить соответствующее значение, в противном случае я всегда получаю нулевое значение.

Вот код:

public static String getMeasureLabel(MeasureSerie measure, TwampMeasure traffic) {
        Key key = new Key(traffic, measure);
        //Key key1 = new Key(traffic, measure); //key.hashCode() != key1.hashCode()
        return labels.get(key); // always returned null
    }

А вот ключ

public static class Key {

        public TwampMeasure trafficType;
        public MeasureSerie measureType;


        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result
                    + ((measureType == null) ? 0 : measureType.getValue().hashCode());
            result = prime * result
                    + ((trafficType == null) ? 0 : trafficType.getValue().hashCode());
            return result;
        }

    }
...