/* * 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. * */ package com.github.ggrandes.kvstore.structures.hash; import java.util.Arrays; import java.util.Random; import com.github.ggrandes.kvstore.utils.GenericFactory; import com.github.ggrandes.kvstore.utils.PrimeFinder; import org.apache.log4j.Logger; /** * Native Integer HashMap with Fixed Size (no collision resolver); * on collision last key/value overwrite old key/value * Suitable only for caches * * This class is NOT Thread-Safe * * @author Guillermo Grandes / guillermo.grandes[at]gmail.com */ public class FixedIntHashMap<T> { private static final Logger log = Logger.getLogger(FixedIntHashMap.class); private int elementCount; //不再使用Entry. 而是使用2个数组. private int[] elementKeys; private T[] elementValues; private final float loadFactor; private final GenericFactory<T> arrayFactory; private int threshold; private int defaultSize = 16; private int collisions = 0; private int[] newKeyArray(final int size) { if (log.isDebugEnabled()) log.debug(this.getClass().getName() + "::newKeyArray("+size+")"); final int[] e = new int[size]; Arrays.fill(e, Integer.MIN_VALUE); return e; } private T[] newElementArray(final int size) { if (log.isDebugEnabled()) log.debug(this.getClass().getName() + "::newElementArray("+size+")"); final T[] e = arrayFactory.newArray(size); Arrays.fill(e, null); return e; } /** * Constructs a new {@code NativeFixedIntHashMap} instance with the specified capacity. * * @param capacity the initial capacity of this hash map. * @throws IllegalArgumentException when the capacity is less than zero. */ public FixedIntHashMap(final int capacity, final Class<T> ctype) { arrayFactory = new GenericFactory<T>(ctype); defaultSize = primeSize(capacity); if (capacity > 0) { elementCount = 0; elementKeys = newKeyArray(defaultSize); elementValues = newElementArray(defaultSize); loadFactor = 0.75f; // Default load factor of 0.75 computeMaxSize(); } else { throw new IllegalArgumentException(); } } private static final int primeSize(final int capacity) { //return java.math.BigInteger.valueOf((long)capacity).nextProbablePrime().intValue(); return PrimeFinder.nextPrime(capacity); } /** * Removes all mappings from this hash map, leaving it empty. * * @see #isEmpty * @see #size */ public void clear(boolean shrink) { if (elementCount > 0) { elementCount = 0; } if (shrink && ((elementKeys.length > 1024) && (elementKeys.length > defaultSize))) { elementKeys = newKeyArray(defaultSize); elementValues = newElementArray(defaultSize); } else { Arrays.fill(elementKeys, Integer.MIN_VALUE); Arrays.fill(elementValues, null); } computeMaxSize(); } public int[] getKeys() { return elementKeys; } public T[] getValues() { return elementValues; } /** * Returns a shallow copy of this map. * * @return a shallow copy of this map. */ private void computeMaxSize() { threshold = (int) (elementKeys.length * loadFactor); if (log.isDebugEnabled()) log.debug(this.getClass().getName() + "::computeMaxSize()="+threshold + " collisions=" + collisions + " (" + (collisions * 100 / elementKeys.length) + ")"); collisions = 0; } /** * Returns the value of the mapping with the specified key. * * @param key the key. * @return the value of the mapping with the specified key, or {@code -1} * if no mapping for the specified key is found. */ public T get(final int key) { int index = ((key & 0x7FFFFFFF) % elementKeys.length); long m = elementKeys[index]; if (key == m) return elementValues[index]; return null; } /** * Returns whether this map is empty. * * @return {@code true} if this map has no elements, {@code false} * otherwise. * @see #size() */ public boolean isEmpty() { return (elementCount == 0); } /** * Maps the specified key to the specified value. * * @param key the key. * @param value the value. * @return the value of any previous mapping with the specified key or * {@code -1} if there was no such mapping. */ public T put(final int key, final T value) { int index = ((key & 0x7FFFFFFF) % elementKeys.length); T oldvalue = null; long entry = elementKeys[index]; if (entry == Integer.MIN_VALUE) { ++elementCount; } else { oldvalue = elementValues[index]; collisions++; } elementKeys[index] = key; elementValues[index] = value; return oldvalue; } /** * Removes the mapping with the specified key from this map. * * @param key the key of the mapping to remove. * @return the value of the removed mapping or {@code null} if no mapping * for the specified key was found. */ public T remove(final int key) { int index = ((key & 0x7FFFFFFF) % elementKeys.length); int entry = elementKeys[index]; if (key == entry) { final T oldvalue = elementValues[index]; elementKeys[index] = Integer.MIN_VALUE; elementValues[index] = null; elementCount--; return oldvalue; } return null; } /** * Returns the number of elements in this map. * * @return the number of elements in this map. */ public int size() { return elementCount; } public static void main(String[] args) throws Exception { FixedIntHashMap<Long> f = new FixedIntHashMap<Long>(1000000, Long.class); long ts, ts2; Random r = new Random(); ts = System.currentTimeMillis(); ts2 = ts; for (int i = 1; i < 1e3; i++) { f.put(i, (long)(i)); } for (int i = 1; i < 1e3; i++) { System.out.println(f.get(i)); } for (int i = 1; i < 1; i++) { f.put((int)(r.nextLong() & 0x7FFFFFFFL), r.nextLong() & 0x7FFFFFFFFFFFFFFFL); if (i % 10000 == 0) { System.out.println(i + "\t" + (System.currentTimeMillis() - ts2)); ts2 = System.currentTimeMillis(); } } System.out.println("INSERT: " + (System.currentTimeMillis() - ts)); } }