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

org.apache.commons.collections.map.Flat3Map Maven / Gradle / Ivy

There is a newer version: 2.0.10
Show newest version
/*
 *  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.commons.collections.map;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Collection;
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.MapIterator;
import org.apache.commons.collections.ResettableIterator;
import org.apache.commons.collections.iterators.EmptyIterator;
import org.apache.commons.collections.iterators.EmptyMapIterator;

/**
 * A Map implementation that stores data in simple fields until
 * the size is greater than 3.
 * 

* This map is designed for performance and can outstrip HashMap. * It also has good garbage collection characteristics. *

    *
  • Optimised for operation at size 3 or less. *
  • Still works well once size 3 exceeded. *
  • Gets at size 3 or less are about 0-10% faster than HashMap, *
  • Puts at size 3 or less are over 4 times faster than HashMap. *
  • Performance 5% slower than HashMap once size 3 exceeded once. *
* The design uses two distinct modes of operation - flat and delegate. * While the map is size 3 or less, operations map straight onto fields using * switch statements. Once size 4 is reached, the map switches to delegate mode * and only switches back when cleared. In delegate mode, all operations are * forwarded straight to a HashMap resulting in the 5% performance loss. *

* The performance gains on puts are due to not needing to create a Map Entry * object. This is a large saving not only in performance but in garbage collection. *

* Whilst in flat mode this map is also easy for the garbage collector to dispatch. * This is because it contains no complex objects or arrays which slow the progress. *

* Do not use Flat3Map if the size is likely to grow beyond 3. *

* Note that Flat3Map is not synchronized and is not thread-safe. * If you wish to use this map from multiple threads concurrently, you must use * appropriate synchronization. The simplest approach is to wrap this map * using {@link java.util.Collections#synchronizedMap(Map)}. This class may throw * exceptions when accessed by concurrent threads without synchronization. * * @since Commons Collections 3.0 * @version $Revision: 1713293 $ $Date: 2015-11-08 22:04:34 +0100 (Sun, 08 Nov 2015) $ * * @author Stephen Colebourne */ public class Flat3Map implements IterableMap, Serializable, Cloneable { /** Serialization version */ private static final long serialVersionUID = -6701087419741928296L; /** The size of the map, used while in flat mode */ private transient int size; /** Hash, used while in flat mode */ private transient int hash1; /** Hash, used while in flat mode */ private transient int hash2; /** Hash, used while in flat mode */ private transient int hash3; /** Key, used while in flat mode */ private transient Object key1; /** Key, used while in flat mode */ private transient Object key2; /** Key, used while in flat mode */ private transient Object key3; /** Value, used while in flat mode */ private transient Object value1; /** Value, used while in flat mode */ private transient Object value2; /** Value, used while in flat mode */ private transient Object value3; /** Map, used while in delegate mode */ private transient AbstractHashedMap delegateMap; /** * Constructor. */ public Flat3Map() { super(); } /** * Constructor copying elements from another map. * * @param map the map to copy * @throws NullPointerException if the map is null */ public Flat3Map(Map map) { super(); putAll(map); } //----------------------------------------------------------------------- /** * 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) { if (delegateMap != null) { return delegateMap.get(key); } if (key == null) { switch (size) { // drop through case 3: if (key3 == null) return value3; case 2: if (key2 == null) return value2; case 1: if (key1 == null) return value1; } } else { if (size > 0) { int hashCode = key.hashCode(); switch (size) { // drop through case 3: if (hash3 == hashCode && key.equals(key3)) return value3; case 2: if (hash2 == hashCode && key.equals(key2)) return value2; case 1: if (hash1 == hashCode && key.equals(key1)) return value1; } } } return null; } /** * Gets the size of the map. * * @return the size */ public int size() { if (delegateMap != null) { return delegateMap.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) { if (delegateMap != null) { return delegateMap.containsKey(key); } if (key == null) { switch (size) { // drop through case 3: if (key3 == null) return true; case 2: if (key2 == null) return true; case 1: if (key1 == null) return true; } } else { if (size > 0) { int hashCode = key.hashCode(); switch (size) { // drop through case 3: if (hash3 == hashCode && key.equals(key3)) return true; case 2: if (hash2 == hashCode && key.equals(key2)) return true; case 1: if (hash1 == hashCode && key.equals(key1)) return true; } } } return false; } /** * Checks whether the map contains the specified value. * * @param value the value to search for * @return true if the map contains the key */ public boolean containsValue(Object value) { if (delegateMap != null) { return delegateMap.containsValue(value); } if (value == null) { // drop through switch (size) { case 3: if (value3 == null) return true; case 2: if (value2 == null) return true; case 1: if (value1 == null) return true; } } else { switch (size) { // drop through case 3: if (value.equals(value3)) return true; case 2: if (value.equals(value2)) return true; case 1: if (value.equals(value1)) return true; } } 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) { if (delegateMap != null) { return delegateMap.put(key, value); } // change existing mapping if (key == null) { switch (size) { // drop through case 3: if (key3 == null) { Object old = value3; value3 = value; return old; } case 2: if (key2 == null) { Object old = value2; value2 = value; return old; } case 1: if (key1 == null) { Object old = value1; value1 = value; return old; } } } else { if (size > 0) { int hashCode = key.hashCode(); switch (size) { // drop through case 3: if (hash3 == hashCode && key.equals(key3)) { Object old = value3; value3 = value; return old; } case 2: if (hash2 == hashCode && key.equals(key2)) { Object old = value2; value2 = value; return old; } case 1: if (hash1 == hashCode && key.equals(key1)) { Object old = value1; value1 = value; return old; } } } } // add new mapping switch (size) { default: convertToMap(); delegateMap.put(key, value); return null; case 2: hash3 = (key == null ? 0 : key.hashCode()); key3 = key; value3 = value; break; case 1: hash2 = (key == null ? 0 : key.hashCode()); key2 = key; value2 = value; break; case 0: hash1 = (key == null ? 0 : key.hashCode()); key1 = key; value1 = value; break; } size++; return null; } /** * Puts all the values from the specified map into this map. * * @param map the map to add * @throws NullPointerException if the map is null */ public void putAll(Map map) { int size = map.size(); if (size == 0) { return; } if (delegateMap != null) { delegateMap.putAll(map); return; } if (size < 4) { for (Iterator it = map.entrySet().iterator(); it.hasNext();) { Map.Entry entry = (Map.Entry) it.next(); put(entry.getKey(), entry.getValue()); } } else { convertToMap(); delegateMap.putAll(map); } } /** * Converts the flat map data to a map. */ private void convertToMap() { delegateMap = createDelegateMap(); switch (size) { // drop through case 3: delegateMap.put(key3, value3); case 2: delegateMap.put(key2, value2); case 1: delegateMap.put(key1, value1); } size = 0; hash1 = hash2 = hash3 = 0; key1 = key2 = key3 = null; value1 = value2 = value3 = null; } /** * Create an instance of the map used for storage when in delegation mode. *

* This can be overridden by subclasses to provide a different map implementation. * Not every AbstractHashedMap is suitable, identity and reference based maps * would be poor choices. * * @return a new AbstractHashedMap or subclass * @since Commons Collections 3.1 */ protected AbstractHashedMap createDelegateMap() { return new HashedMap(); } /** * 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) { if (delegateMap != null) { return delegateMap.remove(key); } if (size == 0) { return null; } if (key == null) { switch (size) { // drop through case 3: if (key3 == null) { Object old = value3; hash3 = 0; key3 = null; value3 = null; size = 2; return old; } if (key2 == null) { Object old = value2; hash2 = hash3; key2 = key3; value2 = value3; hash3 = 0; key3 = null; value3 = null; size = 2; return old; } if (key1 == null) { Object old = value1; hash1 = hash3; key1 = key3; value1 = value3; hash3 = 0; key3 = null; value3 = null; size = 2; return old; } return null; case 2: if (key2 == null) { Object old = value2; hash2 = 0; key2 = null; value2 = null; size = 1; return old; } if (key1 == null) { Object old = value1; hash1 = hash2; key1 = key2; value1 = value2; hash2 = 0; key2 = null; value2 = null; size = 1; return old; } return null; case 1: if (key1 == null) { Object old = value1; hash1 = 0; key1 = null; value1 = null; size = 0; return old; } } } else { if (size > 0) { int hashCode = key.hashCode(); switch (size) { // drop through case 3: if (hash3 == hashCode && key.equals(key3)) { Object old = value3; hash3 = 0; key3 = null; value3 = null; size = 2; return old; } if (hash2 == hashCode && key.equals(key2)) { Object old = value2; hash2 = hash3; key2 = key3; value2 = value3; hash3 = 0; key3 = null; value3 = null; size = 2; return old; } if (hash1 == hashCode && key.equals(key1)) { Object old = value1; hash1 = hash3; key1 = key3; value1 = value3; hash3 = 0; key3 = null; value3 = null; size = 2; return old; } return null; case 2: if (hash2 == hashCode && key.equals(key2)) { Object old = value2; hash2 = 0; key2 = null; value2 = null; size = 1; return old; } if (hash1 == hashCode && key.equals(key1)) { Object old = value1; hash1 = hash2; key1 = key2; value1 = value2; hash2 = 0; key2 = null; value2 = null; size = 1; return old; } return null; case 1: if (hash1 == hashCode && key.equals(key1)) { Object old = value1; hash1 = 0; key1 = null; value1 = null; size = 0; return old; } } } } return null; } /** * Clears the map, resetting the size to zero and nullifying references * to avoid garbage collection issues. */ public void clear() { if (delegateMap != null) { delegateMap.clear(); // should aid gc delegateMap = null; // switch back to flat mode } else { size = 0; hash1 = hash2 = hash3 = 0; key1 = key2 = key3 = null; value1 = value2 = value3 = null; } } //----------------------------------------------------------------------- /** * Gets an iterator over the map. * Changes made to the iterator affect this map. *

* A MapIterator returns the keys in the map. It also provides convenient * methods to get the key and value, and set the value. * It avoids the need to create an entrySet/keySet/values object. * It also avoids creating the Map Entry object. * * @return the map iterator */ public MapIterator mapIterator() { if (delegateMap != null) { return delegateMap.mapIterator(); } if (size == 0) { return EmptyMapIterator.INSTANCE; } return new FlatMapIterator(this); } /** * FlatMapIterator */ static class FlatMapIterator implements MapIterator, ResettableIterator { private final Flat3Map parent; private int nextIndex = 0; private boolean canRemove = false; FlatMapIterator(Flat3Map parent) { super(); this.parent = parent; } public boolean hasNext() { return (nextIndex < parent.size); } public Object next() { if (hasNext() == false) { throw new NoSuchElementException(AbstractHashedMap.NO_NEXT_ENTRY); } canRemove = true; nextIndex++; return getKey(); } public void remove() { if (canRemove == false) { throw new IllegalStateException(AbstractHashedMap.REMOVE_INVALID); } parent.remove(getKey()); nextIndex--; canRemove = false; } public Object getKey() { if (canRemove == false) { throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID); } switch (nextIndex) { case 3: return parent.key3; case 2: return parent.key2; case 1: return parent.key1; } throw new IllegalStateException("Invalid map index"); } public Object getValue() { if (canRemove == false) { throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID); } switch (nextIndex) { case 3: return parent.value3; case 2: return parent.value2; case 1: return parent.value1; } throw new IllegalStateException("Invalid map index"); } public Object setValue(Object value) { if (canRemove == false) { throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID); } Object old = getValue(); switch (nextIndex) { case 3: parent.value3 = value; case 2: parent.value2 = value; case 1: parent.value1 = value; } return old; } public void reset() { nextIndex = 0; canRemove = false; } public String toString() { if (canRemove) { return "Iterator[" + getKey() + "=" + getValue() + "]"; } else { return "Iterator[]"; } } } /** * Gets the entrySet view of the map. * Changes made to the view affect this map. * The Map Entry is not an independent object and changes as the * iterator progresses. * To simply iterate through the entries, use {@link #mapIterator()}. * * @return the entrySet view */ public Set entrySet() { if (delegateMap != null) { return delegateMap.entrySet(); } return new EntrySet(this); } /** * EntrySet */ static class EntrySet extends AbstractSet { private final Flat3Map parent; EntrySet(Flat3Map parent) { super(); this.parent = parent; } public int size() { return parent.size(); } public void clear() { parent.clear(); } public boolean remove(Object obj) { if (obj instanceof Map.Entry == false) { return false; } Map.Entry entry = (Map.Entry) obj; Object key = entry.getKey(); boolean result = parent.containsKey(key); parent.remove(key); return result; } public Iterator iterator() { if (parent.delegateMap != null) { return parent.delegateMap.entrySet().iterator(); } if (parent.size() == 0) { return EmptyIterator.INSTANCE; } return new EntrySetIterator(parent); } } /** * EntrySetIterator and MapEntry */ static class EntrySetIterator implements Iterator, Map.Entry { private final Flat3Map parent; private int nextIndex = 0; private boolean canRemove = false; EntrySetIterator(Flat3Map parent) { super(); this.parent = parent; } public boolean hasNext() { return (nextIndex < parent.size); } public Object next() { if (hasNext() == false) { throw new NoSuchElementException(AbstractHashedMap.NO_NEXT_ENTRY); } canRemove = true; nextIndex++; return this; } public void remove() { if (canRemove == false) { throw new IllegalStateException(AbstractHashedMap.REMOVE_INVALID); } parent.remove(getKey()); nextIndex--; canRemove = false; } public Object getKey() { if (canRemove == false) { throw new IllegalStateException(AbstractHashedMap.GETKEY_INVALID); } switch (nextIndex) { case 3: return parent.key3; case 2: return parent.key2; case 1: return parent.key1; } throw new IllegalStateException("Invalid map index"); } public Object getValue() { if (canRemove == false) { throw new IllegalStateException(AbstractHashedMap.GETVALUE_INVALID); } switch (nextIndex) { case 3: return parent.value3; case 2: return parent.value2; case 1: return parent.value1; } throw new IllegalStateException("Invalid map index"); } public Object setValue(Object value) { if (canRemove == false) { throw new IllegalStateException(AbstractHashedMap.SETVALUE_INVALID); } Object old = getValue(); switch (nextIndex) { case 3: parent.value3 = value; break; case 2: parent.value2 = value; break; case 1: parent.value1 = value; break; } return old; } public boolean equals(Object obj) { if (canRemove == false) { return false; } if (obj instanceof Map.Entry == false) { return false; } Map.Entry other = (Map.Entry) obj; Object key = getKey(); Object value = getValue(); return (key == null ? other.getKey() == null : key.equals(other.getKey())) && (value == null ? other.getValue() == null : value.equals(other.getValue())); } public int hashCode() { if (canRemove == false) { return 0; } Object key = getKey(); Object value = getValue(); return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()); } public String toString() { if (canRemove) { return getKey() + "=" + getValue(); } else { return ""; } } } /** * Gets the keySet view of the map. * Changes made to the view affect this map. * To simply iterate through the keys, use {@link #mapIterator()}. * * @return the keySet view */ public Set keySet() { if (delegateMap != null) { return delegateMap.keySet(); } return new KeySet(this); } /** * KeySet */ static class KeySet extends AbstractSet { private final Flat3Map parent; KeySet(Flat3Map parent) { super(); this.parent = parent; } public int size() { return parent.size(); } public void clear() { parent.clear(); } public boolean contains(Object key) { return parent.containsKey(key); } public boolean remove(Object key) { boolean result = parent.containsKey(key); parent.remove(key); return result; } public Iterator iterator() { if (parent.delegateMap != null) { return parent.delegateMap.keySet().iterator(); } if (parent.size() == 0) { return EmptyIterator.INSTANCE; } return new KeySetIterator(parent); } } /** * KeySetIterator */ static class KeySetIterator extends EntrySetIterator { KeySetIterator(Flat3Map parent) { super(parent); } public Object next() { super.next(); return getKey(); } } /** * Gets the values view of the map. * Changes made to the view affect this map. * To simply iterate through the values, use {@link #mapIterator()}. * * @return the values view */ public Collection values() { if (delegateMap != null) { return delegateMap.values(); } return new Values(this); } /** * Values */ static class Values extends AbstractCollection { private final Flat3Map parent; Values(Flat3Map parent) { super(); this.parent = parent; } public int size() { return parent.size(); } public void clear() { parent.clear(); } public boolean contains(Object value) { return parent.containsValue(value); } public Iterator iterator() { if (parent.delegateMap != null) { return parent.delegateMap.values().iterator(); } if (parent.size() == 0) { return EmptyIterator.INSTANCE; } return new ValuesIterator(parent); } } /** * ValuesIterator */ static class ValuesIterator extends EntrySetIterator { ValuesIterator(Flat3Map parent) { super(parent); } public Object next() { super.next(); return getValue(); } } //----------------------------------------------------------------------- /** * Write the map out using a custom routine. */ private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); out.writeInt(size()); for (MapIterator it = mapIterator(); it.hasNext();) { out.writeObject(it.next()); // key out.writeObject(it.getValue()); // value } } /** * Read the map in using a custom routine. */ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); int count = in.readInt(); if (count > 3) { delegateMap = createDelegateMap(); } for (int i = count; i > 0; i--) { put(in.readObject(), in.readObject()); } } //----------------------------------------------------------------------- /** * Clones the map without cloning the keys or values. * * @return a shallow clone * @since Commons Collections 3.1 */ public Object clone() { try { Flat3Map cloned = (Flat3Map) super.clone(); if (cloned.delegateMap != null) { cloned.delegateMap = (HashedMap) cloned.delegateMap.clone(); } return cloned; } catch (CloneNotSupportedException ex) { throw new InternalError(); } } /** * Compares this map with another. * * @param obj the object to compare to * @return true if equal */ public boolean equals(Object obj) { if (obj == this) { return true; } if (delegateMap != null) { return delegateMap.equals(obj); } if (obj instanceof Map == false) { return false; } Map other = (Map) obj; if (size != other.size()) { return false; } if (size > 0) { Object otherValue = null; switch (size) { // drop through case 3: if (other.containsKey(key3) == false) { return false; } otherValue = other.get(key3); if (value3 == null ? otherValue != null : !value3.equals(otherValue)) { return false; } case 2: if (other.containsKey(key2) == false) { return false; } otherValue = other.get(key2); if (value2 == null ? otherValue != null : !value2.equals(otherValue)) { return false; } case 1: if (other.containsKey(key1) == false) { return false; } otherValue = other.get(key1); if (value1 == null ? otherValue != null : !value1.equals(otherValue)) { return false; } } } return true; } /** * Gets the standard Map hashCode. * * @return the hash code defined in the Map interface */ public int hashCode() { if (delegateMap != null) { return delegateMap.hashCode(); } int total = 0; switch (size) { // drop through case 3: total += (hash3 ^ (value3 == null ? 0 : value3.hashCode())); case 2: total += (hash2 ^ (value2 == null ? 0 : value2.hashCode())); case 1: total += (hash1 ^ (value1 == null ? 0 : value1.hashCode())); } return total; } /** * Gets the map as a String. * * @return a string version of the map */ public String toString() { if (delegateMap != null) { return delegateMap.toString(); } if (size == 0) { return "{}"; } StringBuffer buf = new StringBuffer(128); buf.append('{'); switch (size) { // drop through case 3: buf.append((key3 == this ? "(this Map)" : key3)); buf.append('='); buf.append((value3 == this ? "(this Map)" : value3)); buf.append(','); case 2: buf.append((key2 == this ? "(this Map)" : key2)); buf.append('='); buf.append((value2 == this ? "(this Map)" : value2)); buf.append(','); case 1: buf.append((key1 == this ? "(this Map)" : key1)); buf.append('='); buf.append((value1 == this ? "(this Map)" : value1)); } buf.append('}'); return buf.toString(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy