All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.semanticweb.elk.util.collections.ArrayHashMap Maven / Gradle / Ivy

The newest version!
/*
 * #%L
 * elk-reasoner
 * 
 * $Id$
 * $HeadURL$
 * %%
 * Copyright (C) 2011 Oxford University Computing Laboratory
 * %%
 * 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.
 * #L%
 */
/**
 * @author Yevgeny Kazakov, May 23, 2011
 */
package org.semanticweb.elk.util.collections;

import java.util.AbstractCollection;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;

/**
 * hash maps using array and linear probing for resolving hash collision see [1]
 * p.526. Reuses some code from the implementation of HashMap.
 * 
 * [1] Donald E. Knuth, The Art of Computer Programming, Volume 3, Sorting and
 * Searching, Second Edition
 * 
 * @author Yevgeny Kazakov
 * @param 
 *            the type of the keys
 * @param 
 *            the type of the values
 * 
 */
public class ArrayHashMap extends AbstractMap implements Map {

	/**
	 * The default initial capacity - MUST be a power of two.
	 */
	static final int DEFAULT_INITIAL_CAPACITY = 16;

	/**
	 * The maximum capacity, used if a higher value is implicitly specified by
	 * either of the constructors with arguments. MUST be a power of two <=
	 * 1<<30.
	 */
	static final int MAXIMUM_CAPACITY = 1 << 30;

	/**
	 * The table for the keys; the length MUST always be a power of two.
	 */
	protected volatile transient K[] keys;

	/**
	 * The table for the values; the length MUST be equal to the length of keys
	 * and MUST always be a power of two.
	 */
	protected volatile transient V[] values;

	/**
	 * The number of key-value entries contained in this map.
	 */
	protected transient int size;

	/**
	 * The next upper size value at which to stretch the table.
	 * 
	 * @serial
	 */
	int upperSize;

	/**
	 * The next lower size value at which to shrink the table.
	 * 
	 * @serial
	 */
	int lowerSize;

	@SuppressWarnings("unchecked")
	public ArrayHashMap(int initialCapacity) {
		if (initialCapacity < 0)
			throw new IllegalArgumentException("Illegal Capacity: "
					+ initialCapacity);
		if (initialCapacity > MAXIMUM_CAPACITY)
			initialCapacity = MAXIMUM_CAPACITY;
		// Find a power of 2 >= initialCapacity
		int capacity = 1;
		while (capacity < initialCapacity)
			capacity <<= 1;
		this.keys = (K[]) new Object[capacity];
		this.values = (V[]) new Object[capacity];
		this.size = 0;
		this.upperSize = computeUpperSize(capacity);
		this.lowerSize = computeLowerSize(capacity);
	}

	@SuppressWarnings("unchecked")
	public ArrayHashMap() {
		int capacity = DEFAULT_INITIAL_CAPACITY;
		this.keys = (K[]) new Object[capacity];
		this.values = (V[]) new Object[capacity];
		this.size = 0;
		this.upperSize = computeUpperSize(capacity);
		this.lowerSize = computeLowerSize(capacity);
	}

	@Override
	public int size() {
		return size;
	}

	@Override
	public boolean isEmpty() {
		return size == 0;
	}

	/**
	 * Computes a maximum size of the table for a given capacity after which to
	 * stretch the tables.
	 * 
	 * @param capacity
	 *            the capacity of the table.
	 * @return maximum size of the table for a given capacity after which to
	 *         stretch the tables.
	 */
	static private int computeUpperSize(int capacity) {
		if (capacity > 64)
			return (3 * capacity) / 4; // max 75% filled
		else
			return capacity;
	}

	/**
	 * Computes a minimum size of the table for a given capacity after which to
	 * shrink the tables.
	 * 
	 * @param capacity
	 *            the capacity of the table.
	 * @return minimum size of the table for a given capacity after which to
	 *         shrink the tables
	 */
	static private int computeLowerSize(int capacity) {
		return capacity / 4;
	}

	static private int getIndex(Object key, int length) {
		return key.hashCode() & (length - 1);
	}

