📄 abstracthashedmap.java
字号:
/*
* Copyright 2003-2005 The Apache Software Foundation
*
* 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 org.apache.commons.collections.map;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
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;
import org.apache.commons.collections.IterableMap;
import org.apache.commons.collections.KeyValue;
import org.apache.commons.collections.MapIterator;
import org.apache.commons.collections.iterators.EmptyIterator;
import org.apache.commons.collections.iterators.EmptyMapIterator;
/**
* An abstract implementation of a hash-based map which provides numerous points for
* subclasses to override.
* <p>
* This class implements all the features necessary for a subclass hash-based map.
* Key-value entries are stored in instances of the <code>HashEntry</code> class,
* which can be overridden and replaced. The iterators can similarly be replaced,
* without the need to replace the KeySet, EntrySet and Values view classes.
* <p>
* Overridable methods are provided to change the default hashing behaviour, and
* to change how entries are added to and removed from the map. Hopefully, all you
* need for unusual subclasses is here.
* <p>
* NOTE: From Commons Collections 3.1 this class extends AbstractMap.
* This is to provide backwards compatibility for ReferenceMap between v3.0 and v3.1.
* This extends clause will be removed in v4.0.
*
* @since Commons Collections 3.0
* @version $Revision: 171349 $ $Date: 2005-05-22 18:48:56 +0100 (Sun, 22 May 2005) $
*
* @author java util HashMap
* @author Stephen Colebourne
* @author Christian Siefkes
*/
public class AbstractHashedMap extends AbstractMap implements IterableMap {
protected static final String NO_NEXT_ENTRY = "No next() entry in the iteration";
protected static final String NO_PREVIOUS_ENTRY = "No previous() entry in the iteration";
protected static final String REMOVE_INVALID = "remove() can only be called once after next()";
protected static final String GETKEY_INVALID = "getKey() can only be called after next() and before remove()";
protected static final String GETVALUE_INVALID = "getValue() can only be called after next() and before remove()";
protected static final String SETVALUE_INVALID = "setValue() can only be called after next() and before remove()";
/** The default capacity to use */
protected static final int DEFAULT_CAPACITY = 16;
/** The default threshold to use */
protected static final int DEFAULT_THRESHOLD = 12;
/** The default load factor to use */
protected static final float DEFAULT_LOAD_FACTOR = 0.75f;
/** The maximum capacity allowed */
protected static final int MAXIMUM_CAPACITY = 1 << 30;
/** An object for masking null */
protected static final Object NULL = new Object();
/** Load factor, normally 0.75 */
protected transient float loadFactor;
/** The size of the map */
protected transient int size;
/** Map entries */
protected transient HashEntry[] data;
/** Size at which to rehash */
protected transient int threshold;
/** Modification count for iterators */
protected transient int modCount;
/** Entry set */
protected transient EntrySet entrySet;
/** Key set */
protected transient KeySet keySet;
/** Values */
protected transient Values values;
/**
* Constructor only used in deserialization, do not use otherwise.
*/
protected AbstractHashedMap() {
super();
}
/**
* Constructor which performs no validation on the passed in parameters.
*
* @param initialCapacity the initial capacity, must be a power of two
* @param loadFactor the load factor, must be > 0.0f and generally < 1.0f
* @param threshold the threshold, must be sensible
*/
protected AbstractHashedMap(int initialCapacity, float loadFactor, int threshold) {
super();
this.loadFactor = loadFactor;
this.data = new HashEntry[initialCapacity];
this.threshold = threshold;
init();
}
/**
* Constructs a new, empty map with the specified initial capacity and
* default load factor.
*
* @param initialCapacity the initial capacity
* @throws IllegalArgumentException if the initial capacity is less than one
*/
protected AbstractHashedMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* Constructs a new, empty map with the specified initial capacity and
* load factor.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @throws IllegalArgumentException if the initial capacity is less than one
* @throws IllegalArgumentException if the load factor is less than or equal to zero
*/
protected AbstractHashedMap(int initialCapacity, float loadFactor) {
super();
if (initialCapacity < 1) {
throw new IllegalArgumentException("Initial capacity must be greater than 0");
}
if (loadFactor <= 0.0f || Float.isNaN(loadFactor)) {
throw new IllegalArgumentException("Load factor must be greater than 0");
}
this.loadFactor = loadFactor;
initialCapacity = calculateNewCapacity(initialCapacity);
this.threshold = calculateThreshold(initialCapacity, loadFactor);
this.data = new HashEntry[initialCapacity];
init();
}
/**
* Constructor copying elements from another map.
*
* @param map the map to copy
* @throws NullPointerException if the map is null
*/
protected AbstractHashedMap(Map map) {
this(Math.max(2 * map.size(), DEFAULT_CAPACITY), DEFAULT_LOAD_FACTOR);
putAll(map);
}
/**
* Initialise subclasses during construction, cloning or deserialization.
*/
protected void init() {
}
//-----------------------------------------------------------------------
/**
* Gets the value mapped to the key specified.
*
* @param key the key
* @return the mapped value, null if no match
*/
public Object get(Object key) {
key = convertKey(key);
int hashCode = hash(key);
HashEntry entry = data[hashIndex(hashCode, data.length)]; // no local for hash index
while (entry != null) {
if (entry.hashCode == hashCode && isEqualKey(key, entry.key)) {
return entry.getValue();
}
entry = entry.next;
}
return null;
}
/**
* Gets the size of the map.
*
* @return the size
*/
public int size() {
return size;
}
/**
* Checks whether the map is currently empty.
*
* @return true if the map is currently size zero
*/
public boolean isEmpty() {
return (size == 0);
}
//-----------------------------------------------------------------------
/**
* Checks whether the map contains the specified key.
*
* @param key the key to search for
* @return true if the map contains the key
*/
public boolean containsKey(Object key) {
key = convertKey(key);
int hashCode = hash(key);
HashEntry entry = data[hashIndex(hashCode, data.length)]; // no local for hash index
while (entry != null) {
if (entry.hashCode == hashCode && isEqualKey(key, entry.key)) {
return true;
}
entry = entry.next;
}
return false;
}
/**
* Checks whether the map contains the specified value.
*
* @param value the value to search for
* @return true if the map contains the value
*/
public boolean containsValue(Object value) {
if (value == null) {
for (int i = 0, isize = data.length; i < isize; i++) {
HashEntry entry = data[i];
while (entry != null) {
if (entry.getValue() == null) {
return true;
}
entry = entry.next;
}
}
} else {
for (int i = 0, isize = data.length; i < isize; i++) {
HashEntry entry = data[i];
while (entry != null) {
if (isEqualValue(value, entry.getValue())) {
return true;
}
entry = entry.next;
}
}
}
return false;
}
//-----------------------------------------------------------------------
/**
* Puts a key-value mapping into this map.
*
* @param key the key to add
* @param value the value to add
* @return the value previously mapped to this key, null if none
*/
public Object put(Object key, Object value) {
key = convertKey(key);
int hashCode = hash(key);
int index = hashIndex(hashCode, data.length);
HashEntry entry = data[index];
while (entry != null) {
if (entry.hashCode == hashCode && isEqualKey(key, entry.key)) {
Object oldValue = entry.getValue();
updateEntry(entry, value);
return oldValue;
}
entry = entry.next;
}
addMapping(index, hashCode, key, value);
return null;
}
/**
* Puts all the values from the specified map into this map.
* <p>
* This implementation iterates around the specified map and
* uses {@link #put(Object, Object)}.
*
* @param map the map to add
* @throws NullPointerException if the map is null
*/
public void putAll(Map map) {
int mapSize = map.size();
if (mapSize == 0) {
return;
}
int newSize = (int) ((size + mapSize) / loadFactor + 1);
ensureCapacity(calculateNewCapacity(newSize));
for (Iterator it = map.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
put(entry.getKey(), entry.getValue());
}
}
/**
* Removes the specified mapping from this map.
*
* @param key the mapping to remove
* @return the value mapped to the removed key, null if key not in map
*/
public Object remove(Object key) {
key = convertKey(key);
int hashCode = hash(key);
int index = hashIndex(hashCode, data.length);
HashEntry entry = data[index];
HashEntry previous = null;
while (entry != null) {
if (entry.hashCode == hashCode && isEqualKey(key, entry.key)) {
Object oldValue = entry.getValue();
removeMapping(entry, index, previous);
return oldValue;
}
previous = entry;
entry = entry.next;
}
return null;
}
/**
* Clears the map, resetting the size to zero and nullifying references
* to avoid garbage collection issues.
*/
public void clear() {
modCount++;
HashEntry[] data = this.data;
for (int i = data.length - 1; i >= 0; i--) {
data[i] = null;
}
size = 0;
}
//-----------------------------------------------------------------------
/**
* Converts input keys to another object for storage in the map.
* This implementation masks nulls.
* Subclasses can override this to perform alternate key conversions.
* <p>
* The reverse conversion can be changed, if required, by overriding the
* getKey() method in the hash entry.
*
* @param key the key convert
* @return the converted key
*/
protected Object convertKey(Object key) {
return (key == null ? NULL : key);
}
/**
* Gets the hash code for the key specified.
* This implementation uses the additional hashing routine from JDK1.4.
* Subclasses can override this to return alternate hash codes.
*
* @param key the key to get a hash code for
* @return the hash code
*/
protected int hash(Object key) {
// same as JDK 1.4
int h = key.hashCode();
h += ~(h << 9);
h ^= (h >>> 14);
h += (h << 4);
h ^= (h >>> 10);
return h;
}
/**
* Compares two keys, in internal converted form, to see if they are equal.
* This implementation uses the equals method and assumes neither key is null.
* Subclasses can override this to match differently.
*
* @param key1 the first key to compare passed in from outside
* @param key2 the second key extracted from the entry via <code>entry.key</code>
* @return true if equal
*/
protected boolean isEqualKey(Object key1, Object key2) {
return (key1 == key2 || key1.equals(key2));
}
/**
* Compares two values, in external form, to see if they are equal.
* This implementation uses the equals method and assumes neither value is null.
* Subclasses can override this to match differently.
*
* @param value1 the first value to compare passed in from outside
* @param value2 the second value extracted from the entry via <code>getValue()</code>
* @return true if equal
*/
protected boolean isEqualValue(Object value1, Object value2) {
return (value1 == value2 || value1.equals(value2));
}
/**
* Gets the index into the data storage for the hashCode specified.
* This implementation uses the least significant bits of the hashCode.
* Subclasses can override this to return alternate bucketing.
*
* @param hashCode the hash code to use
* @param dataSize the size of the data to pick a bucket from
* @return the bucket index
*/
protected int hashIndex(int hashCode, int dataSize) {
return hashCode & (dataSize - 1);
}
//-----------------------------------------------------------------------
/**
* Gets the entry mapped to the key specified.
* <p>
* This method exists for subclasses that may need to perform a multi-step
* process accessing the entry. The public methods in this class don't use this
* method to gain a small performance boost.
*
* @param key the key
* @return the entry, null if no match
*/
protected HashEntry getEntry(Object key) {
key = convertKey(key);
int hashCode = hash(key);
HashEntry entry = data[hashIndex(hashCode, data.length)]; // no local for hash index
while (entry != null) {
if (entry.hashCode == hashCode && isEqualKey(key, entry.key)) {
return entry;
}
entry = entry.next;
}
return null;
}
//-----------------------------------------------------------------------
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -