Understanding Guava's Hashing Utilities: HashFunction, Hasher, and HashCode Explained
Guava's hashing framework revolves around three core types—HashFunction as an immutable algorithm factory, Hasher as a mutable data sink, and HashCode as the immutable result—enabling both streaming and one-shot hashing operations.
The google/guava library provides a robust hashing API that abstracts cryptographic and non-cryptographic algorithms through a fluent, type-safe interface. Understanding Guava's Hashing utilities allows developers to compute digests efficiently without managing the low-level complexity of Java's MessageDigest directly.
The Three Core Abstractions of Guava Hashing
HashFunction: The Immutable Algorithm Factory
A HashFunction represents a specific hashing algorithm and acts as a thread-safe factory for creating Hasher instances. Defined in com/google/common/hash/HashFunction.java, this interface declares key methods including newHasher(), hashBytes(byte[]), hashString(CharSequence, Charset), and bits() which returns the bit length of the output. Concrete implementations such as Murmur3_128HashFunction and MessageDigestHashFunction encapsulate the actual algorithm logic while exposing a uniform contract.
Hasher: The Mutable Data Sink
The Hasher interface, located in com/google/common/hash/Hasher.java, extends PrimitiveSink and serves as a mutable accumulator that consumes data incrementally. Obtained via HashFunction.newHasher(), it provides methods like putInt(int), putLong(long), putString(CharSequence, Charset), and putBytes(byte[]) to stream data into the hash computation. Once all input is fed, calling hash() returns an immutable HashCode and effectively finalizes the operation.
HashCode: The Immutable Result
HashCode, defined in com/google/common/hash/HashCode.java, represents the finalized hash value as an immutable object. It offers conversion methods including asInt(), asLong(), asBytes(), and toString() for hexadecimal representation. Because it implements proper equals() and hashCode() contracts, it can safely serve as a key in HashMap instances or be persisted as a string for later comparison.
Working with Guava's Hashing API
The typical workflow follows a clear sequence: select an algorithm, acquire a hasher, feed data, and extract the result.
- Select a hash algorithm using static factory methods from
com/google/common/hash/Hashing.java:
HashFunction hf = Hashing.sha256(); // Cryptographic
// or
HashFunction hf = Hashing.murmur3_128(); // Fast, non-cryptographic
- Create a hasher from the function:
Hasher hasher = hf.newHasher();
- Feed data using the
PrimitiveSinkmethods:
hasher.putInt(42)
.putString("hello", StandardCharsets.UTF_8)
.putLong(123L);
- Obtain the result:
HashCode code = hasher.hash();
- Consume the hash in your preferred format:
int asInt = code.asInt();
long asLong = code.asLong();
byte[] bytes = code.asBytes();
String hex = code.toString(); // e.g., 64-character hex for SHA-256
For single-shot operations, skip the explicit Hasher creation using convenience methods:
HashCode code = Hashing.sha256()
.hashString("payload", StandardCharsets.UTF_8);
Practical Code Examples
The following examples demonstrate common patterns using the google/guava hashing utilities:
// Example 1 – simple SHA-256 hash of a string
HashCode sha256 = Hashing.sha256()
.hashString("Guava is awesome!", StandardCharsets.UTF_8);
System.out.println("SHA-256 (hex): " + sha256.toString());
// Example 2 – incremental hashing of mixed data
Hasher hasher = Hashing.murmur3_128().newHasher();
hasher.putInt(100)
.putLong(0xdeadbeefL)
.putString("foo", StandardCharsets.US_ASCII);
HashCode result = hasher.hash();
System.out.println("Murmur3-128 as long: " + result.asLong());
// Example 3 – using the hash as a cache key
Map<HashCode, String> cache = new HashMap<>();
HashCode key = Hashing.fingerprint2011().hashString("my-resource", StandardCharsets.UTF_8);
cache.put(key, "cached value");
System.out.println("Lookup: " + cache.get(key));
Available Hash Algorithms and Implementations
Guava distinguishes between cryptographic and non-cryptographic algorithms through distinct implementations in the com/google/common/hash package.
Non-Cryptographic (Fast) Hashes:
Murmur3_32HashFunction: 32-bit variant suitable for hash tablesMurmur3_128HashFunction: 128-bit variant for better distributionSipHashFunction: Compact, fast, keyed hash function for hash-flood resistance
Cryptographic Hashes:
MessageDigestHashFunction: Wrapper around Java'sMessageDigestsupporting MD5, SHA-1, and SHA-256
Use Hashing.goodFastHash(int) only for temporary, in-memory hashing where algorithm stability is not required. For persistent or cross-process hashing, prefer explicit algorithms like murmur3_128() or sha256().
Design Rationale and Performance Considerations
The separation of HashFunction and Hasher provides specific architectural benefits. HashFunction instances are immutable and thread-safe, allowing them to be stored as static constants and shared across threads. In contrast, Hasher maintains mutable state during the hashing process and should not be shared between threads.
When hashing large streams or complex objects, prefer the streaming Hasher API to avoid allocating intermediate byte arrays. This approach minimizes memory pressure by processing data incrementally rather than buffering entire inputs.
Summary
HashFunctionacts as an immutable, thread-safe algorithm factory defined inHashFunction.javaHasherprovides a mutable streaming interface for accumulating data, extendingPrimitiveSinkinHasher.javaHashCodedelivers immutable results with multiple output formats viaasInt(),asLong(),asBytes(), andtoString()- Static factory methods in
Hashing.javaprovide access to implementations likeMurmur3_128HashFunctionandMessageDigestHashFunction - Use cryptographic hashes (SHA-256) for security and non-cryptographic hashes (Murmur3) for performance
- One-shot hashing via
hashString()orhashBytes()offers convenience, while theHasherbuilder pattern optimizes large data processing
Frequently Asked Questions
What is the difference between HashFunction and Hasher in Guava?
HashFunction represents the algorithm itself and is immutable and thread-safe, responsible only for creating new Hasher instances via newHasher(). Hasher is the mutable accumulator that actually processes data through methods like putInt() and putString(), maintaining internal state until hash() is called to produce the final HashCode.
Is Guava's HashCode thread-safe?
Yes, HashCode is immutable and thread-safe once created. It can be safely shared between threads, used as a key in concurrent hash maps, or cached without synchronization concerns.
When should I use Murmur3 versus SHA-256 in Guava?
Use Hashing.murmur3_128() for high-performance, non-cryptographic hashing such as hash tables, caches, or checksums where speed is critical and security is not required. Use Hashing.sha256() or other MessageDigest-based functions when you need cryptographic security properties like collision resistance against malicious actors.
How do I hash large files or streams with Guava?
Create a Hasher via HashFunction.newHasher() and feed the stream incrementally using putBytes() in a loop rather than loading the entire file into memory. This streaming approach prevents OutOfMemoryError on large inputs while computing the hash efficiently.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →