001package org.cache2k.impl; 002 003/* 004 * #%L 005 * cache2k core package 006 * %% 007 * Copyright (C) 2000 - 2015 headissue GmbH, Munich 008 * %% 009 * This program is free software: you can redistribute it and/or modify 010 * it under the terms of the GNU General Public License as 011 * published by the Free Software Foundation, either version 3 of the 012 * License, or (at your option) any later version. 013 * 014 * This program is distributed in the hope that it will be useful, 015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 017 * GNU General Public License for more details. 018 * 019 * You should have received a copy of the GNU General Public 020 * License along with this program. If not, see 021 * <http://www.gnu.org/licenses/gpl-3.0.html>. 022 * #L% 023 */ 024 025import java.lang.reflect.Array; 026 027/** 028 * Fast hash table implementation. Why we don't want to use the Java HashMap implementation: 029 * 030 * We need to move the entries back and forth between different hashes. 031 * If we do this, each time an entry object needs to be allocated and deallocated. 032 * 033 * Second, we need to do countermeasures, if a key changes its value and hashcode 034 * during its use in the cache. Although it is a user error and therefore not 035 * our primary concern, the effects can be very curious and hard to find. 036 * 037 * Third, we want to have use the hash partly unsynchronized, so we should know the 038 * implementation details. 039 * 040 * Fourth, we can leave out "details" of a general hash table, like shrinking. Our hash table 041 * only expands. 042 * 043 * Access needs to be public, since we want to access the hash primitives from classes 044 * in another package. 045 */ 046public class Hash<E extends Entry> { 047 048 public int size = 0; 049 public int maxFill = 0; 050 private int suppressExpandCount; 051 052 public static int index(Entry[] _hashTable, int _hashCode) { 053 return _hashCode & (_hashTable.length - 1); 054 } 055 056 057 public static <E extends Entry> E lookup(E[] _hashTable, Object key, int _hashCode) { 058 int i = index(_hashTable, _hashCode); 059 E e = _hashTable[i]; 060 while (e != null) { 061 if (e.hashCode == _hashCode && 062 key.equals(e.key)) { 063 return e; 064 } 065 e = (E) e.another; 066 } 067 return null; 068 } 069 070 public static boolean contains(Entry[] _hashTable, Object key, int _hashCode) { 071 int i = index(_hashTable, _hashCode); 072 Entry e = _hashTable[i]; 073 while (e != null) { 074 if (e.hashCode == _hashCode && 075 (key == e.key || key.equals(e.key))) { 076 return true; 077 } 078 e = e.another; 079 } 080 return false; 081 } 082 083 084 public static void insertWoExpand(Entry[] _hashTable, Entry e) { 085 int i = index(_hashTable, e.hashCode); 086 e.another = _hashTable[i]; 087 _hashTable[i] = e; 088 } 089 090 private static void rehash(Entry[] a1, Entry[] a2) { 091 for (Entry e : a1) { 092 while (e != null) { 093 Entry _next = e.another; 094 insertWoExpand(a2, e); 095 e = _next; 096 } 097 } 098 } 099 100 private static <E extends Entry> E[] expandHash(E[] _hashTable) { 101 E[] a2 = (E[]) Array.newInstance( 102 _hashTable.getClass().getComponentType(), 103 _hashTable.length * 2); 104 rehash(_hashTable, a2); 105 return a2; 106 } 107 108 public static void calcHashCollisionInfo(BaseCache.CollisionInfo inf, Entry[] _hashTable) { 109 for (Entry e : _hashTable) { 110 if (e != null) { 111 e = e.another; 112 if (e != null) { 113 inf.collisionSlotCnt++; 114 int _size = 1; 115 while (e != null) { 116 inf.collisionCnt++; 117 e = e.another; 118 _size++; 119 } 120 if (inf.longestCollisionSize < _size) { 121 inf.longestCollisionSize = _size; 122 } 123 } 124 } 125 } 126 127 } 128 129 /** 130 * Count the entries in the hash table, by scanning through the hash table. 131 * This is used for integrity checks. 132 */ 133 public static int calcEntryCount(Entry[] _hashTable) { 134 int _entryCount = 0; 135 for (Entry e : _hashTable) { 136 while (e != null) { 137 _entryCount++; 138 e = e.another; 139 } 140 } 141 return _entryCount; 142 } 143 144 public boolean remove(Entry[] _hashTable, Entry _entry) { 145 int i = index(_hashTable, _entry.hashCode); 146 Entry e = _hashTable[i]; 147 if (e == _entry) { 148 _hashTable[i] = e.another; 149 size--; 150 return true; 151 } 152 while (e != null) { 153 Entry _another = e.another; 154 if (_another == _entry) { 155 e.another = _another.another; 156 size--; 157 return true; 158 } 159 e = _another; 160 } 161 return false; 162 } 163 164 /** 165 * Remove entry from the hash. We never shrink the hash table, so 166 * the array keeps identical. After this remove operation the entry 167 * object may be inserted in another hash. 168 */ 169 public E remove(E[] _hashTable, Object key, int hc) { 170 int i = index(_hashTable, hc); 171 Entry e = _hashTable[i]; 172 if (e == null) { 173 return null; 174 } 175 if (e.hashCode == hc && key.equals(e.key)) { 176 _hashTable[i] = (E) e.another; 177 size--; 178 return (E) e; 179 } 180 Entry _another = e.another; 181 while (_another != null) { 182 if (_another.hashCode == hc && key.equals(_another.key)) { 183 e.another = _another.another; 184 size--; 185 return (E) _another; 186 } 187 e = _another; 188 _another = _another.another; 189 } 190 return null; 191 } 192 193 194 public E[] insert(E[] _hashTable, Entry _entry) { 195 size++; 196 insertWoExpand(_hashTable, _entry); 197 synchronized (this) { 198 if (size >= maxFill && suppressExpandCount == 0) { 199 maxFill = maxFill * 2; 200 return expandHash(_hashTable); 201 } 202 return _hashTable; 203 } 204 } 205 206 /** 207 * Usage/reference counter for iterations to suspend expand 208 * until the iteration finished. This is needed for correctness 209 * of the iteration, if an expand is done during the iteration 210 * process, the iterations returns duplicate entries or not 211 * all entries. 212 * 213 * <p>Failing to operate the increment/decrement in balance will 214 * mean that hash table expands are blocked forever, which is a 215 * serious error condition. Typical problems arise by thrown 216 * exceptions during an iteration. 217 */ 218 public synchronized void incrementSuppressExpandCount() { 219 suppressExpandCount++; 220 } 221 222 public synchronized void decrementSuppressExpandCount() { 223 suppressExpandCount--; 224 } 225 226 /** 227 * The cache with this hash was cleared and the hash table is no longer 228 * in used. Signal to iterations to abort. 229 */ 230 public void cleared() { 231 if (size >= 0) { 232 size = -1; 233 } 234 } 235 236 /** 237 * Cache was closed. Inform operations/iterators on the hash. 238 */ 239 public void close() { size = -2; } 240 241 /** 242 * Operations should terminate 243 */ 244 public boolean isCleared() { return size == -1; } 245 246 /** 247 * Operations should terminate and throw a {@link CacheClosedException} 248 */ 249 public boolean isClosed() { return size == -2; } 250 251 public boolean shouldAbort() { return size < 0; } 252 253 public E[] init(Class<E> _entryType) { 254 size = 0; 255 maxFill = BaseCache.TUNABLE.initialHashSize * BaseCache.TUNABLE.hashLoadPercent / 100; 256 return (E[]) Array.newInstance(_entryType, BaseCache.TUNABLE.initialHashSize); 257 } 258 259}