blob: a365ad86da4071ecd892efa27bf66af287caf8af [file] [log] [blame]
/*
* Copyright (C) 2011 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
/*
* MurmurHash3 was written by Austin Appleby, and is placed in the public
* domain. The author hereby disclaims copyright to this source code.
*/
/*
* Source:
* https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp
* (Modified to adapt to Guava coding conventions and to use the HashFunction interface)
*/
package com.google.common.hash;
import static com.google.common.primitives.UnsignedBytes.toInt;
import com.google.errorprone.annotations.Immutable;
import java.io.Serializable;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* See MurmurHash3_x64_128 in <a href="http://smhasher.googlecode.com/svn/trunk/MurmurHash3.cpp">the
* C++ implementation</a>.
*
* @author Austin Appleby
* @author Dimitris Andreou
*/
@Immutable
final class Murmur3_128HashFunction extends AbstractHashFunction implements Serializable {
static final HashFunction MURMUR3_128 = new Murmur3_128HashFunction(0);
static final HashFunction GOOD_FAST_HASH_128 =
new Murmur3_128HashFunction(Hashing.GOOD_FAST_HASH_SEED);
// TODO(user): when the shortcuts are implemented, update BloomFilterStrategies
private final int seed;
Murmur3_128HashFunction(int seed) {
this.seed = seed;
}
@Override
public int bits() {
return 128;
}
@Override
public Hasher newHasher() {
return new Murmur3_128Hasher(seed);
}
@Override
public String toString() {
return "Hashing.murmur3_128(" + seed + ")";
}
@Override
public boolean equals(@Nullable Object object) {
if (object instanceof Murmur3_128HashFunction) {
Murmur3_128HashFunction other = (Murmur3_128HashFunction) object;
return seed == other.seed;
}
return false;
}
@Override
public int hashCode() {
return getClass().hashCode() ^ seed;
}
private static final class Murmur3_128Hasher extends AbstractStreamingHasher {
private static final int CHUNK_SIZE = 16;
private static final long C1 = 0x87c37b91114253d5L;
private static final long C2 = 0x4cf5ad432745937fL;
private long h1;
private long h2;
private int length;
Murmur3_128Hasher(int seed) {
super(CHUNK_SIZE);
this.h1 = seed;
this.h2 = seed;
this.length = 0;
}
@Override
protected void process(ByteBuffer bb) {
long k1 = bb.getLong();
long k2 = bb.getLong();
bmix64(k1, k2);
length += CHUNK_SIZE;
}
private void bmix64(long k1, long k2) {
h1 ^= mixK1(k1);
h1 = Long.rotateLeft(h1, 27);
h1 += h2;
h1 = h1 * 5 + 0x52dce729;
h2 ^= mixK2(k2);
h2 = Long.rotateLeft(h2, 31);
h2 += h1;
h2 = h2 * 5 + 0x38495ab5;
}
@Override
protected void processRemaining(ByteBuffer bb) {
long k1 = 0;
long k2 = 0;
length += bb.remaining();
switch (bb.remaining()) {
case 15:
k2 ^= (long) toInt(bb.get(14)) << 48; // fall through
case 14:
k2 ^= (long) toInt(bb.get(13)) << 40; // fall through
case 13:
k2 ^= (long) toInt(bb.get(12)) << 32; // fall through
case 12:
k2 ^= (long) toInt(bb.get(11)) << 24; // fall through
case 11:
k2 ^= (long) toInt(bb.get(10)) << 16; // fall through
case 10:
k2 ^= (long) toInt(bb.get(9)) << 8; // fall through
case 9:
k2 ^= (long) toInt(bb.get(8)); // fall through
case 8:
k1 ^= bb.getLong();
break;
case 7:
k1 ^= (long) toInt(bb.get(6)) << 48; // fall through
case 6:
k1 ^= (long) toInt(bb.get(5)) << 40; // fall through
case 5:
k1 ^= (long) toInt(bb.get(4)) << 32; // fall through
case 4:
k1 ^= (long) toInt(bb.get(3)) << 24; // fall through
case 3:
k1 ^= (long) toInt(bb.get(2)) << 16; // fall through
case 2:
k1 ^= (long) toInt(bb.get(1)) << 8; // fall through
case 1:
k1 ^= (long) toInt(bb.get(0));
break;
default:
throw new AssertionError("Should never get here.");
}
h1 ^= mixK1(k1);
h2 ^= mixK2(k2);
}
@Override
public HashCode makeHash() {
h1 ^= length;
h2 ^= length;
h1 += h2;
h2 += h1;
h1 = fmix64(h1);
h2 = fmix64(h2);
h1 += h2;
h2 += h1;
return HashCode.fromBytesNoCopy(
ByteBuffer.wrap(new byte[CHUNK_SIZE])
.order(ByteOrder.LITTLE_ENDIAN)
.putLong(h1)
.putLong(h2)
.array());
}
private static long fmix64(long k) {
k ^= k >>> 33;
k *= 0xff51afd7ed558ccdL;
k ^= k >>> 33;
k *= 0xc4ceb9fe1a85ec53L;
k ^= k >>> 33;
return k;
}
private static long mixK1(long k1) {
k1 *= C1;
k1 = Long.rotateLeft(k1, 31);
k1 *= C2;
return k1;
}
private static long mixK2(long k2) {
k2 *= C2;
k2 = Long.rotateLeft(k2, 33);
k2 *= C1;
return k2;
}
}
private static final long serialVersionUID = 0L;
}