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

java.util.WeakHashMap Maven / Gradle / Ivy

/*
 *  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 java.util;

import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;

/**
 * WeakHashMap is an implementation of Map with keys which are WeakReferences. A
 * key/value mapping is removed when the key is no longer referenced. All
 * optional operations (adding and removing) are supported. Keys and values can
 * be any objects. Note that the garbage collector acts similar to a second
 * thread on this collection, possibly removing keys.
 *
 * @since 1.2
 * @see HashMap
 * @see WeakReference
 */
public class WeakHashMap extends AbstractMap implements Map {

    private static final int DEFAULT_SIZE = 16;

    private final ReferenceQueue referenceQueue;

    int elementCount;

    Entry[] elementData;

    private final int loadFactor;

    private int threshold;

    volatile int modCount;

    // Simple utility method to isolate unchecked cast for array creation
    @SuppressWarnings("unchecked")
    private static  Entry[] newEntryArray(int size) {
        return new Entry[size];
    }

    private static final class Entry extends WeakReference implements
            Map.Entry {
        final int hash;

        boolean isNull;

        V value;

        Entry next;

        interface Type {
            R get(Map.Entry entry);
        }

        Entry(K key, V object, ReferenceQueue queue) {
            super(key, queue);
            isNull = key == null;
            hash = isNull ? 0 : Collections.secondaryHash(key);
            value = object;
        }

        public K getKey() {
            return super.get();
        }

        public V getValue() {
            return value;
        }

        public V setValue(V object) {
            V result = value;
            value = object;
            return result;
        }

        @Override
        public boolean equals(Object other) {
            if (!(other instanceof Map.Entry)) {
                return false;
            }
            Map.Entry entry = (Map.Entry) other;
            Object key = super.get();
            return (key == null ? key == entry.getKey() : key.equals(entry
                    .getKey()))
                    && (value == null ? value == entry.getValue() : value
                            .equals(entry.getValue()));
        }

        @Override
        public int hashCode() {
            return hash + (value == null ? 0 : value.hashCode());
        }

        @Override
        public String toString() {
            return super.get() + "=" + value;
        }
    }

    class HashIterator implements Iterator {
        private int position = 0, expectedModCount;

        private Entry currentEntry, nextEntry;

        private K nextKey;

        final Entry.Type type;

        HashIterator(Entry.Type type) {
            this.type = type;
            expectedModCount = modCount;
        }

        public boolean hasNext() {
            if (nextEntry != null && (nextKey != null || nextEntry.isNull)) {
                return true;
            }
            while (true) {
                if (nextEntry == null) {
                    while (position < elementData.length) {
                        if ((nextEntry = elementData[position++]) != null) {
                            break;
                        }
                    }
                    if (nextEntry == null) {
                        return false;
                    }
                }
                // ensure key of next entry is not gc'ed
                nextKey = nextEntry.get();
                if (nextKey != null || nextEntry.isNull) {
                    return true;
                }
                nextEntry = nextEntry.next;
            }
        }

        public R next() {
            if (expectedModCount == modCount) {
                if (hasNext()) {
                    currentEntry = nextEntry;
                    nextEntry = currentEntry.next;
                    R result = type.get(currentEntry);
                    // free the key
                    nextKey = null;
                    return result;
                }
                throw new NoSuchElementException();
            }
            throw new ConcurrentModificationException();
        }

        public void remove() {
            if (expectedModCount == modCount) {
                if (currentEntry != null) {
                    removeEntry(currentEntry);
                    currentEntry = null;
                    expectedModCount++;
                    // cannot poll() as that would change the expectedModCount
                } else {
                    throw new IllegalStateException();
                }
            } else {
                throw new ConcurrentModificationException();
            }
        }
    }

    /**
     * Constructs a new empty {@code WeakHashMap} instance.
     */
    public WeakHashMap() {
        this(DEFAULT_SIZE);
    }

    /**
     * Constructs a new {@code WeakHashMap} instance with the specified
     * capacity.
     *
     * @param capacity
     *            the initial capacity of this map.
     * @throws IllegalArgumentException
     *                if the capacity is less than zero.
     */
    public WeakHashMap(int capacity) {
        if (capacity < 0) {
            throw new IllegalArgumentException("capacity < 0: " + capacity);
        }
        elementCount = 0;
        elementData = newEntryArray(capacity == 0 ? 1 : capacity);
        loadFactor = 7500; // Default load factor of 0.75
        computeMaxSize();
        referenceQueue = new ReferenceQueue();
    }

