我正在研究一个优化过程,以将 pdf 文档中的资源与 PDFBox 合并。
经过数小时研究为什么我的代码会生成太多哈希冲突,我惊讶地发现最新版本的 PDFBox 中存在潜在错误。
让我解释一下:在 COSDictionary 中,entrySet 由 SmallMap 支持。 如果你想计算 COSDictionary EntrySet 对象生成的 Map.Entry 的 hashcode,你会发现 hashcode 是这样计算的。
我的代码:
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
并没有按照Map.Entry
的JavaDocs中的定义来实现。同样,
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 中实现为 define,hashCode
通常会因堆栈溢出而失败。因此,防止递归遍历的实现(例如,仅比较条目的键)实际上在这里是有利的。