Add LRUMap implementation

Refactor serialization behaviour in HashedMap hierarchy


git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/collections/trunk@131418 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Stephen Colebourne 2003-12-07 01:23:54 +00:00
parent 4f699c8628
commit c32add2800
13 changed files with 953 additions and 105 deletions

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,5 @@
/*
* $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/map/HashedMap.java,v 1.7 2003/12/06 14:02:11 scolebourne Exp $
* $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/map/HashedMap.java,v 1.8 2003/12/07 01:23:54 scolebourne Exp $
* ====================================================================
*
* The Apache Software License, Version 1.1
@ -87,7 +87,7 @@ import org.apache.commons.collections.MapIterator;
* methods exposed.
*
* @since Commons Collections 3.0
* @version $Revision: 1.7 $ $Date: 2003/12/06 14:02:11 $
* @version $Revision: 1.8 $ $Date: 2003/12/07 01:23:54 $
*
* @author java util HashMap
* @author Stephen Colebourne
@ -113,7 +113,7 @@ public class HashedMap implements IterableMap, Serializable, Cloneable {
protected static final Object NULL = new Object();
/** Load factor, normally 0.75 */
protected final float loadFactor;
protected transient float loadFactor;
/** The size of the map */
protected transient int size;
/** Map entries */
@ -157,14 +157,14 @@ public class HashedMap implements IterableMap, Serializable, Cloneable {
* @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 one
* @throws IllegalArgumentException if the load factor is less than zero
*/
public HashedMap(int initialCapacity, float loadFactor) {
super();
if (initialCapacity < 1) {
throw new IllegalArgumentException("Initial capacity must be greater than 0");
}
if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
if (loadFactor <= 0.0f || Float.isNaN(loadFactor)) {
throw new IllegalArgumentException("Load factor must be greater than 0");
}
this.loadFactor = loadFactor;
@ -296,14 +296,13 @@ public class HashedMap implements IterableMap, Serializable, Cloneable {
while (entry != null) {
if (entry.hashCode == hashCode && isEqualKey(key, entry.key)) {
Object oldValue = entry.getValue();
entry.setValue(value);
updateEntry(entry, value);
return oldValue;
}
entry = entry.next;
}
modCount++;
add(hashCode, index, key, value);
addMapping(index, hashCode, key, value);
return null;
}
@ -335,18 +334,13 @@ public class HashedMap implements IterableMap, Serializable, Cloneable {
key = convertKey(key);
int hashCode = hash(key);
int index = hashIndex(hashCode, data.length);
HashEntry entry = data[index];
HashEntry entry = data[index];
HashEntry previous = null;
while (entry != null) {
if (entry.hashCode == hashCode && isEqualKey(key, entry.key)) {
modCount++;
if (previous == null) {
data[index] = entry.next;
} else {
previous.next = entry.next;
}
size--;
return destroyEntry(entry);
Object oldValue = entry.getValue();
removeMapping(entry, index, previous);
return oldValue;
}
previous = entry;
entry = entry.next;
@ -368,25 +362,6 @@ public class HashedMap implements IterableMap, Serializable, Cloneable {
}
//-----------------------------------------------------------------------
/**
* Gets the entry mapped to the key specified.
*
* @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;
}
/**
* Converts input keys to another object for storage in the map.
* This implementation masks nulls.
@ -459,9 +434,91 @@ public class HashedMap implements IterableMap, Serializable, Cloneable {
return hashCode & (dataSize - 1);
}
//-----------------------------------------------------------------------
/**
* Creates an entry to store the data.
* This implementation creates a HashEntry instance.
* 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;
}
//-----------------------------------------------------------------------
/**
* Updates an existing key-value mapping to change the value.
* <p>
* This implementation calls <code>setValue()</code> on the entry.
* Subclasses could override to handle changes to the map.
*
* @param entry the entry to update
* @param newValue the new value to store
* @return value the previous value
*/
protected void updateEntry(HashEntry entry, Object newValue) {
entry.setValue(newValue);
}
/**
* Reuses an existing key-value mapping, storing completely new data.
* <p>
* This implementation sets all the data fields on the entry.
* Subclasses could populate additional entry fields.
*
* @param entry the entry to update, not null
* @param hashIndex the index in the data array
* @param hashCode the hash code of the key to add
* @param key the key to add
* @param value the value to add
*/
protected void reuseEntry(HashEntry entry, int hashIndex, int hashCode, Object key, Object value) {
entry.next = data[hashIndex];
entry.hashCode = hashCode;
entry.key = key;
entry.value = value;
}
//-----------------------------------------------------------------------
/**
* Adds a new key-value mapping into this map.
* <p>
* This implementation calls <code>createEntry()</code>, <code>addEntry()</code>
* and <code>checkCapacity</code>.
* It also handles changes to <code>modCount</code> and <code>size</code>.
* Subclasses could override to fully control adds to the map.
*
* @param hashIndex the index into the data array to store at
* @param hashCode the hash code of the key to add
* @param key the key to add
* @param value the value to add
* @return the value previously mapped to this key, null if none
*/
protected void addMapping(int hashIndex, int hashCode, Object key, Object value) {
modCount++;
HashEntry entry = createEntry(data[hashIndex], hashCode, key, value);
addEntry(entry, hashIndex);
size++;
checkCapacity();
}
/**
* Creates an entry to store the key-value data.
* <p>
* This implementation creates a new HashEntry instance.
* Subclasses can override this to return a different storage class,
* or implement caching.
*
@ -476,29 +533,81 @@ public class HashedMap implements IterableMap, Serializable, Cloneable {
}
/**
* Kills an entry ready for the garbage collector.
* This implementation prepares the HashEntry for garbage collection.
* Subclasses can override this to implement caching (override clear as well).
*
* @param entry the entry to destroy
* @return the value from the entry
*/
protected Object destroyEntry(HashEntry entry) {
entry.next = null;
return entry.value;
}
/**
* Adds a new key-value mapping into this map.
* Subclasses could override to fix the size of the map.
* Adds an entry into this map.
* <p>
* This implementation adds the entry to the data storage table.
* Subclasses could override to handle changes to the map.
*
* @param hashIndex the index into the data array to store at
* @param hashCode the hash code of the key to add
* @param key the key to add
* @param value the value to add
* @return the value previously mapped to this key, null if none
*/
protected void add(int hashCode, int hashIndex, Object key, Object value) {
data[hashIndex] = createEntry(data[hashIndex], hashCode, key, value);
if (size++ >= threshold) {
protected void addEntry(HashEntry entry, int hashIndex) {
data[hashIndex] = entry;
}
//-----------------------------------------------------------------------
/**
* Removes a mapping from the map.
* <p>
* This implementation calls <code>removeEntry()</code> and <code>destroyEntry()</code>.
* It also handles changes to <code>modCount</code> and <code>size</code>.
* Subclasses could override to fully control removals from the map.
*
* @param entry the entry to remove
* @param hashIndex the index into the data structure
* @param previous the previous entry in the chain
*/
protected void removeMapping(HashEntry entry, int hashIndex, HashEntry previous) {
modCount++;
removeEntry(entry, hashIndex, previous);
size--;
destroyEntry(entry);
}
/**
* Removes an entry from the chain stored in a particular index.
* <p>
* This implementation removes the entry from the data storage table.
* The size is not updated.
* Subclasses could override to handle changes to the map.
*
* @param entry the entry to remove
* @param hashIndex the index into the data structure
* @param previous the previous entry in the chain
*/
protected void removeEntry(HashEntry entry, int hashIndex, HashEntry previous) {
if (previous == null) {
data[hashIndex] = entry.next;
} else {
previous.next = entry.next;
}
}
/**
* Kills an entry ready for the garbage collector.
* <p>
* This implementation prepares the HashEntry for garbage collection.
* Subclasses can override this to implement caching (override clear as well).
*
* @param entry the entry to destroy
*/
protected void destroyEntry(HashEntry entry) {
entry.next = null;
entry.key = null;
entry.value = null;
}
//-----------------------------------------------------------------------
/**
* Checks the capacity of the map and enlarges it if necessary.
* <p>
* This implementation uses the threshold to check if the map needs enlarging
*/
protected void checkCapacity() {
if (size >= threshold) {
ensureCapacity(data.length * 2);
}
}
@ -874,17 +983,21 @@ public class HashedMap implements IterableMap, Serializable, Cloneable {
this.key = key;
this.value = value;
}
public Object getKey() {
return (key == NULL ? null : key);
}
public Object getValue() {
return value;
}
public Object setValue(Object value) {
Object old = this.value;
this.value = value;
return old;
}
public boolean equals(Object obj) {
if (obj == this) {
return true;
@ -897,10 +1010,12 @@ public class HashedMap implements IterableMap, Serializable, Cloneable {
(getKey() == null ? other.getKey() == null : getKey().equals(other.getKey())) &&
(getValue() == null ? other.getValue() == null : getValue().equals(other.getValue()));
}
public int hashCode() {
return (getKey() == null ? 0 : getKey().hashCode()) ^
(getValue() == null ? 0 : getValue().hashCode());
}
public String toString() {
return new StringBuffer().append(getKey()).append('=').append(getValue()).toString();
}
@ -980,13 +1095,49 @@ public class HashedMap implements IterableMap, Serializable, Cloneable {
}
//-----------------------------------------------------------------------
/**
* Write the data of subclasses.
* <p>
* Serialization is not one of the JDK's nicest topics. Normal serialization will
* not load subclass fields until this object is setup. In other words the readObject
* on this class is performed before that on the subclass. Unfortunately, data setup in
* the subclass may affect the ability of methods such as put() to work properly.
* <p>
* The solution adopted here is to have a method called by this class as part of the
* serialization and deserialization process. Override this method if the subclass
* stores additional data needed for put() to work.
* A sub-subclass must call super.doWriteObject().
*/
protected void doWriteObject(ObjectOutputStream out) throws IOException {
// do nothing
}
/**
* Read the data of subclasses.
* <p>
* Serialization is not one of the JDK's nicest topics. Normal serialization will
* not load subclass fields until this object is setup. In other words the readObject
* on this class is performed before that on the subclass. Unfortunately, data setup in
* the subclass may affect the ability of methods such as put() to work properly.
* <p>
* The solution adopted here is to have a method called by this class as part of the
* serialization and deserialization process. Override this method if the subclass
* stores additional data needed for put() to work.
* A sub-subclass must call super.doReadObject().
*/
protected void doReadObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
// do nothing
}
/**
* Write the map out using a custom routine.
*/
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeFloat(loadFactor);
out.writeInt(data.length);
out.writeInt(size);
doWriteObject(out);
for (MapIterator it = mapIterator(); it.hasNext();) {
out.writeObject(it.next());
out.writeObject(it.getValue());
@ -998,16 +1149,20 @@ public class HashedMap implements IterableMap, Serializable, Cloneable {
*/
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
loadFactor = in.readFloat();
int capacity = in.readInt();
int size = in.readInt();
data = new HashEntry[capacity];
doReadObject(in);
init();
data = new HashEntry[capacity];
for (int i = 0; i < size; i++) {
Object key = in.readObject();
Object value = in.readObject();
put(key, value);
}
threshold = calculateThreshold(data.length, loadFactor);
}
//-----------------------------------------------------------------------
/**
* Clones the map without cloning the keys or values.

View File

@ -0,0 +1,327 @@
/*
* $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/map/LRUMap.java,v 1.1 2003/12/07 01:23:54 scolebourne Exp $
* ====================================================================
*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2001-2003 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowledgement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgement may appear in the software itself,
* if and wherever such third-party acknowledgements normally appear.
*
* 4. The names "The Jakarta Project", "Commons", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.commons.collections.map;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Map;
import org.apache.commons.collections.OrderedMap;
/**
* A <code>Map</code> implementation with a fixed maximum size which removes
* the least recently used entry if an entry is added when full.
* <p>
* The least recently used algorithm works on the get and put operations only.
* Iteration of any kind, including setting the value by iteration, does not
* change the order. Queries such as containsKey and containsValue or access
* via views also do not change the order.
* <p>
* The map implements <code>OrderedMap</code> and entries may be queried using
* the bidirectional <code>OrderedMapIterator</code>. The order returned is
* most recently used to least recently used. Iterators from map views can
* also be cast to <code>OrderedIterator</code> if required.
* <p>
* All the available iterators can be reset back to the start by casting to
* <code>ResettableIterator</code> and calling <code>reset()</code>.
* <p>
* NOTE: The order of the map has changed from the previous version located
* in the main collections package. The map is now ordered most recently used
* to least recently used.
*
* @since Commons Collections 3.0
* @version $Revision: 1.1 $ $Date: 2003/12/07 01:23:54 $
*
* @author James Strachan
* @author Morgan Delagrange
* @author Stephen Colebourne
*/
public class LRUMap extends LinkedMap implements OrderedMap {
/** Serialisation version */
static final long serialVersionUID = -2848625157350244215L;
/** Default maximum size */
protected static final int DEFAULT_MAX_SIZE = 100;
/** Maximum size */
private transient int maxSize;
/**
* Constructs a new empty map with a maximum size of 100.
*/
public LRUMap() {
this(DEFAULT_MAX_SIZE, DEFAULT_LOAD_FACTOR);
}
/**
* Constructs a new, empty map with the specified maximum size.
*
* @param maxSize the maximum size of the map
* @throws IllegalArgumentException if the maximum size is less than one
*/
public LRUMap(int maxSize) {
this(maxSize, DEFAULT_LOAD_FACTOR);
}
/**
* Constructs a new, empty map with the specified initial capacity and
* load factor.
*
* @param maxSize the maximum size of the map, -1 for no limit,
* @param loadFactor the load factor
* @throws IllegalArgumentException if the maximum size is less than one
* @throws IllegalArgumentException if the load factor is less than zero
*/
public LRUMap(int maxSize, float loadFactor) {
super((maxSize < 1 ? DEFAULT_CAPACITY : maxSize), loadFactor);
if (maxSize < 1) {
throw new IllegalArgumentException("LRUMap max size must be greater than 0");
}
this.maxSize = maxSize;
}
/**
* Constructor copying elements from another map.
* <p>
* The maximum size is set from the map's size.
*
* @param map the map to copy
* @throws NullPointerException if the map is null
* @throws IllegalArgumentException if the map is empty
*/
public LRUMap(Map map) {
this(map.size(), DEFAULT_LOAD_FACTOR);
putAll(map);
}
//-----------------------------------------------------------------------
/**
* Gets the value mapped to the key specified.
* <p>
* This operation changes the position of the key in the map to the
* most recently used position (first).
*
* @param key the key
* @return the mapped value, null if no match
*/
public Object get(Object key) {
LinkEntry entry = (LinkEntry) getEntry(key);
if (entry == null) {
return null;
}
moveFirst(entry);
return entry.getValue();
}
//-----------------------------------------------------------------------
/**
* Updates an existing key-value mapping.
* This implementation moves the updated entry to the top of the list.
*
* @param entry the entry to update
* @param newValue the new value to store
* @return value the previous value
*/
protected void moveFirst(LinkEntry entry) {
if (entry.before != header) {
modCount++;
// remove
entry.after.before = entry.before;
entry.before.after = entry.after;
// add first
entry.before = header;
entry.after = header.after;
header.after.before = entry;
header.after = entry;
}
}
/**
* Updates an existing key-value mapping.
* This implementation moves the updated entry to the top of the list.
*
* @param entry the entry to update
* @param newValue the new value to store
* @return value the previous value
*/
protected void updateEntry(HashEntry entry, Object newValue) {
moveFirst((LinkEntry) entry); // handles modCount
entry.setValue(newValue);
}
/**
* Adds a new key-value mapping into this map.
* This implementation checks the LRU size and determines whether to
* discard an entry or not.
*
* @param hashIndex the index into the data array to store at
* @param hashCode the hash code of the key to add
* @param key the key to add
* @param value the value to add
* @return the value previously mapped to this key, null if none
*/
protected void addMapping(int hashIndex, int hashCode, Object key, Object value) {
if (size >= maxSize && removeLRU(header.before)) {
LinkEntry entry = header.before;
// remove from current location
int removeIndex = hashIndex(entry.hashCode, data.length);
HashEntry loop = data[removeIndex];
HashEntry previous = null;
while (loop != entry) {
previous = loop;
loop = loop.next;
}
modCount++;
removeEntry(entry, removeIndex, previous);
reuseEntry(entry, hashIndex, hashCode, key, value);
addEntry(entry, hashIndex);
} else {
super.addMapping(hashIndex, hashCode, key, value);
}
}
/**
* Adds a new entry into this map using access order.
* <p>
* This implementation adds the entry to the data storage table and
* to the start of the linked list.
*
* @param entry the entry to add
* @param hashIndex the index into the data array to store at
*/
protected void addEntry(HashEntry entry, int hashIndex) {
LinkEntry link = (LinkEntry) entry;
link.before = header;
link.after = header.after;
header.after.before = link;
header.after = link;
data[hashIndex] = entry;
}
/**
* Subclass method to control removal of the least recently used entry from the map.
* <p>
* This method exists for subclasses to override. A subclass may wish to
* provide cleanup of resources when an entry is removed. For example:
* <pre>
* protected boolean removeLRU(LinkEntry entry) {
* releaseResources(entry.getValue()); // release resources held by entry
* return true; // actually delete entry
* }
* </pre>
* <p>
* Alternatively, a subclass may choose to not remove the entry or selectively
* keep certain LRU entries. For example:
* <pre>
* protected boolean removeLRU(LinkEntry entry) {
* if (entry.getKey().toString().startsWith("System.")) {
* return false; // entry not removed from LRUMap
* } else {
* return true; // actually delete entry
* }
* }
* </pre>
* Note that the effect of not removing an LRU is for the Map to exceed the maximum size.
*
* @param entry the entry to be removed
*/
protected boolean removeLRU(LinkEntry entry) {
return true;
}
//-----------------------------------------------------------------------
/**
* Returns true if this map is full and no new mappings can be added.
*
* @return <code>true</code> if the map is full
*/
public boolean isFull() {
return (size >= maxSize);
}
/**
* Gets the maximum size of the map (the bound).
*
* @return the maximum number of elements the map can hold
*/
public int maxSize() {
return maxSize;
}
//-----------------------------------------------------------------------
/**
* Write the data of subclasses.
* A sub-subclass must call super.doWriteObject().
*/
protected void doWriteObject(ObjectOutputStream out) throws IOException {
out.writeInt(maxSize);
super.doWriteObject(out);
}
/**
* Read the data of subclasses.
* A sub-subclass must call super.doReadObject().
*/
protected void doReadObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
maxSize = in.readInt();
super.doReadObject(in);
}
}

View File

@ -1,5 +1,5 @@
/*
* $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/map/LinkedMap.java,v 1.2 2003/12/06 14:02:11 scolebourne Exp $
* $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/java/org/apache/commons/collections/map/LinkedMap.java,v 1.3 2003/12/07 01:23:54 scolebourne Exp $
* ====================================================================
*
* The Apache Software License, Version 1.1
@ -71,7 +71,8 @@ import org.apache.commons.collections.ResettableIterator;
/**
* A <code>Map</code> implementation that maintains the order of the entries.
* The order maintained is by insertion.
* In this implementation order is maintained is by original insertion, but
* subclasses may work differently.
* <p>
* This implementation improves on the JDK1.4 LinkedHashMap by adding the
* {@link org.apache.commons.collections.iterators.MapIterator MapIterator}
@ -84,9 +85,12 @@ import org.apache.commons.collections.ResettableIterator;
* <p>
* All the available iterators can be reset back to the start by casting to
* <code>ResettableIterator</code> and calling <code>reset()</code>.
* <p>
* The implementation is also designed to be subclassed, with lots of useful
* methods exposed.
*
* @since Commons Collections 3.0
* @version $Revision: 1.2 $ $Date: 2003/12/06 14:02:11 $
* @version $Revision: 1.3 $ $Date: 2003/12/07 01:23:54 $
*
* @author java util LinkedHashMap
* @author Stephen Colebourne
@ -97,7 +101,7 @@ public class LinkedMap extends HashedMap implements OrderedMap {
static final long serialVersionUID = -1954063410665686469L;
/** Header in the linked list */
private transient LinkedEntry header;
protected transient LinkEntry header;
/**
* Constructs a new empty map with default size and load factor.
@ -123,7 +127,7 @@ public class LinkedMap extends HashedMap implements OrderedMap {
* @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 one
* @throws IllegalArgumentException if the load factor is less than zero
*/
public LinkedMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
@ -143,7 +147,7 @@ public class LinkedMap extends HashedMap implements OrderedMap {
* Initialise this subclass during construction.
*/
protected void init() {
header = new LinkedEntry(null, -1, null, null);
header = new LinkEntry(null, -1, null, null);
header.before = header.after = header;
}
@ -157,13 +161,13 @@ public class LinkedMap extends HashedMap implements OrderedMap {
public boolean containsValue(Object value) {
// override uses faster iterator
if (value == null) {
for (LinkedEntry entry = header.after; entry != header; entry = entry.after) {
for (LinkEntry entry = header.after; entry != header; entry = entry.after) {
if (entry.getValue() == null) {
return true;
}
}
} else {
for (LinkedEntry entry = header.after; entry != header; entry = entry.after) {
for (LinkEntry entry = header.after; entry != header; entry = entry.after) {
if (isEqualValue(value, entry.getValue())) {
return true;
}
@ -214,7 +218,7 @@ public class LinkedMap extends HashedMap implements OrderedMap {
* @return the next key
*/
public Object nextKey(Object key) {
LinkedEntry entry = (LinkedEntry) getEntry(key);
LinkEntry entry = (LinkEntry) getEntry(key);
return (entry == null || entry.after == header ? null : entry.after.getKey());
}
@ -225,14 +229,33 @@ public class LinkedMap extends HashedMap implements OrderedMap {
* @return the previous key
*/
public Object previousKey(Object key) {
LinkedEntry entry = (LinkedEntry) getEntry(key);
LinkEntry entry = (LinkEntry) getEntry(key);
return (entry == null || entry.before == header ? null : entry.before.getKey());
}
//-----------------------------------------------------------------------
/**
* Adds an entry into this map, maintaining insertion order.
* <p>
* This implementation adds the entry to the data storage table and
* to the end of the linked list.
*
* @param entry the entry to add
* @param hashIndex the index into the data array to store at
*/
protected void addEntry(HashEntry entry, int hashIndex) {
LinkEntry link = (LinkEntry) entry;
link.after = header;
link.before = header.before;
header.before.after = link;
header.before = link;
data[hashIndex] = entry;
}
/**
* Creates an entry to store the data.
* This implementation creates a LinkEntry instance in the linked list.
* <p>
* This implementation creates a new LinkEntry instance.
*
* @param next the next entry in sequence
* @param hashCode the hash code to use
@ -241,30 +264,26 @@ public class LinkedMap extends HashedMap implements OrderedMap {
* @return the newly created entry
*/
protected HashEntry createEntry(HashEntry next, int hashCode, Object key, Object value) {
LinkedEntry entry = new LinkedEntry(next, hashCode, key, value);
entry.after = header;
entry.before = header.before;
header.before.after = entry;
header.before = entry;
return entry;
return new LinkEntry(next, hashCode, key, value);
}
/**
* Kills an entry ready for the garbage collector.
* This implementation manages the linked list and prepares the
* LinkEntry for garbage collection.
* Removes an entry from the map and the linked list.
* <p>
* This implementation removes the entry from the linked list chain, then
* calls the superclass implementation.
*
* @param entry the entry to destroy
* @return the value from the entry
* @param entry the entry to remove
* @param hashIndex the index into the data structure
* @param previous the previous entry in the chain
*/
protected Object destroyEntry(HashEntry entry) {
LinkedEntry link = (LinkedEntry) entry;
protected void removeEntry(HashEntry entry, int hashIndex, HashEntry previous) {
LinkEntry link = (LinkEntry) entry;
link.before.after = link.after;
link.after.before = link.before;
link.next = null;
link.after = null;
link.before = null;
return entry.value;
super.removeEntry(entry, hashIndex, previous);
}
//-----------------------------------------------------------------------
@ -283,7 +302,7 @@ public class LinkedMap extends HashedMap implements OrderedMap {
if (size == 0) {
return IteratorUtils.EMPTY_ORDERED_MAP_ITERATOR;
}
return new LinkedMapIterator(this);
return new LinkMapIterator(this);
}
/**
@ -301,15 +320,15 @@ public class LinkedMap extends HashedMap implements OrderedMap {
if (size == 0) {
return IteratorUtils.EMPTY_ORDERED_MAP_ITERATOR;
}
return new LinkedMapIterator(this);
return new LinkMapIterator(this);
}
/**
* MapIterator
*/
static class LinkedMapIterator extends LinkedIterator implements OrderedMapIterator {
static class LinkMapIterator extends LinkIterator implements OrderedMapIterator {
LinkedMapIterator(LinkedMap map) {
LinkMapIterator(LinkedMap map) {
super(map);
}
@ -363,7 +382,7 @@ public class LinkedMap extends HashedMap implements OrderedMap {
/**
* EntrySetIterator and MapEntry
*/
static class EntrySetIterator extends LinkedIterator {
static class EntrySetIterator extends LinkIterator {
EntrySetIterator(LinkedMap map) {
super(map);
@ -427,7 +446,7 @@ public class LinkedMap extends HashedMap implements OrderedMap {
/**
* ValuesIterator
*/
static class ValuesIterator extends LinkedIterator {
static class ValuesIterator extends LinkIterator {
ValuesIterator(LinkedMap map) {
super(map);
@ -446,12 +465,12 @@ public class LinkedMap extends HashedMap implements OrderedMap {
/**
* LinkEntry
*/
protected static class LinkedEntry extends HashEntry {
protected static class LinkEntry extends HashEntry {
LinkedEntry before;
LinkedEntry after;
protected LinkEntry before;
protected LinkEntry after;
protected LinkedEntry(HashEntry next, int hashCode, Object key, Object value) {
protected LinkEntry(HashEntry next, int hashCode, Object key, Object value) {
super(next, hashCode, key, value);
}
}
@ -459,15 +478,15 @@ public class LinkedMap extends HashedMap implements OrderedMap {
/**
* Base Iterator
*/
protected static abstract class LinkedIterator
protected static abstract class LinkIterator
implements OrderedIterator, ResettableIterator {
private final LinkedMap map;
private LinkedEntry current;
private LinkedEntry next;
private int expectedModCount;
protected final LinkedMap map;
protected LinkEntry current;
protected LinkEntry next;
protected int expectedModCount;
protected LinkedIterator(LinkedMap map) {
protected LinkIterator(LinkedMap map) {
super();
this.map = map;
this.next = map.header.after;
@ -482,7 +501,7 @@ public class LinkedMap extends HashedMap implements OrderedMap {
return (next.before != map.header);
}
protected LinkedEntry nextEntry() {
protected LinkEntry nextEntry() {
if (map.modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
@ -494,11 +513,11 @@ public class LinkedMap extends HashedMap implements OrderedMap {
return current;
}
protected LinkedEntry previousEntry() {
protected LinkEntry previousEntry() {
if (map.modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
LinkedEntry previous = next.before;
LinkEntry previous = next.before;
if (previous == map.header) {
throw new NoSuchElementException(HashedMap.NO_PREVIOUS_ENTRY);
}
@ -507,7 +526,7 @@ public class LinkedMap extends HashedMap implements OrderedMap {
return current;
}
protected LinkedEntry currentEntry() {
protected LinkEntry currentEntry() {
return current;
}

View File

@ -1,5 +1,5 @@
/*
* $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/test/org/apache/commons/collections/map/TestAll.java,v 1.9 2003/12/03 19:04:41 scolebourne Exp $
* $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/test/org/apache/commons/collections/map/TestAll.java,v 1.10 2003/12/07 01:23:54 scolebourne Exp $
* ====================================================================
*
* The Apache Software License, Version 1.1
@ -65,7 +65,7 @@ import junit.framework.TestSuite;
* Entry point for tests.
*
* @since Commons Collections 3.0
* @version $Revision: 1.9 $ $Date: 2003/12/03 19:04:41 $
* @version $Revision: 1.10 $ $Date: 2003/12/07 01:23:54 $
*
* @author Stephen Colebourne
*/
@ -87,6 +87,7 @@ public class TestAll extends TestCase {
suite.addTest(TestHashedMap.suite());
suite.addTest(TestIdentityMap.suite());
suite.addTest(TestLinkedMap.suite());
suite.addTest(TestLRUMap.suite());
suite.addTest(TestReferenceMap.suite());
suite.addTest(TestStaticBucketMap.suite());

View File

@ -0,0 +1,346 @@
/*
* $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//collections/src/test/org/apache/commons/collections/map/TestLRUMap.java,v 1.1 2003/12/07 01:23:54 scolebourne Exp $
* ====================================================================
*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2001-2003 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowledgement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgement may appear in the software itself,
* if and wherever such third-party acknowledgements normally appear.
*
* 4. The names "The Jakarta Project", "Commons", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.commons.collections.map;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import junit.framework.Test;
import junit.textui.TestRunner;
import org.apache.commons.collections.BulkTest;
import org.apache.commons.collections.OrderedMap;
import org.apache.commons.collections.ResettableIterator;
/**
* JUnit tests.
*
* @version $Revision: 1.1 $ $Date: 2003/12/07 01:23:54 $
*
* @author Stephen Colebourne
*/
public class TestLRUMap extends AbstractTestOrderedMap {
public TestLRUMap(String testName) {
super(testName);
}
public static void main(String[] args) {
TestRunner.run(suite());
}
public static Test suite() {
return BulkTest.makeSuite(TestLRUMap.class);
}
public Map makeEmptyMap() {
return new LRUMap();
}
public boolean isGetStructuralModify() {
return true;
}
public String getCompatibilityVersion() {
return "3";
}
//-----------------------------------------------------------------------
public void testLRU() {
if (isPutAddSupported() == false || isPutChangeSupported() == false) return;
Object[] keys = getSampleKeys();
Object[] values = getSampleValues();
Iterator it = null;
LRUMap map = new LRUMap(2);
assertEquals(0, map.size());
assertEquals(false, map.isFull());
assertEquals(2, map.maxSize());
map.put(keys[0], values[0]);
assertEquals(1, map.size());
assertEquals(false, map.isFull());
assertEquals(2, map.maxSize());
map.put(keys[1], values[1]);
assertEquals(2, map.size());
assertEquals(true, map.isFull());
assertEquals(2, map.maxSize());
it = map.keySet().iterator();
assertSame(keys[1], it.next());
assertSame(keys[0], it.next());
it = map.values().iterator();
assertSame(values[1], it.next());
assertSame(values[0], it.next());
map.put(keys[2], values[2]);
assertEquals(2, map.size());
assertEquals(true, map.isFull());
assertEquals(2, map.maxSize());
it = map.keySet().iterator();
assertSame(keys[2], it.next());
assertSame(keys[1], it.next());
it = map.values().iterator();
assertSame(values[2], it.next());
assertSame(values[1], it.next());
map.put(keys[2], values[0]);
assertEquals(2, map.size());
assertEquals(true, map.isFull());
assertEquals(2, map.maxSize());
it = map.keySet().iterator();
assertSame(keys[2], it.next());
assertSame(keys[1], it.next());
it = map.values().iterator();
assertSame(values[0], it.next());
assertSame(values[1], it.next());
map.put(keys[1], values[3]);
assertEquals(2, map.size());
assertEquals(true, map.isFull());
assertEquals(2, map.maxSize());
it = map.keySet().iterator();
assertSame(keys[1], it.next());
assertSame(keys[2], it.next());
it = map.values().iterator();
assertSame(values[3], it.next());
assertSame(values[0], it.next());
}
//-----------------------------------------------------------------------
public void testReset() {
resetEmpty();
OrderedMap ordered = (OrderedMap) map;
((ResettableIterator) ordered.mapIterator()).reset();
resetFull();
ordered = (OrderedMap) map;
List list = new ArrayList(ordered.keySet());
ResettableIterator it = (ResettableIterator) ordered.mapIterator();
assertSame(list.get(0), it.next());
assertSame(list.get(1), it.next());
it.reset();
assertSame(list.get(0), it.next());
}
//-----------------------------------------------------------------------
public void testAccessOrder() {
if (isPutAddSupported() == false || isPutChangeSupported() == false) return;
Object[] keys = getSampleKeys();
Object[] values = getSampleValues();
Iterator it = null;
resetEmpty();
map.put(keys[1], values[1]);
map.put(keys[0], values[0]);
it = map.keySet().iterator();
assertSame(keys[0], it.next());
assertSame(keys[1], it.next());
it = map.values().iterator();
assertSame(values[0], it.next());
assertSame(values[1], it.next());
// change to order
map.put(keys[1], values[1]);
it = map.keySet().iterator();
assertSame(keys[1], it.next());
assertSame(keys[0], it.next());
it = map.values().iterator();
assertSame(values[1], it.next());
assertSame(values[0], it.next());
// no change to order
map.put(keys[1], values[2]);
it = map.keySet().iterator();
assertSame(keys[1], it.next());
assertSame(keys[0], it.next());
it = map.values().iterator();
assertSame(values[2], it.next());
assertSame(values[0], it.next());
// change to order
map.put(keys[0], values[3]);
it = map.keySet().iterator();
assertSame(keys[0], it.next());
assertSame(keys[1], it.next());
it = map.values().iterator();
assertSame(values[3], it.next());
assertSame(values[2], it.next());
// change to order
map.get(keys[1]);
it = map.keySet().iterator();
assertSame(keys[1], it.next());
assertSame(keys[0], it.next());
it = map.values().iterator();
assertSame(values[2], it.next());
assertSame(values[3], it.next());
// change to order
map.get(keys[0]);
it = map.keySet().iterator();
assertSame(keys[0], it.next());
assertSame(keys[1], it.next());
it = map.values().iterator();
assertSame(values[3], it.next());
assertSame(values[2], it.next());
// no change to order
map.get(keys[0]);
it = map.keySet().iterator();
assertSame(keys[0], it.next());
assertSame(keys[1], it.next());
it = map.values().iterator();
assertSame(values[3], it.next());
assertSame(values[2], it.next());
}
//-----------------------------------------------------------------------
public void testFirstKey() { // override
resetEmpty();
OrderedMap ordered = (OrderedMap) map;
try {
ordered.firstKey();
fail();
} catch (NoSuchElementException ex) {}
resetFull();
ordered = (OrderedMap) map;
Object confirmedFirst = confirmed.keySet().iterator().next();
ordered.get(confirmedFirst);
assertEquals(confirmedFirst, ordered.firstKey());
}
public void testLastKey() { // override
resetEmpty();
OrderedMap ordered = (OrderedMap) map;
try {
ordered.lastKey();
fail();
} catch (NoSuchElementException ex) {}
resetFull();
ordered = (OrderedMap) map;
Object confirmedFirst = confirmed.keySet().iterator().next();
// access order, thus first in is now in last place
assertEquals(confirmedFirst, ordered.lastKey());
}
//-----------------------------------------------------------------------
public void testNextKey() { // override
resetEmpty();
OrderedMap ordered = (OrderedMap) map;
assertEquals(null, ordered.nextKey(getOtherKeys()[0]));
if (isAllowNullKey() == false) {
try {
assertEquals(null, ordered.nextKey(null)); // this is allowed too
} catch (NullPointerException ex) {}
} else {
assertEquals(null, ordered.nextKey(null));
}
resetFull();
ordered = (OrderedMap) map;
List list = new ArrayList(confirmed.keySet());
Collections.reverse(list); // first into map is eldest
Iterator it = list.iterator();
Object confirmedLast = it.next();
while (it.hasNext()) {
Object confirmedObject = it.next();
assertEquals(confirmedObject, ordered.nextKey(confirmedLast));
confirmedLast = confirmedObject;
}
assertEquals(null, ordered.nextKey(confirmedLast));
}
public void testPreviousKey() { // override
resetEmpty();
OrderedMap ordered = (OrderedMap) map;
assertEquals(null, ordered.previousKey(getOtherKeys()[0]));
if (isAllowNullKey() == false) {
try {
assertEquals(null, ordered.previousKey(null)); // this is allowed too
} catch (NullPointerException ex) {}
} else {
assertEquals(null, ordered.previousKey(null));
}
resetFull();
ordered = (OrderedMap) map;
List list = new ArrayList(confirmed.keySet());
Iterator it = list.iterator();
Object confirmedLast = it.next();
while (it.hasNext()) {
Object confirmedObject = it.next();
assertEquals(confirmedObject, ordered.previousKey(confirmedLast));
confirmedLast = confirmedObject;
}
assertEquals(null, ordered.previousKey(confirmedLast));
}
// public void testCreate() throws Exception {
// resetEmpty();
// writeExternalFormToDisk((Serializable) map, "D:/dev/collections/data/test/LRUMap.emptyCollection.version3.obj");
// resetFull();
// writeExternalFormToDisk((Serializable) map, "D:/dev/collections/data/test/LRUMap.fullCollection.version3.obj");
// }
}