    /**
     * Constructs a new {@code WeakHashMap} instance with the specified capacity
     * and load factor.
     *
     * @param capacity
     *            the initial capacity of this map.
     * @param loadFactor
     *            the initial load factor.
     * @throws IllegalArgumentException
     *             if the capacity is less than zero or the load factor is less
     *             or equal to zero.
     */
    public WeakHashMap(int capacity, float loadFactor) {
        if (capacity < 0) {
            throw new IllegalArgumentException("capacity < 0: " + capacity);
        }
        if (loadFactor <= 0) {
            throw new IllegalArgumentException("loadFactor <= 0: " + loadFactor);
        }
        elementCount = 0;
        elementData = newEntryArray(capacity == 0 ? 1 : capacity);
        this.loadFactor = (int) (loadFactor * 10000);
        computeMaxSize();
        referenceQueue = new ReferenceQueue();
    }

    /**
     * Constructs a new {@code WeakHashMap} instance containing the mappings
     * from the specified map.
     *
     * @param map
     *            the mappings to add.
     */
    public WeakHashMap(Map map) {
        this(map.size() < 6 ? 11 : map.size() * 2);
        putAllImpl(map);
    }

    /**
     * Removes all mappings from this map, leaving it empty.
     *
     * @see #isEmpty()
     * @see #size()
     */
    @Override
    public void clear() {
        if (elementCount > 0) {
            elementCount = 0;
            Arrays.fill(elementData, null);
            modCount++;
            while (referenceQueue.poll() != null) {
                // do nothing
            }
        }
    }

    private void computeMaxSize() {
        threshold = (int) ((long) elementData.length * loadFactor / 10000);
    }

    /**
     * Returns whether this map contains the specified key.
     *
     * @param key
     *            the key to search for.
     * @return {@code true} if this map contains the specified key,
     *         {@code false} otherwise.
     */
    @Override
    public boolean containsKey(Object key) {
        return getEntry(key) != null;
    }

    /**
     * Returns a set containing all of the mappings in this map. Each mapping is
     * an instance of {@link Map.Entry}. As the set is backed by this map,
     * changes in one will be reflected in the other. It does not support adding
     * operations.
     *
     * @return a set of the mappings.
     */
    @Override
    public Set> entrySet() {
        poll();
        return new AbstractSet>() {
            @Override
            public int size() {
                return WeakHashMap.this.size();
            }

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

            @Override
            public boolean remove(Object object) {
                if (contains(object)) {
                    WeakHashMap.this
                            .remove(((Map.Entry) object).getKey());
                    return true;
                }
                return false;
            }

            @Override
            public boolean contains(Object object) {
                if (object instanceof Map.Entry) {
                    Entry entry = getEntry(((Map.Entry) object)
                            .getKey());
                    if (entry != null) {
                        Object key = entry.get();
                        if (key != null || entry.isNull) {
                            return object.equals(entry);
                        }
                    }
                }
                return false;
            }

            @Override
            public Iterator> iterator() {
                return new HashIterator>(
                        new Entry.Type, K, V>() {
                            public Map.Entry get(Map.Entry entry) {
                                return entry;
                            }
                        });
            }
        };
    }

    /**
     * Returns a set of the keys contained in this map. The set is backed by
     * this map so changes to one are reflected by the other. The set does not
     * support adding.
     *
     * @return a set of the keys.
     */
    @Override
    public Set keySet() {
        poll();
        if (keySet == null) {
            keySet = new AbstractSet() {
                @Override
                public boolean contains(Object object) {
                    return containsKey(object);
                }

                @Override
                public int size() {
                    return WeakHashMap.this.size();
                }

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

                @Override
                public boolean remove(Object key) {
                    if (containsKey(key)) {
                        WeakHashMap.this.remove(key);
                        return true;
                    }
                    return false;
                }

                @Override
                public Iterator iterator() {
                    return new HashIterator(new Entry.Type() {
                        public K get(Map.Entry entry) {
                            return entry.getKey();
                        }
                    });
                }
            };
        }
        return keySet;
    }

