提交 8b7a5855 编写于 作者: B bchristi

8005698: Handle Frequent HashMap Collisions with Balanced Trees

Summary: HashMap bins with many collisions store entries in balanced trees
Reviewed-by: alanb, dl, mduigou
上级 a5d3f3d6
...@@ -180,13 +180,27 @@ public class Hashtable<K,V> ...@@ -180,13 +180,27 @@ public class Hashtable<K,V>
*/ */
static final long HASHSEED_OFFSET; static final long HASHSEED_OFFSET;
static final boolean USE_HASHSEED;
static { static {
try { String hashSeedProp = java.security.AccessController.doPrivileged(
UNSAFE = sun.misc.Unsafe.getUnsafe(); new sun.security.action.GetPropertyAction(
HASHSEED_OFFSET = UNSAFE.objectFieldOffset( "jdk.map.useRandomSeed"));
Hashtable.class.getDeclaredField("hashSeed")); boolean localBool = (null != hashSeedProp)
} catch (NoSuchFieldException | SecurityException e) { ? Boolean.parseBoolean(hashSeedProp) : false;
throw new InternalError("Failed to record hashSeed offset", e); USE_HASHSEED = localBool;
if (USE_HASHSEED) {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
HASHSEED_OFFSET = UNSAFE.objectFieldOffset(
Hashtable.class.getDeclaredField("hashSeed"));
} catch (NoSuchFieldException | SecurityException e) {
throw new InternalError("Failed to record hashSeed offset", e);
}
} else {
UNSAFE = null;
HASHSEED_OFFSET = 0;
} }
} }
} }
...@@ -194,21 +208,24 @@ public class Hashtable<K,V> ...@@ -194,21 +208,24 @@ public class Hashtable<K,V>
/** /**
* A randomizing value associated with this instance that is applied to * A randomizing value associated with this instance that is applied to
* hash code of keys to make hash collisions harder to find. * hash code of keys to make hash collisions harder to find.
*
* Non-final so it can be set lazily, but be sure not to set more than once.
*/ */
transient final int hashSeed = sun.misc.Hashing.randomHashSeed(this); transient final int hashSeed;
private int hash(Object k) { /**
if (k instanceof String) { * Return an initial value for the hashSeed, or 0 if the random seed is not
return ((String)k).hash32(); * enabled.
*/
final int initHashSeed() {
if (sun.misc.VM.isBooted() && Holder.USE_HASHSEED) {
return sun.misc.Hashing.randomHashSeed(this);
} }
return 0;
}
int h = hashSeed ^ k.hashCode(); private int hash(Object k) {
return hashSeed ^ k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
} }
/** /**
...@@ -232,6 +249,7 @@ public class Hashtable<K,V> ...@@ -232,6 +249,7 @@ public class Hashtable<K,V>
this.loadFactor = loadFactor; this.loadFactor = loadFactor;
table = new Entry<?,?>[initialCapacity]; table = new Entry<?,?>[initialCapacity];
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
hashSeed = initHashSeed();
} }
/** /**
...@@ -1187,8 +1205,10 @@ public class Hashtable<K,V> ...@@ -1187,8 +1205,10 @@ public class Hashtable<K,V>
s.defaultReadObject(); s.defaultReadObject();
// set hashMask // set hashMask
Holder.UNSAFE.putIntVolatile(this, Holder.HASHSEED_OFFSET, if (Holder.USE_HASHSEED) {
sun.misc.Hashing.randomHashSeed(this)); Holder.UNSAFE.putIntVolatile(this, Holder.HASHSEED_OFFSET,
sun.misc.Hashing.randomHashSeed(this));
}
// Read the original length of the array and number of elements // Read the original length of the array and number of elements
int origlength = s.readInt(); int origlength = s.readInt();
......
...@@ -55,9 +55,9 @@ import java.io.*; ...@@ -55,9 +55,9 @@ import java.io.*;
* order they were presented.) * order they were presented.)
* *
* <p>A special {@link #LinkedHashMap(int,float,boolean) constructor} is * <p>A special {@link #LinkedHashMap(int,float,boolean) constructor} is
* provided to create a linked hash map whose order of iteration is the order * provided to create a <tt>LinkedHashMap</tt> whose order of iteration is the
* in which its entries were last accessed, from least-recently accessed to * order in which its entries were last accessed, from least-recently accessed
* most-recently (<i>access-order</i>). This kind of map is well-suited to * to most-recently (<i>access-order</i>). This kind of map is well-suited to
* building LRU caches. Invoking the <tt>put</tt> or <tt>get</tt> method * building LRU caches. Invoking the <tt>put</tt> or <tt>get</tt> method
* results in an access to the corresponding entry (assuming it exists after * results in an access to the corresponding entry (assuming it exists after
* the invocation completes). The <tt>putAll</tt> method generates one entry * the invocation completes). The <tt>putAll</tt> method generates one entry
...@@ -242,23 +242,6 @@ public class LinkedHashMap<K,V> ...@@ -242,23 +242,6 @@ public class LinkedHashMap<K,V>
header.before = header.after = header; header.before = header.after = header;
} }
/**
* Transfers all entries to new table array. This method is called
* by superclass resize. It is overridden for performance, as it is
* faster to iterate using our linked list.
*/
@Override
@SuppressWarnings("unchecked")
void transfer(HashMap.Entry[] newTable) {
int newCapacity = newTable.length;
for (Entry<K,V> e = header.after; e != header; e = e.after) {
int index = indexFor(e.hash, newCapacity);
e.next = (HashMap.Entry<K,V>)newTable[index];
newTable[index] = e;
}
}
/** /**
* Returns <tt>true</tt> if this map maps one or more keys to the * Returns <tt>true</tt> if this map maps one or more keys to the
* specified value. * specified value.
...@@ -320,7 +303,7 @@ public class LinkedHashMap<K,V> ...@@ -320,7 +303,7 @@ public class LinkedHashMap<K,V>
// These fields comprise the doubly linked list used for iteration. // These fields comprise the doubly linked list used for iteration.
Entry<K,V> before, after; Entry<K,V> before, after;
Entry(int hash, K key, V value, HashMap.Entry<K,V> next) { Entry(int hash, K key, V value, Object next) {
super(hash, key, value, next); super(hash, key, value, next);
} }
...@@ -344,7 +327,7 @@ public class LinkedHashMap<K,V> ...@@ -344,7 +327,7 @@ public class LinkedHashMap<K,V>
/** /**
* This method is invoked by the superclass whenever the value * This method is invoked by the superclass whenever the value
* of a pre-existing entry is read by Map.get or modified by Map.set. * of a pre-existing entry is read by Map.get or modified by Map.put.
* If the enclosing Map is access-ordered, it moves the entry * If the enclosing Map is access-ordered, it moves the entry
* to the end of the list; otherwise, it does nothing. * to the end of the list; otherwise, it does nothing.
*/ */
...@@ -422,8 +405,9 @@ public class LinkedHashMap<K,V> ...@@ -422,8 +405,9 @@ public class LinkedHashMap<K,V>
* allocated entry to get inserted at the end of the linked list and * allocated entry to get inserted at the end of the linked list and
* removes the eldest entry if appropriate. * removes the eldest entry if appropriate.
*/ */
void addEntry(int hash, K key, V value, int bucketIndex) { @Override
super.addEntry(hash, key, value, bucketIndex); void addEntry(int hash, K key, V value, int bucketIndex, boolean checkIfNeedTree) {
super.addEntry(hash, key, value, bucketIndex, checkIfNeedTree);
// Remove eldest entry if instructed // Remove eldest entry if instructed
Entry<K,V> eldest = header.after; Entry<K,V> eldest = header.after;
...@@ -432,17 +416,14 @@ public class LinkedHashMap<K,V> ...@@ -432,17 +416,14 @@ public class LinkedHashMap<K,V>
} }
} }
/** /*
* This override differs from addEntry in that it doesn't resize the * Create a new LinkedHashMap.Entry and setup the before/after pointers
* table or remove the eldest entry.
*/ */
void createEntry(int hash, K key, V value, int bucketIndex) { @Override
@SuppressWarnings("unchecked") HashMap.Entry<K,V> newEntry(int hash, K key, V value, Object next) {
HashMap.Entry<K,V> old = (HashMap.Entry<K,V>)table[bucketIndex]; Entry<K,V> newEntry = new Entry<>(hash, key, value, next);
Entry<K,V> e = new Entry<>(hash, key, value, old); newEntry.addBefore(header);
table[bucketIndex] = e; return newEntry;
e.addBefore(header);
size++;
} }
/** /**
......
...@@ -187,11 +187,37 @@ public class WeakHashMap<K,V> ...@@ -187,11 +187,37 @@ public class WeakHashMap<K,V>
*/ */
int modCount; int modCount;
private static class Holder {
static final boolean USE_HASHSEED;
static {
String hashSeedProp = java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction(
"jdk.map.useRandomSeed"));
boolean localBool = (null != hashSeedProp)
? Boolean.parseBoolean(hashSeedProp) : false;
USE_HASHSEED = localBool;
}
}
/** /**
* A randomizing value associated with this instance that is applied to * A randomizing value associated with this instance that is applied to
* hash code of keys to make hash collisions harder to find. * hash code of keys to make hash collisions harder to find.
*
* Non-final so it can be set lazily, but be sure not to set more than once.
*/
transient int hashSeed;
/**
* Initialize the hashing mask value.
*/ */
transient final int hashSeed = sun.misc.Hashing.randomHashSeed(this); final void initHashSeed() {
if (sun.misc.VM.isBooted() && Holder.USE_HASHSEED) {
// Do not set hashSeed more than once!
// assert hashSeed == 0;
hashSeed = sun.misc.Hashing.randomHashSeed(this);
}
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Entry<K,V>[] newTable(int n) { private Entry<K,V>[] newTable(int n) {
...@@ -223,6 +249,7 @@ public class WeakHashMap<K,V> ...@@ -223,6 +249,7 @@ public class WeakHashMap<K,V>
table = newTable(capacity); table = newTable(capacity);
this.loadFactor = loadFactor; this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor); threshold = (int)(capacity * loadFactor);
initHashSeed();
} }
/** /**
...@@ -298,10 +325,7 @@ public class WeakHashMap<K,V> ...@@ -298,10 +325,7 @@ public class WeakHashMap<K,V>
* in lower bits. * in lower bits.
*/ */
final int hash(Object k) { final int hash(Object k) {
if (k instanceof String) { int h = hashSeed ^ k.hashCode();
return ((String) k).hash32();
}
int h = hashSeed ^ k.hashCode();
// This function ensures that hashCodes that differ only by // This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded // constant multiples at each bit position have a bounded
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
*/ */
package sun.misc; package sun.misc;
import java.util.Random; import java.util.concurrent.ThreadLocalRandom;
/** /**
* Hashing utilities. * Hashing utilities.
...@@ -207,28 +207,16 @@ public class Hashing { ...@@ -207,28 +207,16 @@ public class Hashing {
} }
/** /**
* Holds references to things that can't be initialized until after VM * Return a non-zero 32-bit pseudo random value. The {@code instance} object
* is fully booted. * may be used as part of the value.
*
* @param instance an object to use if desired in choosing value.
* @return a non-zero 32-bit pseudo random value.
*/ */
private static class Holder {
/**
* Used for generating per-instance hash seeds.
*
* We try to improve upon the default seeding.
*/
static final Random SEED_MAKER = new Random(
Double.doubleToRawLongBits(Math.random())
^ System.identityHashCode(Hashing.class)
^ System.currentTimeMillis()
^ System.nanoTime()
^ Runtime.getRuntime().freeMemory());
}
public static int randomHashSeed(Object instance) { public static int randomHashSeed(Object instance) {
int seed; int seed;
if (sun.misc.VM.isBooted()) { if (sun.misc.VM.isBooted()) {
seed = Holder.SEED_MAKER.nextInt(); seed = ThreadLocalRandom.current().nextInt();
} else { } else {
// lower quality "random" seed value--still better than zero and not // lower quality "random" seed value--still better than zero and not
// not practically reversible. // not practically reversible.
......
/*
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 8005698
* @summary Check operation of jdk.map.useRandomSeed property
* @run main CheckRandomHashSeed
* @run main/othervm -Djdk.map.useRandomSeed=false CheckRandomHashSeed
* @run main/othervm -Djdk.map.useRandomSeed=bogus CheckRandomHashSeed
* @run main/othervm -Djdk.map.useRandomSeed=true CheckRandomHashSeed true
* @author Brent Christian
*/
import java.lang.reflect.Field;
import java.util.Map;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Hashtable;
import java.util.WeakHashMap;
public class CheckRandomHashSeed {
private final static String PROP_NAME = "jdk.map.useRandomSeed";
static boolean expectRandom = false;
public static void main(String[] args) {
if (args.length > 0 && args[0].equals("true")) {
expectRandom = true;
}
String hashSeedProp = System.getProperty(PROP_NAME);
boolean propSet = (null != hashSeedProp)
? Boolean.parseBoolean(hashSeedProp) : false;
if (expectRandom != propSet) {
throw new Error("Error in test setup: " + (expectRandom ? "" : "not " ) + "expecting random hashSeed, but " + PROP_NAME + " is " + (propSet ? "" : "not ") + "enabled");
}
testMap(new HashMap());
testMap(new LinkedHashMap());
testMap(new WeakHashMap());
testMap(new Hashtable());
}
private static void testMap(Map map) {
int hashSeed = getHashSeed(map);
boolean hashSeedIsZero = (hashSeed == 0);
if (expectRandom != hashSeedIsZero) {
System.out.println("Test passed for " + map.getClass().getSimpleName() + " - expectRandom: " + expectRandom + ", hashSeed: " + hashSeed);
} else {
throw new Error ("Test FAILED for " + map.getClass().getSimpleName() + " - expectRandom: " + expectRandom + ", hashSeed: " + hashSeed);
}
}
private static int getHashSeed(Map map) {
try {
if (map instanceof HashMap || map instanceof LinkedHashMap) {
map.put("Key", "Value");
Field hashSeedField = HashMap.class.getDeclaredField("hashSeed");
hashSeedField.setAccessible(true);
int hashSeed = hashSeedField.getInt(map);
return hashSeed;
} else {
map.put("Key", "Value");
Field hashSeedField = map.getClass().getDeclaredField("hashSeed");
hashSeedField.setAccessible(true);
int hashSeed = hashSeedField.getInt(map);
return hashSeed;
}
} catch(Exception e) {
e.printStackTrace();
throw new Error(e);
}
}
}
...@@ -26,6 +26,7 @@ ...@@ -26,6 +26,7 @@
* @bug 7126277 * @bug 7126277
* @run main Collisions -shortrun * @run main Collisions -shortrun
* @run main/othervm -Djdk.map.althashing.threshold=0 Collisions -shortrun * @run main/othervm -Djdk.map.althashing.threshold=0 Collisions -shortrun
* @run main/othervm -Djdk.map.useRandomSeed=true Collisions -shortrun
* @summary Ensure Maps behave well with lots of hashCode() collisions. * @summary Ensure Maps behave well with lots of hashCode() collisions.
* @author Mike Duigou * @author Mike Duigou
*/ */
......
此差异已折叠。
/*
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.util.*;
import java.lang.reflect.Field;
/*
* @test
* @bug 8005698
* @summary Test the case where TreeBin.splitTreeBin() converts a bin back to an Entry list
* @run main TreeBinSplitBackToEntries unused
* @author Brent Christian
*/
public class TreeBinSplitBackToEntries {
private static int EXPECTED_TREE_THRESHOLD = 16;
// Easiest if this covers one bit higher then 'bit' in splitTreeBin() on the
// call where the TreeBin is converted back to an Entry list
private static int HASHMASK = 0x7F;
private static boolean verbose = false;
private static boolean fastFail = false;
private static boolean failed = false;
static void printlnIfVerbose(String msg) {
if (verbose) {System.out.println(msg); }
}
public static void main(String[] args) {
for (String arg : args) {
switch(arg) {
case "-verbose":
verbose = true;
break;
case "-fastfail":
fastFail = true;
break;
}
}
checkTreeThreshold();
testMapHiTree();
testMapLoTree();
if (failed) {
System.out.println("Test Failed");
System.exit(1);
} else {
System.out.println("Test Passed");
}
}
public static void checkTreeThreshold() {
int threshold = -1;
try {
Class treeBinClass = Class.forName("java.util.HashMap$TreeBin");
Field treeThreshold = treeBinClass.getDeclaredField("TREE_THRESHOLD");
treeThreshold.setAccessible(true);
threshold = treeThreshold.getInt(treeBinClass);
} catch (ClassNotFoundException|NoSuchFieldException|IllegalAccessException e) {
e.printStackTrace();
throw new Error("Problem accessing TreeBin.TREE_THRESHOLD", e);
}
check("Expected TREE_THRESHOLD: " + EXPECTED_TREE_THRESHOLD +", found: " + threshold,
threshold == EXPECTED_TREE_THRESHOLD);
printlnIfVerbose("TREE_THRESHOLD: " + threshold);
}
public static void testMapHiTree() {
Object[][] mapKeys = makeHiTreeTestData();
testMapsForKeys(mapKeys, "hiTree");
}
public static void testMapLoTree() {
Object[][] mapKeys = makeLoTreeTestData();
testMapsForKeys(mapKeys, "loTree");
}
public static void testMapsForKeys(Object[][] mapKeys, String desc) {
// loop through data sets
for (Object[] keys_desc : mapKeys) {
Map<Object, Object>[] maps = (Map<Object, Object>[]) new Map[]{
new HashMap<>(4, 0.8f),
new LinkedHashMap<>(4, 0.8f),
};
// for each map type.
for (Map<Object, Object> map : maps) {
Object[] keys = (Object[]) keys_desc[1];
System.out.println(desc + ": testPutThenGet() for " + map.getClass());
testPutThenGet(map, keys);
}
}
}
private static <T> void testPutThenGet(Map<T, T> map, T[] keys) {
for (T key : keys) {
printlnIfVerbose("put()ing 0x" + Integer.toHexString(Integer.parseInt(key.toString())) + ", hashCode=" + Integer.toHexString(key.hashCode()));
map.put(key, key);
}
for (T key : keys) {
check("key: 0x" + Integer.toHexString(Integer.parseInt(key.toString())) + " not found in resulting " + map.getClass().getSimpleName(), map.get(key) != null);
}
}
/* Data to force a non-empty loTree in TreeBin.splitTreeBin() to be converted back
* into an Entry list
*/
private static Object[][] makeLoTreeTestData() {
HashableInteger COLLIDING_OBJECTS[] = new HashableInteger[] {
new HashableInteger( 0x23, HASHMASK),
new HashableInteger( 0x123, HASHMASK),
new HashableInteger( 0x323, HASHMASK),
new HashableInteger( 0x523, HASHMASK),
new HashableInteger( 0x723, HASHMASK),
new HashableInteger( 0x923, HASHMASK),
new HashableInteger( 0xB23, HASHMASK),
new HashableInteger( 0xD23, HASHMASK),
new HashableInteger( 0xF23, HASHMASK),
new HashableInteger( 0xF123, HASHMASK),
new HashableInteger( 0x1023, HASHMASK),
new HashableInteger( 0x1123, HASHMASK),
new HashableInteger( 0x1323, HASHMASK),
new HashableInteger( 0x1523, HASHMASK),
new HashableInteger( 0x1723, HASHMASK),
new HashableInteger( 0x1923, HASHMASK),
new HashableInteger( 0x1B23, HASHMASK),
new HashableInteger( 0x1D23, HASHMASK),
new HashableInteger( 0x3123, HASHMASK),
new HashableInteger( 0x3323, HASHMASK),
new HashableInteger( 0x3523, HASHMASK),
new HashableInteger( 0x3723, HASHMASK),
new HashableInteger( 0x1001, HASHMASK),
new HashableInteger( 0x4001, HASHMASK),
new HashableInteger( 0x1, HASHMASK),
};
return new Object[][] {
new Object[]{"Colliding Objects", COLLIDING_OBJECTS},
};
}
/* Data to force the hiTree in TreeBin.splitTreeBin() to be converted back
* into an Entry list
*/
private static Object[][] makeHiTreeTestData() {
HashableInteger COLLIDING_OBJECTS[] = new HashableInteger[] {
new HashableInteger( 0x1, HASHMASK),
new HashableInteger( 0x101, HASHMASK),
new HashableInteger( 0x301, HASHMASK),
new HashableInteger( 0x501, HASHMASK),
new HashableInteger( 0x701, HASHMASK),
new HashableInteger( 0x1001, HASHMASK),
new HashableInteger( 0x1101, HASHMASK),
new HashableInteger( 0x1301, HASHMASK),
new HashableInteger( 0x1501, HASHMASK),
new HashableInteger( 0x1701, HASHMASK),
new HashableInteger( 0x4001, HASHMASK),
new HashableInteger( 0x4101, HASHMASK),
new HashableInteger( 0x4301, HASHMASK),
new HashableInteger( 0x4501, HASHMASK),
new HashableInteger( 0x4701, HASHMASK),
new HashableInteger( 0x8001, HASHMASK),
new HashableInteger( 0x8101, HASHMASK),
new HashableInteger( 0x8301, HASHMASK),
new HashableInteger( 0x8501, HASHMASK),
new HashableInteger( 0x8701, HASHMASK),
new HashableInteger( 0x9001, HASHMASK),
new HashableInteger( 0x23, HASHMASK),
new HashableInteger( 0x123, HASHMASK),
new HashableInteger( 0x323, HASHMASK),
new HashableInteger( 0x523, HASHMASK),
};
return new Object[][] {
new Object[]{"Colliding Objects", COLLIDING_OBJECTS},
};
}
static void check(String desc, boolean cond) {
if (!cond) {
fail(desc);
}
}
static void fail(String msg) {
failed = true;
(new Error("Failure: " + msg)).printStackTrace(System.err);
if (fastFail) {
System.exit(1);
}
}
final static class HashableInteger implements Comparable<HashableInteger> {
final int value;
final int hashmask; //yes duplication
HashableInteger(int value, int hashmask) {
this.value = value;
this.hashmask = hashmask;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof HashableInteger) {
HashableInteger other = (HashableInteger) obj;
return other.value == value;
}
return false;
}
@Override
public int hashCode() {
// This version ANDs the mask
return value & hashmask;
}
@Override
public int compareTo(HashableInteger o) {
return value - o.value;
}
@Override
public String toString() {
return Integer.toString(value);
}
}
}
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册