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

org.htmlunit.cyberneko.util.FastHashMap Maven / Gradle / Ivy

/*
 * Copyright (c) 2005-2024 René Schwietzke
 *
 * 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.htmlunit.cyberneko.util;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Simple hash map implementation taken from here
 * https://github.com/mikvor/hashmapTest/blob/master/src/main/java/map/objobj/ObjObjMap.java
 * No concrete license specified at the source. The project is public domain.
 *
 * Not thread-safe! Null support was removed.
 *
 * @param  the type of the key
 * @param  the type of the value
 *
 * @author René Schwietzke
 * @since 3.10.0
 */
public class FastHashMap implements Serializable {
    private static final Object FREE_KEY = new Object();
    private static final Object REMOVED_KEY = new Object();

    /** Keys and values */
    private Object[] m_data_;

    /** Fill factor, must be between (0 and 1) */
    private final float m_fillFactor_;
    /** We will resize a map once it reaches this size */
    private int m_threshold_;
    /** Current map size */
    private int m_size_;
    /** Mask to calculate the original position */
    private int m_mask_;
    /** Mask to wrap the actual array pointer */
    private int m_mask2_;

    public FastHashMap() {
        this(7, 0.5f);
    }

    public FastHashMap(final int size, final float fillFactor) {
        if (fillFactor <= 0 || fillFactor >= 1) {
            throw new IllegalArgumentException("FillFactor must be in (0, 1)");
        }

        if (size <= 0) {
            throw new IllegalArgumentException("Size must be positive!");
        }
        final int capacity = arraySize(size, fillFactor);
        m_mask_ = capacity - 1;
        m_mask2_ = capacity * 2 - 1;
        m_fillFactor_ = fillFactor;

        m_data_ = new Object[capacity * 2];
        Arrays.fill(m_data_, FREE_KEY);

        m_threshold_ = (int) (capacity * fillFactor);
    }

    public V get(final K key) {
        final int srcHashCode = key.hashCode();

        int ptr = (srcHashCode & m_mask_) << 1;
        Object k = m_data_[ptr];

        if (k == FREE_KEY) {
            //end of chain already
            return null;
        }

        //we check FREE and REMOVED prior to this call
        if (k.hashCode() == srcHashCode && k.equals(key)) {
            return (V) m_data_[ptr + 1];
        }

        while (true) {
            ptr = (ptr + 2) & m_mask2_; //that's next index
            k = m_data_[ ptr ];
            if (k == FREE_KEY) {
                return null;
            }
            if (k.hashCode() == srcHashCode && k.equals(key)) {
                return (V) m_data_[ptr + 1];
            }
        }
    }

    public V put(final K key, final V value) {
        int ptr = getStartIndex(key) << 1;
        Object k = m_data_[ptr];

        if (k == FREE_KEY) {
            //end of chain already
            m_data_[ ptr ] = key;
            m_data_[ ptr + 1 ] = value;
            if (m_size_ >= m_threshold_) {
                rehash(m_data_.length * 2); //size is set inside
            }
            else {
                ++m_size_;
            }
            return null;
        }
        else if (k.equals(key)) {
            //we check FREE and REMOVED prior to this call
            final Object ret = m_data_[ ptr + 1 ];
            m_data_[ ptr + 1 ] = value;
            return (V) ret;
        }

        int firstRemoved = -1;
        if (k == REMOVED_KEY) {
            //we may find a key later
            firstRemoved = ptr;
        }

        while (true) {
            // that's next index calculation
            ptr = (ptr + 2) & m_mask2_;
            k = m_data_[ ptr ];
            if (k == FREE_KEY) {
                if (firstRemoved != -1) {
                    ptr = firstRemoved;
                }
                m_data_[ ptr ] = key;
                m_data_[ ptr + 1 ] = value;
                if (m_size_ >= m_threshold_) {
                    //size is set inside
                    rehash(m_data_.length * 2);
                }
                else {
                    ++m_size_;
                }
                return null;
            }
            else if (k.equals(key)) {
                final Object ret = m_data_[ ptr + 1 ];
                m_data_[ ptr + 1 ] = value;
                return (V) ret;
            }
            else if (k == REMOVED_KEY) {
                if (firstRemoved == -1) {
                    firstRemoved = ptr;
                }
            }
        }
    }