    /**
     * Returns a collection of the values contained in this map. The collection
     * is backed by this map so changes to one are reflected by the other. The
     * collection supports remove, removeAll, retainAll and clear operations,
     * and it does not support add or addAll operations.
     * 

* This method returns a collection which is the subclass of * AbstractCollection. The iterator method of this subclass returns a * "wrapper object" over the iterator of map's entrySet(). The size method * wraps the map's size method and the contains method wraps the map's * containsValue method. *

* The collection is created when this method is called at first time and * returned in response to all subsequent calls. This method may return * different Collection when multiple calls to this method, since it has no * synchronization performed. * * @return a collection of the values contained in this map. */ @Override public Collection values() { poll(); if (valuesCollection == null) { valuesCollection = new AbstractCollection() { @Override public int size() { return WeakHashMap.this.size(); } @Override public void clear() { WeakHashMap.this.clear(); } @Override public boolean contains(Object object) { return containsValue(object); } @Override public Iterator iterator() { return new HashIterator(new Entry.Type() { public V get(Map.Entry entry) { return entry.getValue(); } }); } }; } return valuesCollection; } /** * Returns the value of the mapping with the specified key. * * @param key * the key. * @return the value of the mapping with the specified key, or {@code null} * if no mapping for the specified key is found. */ @Override public V get(Object key) { poll(); if (key != null) { int index = (Collections.secondaryHash(key) & 0x7FFFFFFF) % elementData.length; Entry entry = elementData[index]; while (entry != null) { if (key.equals(entry.get())) { return entry.value; } entry = entry.next; } return null; } Entry entry = elementData[0]; while (entry != null) { if (entry.isNull) { return entry.value; } entry = entry.next; } return null; } Entry getEntry(Object key) { poll(); if (key != null) { int index = (Collections.secondaryHash(key) & 0x7FFFFFFF) % elementData.length; Entry entry = elementData[index]; while (entry != null) { if (key.equals(entry.get())) { return entry; } entry = entry.next; } return null; } Entry entry = elementData[0]; while (entry != null) { if (entry.isNull) { return entry; } entry = entry.next; } return null; } /** * Returns whether this map contains the specified value. * * @param value * the value to search for. * @return {@code true} if this map contains the specified value, * {@code false} otherwise. */ @Override public boolean containsValue(Object value) { poll(); if (value != null) { for (int i = elementData.length; --i >= 0;) { Entry entry = elementData[i]; while (entry != null) { K key = entry.get(); if ((key != null || entry.isNull) && value.equals(entry.value)) { return true; } entry = entry.next; } } } else { for (int i = elementData.length; --i >= 0;) { Entry entry = elementData[i]; while (entry != null) { K key = entry.get(); if ((key != null || entry.isNull) && entry.value == null) { return true; } entry = entry.next; } } } return false; } /** * Returns the number of elements in this map. * * @return the number of elements in this map. */ @Override public boolean isEmpty() { return size() == 0; } @SuppressWarnings("unchecked") void poll() { Entry toRemove; while ((toRemove = (Entry) referenceQueue.poll()) != null) { removeEntry(toRemove); } } void removeEntry(Entry toRemove) { Entry entry, last = null; int index = (toRemove.hash & 0x7FFFFFFF) % elementData.length; entry = elementData[index]; // Ignore queued entries which cannot be found, the user could // have removed them before they were queued, i.e. using clear() while (entry != null) { if (toRemove == entry) { modCount++; if (last == null) { elementData[index] = entry.next; } else { last.next = entry.next; } elementCount--; break; } last = entry; entry = entry.next; } } /** * Maps the specified key to the specified value. * * @param key * the key. * @param value * the value. * @return the value of any previous mapping with the specified key or * {@code null} if there was no mapping. */ @Override public V put(K key, V value) { poll(); int index = 0; Entry entry; if (key != null) { index = (Collections.secondaryHash(key) & 0x7FFFFFFF) % elementData.length; entry = elementData[index]; while (entry != null && !key.equals(entry.get())) { entry = entry.next; } } else { entry = elementData[0]; while (entry != null && !entry.isNull) { entry = entry.next; } } if (entry == null) { modCount++; if (++elementCount > threshold) { rehash(); index = key == null ? 0 : (Collections.secondaryHash(key) & 0x7FFFFFFF) % elementData.length; } entry = new Entry(key, value, referenceQueue); entry.next = elementData[index]; elementData[index] = entry; return null; } V result = entry.value; entry.value = value; return result; } private void rehash() { int length = elementData.length * 2; if (length == 0) { length = 1; } Entry[] newData = newEntryArray(length); for (int i = 0; i < elementData.length; i++) { Entry entry = elementData[i]; while (entry != null) { int index = entry.isNull ? 0 : (entry.hash & 0x7FFFFFFF) % length; Entry next = entry.next; entry.next = newData[index]; newData[index] = entry; entry = next; } } elementData = newData; computeMaxSize(); } /** * Copies all the mappings in the given map to this map. These mappings will * replace all mappings that this map had for any of the keys currently in * the given map. * * @param map * the map to copy mappings from. * @throws NullPointerException * if {@code map} is {@code null}. */ @Override public void putAll(Map map) { putAllImpl(map); } /** * Removes the mapping with the specified key from this map. * * @param key * the key of the mapping to remove. * @return the value of the removed mapping or {@code null} if no mapping * for the specified key was found. */ @Override public V remove(Object key) { poll(); int index = 0; Entry entry, last = null; if (key != null) { index = (Collections.secondaryHash(key) & 0x7FFFFFFF) % elementData.length; entry = elementData[index]; while (entry != null && !key.equals(entry.get())) { last = entry; entry = entry.next; } } else { entry = elementData[0]; while (entry != null && !entry.isNull) { last = entry; entry = entry.next; } } if (entry != null) { modCount++; if (last == null) { elementData[index] = entry.next; } else { last.next = entry.next; } elementCount--; return entry.value; } return null; } /** * Returns the number of elements in this map. * * @return the number of elements in this map. */ @Override public int size() { poll(); return elementCount; } private void putAllImpl(Map map) { if (map.entrySet() != null) { super.putAll(map); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy