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 019package org.apache.hadoop.util; 020 021import org.apache.hadoop.classification.InterfaceAudience; 022import org.apache.hadoop.classification.InterfaceStability; 023 024import 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") 048public 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}