public interface PrimitiveSinkable
PrimitiveSink
for strong hashing.
An example primitive sink is a Hasher) from Guava's Hashing module, which can be
created e.g., using Hashing.sha256.newHasher() for a SHA256 hash.
In other words, given two strongly hashable objects o1, o2, if they ever share the same
data-structure (e.g., HashTable), then o1.putTo(h1) and o2.putTo(h2)
must put the same data into the hasher if and only if
o1.equals(o2).
Note this is a stronger contract than the one of Object.hashCode(), which only
requires that equal objects yield equal hash codes (but not that unequal objects yield
unequal hash codes).
SortedSet, e.g., a TreeSet. Note that the most commonly used sets
all don't provide an guaranteed order: HashSet iterates in arbitrary, non-stable
order (e.g., item with hash-code collisions are returned in insertion order).
LinkedHashSet or Guava's ImmutableSet always yield objects in insertion order.
Similarly watch out for cases where logical sets are represented as arrays or lists.
class ObjectWithSet implements PrimitiveSinkable {
private Set<PrimitiveSinkable> children = new HashSet<>();
// BAD: don't do this:
public void putTo(PrimitiveSink s) {
// children may come in arbitrary order! So you may end up with a different hash
/ for the same object
for(PrimitiveSinkable child: children) {
child.putTo(sink);
}
}
// better:
public void putTo(PrimitiveSink s) {
// set a sorted version (or use a treeset to begin with?)
private TreeSet<String> sorted = Sets.newTreeSet(children);
for(PrimitiveSinkable child: children) {
// note: this still requires children to be self delimitating; see caveat
/ (2), below.
child.putTo(sink);
}
}
}
The same applies for recursive application of PrimitiveSinkable; make sure the child objects are fixed length or unambiguously delimited, or put explicit delimiters between child objects to avoid cross-talk.
public void putTo(PrimitiveSink s) {
///////// variable length:
String a, b;
// BAD: Don't do this! same hash code will result for a="X", b="YZ"
// and a="XY" and b="Z"
s.putString(a);
s.putString(b);
// OK if a and b are known to to not contain '|'
s.putString(a, Charsets.UTF_8);
s.putChar('|');
s.putString(b, Charsets.UTF_8););
s.putChar('|');
// GOOD!: Encodes the length as a marker value - safe!
PrimitiveSinkUtils.putNullableStringTo(a);
PrimitiveSinkUtils.putNullableStringTo(b);
///////// conditional:
Integer valueA, valueB;
// BAD: May end up with the same hash code depending on which conditional is taken
if(conditionA) {
s.putInteger(valueA);
}
if(conditionB) {
s.putInteger(valueB);
}
// GOOD: Use delimiter to separate values
if(conditionA) {
s.putValue(valueA);
}
s.putChar('|');
if(conditionB) {
s.putInteger(valueB);
}
s.putChar('|');
// GOOD: Conditional decisions are recorded in marker values.
if(conditionA) {
s.putBoolean(true);
s.putInteger(valueA);
} else {
s.putBoolean(false);
}
if(conditionB) {
s.putBoolean(true);
s.putInteger(valueB);
} else {
s.putBoolean(false);
}
// Careful: Make sure your children are fixed length, or delimited:
for(PrimitiveSinkable child: children) {
child.putTo(s);
// may be safer to (if the child serialization does not contain '|').
s.putChar('|')
}
}
Take a look at PrimitiveSinkUtils for functions that help with these caveats.PrimitiveSinkUtils| Modifier and Type | Method and Description |
|---|---|
void |
putTo(com.google.common.hash.PrimitiveSink sink)
Dump the state of this object into a
PrimitiveSink (e.g., a Hasher) for the purpose
of computing a strong hash. |
void putTo(com.google.common.hash.PrimitiveSink sink)
PrimitiveSink (e.g., a Hasher) for the purpose
of computing a strong hash.
Equality contract:
Equal objects must dump the equal data into the sink, non-equal objects must dump different
data into the sink. Please see the interface documentation of PrimitiveSink for more
details and caveats/traps.sink - the sink to dump the object state into.Copyright © 2020. All rights reserved.