📄 multikeymap.java
字号:
/*
* Copyright 2004 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.Serializable;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections.IterableMap;
import org.apache.commons.collections.MapIterator;
import org.apache.commons.collections.keyvalue.MultiKey;
/**
* A <code>Map</code> implementation that uses multiple keys to map the value.
* <p>
* This class is the most efficient way to uses multiple keys to map to a value.
* The best way to use this class is via the additional map-style methods.
* These provide <code>get</code>, <code>containsKey</code>, <code>put</code> and
* <code>remove</code> for individual keys which operate without extra object creation.
* <p>
* The additional methods are the main interface of this map.
* As such, you will not normally hold this map in a variable of type <code>Map</code>.
* <p>
* The normal map methods take in and return a {@link MultiKey}.
* If you try to use <code>put()</code> with any other object type a
* <code>ClassCastException</code> is thrown. If you try to use <code>null</code> as
* the key in <code>put()</code> a <code>NullPointerException</code> is thrown.
* <p>
* This map is implemented as a decorator of a <code>AbstractHashedMap</code> which
* enables extra behaviour to be added easily.
* <ul>
* <li><code>MultiKeyMap.decorate(new LinkedMap())</code> creates an ordered map.
* <li><code>MultiKeyMap.decorate(new LRUMap())</code> creates an least recently used map.
* <li><code>MultiKeyMap.decorate(new ReferenceMap())</code> creates a garbage collector sensitive map.
* </ul>
* Note that <code>IdentityMap</code> and <code>ReferenceIdentityMap</code> are unsuitable
* for use as the key comparison would work on the whole MultiKey, not the elements within.
* <p>
* As an example, consider a least recently used cache that uses a String airline code
* and a Locale to lookup the airline's name:
* <pre>
* private MultiKeyMap cache = MultiKeyMap.decorate(new LRUMap(50));
*
* public String getAirlineName(String code, String locale) {
* String name = (String) cache.get(code, locale);
* if (name == null) {
* name = getAirlineNameFromDB(code, locale);
* cache.put(code, locale, name);
* }
* return name;
* }
* </pre>
* <p>
* <strong>Note that MultiKeyMap is not synchronized and is not thread-safe.</strong>
* If you wish to use this map from multiple threads concurrently, you must use
* appropriate synchronization. This class may throw exceptions when accessed
* by concurrent threads without synchronization.
*
* @since Commons Collections 3.1
* @version $Revision: 348007 $ $Date: 2005-11-21 22:52:57 +0000 (Mon, 21 Nov 2005) $
*
* @author Stephen Colebourne
*/
public class MultiKeyMap
implements IterableMap, Serializable {
/** Serialisation version */
private static final long serialVersionUID = -1788199231038721040L;
/** The decorated map */
protected final AbstractHashedMap map;
//-----------------------------------------------------------------------
/**
* Decorates the specified map to add the MultiKeyMap API and fast query.
* The map must not be null and must be empty.
*
* @param map the map to decorate, not null
* @throws IllegalArgumentException if the map is null or not empty
*/
public static MultiKeyMap decorate(AbstractHashedMap map) {
if (map == null) {
throw new IllegalArgumentException("Map must not be null");
}
if (map.size() > 0) {
throw new IllegalArgumentException("Map must be empty");
}
return new MultiKeyMap(map);
}
//-----------------------------------------------------------------------
/**
* Constructs a new MultiKeyMap that decorates a <code>HashedMap</code>.
*/
public MultiKeyMap() {
super();
map = new HashedMap();
}
/**
* Constructor that decorates the specified map and is called from
* {@link #decorate(AbstractHashedMap)}.
* The map must not be null and should be empty or only contain valid keys.
* This constructor performs no validation.
*
* @param map the map to decorate
*/
protected MultiKeyMap(AbstractHashedMap map) {
super();
this.map = map;
}
//-----------------------------------------------------------------------
/**
* Gets the value mapped to the specified multi-key.
*
* @param key1 the first key
* @param key2 the second key
* @return the mapped value, null if no match
*/
public Object get(Object key1, Object key2) {
int hashCode = hash(key1, key2);
AbstractHashedMap.HashEntry entry = map.data[map.hashIndex(hashCode, map.data.length)];
while (entry != null) {
if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2)) {
return entry.getValue();
}
entry = entry.next;
}
return null;
}
/**
* Checks whether the map contains the specified multi-key.
*
* @param key1 the first key
* @param key2 the second key
* @return true if the map contains the key
*/
public boolean containsKey(Object key1, Object key2) {
int hashCode = hash(key1, key2);
AbstractHashedMap.HashEntry entry = map.data[map.hashIndex(hashCode, map.data.length)];
while (entry != null) {
if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2)) {
return true;
}
entry = entry.next;
}
return false;
}
/**
* Stores the value against the specified multi-key.
*
* @param key1 the first key
* @param key2 the second key
* @param value the value to store
* @return the value previously mapped to this combined key, null if none
*/
public Object put(Object key1, Object key2, Object value) {
int hashCode = hash(key1, key2);
int index = map.hashIndex(hashCode, map.data.length);
AbstractHashedMap.HashEntry entry = map.data[index];
while (entry != null) {
if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2)) {
Object oldValue = entry.getValue();
map.updateEntry(entry, value);
return oldValue;
}
entry = entry.next;
}
map.addMapping(index, hashCode, new MultiKey(key1, key2), value);
return null;
}
/**
* Removes the specified multi-key from this map.
*
* @param key1 the first key
* @param key2 the second key
* @return the value mapped to the removed key, null if key not in map
*/
public Object remove(Object key1, Object key2) {
int hashCode = hash(key1, key2);
int index = map.hashIndex(hashCode, map.data.length);
AbstractHashedMap.HashEntry entry = map.data[index];
AbstractHashedMap.HashEntry previous = null;
while (entry != null) {
if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2)) {
Object oldValue = entry.getValue();
map.removeMapping(entry, index, previous);
return oldValue;
}
previous = entry;
entry = entry.next;
}
return null;
}
/**
* Gets the hash code for the specified multi-key.
*
* @param key1 the first key
* @param key2 the second key
* @return the hash code
*/
protected int hash(Object key1, Object key2) {
int h = 0;
if (key1 != null) {
h ^= key1.hashCode();
}
if (key2 != null) {
h ^= key2.hashCode();
}
h += ~(h << 9);
h ^= (h >>> 14);
h += (h << 4);
h ^= (h >>> 10);
return h;
}
/**
* Is the key equal to the combined key.
*
* @param entry the entry to compare to
* @param key1 the first key
* @param key2 the second key
* @return true if the key matches
*/
protected boolean isEqualKey(AbstractHashedMap.HashEntry entry, Object key1, Object key2) {
MultiKey multi = (MultiKey) entry.getKey();
return
multi.size() == 2 &&
(key1 == null ? multi.getKey(0) == null : key1.equals(multi.getKey(0))) &&
(key2 == null ? multi.getKey(1) == null : key2.equals(multi.getKey(1)));
}
//-----------------------------------------------------------------------
/**
* Gets the value mapped to the specified multi-key.
*
* @param key1 the first key
* @param key2 the second key
* @param key3 the third key
* @return the mapped value, null if no match
*/
public Object get(Object key1, Object key2, Object key3) {
int hashCode = hash(key1, key2, key3);
AbstractHashedMap.HashEntry entry = map.data[map.hashIndex(hashCode, map.data.length)];
while (entry != null) {
if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3)) {
return entry.getValue();
}
entry = entry.next;
}
return null;
}
/**
* Checks whether the map contains the specified multi-key.
*
* @param key1 the first key
* @param key2 the second key
* @param key3 the third key
* @return true if the map contains the key
*/
public boolean containsKey(Object key1, Object key2, Object key3) {
int hashCode = hash(key1, key2, key3);
AbstractHashedMap.HashEntry entry = map.data[map.hashIndex(hashCode, map.data.length)];
while (entry != null) {
if (entry.hashCode == hashCode && isEqualKey(entry, key1, key2, key3)) {
return true;
}
entry = entry.next;
}
return false;
}
/**
* Stores the value against the specified multi-key.
*
* @param key1 the first key
* @param key2 the second key
* @param key3 the third key
* @param value the value to store
* @return the value previously mapped to this combined key, null if none
*/
public Object put(Object key1, Object key2, Object key3, Object value) {
int hashCode = hash(key1, key2, key3);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -