Я работаю над этапом оптимизации для объединения ресурсов в PDF-документе с помощью PDFBox.
После многих часов исследования того, почему мой код генерирует слишком много хеш-коллизий, я с удивлением обнаружил потенциальную ошибку в последней версии PDFBox.
Позвольте мне объяснить: в COSDictionary набор записей поддерживается SmallMap. Если вы хотите вычислить хэш-код Map.Entry, сгенерированный объектом входа COSDictionary, вы обнаружите, что хэш-код будет рассчитываться следующим образом.
Мой код:
if (object instanceof COSDictionary) {
int result = 3;
for (Map.Entry<COSName, COSBase> entry : ((COSDictionary) object).entrySet())
result += entry.hashCode();
if (object instanceof COSStream) { .... ..... ....}
В классе SmallMap и внутреннем классе SmallMapEntry (PDFBox):
public int hashCode() {
return this.getKey().hashCode();
}
Но в общем контракте Map.Entry прямо указано (JavaDoc):
/**
* Returns the hash code value for this map entry. The hash code
* of a map entry {@code e} is defined to be: <pre>
* (e.getKey()==null ? 0 : e.getKey().hashCode()) ^
* (e.getValue()==null ? 0 : e.getValue().hashCode())
* </pre>
* This ensures that {@code e1.equals(e2)} implies that
* {@code e1.hashCode()==e2.hashCode()} for any two Entries
* {@code e1} and {@code e2}, as required by the general
* contract of {@code Object.hashCode}.
*
* @return the hash code value for this map entry
* @see Object#hashCode()
* @see Object#equals(Object)
* @see #equals(Object)
*/
int hashCode();
Я думаю, что это ошибка в PDFBox, но я не уверен, что вы об этом думаете?
Действительно, метод SmallMapEntry
hashCode
не реализован так, как определено в JavaDocs Map.Entry
. Аналогично, SmallMapEntry.equals
не реализуется так, как определено для Map.Entry.equals
, SmallMap.hashCode
не так, как определено для Map.hashCode
, и SmallMap.equals
не так, как определено для Map.equals
.
Но для этого есть причина, и это не просто лень или производительность: сами Map
JavaDocs объясняют недостаток определений hashCode
и equals
:
/**
* ...
* <p>Some map operations which perform recursive traversal of the map may fail
* with an exception for self-referential instances where the map directly or
* indirectly contains itself. This includes the {@code clone()},
* {@code equals()}, {@code hashCode()} and {@code toString()} methods.
* ...
Словари в PDF-файле часто косвенно содержат сами себя (например, через записи Kids и Parent), поэтому, если они реализованы так, как определено в JavaDocs, hashCode
часто приводит к сбою из-за переполнения стека. Таким образом, реализация, которая предотвращает рекурсивный обход (например, путем сравнения только ключей записей), здесь действительно выгодна.
Очень понятное объяснение, спасибо. Это также объясняет, почему у меня возникли большие проблемы со Stackoverflow теперь, когда я вычислил более точный хеш-код... Фактически, даже отладчик аварийно завершает работу из-за рекурсии toString, вызванной бесконечным циклом в COSDictionnary, когда отладчик пытается для отображения имени объекта. Нелегко работать. Спасибо, подумаю, как улучшить.
Интерфейс Map.Entry, сюр!