433 lines
11 KiB
Java
433 lines
11 KiB
Java
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership.
|
|
* The ASF licenses this file to You 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 org.apache.jdbm;
|
|
|
|
import java.io.Serializable;
|
|
import java.util.Arrays;
|
|
import java.util.Iterator;
|
|
import java.util.NoSuchElementException;
|
|
|
|
/**
|
|
* Hash Map which uses primitive long as key.
|
|
* Main advantage is new instanceof of Long does not have to be created for each lookup.
|
|
* <p/>
|
|
* This code comes from Android, which in turns comes from Apache Harmony.
|
|
* This class was modified to use primitive longs and stripped down to consume less space.
|
|
* <p/>
|
|
* Author of JDBM modifications: Jan Kotek
|
|
*/
|
|
class LongHashMap<V> implements Serializable {
|
|
private static final long serialVersionUID = 362499999763181265L;
|
|
|
|
private int elementCount;
|
|
|
|
private Entry<V>[] elementData;
|
|
|
|
private final float loadFactor;
|
|
|
|
private int threshold;
|
|
|
|
private int defaultSize = 16;
|
|
|
|
private transient Entry<V> reuseAfterDelete = null;
|
|
|
|
static final class Entry<V> implements Serializable{
|
|
private static final long serialVersionUID = 362445231113181265L;
|
|
|
|
Entry<V> next;
|
|
|
|
V value;
|
|
|
|
long key;
|
|
|
|
Entry(long theKey) {
|
|
this.key = theKey;
|
|
this.value = null;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
static class HashMapIterator<V> implements Iterator<V> {
|
|
private int position = 0;
|
|
|
|
|
|
boolean canRemove = false;
|
|
|
|
Entry<V> entry;
|
|
|
|
Entry<V> lastEntry;
|
|
|
|
final LongHashMap<V> associatedMap;
|
|
|
|
HashMapIterator(LongHashMap<V> hm) {
|
|
associatedMap = hm;
|
|
}
|
|
|
|
public boolean hasNext() {
|
|
if (entry != null) {
|
|
return true;
|
|
}
|
|
|
|
Entry<V>[] elementData = associatedMap.elementData;
|
|
int length = elementData.length;
|
|
int newPosition = position;
|
|
boolean result = false;
|
|
|
|
while (newPosition < length) {
|
|
if (elementData[newPosition] == null) {
|
|
newPosition++;
|
|
} else {
|
|
result = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
position = newPosition;
|
|
return result;
|
|
}
|
|
|
|
public V next() {
|
|
|
|
if (!hasNext()) {
|
|
throw new NoSuchElementException();
|
|
}
|
|
|
|
Entry<V> result;
|
|
Entry<V> _entry = entry;
|
|
if (_entry == null) {
|
|
result = lastEntry = associatedMap.elementData[position++];
|
|
entry = lastEntry.next;
|
|
} else {
|
|
if (lastEntry.next != _entry) {
|
|
lastEntry = lastEntry.next;
|
|
}
|
|
result = _entry;
|
|
entry = _entry.next;
|
|
}
|
|
canRemove = true;
|
|
return result.value;
|
|
}
|
|
|
|
public void remove() {
|
|
if (!canRemove) {
|
|
throw new IllegalStateException();
|
|
}
|
|
|
|
canRemove = false;
|
|
|
|
if (lastEntry.next == entry) {
|
|
while (associatedMap.elementData[--position] == null) {
|
|
// Do nothing
|
|
}
|
|
associatedMap.elementData[position] = associatedMap.elementData[position].next;
|
|
entry = null;
|
|
} else {
|
|
lastEntry.next = entry;
|
|
}
|
|
if (lastEntry != null) {
|
|
Entry<V> reuse = lastEntry;
|
|
lastEntry = null;
|
|
reuse.key = Long.MIN_VALUE;
|
|
reuse.value = null;
|
|
associatedMap.reuseAfterDelete = reuse;
|
|
}
|
|
|
|
associatedMap.elementCount--;
|
|
}
|
|
}
|
|
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private Entry<V>[] newElementArray(int s) {
|
|
return new Entry[s];
|
|
}
|
|
|
|
/**
|
|
* Constructs a new empty {@code HashMap} instance.
|
|
*
|
|
* @since Android 1.0
|
|
*/
|
|
public LongHashMap() {
|
|
this(16);
|
|
}
|
|
|
|
/**
|
|
* Constructs a new {@code HashMap} instance with the specified capacity.
|
|
*
|
|
* @param capacity the initial capacity of this hash map.
|
|
* @throws IllegalArgumentException when the capacity is less than zero.
|
|
* @since Android 1.0
|
|
*/
|
|
public LongHashMap(int capacity) {
|
|
defaultSize = capacity;
|
|
if (capacity >= 0) {
|
|
elementCount = 0;
|
|
elementData = newElementArray(capacity == 0 ? 1 : capacity);
|
|
loadFactor = 0.75f; // Default load factor of 0.75
|
|
computeMaxSize();
|
|
} else {
|
|
throw new IllegalArgumentException();
|
|
}
|
|
}
|
|
|
|
|
|
// BEGIN android-changed
|
|
|
|
/**
|
|
* Removes all mappings from this hash map, leaving it empty.
|
|
*
|
|
* @see #isEmpty
|
|
* @see #size
|
|
* @since Android 1.0
|
|
*/
|
|
|
|
public void clear() {
|
|
if (elementCount > 0) {
|
|
elementCount = 0;
|
|
}
|
|
if(elementData.length>1024 && elementData.length>defaultSize)
|
|
elementData = new Entry[defaultSize];
|
|
else
|
|
Arrays.fill(elementData, null);
|
|
computeMaxSize();
|
|
}
|
|
// END android-changed
|
|
|
|
/**
|
|
* Returns a shallow copy of this map.
|
|
*
|
|
* @return a shallow copy of this map.
|
|
* @since Android 1.0
|
|
*/
|
|
|
|
|
|
private void computeMaxSize() {
|
|
threshold = (int) (elementData.length * loadFactor);
|
|
}
|
|
|
|
|
|
/**
|
|
* 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 null}
|
|
* if no mapping for the specified key is found.
|
|
* @since Android 1.0
|
|
*/
|
|
|
|
public V get(final long key) {
|
|
|
|
final int hash = powerHash(key);
|
|
final int index = (hash & 0x7FFFFFFF) % elementData.length;
|
|
|
|
//find non null entry
|
|
Entry<V> m = elementData[index];
|
|
while (m != null) {
|
|
if (key == m.key)
|
|
return m.value;
|
|
m = m.next;
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns whether this map is empty.
|
|
*
|
|
* @return {@code true} if this map has no elements, {@code false}
|
|
* otherwise.
|
|
* @see #size()
|
|
* @since Android 1.0
|
|
*/
|
|
|
|
public boolean isEmpty() {
|
|
return elementCount == 0;
|
|
}
|
|
|
|
/**
|
|
* @return iterator over keys
|
|
*/
|
|
|
|
// public Iterator<K> keyIterator(){
|
|
// return new HashMapIterator<K, K, V>(
|
|
// new MapEntry.Type<K, K, V>() {
|
|
// public K get(Entry<K, V> entry) {
|
|
// return entry.key;
|
|
// }
|
|
// }, HashMap.this);
|
|
//
|
|
// }
|
|
|
|
/**
|
|
* 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 null} if there was no such mapping.
|
|
* @since Android 1.0
|
|
*/
|
|
|
|
|
|
public V put(final long key, final V value) {
|
|
|
|
int hash = powerHash(key);
|
|
int index = (hash & 0x7FFFFFFF) % elementData.length;
|
|
|
|
//find non null entry
|
|
Entry<V> entry = elementData[index];
|
|
while (entry != null && key != entry.key) {
|
|
entry = entry.next;
|
|
}
|
|
|
|
if (entry == null) {
|
|
if (++elementCount > threshold) {
|
|
rehash();
|
|
index = (hash & 0x7FFFFFFF) % elementData.length;
|
|
}
|
|
entry = createHashedEntry(key, index);
|
|
}
|
|
|
|
|
|
V result = entry.value;
|
|
entry.value = value;
|
|
return result;
|
|
}
|
|
|
|
|
|
Entry<V> createHashedEntry(final long key, final int index) {
|
|
Entry<V> entry = reuseAfterDelete;
|
|
if (entry == null) {
|
|
entry = new Entry<V>(key);
|
|
} else {
|
|
reuseAfterDelete = null;
|
|
entry.key = key;
|
|
entry.value = null;
|
|
}
|
|
|
|
entry.next = elementData[index];
|
|
elementData[index] = entry;
|
|
return entry;
|
|
}
|
|
|
|
|
|
void rehash(final int capacity) {
|
|
int length = (capacity == 0 ? 1 : capacity << 1);
|
|
|
|
Entry<V>[] newData = newElementArray(length);
|
|
for (int i = 0; i < elementData.length; i++) {
|
|
Entry<V> entry = elementData[i];
|
|
while (entry != null) {
|
|
int index = ((int) powerHash(entry.key) & 0x7FFFFFFF) % length;
|
|
Entry<V> next = entry.next;
|
|
entry.next = newData[index];
|
|
newData[index] = entry;
|
|
entry = next;
|
|
}
|
|
}
|
|
elementData = newData;
|
|
computeMaxSize();
|
|
}
|
|
|
|
void rehash() {
|
|
rehash(elementData.length);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* @since Android 1.0
|
|
*/
|
|
|
|
public V remove(final long key) {
|
|
Entry<V> entry = removeEntry(key);
|
|
if (entry == null)
|
|
return null;
|
|
V ret = entry.value;
|
|
entry.value = null;
|
|
entry.key = Long.MIN_VALUE;
|
|
reuseAfterDelete = entry;
|
|
|
|
return ret;
|
|
}
|
|
|
|
Entry<V> removeEntry(final long key) {
|
|
Entry<V> last = null;
|
|
|
|
final int hash = powerHash(key);
|
|
final int index = (hash & 0x7FFFFFFF) % elementData.length;
|
|
Entry<V> entry = elementData[index];
|
|
|
|
while (true) {
|
|
if (entry == null) {
|
|
return null;
|
|
}
|
|
|
|
if (key == entry.key) {
|
|
if (last == null) {
|
|
elementData[index] = entry.next;
|
|
} else {
|
|
last.next = entry.next;
|
|
}
|
|
elementCount--;
|
|
return entry;
|
|
}
|
|
|
|
last = entry;
|
|
entry = entry.next;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the number of elements in this map.
|
|
*
|
|
* @return the number of elements in this map.
|
|
* @since Android 1.0
|
|
*/
|
|
|
|
public int size() {
|
|
return elementCount;
|
|
}
|
|
|
|
/**
|
|
* @returns iterator over values in map
|
|
*/
|
|
public Iterator<V> valuesIterator() {
|
|
return new HashMapIterator<V>(this);
|
|
|
|
}
|
|
|
|
static final private int powerHash(final long key){
|
|
int h = (int)(key ^ (key >>> 32));
|
|
h ^= (h >>> 20) ^ (h >>> 12);
|
|
return h ^ (h >>> 7) ^ (h >>> 4);
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|