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 int hash = System.identityHashCode(k); 096 final int numEntries = buffer.length / 2; 097 int index = hash % numEntries; 098 while (true) { 099 if (buffer[2 * index] == null) { 100 buffer[2 * index] = k; 101 buffer[1 + (2 * index)] = v; 102 numInserted++; 103 return; 104 } 105 index = (index + 1) % numEntries; 106 } 107 } 108 109 /** 110 * Add a new (key, value) mapping. 111 * 112 * Inserting a new (key, value) never overwrites a previous one. 113 * In other words, you can insert the same key multiple times and it will 114 * lead to multiple entries. 115 */ 116 public void put(K k, V v) { 117 Preconditions.checkNotNull(k); 118 if (buffer == null) { 119 realloc(DEFAULT_MAX_CAPACITY); 120 } else if (numInserted + 1 > capacity) { 121 realloc(capacity * 2); 122 } 123 putInternal(k, v); 124 } 125 126 private int getElementIndex(K k) { 127 if (buffer == null) { 128 return -1; 129 } 130 final int numEntries = buffer.length / 2; 131 int hash = System.identityHashCode(k); 132 int index = hash % numEntries; 133 int firstIndex = index; 134 do { 135 if (buffer[2 * index] == k) { 136 return index; 137 } 138 index = (index + 1) % numEntries; 139 } while (index != firstIndex); 140 return -1; 141 } 142 143 /** 144 * Retrieve a value associated with a given key. 145 */ 146 public V get(K k) { 147 int index = getElementIndex(k); 148 if (index < 0) { 149 return null; 150 } 151 return (V)buffer[1 + (2 * index)]; 152 } 153 154 /** 155 * Retrieve a value associated with a given key, and delete the 156 * relevant entry. 157 */ 158 public V remove(K k) { 159 int index = getElementIndex(k); 160 if (index < 0) { 161 return null; 162 } 163 V val = (V)buffer[1 + (2 * index)]; 164 buffer[2 * index] = null; 165 buffer[1 + (2 * index)] = null; 166 numInserted--; 167 return val; 168 } 169 170 public boolean isEmpty() { 171 return numInserted == 0; 172 } 173 174 public int numElements() { 175 return numInserted; 176 } 177 178 public int capacity() { 179 return capacity; 180 } 181 182 public interface Visitor<K, V> { 183 void accept(K k, V v); 184 } 185 186 /** 187 * Visit all key, value pairs in the IdentityHashStore. 188 */ 189 public void visitAll(Visitor<K, V> visitor) { 190 int length = buffer == null ? 0 : buffer.length; 191 for (int i = 0; i < length; i += 2) { 192 if (buffer[i] != null) { 193 visitor.accept((K)buffer[i], (V)buffer[i + 1]); 194 } 195 } 196 } 197 }