001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package org.apache.hadoop.util;
020    
021    import org.apache.hadoop.classification.InterfaceAudience;
022    import org.apache.hadoop.classification.InterfaceStability;
023    
024    import com.google.common.base.Preconditions;
025    
026    /**
027     * The IdentityHashStore stores (key, value) mappings in an array.
028     * It is similar to java.util.HashTable, but much more lightweight.
029     * Neither inserting nor removing an element ever leads to any garbage
030     * getting created (assuming the array doesn't need to be enlarged).
031     *
032     * Unlike HashTable, it compares keys using
033     * {@link System#identityHashCode(Object)} and the identity operator.
034     * This is useful for types like ByteBuffer which have expensive hashCode
035     * and equals operators.
036     *
037     * We use linear probing to resolve collisions.  This avoids the need for
038     * the overhead of linked list data structures.  It also means that it is
039     * expensive to attempt to remove an element that isn't there, since we
040     * have to look at the entire array to be sure that it doesn't exist.
041     *
042     * @param <K>    The key type to use.
043     * @param <V>    THe value type to use.
044     */
045    @InterfaceAudience.Private
046    @InterfaceStability.Evolving
047    @SuppressWarnings("unchecked")
048    public final class IdentityHashStore<K, V> {
049      /**
050       * Even elements are keys; odd elements are values.
051       * The array has size 1 + Math.pow(2, capacity).
052       */
053      private Object buffer[];
054    
055      private int numInserted = 0;
056    
057      private int capacity;
058    
059      /**
060       * The default maxCapacity value to use.
061       */
062      private static final int DEFAULT_MAX_CAPACITY = 2;
063    
064      public IdentityHashStore(int capacity) {
065        Preconditions.checkArgument(capacity >= 0);
066        if (capacity == 0) {
067          this.capacity = 0;
068          this.buffer = null;
069        } else {
070          // Round the capacity we need up to a power of 2.
071          realloc((int)Math.pow(2,
072              Math.ceil(Math.log(capacity) / Math.log(2))));
073        }
074      }
075    
076      private void realloc(int newCapacity) {
077        Preconditions.checkArgument(newCapacity > 0);
078        Object prevBuffer[] = buffer;
079        this.capacity = newCapacity;
080        // Each element takes two array slots -- one for the key, 
081        // and another for the value.  We also want a load factor 
082        // of 0.50.  Combine those together and you get 4 * newCapacity.
083        this.buffer = new Object[4 * newCapacity];
084        this.numInserted = 0;
085        if (prevBuffer != null) {
086          for (int i = 0; i < prevBuffer.length; i += 2) {
087            if (prevBuffer[i] != null) {
088              putInternal(prevBuffer[i], prevBuffer[i + 1]);
089            }
090          }
091        }
092      }
093    
094      private void putInternal(Object k, Object v) {
095        final int hash = System.identityHashCode(k);
096        final int numEntries = buffer.length >>  1;
097        //computing modulo with the assumption buffer.length is power of 2
098        int index = hash & (numEntries-1);
099        while (true) {
100          if (buffer[2 * index] == null) {
101            buffer[2 * index] = k;
102            buffer[1 + (2 * index)] = v;
103            numInserted++;
104            return;
105          }
106          index = (index + 1) % numEntries;
107        }
108      }
109    
110      /**
111       * Add a new (key, value) mapping.
112       *
113       * Inserting a new (key, value) never overwrites a previous one.
114       * In other words, you can insert the same key multiple times and it will
115       * lead to multiple entries.
116       */
117      public void put(K k, V v) {
118        Preconditions.checkNotNull(k);
119        if (buffer == null) {
120          realloc(DEFAULT_MAX_CAPACITY);
121        } else if (numInserted + 1 > capacity) {
122          realloc(capacity * 2);
123        }
124        putInternal(k, v);
125      }
126    
127      private int getElementIndex(K k) {
128        if (buffer == null) {
129          return -1;
130        }
131        final int numEntries = buffer.length >> 1;
132        final int hash = System.identityHashCode(k);
133        //computing modulo with the assumption buffer.length is power of 2
134        int index = hash & (numEntries -1);
135        int firstIndex = index;
136        do {
137          if (buffer[2 * index] == k) {
138            return index;
139          }
140          index = (index + 1) % numEntries;
141        } while (index != firstIndex);
142        return -1;
143      }
144    
145      /**
146       * Retrieve a value associated with a given key.
147       */
148      public V get(K k) {
149        int index = getElementIndex(k);
150        if (index < 0) {
151          return null;
152        }
153        return (V)buffer[1 + (2 * index)];
154      }
155    
156      /**
157       * Retrieve a value associated with a given key, and delete the
158       * relevant entry.
159       */
160      public V remove(K k) {
161        int index = getElementIndex(k);
162        if (index < 0) {
163          return null;
164        }
165        V val = (V)buffer[1 + (2 * index)];
166        buffer[2 * index] = null;
167        buffer[1 + (2 * index)] = null;
168        numInserted--;
169        return val;
170      }
171    
172      public boolean isEmpty() {
173        return numInserted == 0;
174      }
175    
176      public int numElements() {
177        return numInserted;
178      }
179    
180      public int capacity() {
181        return capacity;
182      }
183    
184      public interface Visitor<K, V> {
185        void accept(K k, V v);
186      }
187    
188      /**
189       * Visit all key, value pairs in the IdentityHashStore.
190       */
191      public void visitAll(Visitor<K, V> visitor) {
192        int length = buffer == null ? 0 : buffer.length;
193        for (int i = 0; i < length; i += 2) {
194          if (buffer[i] != null) {
195            visitor.accept((K)buffer[i], (V)buffer[i + 1]);
196          }
197        }
198      }
199    }