	@Override
	public boolean containsKey(Object key) {
		if (key == null)
			throw new NullPointerException();
		K[] keys = this.keys;
		int i = getIndex(key, keys.length);
		int j = i; // for cycle detection
		for (;;) {
			K probe = keys[i];
			if (probe == null)
				return false;
			else if (key.equals(probe))
				return true;
			if (i == 0)
				i = keys.length - 1;
			else
				i--;
			if (i == j) // full cycle
				return false;
		}
	}

	@Override
	public boolean containsValue(Object value) {
		if (value == null)
			throw new NullPointerException();
		V[] values = this.values;
		for (int i = 0; i < keys.length; i++)
			if (value.equals(values[i]))
				return true;
		return false;
	}

	@Override
	public V get(Object key) {
		if (key == null)
			throw new NullPointerException();
		// to avoid problems in the middle of resizing, we copy keys and values
		// when they have the same size
		K[] keys;
		V[] values;
		for (;;) {
			keys = this.keys;
			values = this.values;
			if (keys.length == values.length)
				break;
		}
		int i = getIndex(key, keys.length);
		int j = i;
		for (;;) {
			K probe = keys[i];
			if (probe == null)
				return null;
			else if (key.equals(probe))
				return values[i];
			if (i == 0)
				i = keys.length - 1;
			else
				i--;
			if (i == j) // full cycle
				return null;
		}
	}

	/**
	 * Associates the given key with the given value in the map defined by the
	 * keys and value arrays. If an entry with the key equal to the given one
	 * already exists in the map, the value for this key will be overwritten
	 * with the given value.
	 * 
	 * @param keys
	 *            the keys of the map
	 * @param values
	 *            the values of the map
	 * @param key
	 *            the key of the entry
	 * @param value
	 *            the value of the entry
	 * @return the previous value associated with the key or null if
	 *         there was no such a previous value.
	 */
	private V putKeyValue(K[] keys, V[] values, K key, V value) {
		int i = getIndex(key, keys.length);
		for (;;) {
			K probe = keys[i];
			if (probe == null) {
				keys[i] = key;
				values[i] = value;
				return null;
			} else if (key.equals(probe)) {
				V oldValue = values[i];
				values[i] = value;
				return oldValue;
			}
			if (i == 0)
				i = keys.length - 1;
			else
				i--;
		}
	}

	/**
	 * Removes an element at position i of keys and
	 * values shifting, if necessary, other elements so that all
	 * elements can be found by linear probing.
	 * 
	 * @param keys
	 *            the keys of the map
	 * @param values
	 *            the values of the map
	 * @param i
	 *            the position at which to delete the key and value
	 */
	private void shift(K[] keys, V[] values, int i) {
		int del = i; // the position at which to delete
		int j = i; // the position at which to test if the key can be
					// found by linear probing
		for (;;) {
			// decrement j modulo the length of the arrays
			if (j == 0)
				j = keys.length - 1;
			else
				j--;
			// invariant: interval [j, del[ contains non-null elements whose
			// index is in [j, del[
			if (j == del) {
				// we made a full cycle; no elements have to be shifted
				keys[del] = null;
				values[del] = null;
				return;
			}
			K test = keys[j];
			if (test == null) {
				// no further elements to the left need to be shifted
				keys[del] = null;
				values[del] = null;
				return;
			}
			int k = getIndex(test, keys.length);
			// check if k is in [j, del[
			if ((j < del) ? (j <= k) && (k < del) : (j <= k) || (k < del))
				// the index is in [j, del[, so the test element should not be
				// shifted
				continue;
			else {
				// copying the keys and values to the position of deleted
				// element and start deleting their previous locations
				keys[del] = test;
				values[del] = values[j];
				del = j;
				continue;
			}
		}
	}

	/**
	 * Remove the entry in the keys and values such that the key of the entry is
	 * equal to the given object according to the equality function.
	 * 
	 * @param keys
	 *            the array of keys
	 * @param values
	 *            the arrays of values
	 * @param key
	 *            the key for which to delete the entry
	 * @return the value of the deleted entry, null if nothing has been
	 *         deleted
	 */
	private V removeEntry(K[] keys, V[] values, Object key) {
		int i = getIndex(key, keys.length);
		int j = i; // for cycle detection
		for (;;) {
			Object probe = keys[i];
			if (probe == null) {
				return null;
			} else if (key.equals(probe)) {
				V result = values[i];
				shift(keys, values, i);
				return result;
			}
			if (i == 0)
				i = keys.length - 1;
			else
				i--;
			if (i == j) // full cycle
				return null;
		}
	}

