Если вы просто хотите узнать, равны ли наборы, equals
метод AbstractSet
реализован примерно так, как показано ниже:
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Set))
return false;
Collection c = (Collection) o;
if (c.size() != size())
return false;
return containsAll(c);
}
Обратите внимание, как он оптимизирует общие случаи, когда:
- два объекта одинаковые
- другой объект вообще не набор, и
- Размеры двух комплектов различаются.
После этого containsAll(...)
вернется, false
как только найдет элемент в другом наборе, которого также нет в этом наборе. Но если все элементы присутствуют в обоих наборах, необходимо будет протестировать их все.
Таким образом, в худшем случае производительность возникает, когда два набора равны, но не являются одинаковыми объектами. Эта стоимость обычно составляет O(N)
или O(NlogN)
зависит от реализацииthis.containsAll(c)
.
И вы получите производительность, близкую к наихудшей, если наборы большие и отличаются лишь небольшим процентом элементов.
ОБНОВИТЬ
Если вы готовы потратить время на реализацию настраиваемого набора, существует подход, который может улучшить «почти такой же» случай.
Идея состоит в том, что вам нужно предварительно вычислить и кэшировать хэш для всего набора, чтобы вы могли получить текущее значение хэш-кода набора O(1)
. Затем вы можете сравнить хэш-код для двух наборов в качестве ускорения.
Как можно реализовать такой хэш-код? Хорошо, если бы установленный хэш-код был:
- ноль для пустого набора и
- XOR всех хэш-кодов элементов для непустого набора,
тогда вы можете дешево обновлять кэшированный хэш-код набора каждый раз, когда вы добавляете или удаляете элемент. В обоих случаях вы просто выполняете XOR хэш-кода элемента с текущим установленным хэш-кодом.
Конечно, это предполагает, что хэш-коды элементов стабильны, в то время как элементы являются членами наборов. Также предполагается, что функция хэш-кода классов элементов дает хороший разброс. Это связано с тем, что, когда два набора хэш-кода совпадают, вам все равно придется вернуться к O(N)
сравнению всех элементов.
Вы могли бы развить эту идею немного дальше ... по крайней мере, теоретически.
ПРЕДУПРЕЖДЕНИЕ. Это весьма умозрительно. «Мысленный эксперимент», если хотите.
Предположим, что у вашего класса элемента set есть метод для возврата криптографических контрольных сумм для элемента. Теперь реализуйте контрольные суммы набора, выполняя операцию XOR с контрольными суммами, возвращаемыми для элементов.
Что это нам дает?
Что ж, если мы предположим, что ничего скрытого не происходит, вероятность того, что любые два неравных элемента набора имеют одинаковые N-битные контрольные суммы, равна 2 -N . И вероятность того, что 2 неравных набора имеют одинаковые N-битные контрольные суммы, также равна 2 -N . Итак, моя идея состоит в том, что вы можете реализовать equals
как:
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Set))
return false;
Collection c = (Collection) o;
if (c.size() != size())
return false;
return checksums.equals(c.checksums);
}
В предположениях выше, это даст вам неправильный ответ только один раз в 2- N раз. Если вы сделаете N достаточно большим (например, 512 бит), вероятность неправильного ответа станет незначительной (например, примерно 10 -150 ).
Обратной стороной является то, что вычисление криптографических контрольных сумм для элементов очень дорогое, особенно при увеличении количества битов. Так что вам действительно нужен эффективный механизм для запоминания контрольных сумм. И это может быть проблематично.
Другой недостаток заключается в том, что ненулевая вероятность ошибки может быть неприемлемой, независимо от того, насколько мала вероятность. (Но если это так ... как поступить со случаем, когда космический луч переворачивает критический бит? Или если он одновременно меняет один и тот же бит в двух экземплярах избыточной системы?)