Вложенные карты или комбинированные ключи в Java - PullRequest
9 голосов
/ 17 ноября 2011

Мне нужна карта, чтобы сделать кэш в Java для тех же значений, что у меня есть два ключа String.Мой вопрос, лучше ли сделать вложенные Карты (по одной для каждого ключа) или сделать какой-то тип настраиваемого ключа, помеченного двумя строками?

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

Тогда, если лучше, объединить строковый ключ только с одним, что лучше?

  • Пользовательский класс с пользовательским getHash методом.Но тогда проблема в том, что реализует хеш-функция?
  • Просто объедините две строки вместе.Например:

    cache.put (ключ1 + ключ2, значение)

Ответы [ 6 ]

12 голосов
/ 17 ноября 2011

Вы можете рассмотреть классы Таблицы Гуавы.Они реализуют Map с двумя ключами на значение.Это может обеспечить более удобочитаемое решение, чем составной ключ, в зависимости от ваших требований.

Table<String, String, MyValue>
11 голосов
/ 17 ноября 2011

Вы можете создавать вложенные карты или использовать пользовательский класс, определяющий hashCode().

Как правило, объединять ключи не очень хорошая идея, вы можете столкнуться с коллизиями, как в случае с ключами 1 и 22 и клавиши 12 и 2.Они будут отображаться на одно и то же значение 122.

Если вы всегда будете использовать обе клавиши, использование одной Map всегда будет немного более эффективным, и вы всегда можете определить свой собственный адаптер длякарта, которая будет принимать два аргумента:

public class MyCache { 

    private Map<MyKey, Object> cache = new HashMap<MyKey, Object>();

    public Object getObject(Object key1, Object key2){
        return cache.get(new MyKey(key1, key2));
    }
    public void putObject(Object key1, Object key2, Object value){
        cache.put(new MyKey(key1, key2), value);
    }
}

Не забудьте определить equals() и hashCode() в вашем пользовательском классе ключей (при необходимости добавьте проверки на недействительность).

public int hashCode() {
    int result = 17;
    result = 37 * result + keyA.hashCode();
    result = 37 * result + keyB.hashCode();
    return result;
}

public boolean equals(Object another) { 
    return another.keyA.equals(keyA) && another.keyB.equals(keyB);
} 
3 голосов
/ 17 ноября 2011

лучше сделать вложенные Карты (по одной для каждого ключа) или создать какой-то тип настраиваемого ключа, помеченного двумя строками?

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

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

2 голосов
/ 17 ноября 2011

Использование комбинированных ключей мне кажется более логичным и более простым в использовании (вам не нужно беспокоиться о создании вложенной карты).

О комбинации клавиш используйте пользовательский класс.Это имеет смысл с точки зрения семантики и предотвращает коллизии, если у вас есть похожие строки.Такое столкновение может появиться, если у вас есть ключ ["ab", "c"] и ключ ["a", "bc"].

Помните, что когда вы пишете свой собственный класс, вам нужно правильно написать методы equals и hashCode илиВаш кеш может не работать (и может также страдать от проблем с производительностью).

1 голос
/ 17 ноября 2011

У меня была похожая проблема с системой кеширования, которая должна была использовать параметры метода для генерации ключа.Я закончил тем, что написал класс-оболочку, похожий на ответ Хави.

public final class ArrayWrapper 
{
    private final Object[] array;

    public ArrayWrapper(final Object... array)
    {
        this.array = array;
    }

    public Object[] getArray()
    {
        return this.array;
    }

    public boolean equals(Object o)
    {
        if (o == null) return false;
        if (o == this) return true;
        if (o instanceof ArrayWrapper)
        {
            return Arrays.equals(this.array, ((ArrayWrapper)o).array);
        }
        return false;
    }

    private int hashCode(Object o)
    {
        if (o == null) return 0;
        return o.hashCode();
    }

    public int hashCode()
    {
        int sum = 17;
        if (this.array != null) for(int i = 0;i<this.array.length;i++)
        {
            sum = 37 * sum + this.hashCode(this.array[i]);
        }
        return sum;
    }

    public String toString()
    {
        if (this.array != null)
        {
            return "Wrapper " + Arrays.toString(array);
        }
        else return "Wrapper []";
    }
}

Создайте новый Wrapper и используйте его в качестве ключа к карте.Вы также можете написать пользовательскую карту и перегрузить методы get и put для принятия массива (для get также vararg) вместо необходимости оборачивать его вручную.

0 голосов
/ 17 ноября 2011

Я думаю, что использование вложенных карт является менее оптимальным способом. Я согласен с вашей второй мыслью - реализовать пользовательский класс с переопределением hashCode

public int hashCode(){
   return str1+str2.hashCode();
}


и equals

public boolean equals(Object o){
   //compare both keys here
}

Также не забудьте переопределить те же методы в классе хранимого объекта.

Тогда, в случае ["ab", "c"] у вас будет тот же хеш-код, но результат будет другим равным

...