	/**
	 * Increasing the capacity of the map
	 */
	private void stretch() {
		int oldCapacity = keys.length;
		if (oldCapacity == MAXIMUM_CAPACITY)
			throw new IllegalArgumentException(
					"Map cannot grow beyond capacity: " + MAXIMUM_CAPACITY);
		K oldKeys[] = keys;
		V oldValues[] = values;
		int newCapacity = oldCapacity << 1;
		@SuppressWarnings("unchecked")
		K newKeys[] = (K[]) new Object[newCapacity];
		@SuppressWarnings("unchecked")
		V newValues[] = (V[]) new Object[newCapacity];
		for (int i = 0; i < oldCapacity; i++) {
			K key = oldKeys[i];
			if (key != null)
				putKeyValue(newKeys, newValues, key, oldValues[i]);
		}
		this.keys = newKeys;
		this.values = newValues;
		this.upperSize = computeUpperSize(newCapacity);
		this.lowerSize = computeLowerSize(newCapacity);
	}

	/**
	 * Decreasing the capacity of the map
	 */
	private void shrink() {
		int oldCapacity = keys.length;
		if (oldCapacity <= DEFAULT_INITIAL_CAPACITY)
			return;
		K oldKeys[] = keys;
		V oldValues[] = values;
		int newCapacity = oldCapacity >> 1;
		@SuppressWarnings("unchecked")
		K newKeys[] = (K[]) new Object[newCapacity];
		@SuppressWarnings("unchecked")
		V newValues[] = (V[]) new Object[newCapacity];
		for (int i = 0; i < oldCapacity; i++) {
			K key = oldKeys[i];
			if (key != null)
				putKeyValue(newKeys, newValues, key, oldValues[i]);
		}
		this.keys = newKeys;
		this.values = newValues;
		this.upperSize = computeUpperSize(newCapacity);
		this.lowerSize = computeLowerSize(newCapacity);
	}

	@Override
	public V put(K key, V value) {
		if (key == null)
			throw new NullPointerException();
		if (size == upperSize)
			stretch();
		V result = putKeyValue(keys, values, key, value);
		if (result == null)
			size++;
		return result;
	}

	@Override
	public V remove(Object key) {
		if (key == null)
			throw new NullPointerException();
		V result = removeEntry(keys, values, key);
		if (result != null)
			size--;
		if (size == lowerSize)
			shrink();
		return result;
	}

	@SuppressWarnings("unchecked")
	@Override
	public void clear() {
		int capacity = keys.length >> 2;
		if (capacity == 0)
			capacity = 1;
		size = 0;
		upperSize = computeUpperSize(capacity);
		lowerSize = computeLowerSize(capacity);
		this.keys = (K[]) new Object[capacity];
		this.values = (V[]) new Object[capacity];
	}

	@Override
	public Set keySet() {
		return new KeySet();
	}

	@Override
	public Collection values() {
		return new ValueCollection();
	}

	@Override
	public Set> entrySet() {
		return new EntrySet();
	}

	private class KeyIterator implements Iterator {
		// copy of the keys
		final K[] keysSnapshot;
		// expected size to check for concurrent modifications
		final int expectedSize;
		// current cursor
		int cursor;
		// reference to the next key
		K nextKey;

		KeyIterator() {
			this.expectedSize = size;
			this.keysSnapshot = keys;
			cursor = 0;
			seekNext();
		}

		void seekNext() {
			while (cursor < keysSnapshot.length)
				if ((nextKey = keysSnapshot[cursor++]) != null)
					return;
			// no next key
			nextKey = null;
		}

		@Override
		public boolean hasNext() {
			return nextKey != null;
		}

		@Override
		public K next() {
			if (expectedSize != size)
				throw new ConcurrentModificationException();
			if (nextKey == null)
				throw new NoSuchElementException();
			K result = nextKey;
			seekNext();
			return result;
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		}

	}

