org.apache.niolex.commons.hash.DoubleHash.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.niolex.commons.hash.DoubleHash.java

Source

/**
 * DoubleHash.java
 *
 * Copyright 2013 the original author or authors.
 *
 * We 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.niolex.commons.hash;

import java.util.Collection;

import org.apache.niolex.commons.bean.Pair;

import com.google.common.hash.Funnel;
import com.google.common.hash.HashCode;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;

/**
 * The DoubleHash is to replace the <code>ConsistentHash</code> in some conditions.
 * The <code>ConsistentHash</code> is using the random hash ring, so there
 * are some kind of uncertainty. In order to minimize this uncertainty, we need
 * to add many replicas for one node.<br>
 *
 * This DoubleHash is very simple and has no uncertainty. We use two independent hash functions to
 * find two candidates. Although we can only provide two candidates, but it is enough in most of the times.<br>
 *
 * We guarantee that the two candidates returned is not the same node.<br>
 *
 * User can add and remove nodes at runtime dynamically. We use copy on write to remove the need of lock.
 *
 * @param <T> the server node type
 * @author <a href="mailto:xiejiyun@foxmail.com">Xie, Jiyun</a>
 * @version 1.0.0
 * @since 2013-5-25
 */
public class DoubleHash<T> {

    // the primary hash function
    private final HashFunction primary;
    // the secondary hash function
    private final HashFunction secondary;
    // the server node array
    private volatile Object[] nodeArray;

    /**
     * The only * Constructor to create this object. All Parameters must be filled.
     *
     * @param primary the primary hash function
     * @param secondary the secondary hash function
     * @param nodeList the server node list
     */
    public DoubleHash(HashFunction primary, HashFunction secondary, Collection<T> nodeList) {
        super();
        this.primary = primary;
        this.secondary = secondary;
        this.nodeArray = nodeList.toArray();
    }

    /**
     * Add this node into the candidate list.<br>
     * We will use Guava's consistent hash method, so only 1/n hash values will be affected by
     * adding new node.
     *
     * @param node the node to be added
     */
    public synchronized void add(T node) {
        Object[] tmpArray = new Object[this.nodeArray.length + 1];
        System.arraycopy(this.nodeArray, 0, tmpArray, 0, this.nodeArray.length);
        tmpArray[this.nodeArray.length] = node;
        this.nodeArray = tmpArray;
    }

    /**
     * Remove the first occurrence of this node from the candidate list at runtime.
     * the remove of last node will cause 1/n, and others 2/n - 1/n^2 hash values be affected.<br>
     *
     * We will use Guava's consistent hash method, but there is no hash ring, so lots of nodes will
     * be affected by remove node from the hash list. In order to minimize the effect, we always replace
     * the target node with the last node, and remove the last slot to keep the node list stable.<br>
     *
     * So according to Guava's consistent hash method, there will be 1/n hash values affected by remove the last
     * node, and there is another 1/n hash values affected by the replace. In total we have 2/n hash values affected.<br>
     *
     * It's better to keep it there than remove it if possible.
     *
     * @param node the node to be removed
     */
    public synchronized void remove(T node) {
        int i = 0;
        final int length = this.nodeArray.length;
        for (; i < length; ++i) {
            if (node.equals(nodeArray[i])) {
                break;
            }
        }

        if (i != length) {
            Object[] tmpArray = new Object[length - 1];
            // First we copy all the old array into new array except the last one.
            System.arraycopy(this.nodeArray, 0, tmpArray, 0, length - 1);
            if (i != length - 1) {
                // Then we replace target with last node.
                tmpArray[i] = this.nodeArray[length - 1];
            }
            this.nodeArray = tmpArray;
        }
    }

    /**
     * @return the size of the candidate list.
     */
    public int size() {
        return this.nodeArray.length;
    }

    /**
     * Get the pair of server nodes by this key. We guarantee the first and second node are
     * not the same.<br>
     *
     * @param key the key to be hashed
     * @return the pair of server nodes
     */
    public Pair<T, T> getPairNodes(String key) {
        return getPairNodes(primary.hashString(key), secondary.hashString(key));
    }

    /**
     * Get the pair of server nodes by this key. We guarantee the first and second node are
     * not the same.<br>
     *
     * @param key the key to be hashed
     * @return the pair of server nodes
     */
    public Pair<T, T> getPairNodes(long key) {
        return getPairNodes(primary.hashLong(key), secondary.hashLong(key));
    }

    /**
     * Get the pair of server nodes by this key. We guarantee the first and second node are
     * not the same.<br>
     *
     * @param key the key to be hashed
     * @return the pair of server nodes
     */
    public Pair<T, T> getPairNodes(int key) {
        return getPairNodes(primary.hashInt(key), secondary.hashInt(key));
    }

    /**
     * Get the pair of server nodes by this key. We guarantee the first and second node are
     * not the same.<br>
     *
     * @param key the key to be hashed
     * @param funnel the funnel to be used
     * @return the pair of server nodes
     */
    public <K> Pair<T, T> getPairNodes(K key, Funnel<? super K> funnel) {
        return getPairNodes(primary.hashObject(key, funnel), secondary.hashObject(key, funnel));
    }

    /**
     * Get the pair of server nodes by these hash codes. We guarantee the first and second node are
     * not the same.<br>
     *
     * @param primaryHashCode the primary hash code
     * @param secondaryHashCode the secondary hash code
     * @return the pair of server nodes
     */
    protected Pair<T, T> getPairNodes(HashCode primaryHashCode, HashCode secondaryHashCode) {
        final Object[] tmpArray = this.nodeArray;
        final int length = tmpArray.length;
        /**
         * The node array
         * ---------------------------------------
         *                    ^
         *      The idx1 in any of the positions
         * ------------------- -------------------
         *      The idx2 will be in the remains
         * So the length must - 1
         */
        final int idx1 = Hashing.consistentHash(primaryHashCode, length);
        int idx2 = Hashing.consistentHash(secondaryHashCode, length - 1);
        if (idx2 >= idx1) {
            ++idx2;
        }

        @SuppressWarnings("unchecked")
        Pair<T, T> r = Pair.<T, T>create((T) tmpArray[idx1], (T) tmpArray[idx2]);
        return r;
    }

}