Эффективная итерация по объединению нескольких наборов ключей Java Map - PullRequest
7 голосов
/ 29 июня 2011

В одном из моих проектов Java 6 у меня есть массив LinkedHashMap экземпляров в качестве входных данных для метода, который должен выполнять итерацию по всем ключам (т.е. через объединение наборов ключей всех карт) и работать со связанными значениями. Не все ключи существуют во всех картах, и метод не должен проходить через каждую клавишу более одного раза или изменять входные карты.

Моя текущая реализация выглядит так:

Set<Object> keyset = new HashSet<Object>();

for (Map<Object, Object> map : input) {
    for (Object key : map.keySet()) {
        if (keyset.add(key)) {
            ...
        }
    }
}

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

К сожалению, эта часть кода довольно критична с точки зрения производительности, так как ее очень часто называют . Фактически, согласно профилировщику, более 10% процессорного времени тратится в методе HashSet.add().

Я пытаюсь максимально оптимизировать этот код. Использование LinkedHashMap с его более эффективными итераторами (по сравнению с простым HashMap ) было значительным стимулом, но я надеялся сократить до минимума то, что по сути является временем бухгалтерского учета .

Поместить все ключи в HashSet заранее, используя addAll(), оказалось менее эффективным, из-за стоимости звонка HashSet.contains() впоследствии. В данный момент я смотрю, могу ли я использовать растровое изображение (точнее, boolean[]), чтобы полностью избежать HashSet, но это может оказаться невозможным вообще, в зависимости от моего диапазона ключей.

Есть ли более эффективный способ сделать это? Желательно что-то, что не будет накладывать ограничения на клавиши?

EDIT:

Несколько уточнений и комментариев:

  • Мне нужны все значения из карт - я не могу отбросить ни одно из них.

  • Мне также нужно знать, с какой карты получено каждое значение. Недостающая часть (...) в моем коде будет выглядеть примерно так:

    for (Map<Object, Object> m : input) {
        Object v = m.get(key);
    
        // Do something with v
    }
    

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

    Key Map0 Map1 Map2
    F   1    null 2
    B   2    3    null
    C   null null 5
    ...
    

    Это не то, чем я на самом деле занимаюсь, но вы должны понять.

  • Входные карты имеют переменную чрезвычайно . Фактически, каждый вызов этого метода использует различный набор из них. Поэтому я бы ничего не получил, кэшируя объединение их ключей.

  • Все мои ключи - строковые экземпляры. Они как бы интернируются в куче, используя отдельный HashMap, так как они довольно повторяющиеся, поэтому их хеш-код уже кэширован и большинство хеш-проверок (когда реализация HashMap проверяет, действительно ли два ключа равны, после их хеш-кодов совпадение) сводится к сравнению идентичности (==). Профилировщик подтверждает, что только 0,5% процессорного времени расходуется на String.equals() и String.hashCode().

РЕДАКТИРОВАТЬ 2:

Основываясь на предложениях в ответах, я сделал несколько тестов, профилируя и тестируя по пути. В итоге я увеличил производительность примерно на 7%. Что я сделал:

  • Я установил начальную емкость HashSet, чтобы удвоить общий размер всех входных карт. Это дало мне что-то в районе 1-2%, исключив большинство (все?) resize() вызовов в HashSet.

  • Я использовал Map.entrySet() для карты, которую я сейчас перебираю. Первоначально я избегал такого подхода из-за дополнительного кода и страха, что дополнительные проверки и вызовы методов получения Map.Entry перевесят любые преимущества. Оказалось, что общий код был немного быстрее.

  • Я уверен, что некоторые люди начнут кричать на меня, но вот оно: Необработанные типы.В частности, я использовал необработанную форму HashSet в приведенном выше коде.Поскольку я уже использовал Object в качестве типа контента, я не теряю безопасность типов.Стоимость этой бесполезной операции checkcast при вызове HashSet.add() была, по-видимому, достаточно важной, чтобы обеспечить увеличение производительности на 4% после удаления.Почему JVM настаивает на проверке приведений к Object, я не знаю ...

Ответы [ 4 ]

2 голосов
/ 29 июня 2011

Невозможно заменить ваш подход, но есть несколько предложений (немного) по оптимизации существующего кода.

  1. Рассмотрите возможность инициализации хеш-набора с емкостью (суммой размеров всехкарты).Это позволяет избежать / уменьшить изменение размера набора во время операции добавления
  2. Не используйте keySet(), поскольку это всегда создает новый набор в фоновом режиме.Используйте entrySet(), это должно быть намного быстрее
  3. Посмотрите на реализации equals() и hashCode() - если они "дорогие", то вы отрицательно скажетесь на addМетод.
1 голос
/ 29 июня 2011

То, как вы избегаете использования HashSet, зависит от того, что вы делаете.

Я бы вычислял объединение только один раз при каждом изменении input.Это должно быть относительно редко по сравнению с количеством поисков.

// on an update.
Map<Key, Value> union = new LinkedHashMap<Key, Value>();
for (Map<Key, Value> map : input) 
    union.putAll(map);


// on a lookup.
Value value = union.get(key);
// process each key once
for(Entry<Key, Value> entry: union) {
   // do something.
}
0 голосов
/ 29 июня 2011
0 голосов
/ 29 июня 2011

Вариант A - использовать метод .values ​​() и выполнять его итерацию. Но я полагаю, вы уже подумали об этом.

Если код вызывается так часто, возможно, стоит создать дополнительные структуры (в зависимости от того, как часто меняются данные). Создать новый HashMap; каждый ключ в любом из ваших хеш-карт является ключом в этом, и список хранит HashMaps, где этот ключ появляется.

Это поможет, если данные несколько статичны (связаны с частотой запросов), поэтому перегрузка от управления структурой относительно мала, и если пространство ключей не очень плотно (ключи не повторяются в различные HashMaps), так как это сэкономит много ненужного содержимого ().

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

...