	private final class KeySet extends AbstractSet implements DirectAccess {

		@Override
		public Iterator iterator() {
			return new KeyIterator();
		}

		@Override
		public boolean contains(Object o) {
			return ArrayHashMap.this.containsKey(o);
		}

		@Override
		public boolean remove(Object o) {
			return ArrayHashMap.this.remove(o) != null;
		}

		@Override
		public int size() {
			return size;
		}

		@Override
		public K[] getRawData() {
			return keys;
		}

	}

	private class ValueIterator implements Iterator {
		// copy of the values
		final V[] valuesSnapshot;
		// expected size to check for concurrent modifications
		final int expectedSize;
		// current cursor
		int cursor;
		// reference to the next key
		V nextValue;

		ValueIterator() {
			this.expectedSize = size;
			this.valuesSnapshot = values;
			cursor = 0;
			seekNext();
		}

		void seekNext() {
			while (cursor < valuesSnapshot.length)
				if ((nextValue = valuesSnapshot[cursor++]) != null)
					return;
			// no next value
			nextValue = null;
		}

		@Override
		public boolean hasNext() {
			return nextValue != null;
		}

		@Override
		public V next() {
			if (expectedSize != size)
				throw new ConcurrentModificationException();
			if (nextValue == null)
				throw new NoSuchElementException();
			V result = nextValue;
			seekNext();
			return result;
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		}
	}

	private final class ValueCollection extends AbstractCollection implements
			DirectAccess {

		@Override
		public Iterator iterator() {
			return new ValueIterator();
		}

		@Override
		public int size() {
			return size;
		}

		@Override
		public V[] getRawData() {
			return values;
		}

	}

	private class EntryIterator implements Iterator> {
		// copy of the keys
		final K[] keysSnapshot;
		// copy of the values
		final V[] valuesSnapshot;
		// expected size to check for modification
		final int expectedSize;

		// current cursor
		int cursor;
		// reference to the next key
		K nextKey;

		EntryIterator() {
			this.expectedSize = size;
			this.keysSnapshot = keys;
			this.valuesSnapshot = values;
			this.cursor = 0;
			seekNext();
		}

		void seekNext() {
			while (cursor < keysSnapshot.length)
				if ((nextKey = keysSnapshot[cursor++]) != null)
					return;
			// no next key found
			nextKey = null;
		}

		@Override
		public boolean hasNext() {
			return nextKey != null;
		}

		@Override
		public Entry next() {
			if (expectedSize != size)
				throw new ConcurrentModificationException();
			if (nextKey == null)
				throw new NoSuchElementException();
			Entry result = new Entry(this, cursor - 1);
			seekNext();
			return result;
		}

		@Override
		public void remove() {
			throw new UnsupportedOperationException();
		}

	}

	class Entry implements Map.Entry {
		// copy of the iterator
		final EntryIterator iterator;
		// position of the cursor
		final int cursor;

		Entry(EntryIterator iterator, int cursor) {
			this.iterator = iterator;
			this.cursor = cursor;
		}

		@Override
		public K getKey() {
			return iterator.keysSnapshot[cursor];
		}

		@Override
		public V getValue() {
			return iterator.valuesSnapshot[cursor];
		}

		@Override
		public V setValue(V value) {
			V previous = iterator.valuesSnapshot[cursor];
			iterator.valuesSnapshot[cursor] = value;
			return previous;
		}

	}

	private final class EntrySet extends AbstractSet> {
		@Override
		public Iterator> iterator() {
			return new EntryIterator();
		}

		@Override
		public boolean contains(Object o) {
			if (!(o instanceof Map.Entry))
				return false;
			Object k = ((Map.Entry) o).getKey();
			return ArrayHashMap.this.containsKey(k);
		}

		@Override
		public boolean remove(Object o) {
			if (!(o instanceof Map.Entry))
				return false;
			Object k = ((Map.Entry) o).getKey();
			return ArrayHashMap.this.remove(k) != null;
		}

		@Override
		public int size() {
			return size;
		}

		@Override
		public void clear() {
			ArrayHashMap.this.clear();
		}
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy