В одном из моих проектов 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
, я не знаю ...