    public V remove(final K key) {
        int ptr = getStartIndex(key) << 1;
        Object k = m_data_[ ptr ];
        if (k == FREE_KEY) {
            // end of chain already
            return null;
        }
        else if (k.equals(key)) {
            // we check FREE and REMOVED prior to this call
            --m_size_;
            if (m_data_[ (ptr + 2) & m_mask2_ ] == FREE_KEY) {
                m_data_[ ptr ] = FREE_KEY;
            }
            else {
                m_data_[ ptr ] = REMOVED_KEY;
            }
            final V ret = (V) m_data_[ ptr + 1 ];
            m_data_[ ptr + 1 ] = null;
            return ret;
        }
        while (true) {
            //that's next index calculation
            ptr = (ptr + 2) & m_mask2_;
            k = m_data_[ ptr ];
            if (k == FREE_KEY) {
                return null;
            }
            else if (k.equals(key)) {
                --m_size_;
                if (m_data_[ (ptr + 2) & m_mask2_ ] == FREE_KEY) {
                    m_data_[ ptr ] = FREE_KEY;
                }
                else {
                    m_data_[ ptr ] = REMOVED_KEY;
                }
                final V ret = (V) m_data_[ ptr + 1 ];
                m_data_[ ptr + 1 ] = null;
                return ret;
            }
        }
    }

    public int size() {
        return m_size_;
    }

    private void rehash(final int newCapacity) {
        m_threshold_ = (int) (newCapacity / 2 * m_fillFactor_);
        m_mask_ = newCapacity / 2 - 1;
        m_mask2_ = newCapacity - 1;

        final int oldCapacity = m_data_.length;
        final Object[] oldData = m_data_;

        m_data_ = new Object[ newCapacity ];
        Arrays.fill(m_data_, FREE_KEY);

        m_size_ = 0;

        for (int i = 0; i < oldCapacity; i += 2) {
            final Object oldKey = oldData[ i ];
            if (oldKey != FREE_KEY && oldKey != REMOVED_KEY) {
                put((K) oldKey, (V) oldData[ i + 1 ]);
            }
        }
    }

    /**
     * @return a list of all keys
     */
    public List keys() {
        final List result = new ArrayList<>(this.size());

        final int length = m_data_.length;
        for (int i = 0; i < length; i += 2) {
            final Object o = m_data_[i];
            if (o != FREE_KEY && o != REMOVED_KEY) {
                result.add((K) o);
            }
        }

        return result;
    }

    /**
     * @return a list of all values
     */
    public List values() {
        final List result = new ArrayList<>(this.size());

        final int length = m_data_.length;
        for (int i = 0; i < length; i += 2) {
            final Object o = m_data_[i];
            if (o != FREE_KEY && o != REMOVED_KEY) {
                result.add((V) m_data_[i + 1]);
            }
        }

        return result;
    }

    /**
     * Clears the map, reuses the data structure by clearing it out.
     * It won't shrink the underlying array!
     */
    public void clear() {
        this.m_size_ = 0;
        Arrays.fill(m_data_, FREE_KEY);
    }

    private int getStartIndex(final Object key) {
        //key is not null here
        return key.hashCode() & m_mask_;
    }

    /** Return the least power of two greater than or equal to the specified value.
     *
     * 

Note that this function will return 1 when the argument is 0. * * @param x a long integer smaller than or equal to 262. * @return the least power of two greater than or equal to the specified value. */ public static long nextPowerOfTwo(long x) { if (x == 0) { return 1; } x--; x |= x >> 1; x |= x >> 2; x |= x >> 4; x |= x >> 8; x |= x >> 16; return (x | x >> 32) + 1; } /** Returns the least power of two smaller than or equal to 230 and larger than or equal to Math.ceil( expected / f ). * * @param expected the expected number of elements in a hash table. * @param f the load factor. * @return the minimum possible size for a backing array. * @throws IllegalArgumentException if the necessary size is larger than 230. */ public static int arraySize(final int expected, final float f) { final long s = Math.max(2, nextPowerOfTwo((long) Math.ceil(expected / f))); if (s > (1 << 30)) { throw new IllegalArgumentException("Too large (" + expected + " expected elements with load factor " + f + ")"); } return (int) s; } /** * We have to overwrite the import due to the use of static object as marker * * @param aInputStream the inputstream to read from * @throws IOException when the reading from the source fails * @throws ClassNotFoundException in case we cannot restore a class */ private void readObject(final ObjectInputStream aInputStream) throws ClassNotFoundException, IOException { // perform the default de-serialization first aInputStream.defaultReadObject(); // we have to restore order final Object[] srcData = Arrays.copyOf(this.m_data_, this.m_data_.length); // now, empty it the original map clear(); // sort things in, so we get a nice clean new map, this will // also cleanup what was previously a removed entry, we have not // kept that information anyway for (int i = 0; i < srcData.length; i += 2) { final Object key = srcData[i]; if (key != null) { final V value = (V) srcData[i + 1]; put((K) key, value); } } } /** * We have to overwrite the export due to the use of static object as marker * * @param aOutputStream the stream to write to * @throws IOException when a problem during writing occurs */ private void writeObject(final ObjectOutputStream aOutputStream) throws IOException { // we will remove all placeholder object references, // when putting it back together, we rebuild the map from scratch for (int i = 0; i < this.m_data_.length; i++) { final Object entry = this.m_data_[i]; if (entry == FREE_KEY || entry == REMOVED_KEY) { this.m_data_[i] = null; } } // perform the default serialization for all non-transient, non-static fields aOutputStream.defaultWriteObject(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy