001/* 002 * Copyright (c) 2016-2017 Chris K Wensel <chris@wensel.net>. All Rights Reserved. 003 * Copyright (c) 2007-2017 Xplenty, Inc. All Rights Reserved. 004 * 005 * Project and contact information: http://www.cascading.org/ 006 * 007 * This file is part of the Cascading project. 008 * 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 */ 021 022package cascading.util.cache; 023 024import java.util.AbstractCollection; 025import java.util.AbstractSet; 026import java.util.Collection; 027import java.util.Iterator; 028import java.util.Map; 029import java.util.NoSuchElementException; 030import java.util.Set; 031 032import org.slf4j.Logger; 033import org.slf4j.LoggerFactory; 034 035/** 036 * DirectMappedCache is an implementation of the {@link cascading.util.cache.CascadingCache} interface following the semantics of 037 * http://en.wikipedia.org/wiki/CPU_cache#Direct-mapped_cache. The Cache is backed by an array that stays constant in size. 038 * <p> 039 * Unlike other implementation of a Map a hash collision will lead to the entry being overwritten. 040 * The {@link CacheEvictionCallback} is called with the entry that will be overwritten. 041 * <p> 042 * Use this cache if the keys are arriving in a random if not uniformly distributed order in order to reduce the number 043 * of hash and equality comparisons. 044 * <p> 045 * If duplicate keys are clustered near each other in the incoming tuple stream, consider the {@link cascading.util.cache.LRUHashMapCache} cache 046 * instead. 047 * <p> 048 * DirectMappedCache does not permit {@code null} keys nor {@code null} values 049 * 050 * @see cascading.pipe.assembly.Unique 051 * @see cascading.pipe.assembly.AggregateBy 052 * @see DirectMappedCacheFactory 053 * @see LRUHashMapCacheFactory 054 * @see LRUHashMapCache 055 */ 056public final class DirectMappedCache<Key, Value> implements CascadingCache<Key, Value> 057 { 058 /** logger */ 059 private static final Logger LOG = LoggerFactory.getLogger( DirectMappedCache.class ); 060 061 /** size of the backing array. */ 062 private int capacity; 063 064 /** number of actual key in the map. */ 065 private int actualSize = 0; 066 067 /** array used for storing the entries */ 068 private Entry<Key, Value>[] elements; 069 070 private long collisions = 0; 071 072 private long putCalls = 0; 073 074 public boolean initialized = false; 075 076 /** callback that is called whenever an entry is overwritten or removed from the cache. */ 077 private CacheEvictionCallback evictionCallBack = CacheEvictionCallback.NULL; 078 079 @Override 080 public int size() 081 { 082 return actualSize; 083 } 084 085 @Override 086 public boolean isEmpty() 087 { 088 return actualSize < 1; 089 } 090 091 @Override 092 public boolean containsKey( Object key ) 093 { 094 if( key == null ) 095 throw new IllegalArgumentException( "null keys are not permitted" ); 096 097 return get( key ) != null; 098 } 099 100 @Override 101 public boolean containsValue( Object value ) 102 { 103 if( value == null ) 104 throw new IllegalArgumentException( "value cannot be null" ); 105 106 Iter<Entry<Key, Value>> iter = new Iter<Entry<Key, Value>>( elements ); 107 108 while( iter.hasNext() ) 109 { 110 Value current = iter.next().getValue(); 111 if( current.equals( value ) ) 112 return true; 113 } 114 115 return false; 116 } 117 118 @Override 119 public Value get( Object key ) 120 { 121 int index = index( key ); 122 Entry<Key, Value> existing = elements[ index ]; 123 124 if( existing == null || !key.equals( existing.getKey() ) ) 125 return null; 126 127 return existing.getValue(); 128 } 129 130 @Override 131 public Value put( Key key, Value value ) 132 { 133 if( key == null ) 134 throw new IllegalArgumentException( "key cannot be null" ); 135 136 if( value == null ) 137 throw new IllegalArgumentException( "value cannot be null" ); 138 139 putCalls++; 140 141 Value previous = null; 142 int index = index( key ); 143 Entry<Key, Value> existing = elements[ index ]; 144 145 if( existing != null ) 146 { 147 previous = existing.getValue(); 148 collisions++; 149 evictionCallBack.evict( existing ); 150 } 151 else 152 actualSize++; 153 154 elements[ index ] = new CacheEntry<Key, Value>( key, value ); 155 156 if( putCalls % getCapacity() == 0 ) 157 { 158 Runtime runtime = Runtime.getRuntime(); 159 long freeMem = runtime.freeMemory() / 1024 / 1024; 160 long maxMem = runtime.maxMemory() / 1024 / 1024; 161 long totalMem = runtime.totalMemory() / 1024 / 1024; 162 LOG.info( "mem on flush (mb), free: " + freeMem + ", total: " + totalMem + ", max: " + maxMem ); 163 LOG.info( "capacity={}, puts={}, collisions={}, fill factor={}%", getCapacity(), putCalls, collisions, 164 ( (double) getCapacity() / actualSize ) * 100 ); 165 float percent = (float) totalMem / (float) maxMem; 166 if( percent < 0.80F ) 167 LOG.info( "total mem is {}% of max mem, to better utilize unused memory consider increasing the cache size", (int) ( percent * 100.0F ) ); 168 } 169 170 return previous; 171 } 172 173 private int index( Object key ) 174 { 175 return Math.abs( key.hashCode() % capacity ); 176 } 177 178 @Override 179 public Value remove( Object key ) 180 { 181 if( key == null ) 182 throw new IllegalArgumentException( "key cannot be null" ); 183 184 int index = index( key ); 185 Entry<Key, Value> existing = elements[ index ]; 186 187 if( existing == null || !existing.getKey().equals( key ) ) 188 return null; 189 190 elements[ index ] = null; 191 actualSize--; 192 evictionCallBack.evict( existing ); 193 194 return existing.getValue(); 195 } 196 197 @Override 198 public void putAll( Map<? extends Key, ? extends Value> m ) 199 { 200 for( Entry<? extends Key, ? extends Value> entry : m.entrySet() ) 201 put( entry.getKey(), entry.getValue() ); 202 } 203 204 @Override 205 public void clear() 206 { 207 elements = new CacheEntry[ capacity ]; 208 actualSize = 0; 209 } 210 211 @Override 212 public Set<Key> keySet() 213 { 214 return new KeySetView<Key>( new SetView<Map.Entry<Key, ?>>( elements, actualSize ) ); 215 } 216 217 @Override 218 public Collection<Value> values() 219 { 220 return new ValueCollection( new SetView<Map.Entry<?, Value>>( elements, actualSize ) ); 221 } 222 223 @Override 224 public Set<Map.Entry<Key, Value>> entrySet() 225 { 226 return new SetView<Entry<Key, Value>>( elements, actualSize ); 227 } 228 229 @Override 230 public int getCapacity() 231 { 232 return capacity; 233 } 234 235 @Override 236 public void setCacheEvictionCallback( CacheEvictionCallback cacheEvictionCallback ) 237 { 238 if( initialized ) 239 throw new IllegalStateException( "cannot set callback after initialization" ); 240 241 this.evictionCallBack = cacheEvictionCallback; 242 } 243 244 @Override 245 public void setCapacity( int capacity ) 246 { 247 if( initialized ) 248 throw new IllegalArgumentException( "cannot set size after initialization" ); 249 250 this.capacity = capacity; 251 } 252 253 @Override 254 public void initialize() 255 { 256 if( capacity < 1 ) 257 throw new IllegalStateException( "capacity must be larger than 0" ); 258 259 if( evictionCallBack == null ) 260 throw new IllegalStateException( "evictionCallback cannot be null" ); 261 262 elements = new CacheEntry[ capacity ]; 263 initialized = true; 264 } 265 266 static class CacheEntry<Key, Value> implements Entry<Key, Value> 267 { 268 private final Key key; 269 private Value value; 270 271 CacheEntry( Key key, Value value ) 272 { 273 this.key = key; 274 this.value = value; 275 } 276 277 @Override 278 public Key getKey() 279 { 280 return key; 281 } 282 283 @Override 284 public Value getValue() 285 { 286 return value; 287 } 288 289 @Override 290 public Value setValue( Value value ) 291 { 292 Value oldValue = this.value; 293 this.value = value; 294 return oldValue; 295 } 296 297 @Override 298 public boolean equals( Object object ) 299 { 300 if( this == object ) 301 return true; 302 if( object == null || getClass() != object.getClass() ) 303 return false; 304 305 Entry that = (Entry) object; 306 307 if( key != null ? !key.equals( that.getKey() ) : that.getKey() != null ) 308 return false; 309 310 if( value != null ? !value.equals( that.getValue() ) : that.getValue() != null ) 311 return false; 312 313 return true; 314 } 315 316 @Override 317 public int hashCode() 318 { 319 int result = key != null ? key.hashCode() : 0; 320 result = 31 * result + ( value != null ? value.hashCode() : 0 ); 321 return result; 322 } 323 324 @Override 325 public String toString() 326 { 327 return "CacheEntry{" + 328 "key=" + key + 329 ", value=" + value + 330 '}'; 331 } 332 } 333 334 class SetView<T> extends AbstractSet<T> 335 { 336 private final T[] elements; 337 private final int size; 338 private final Iter<T> iter; 339 340 SetView( T[] elements, int size ) 341 { 342 this.elements = elements; 343 this.size = size; 344 this.iter = new Iter<T>( elements ); 345 } 346 347 @Override 348 public int size() 349 { 350 return size; 351 } 352 353 @Override 354 public Iterator<T> iterator() 355 { 356 return new Iter<T>( elements ); 357 } 358 359 @Override 360 public boolean containsAll( Collection<?> c ) 361 { 362 for( Object object : c ) 363 { 364 if( !contains( object ) ) 365 return false; 366 } 367 368 return true; 369 } 370 371 @Override 372 public boolean addAll( Collection<? extends T> c ) 373 { 374 throw new UnsupportedOperationException( "SetView is read only." ); 375 } 376 377 @Override 378 public boolean retainAll( Collection<?> c ) 379 { 380 throw new UnsupportedOperationException( "SetView is read only." ); 381 } 382 383 @Override 384 public boolean removeAll( Collection<?> c ) 385 { 386 throw new UnsupportedOperationException( "SetView is read only." ); 387 } 388 389 @Override 390 public void clear() 391 { 392 throw new UnsupportedOperationException( "SetView is read only." ); 393 } 394 } 395 396 static class Iter<T> implements Iterator<T> 397 { 398 private final T[] elements; 399 private T nextElement; 400 int currentIndex = -1; 401 402 public Iter( T[] elements ) 403 { 404 this.elements = elements; 405 this.nextElement = findNext(); 406 } 407 408 private T findNext() 409 { 410 while( currentIndex < elements.length - 1 ) 411 { 412 currentIndex++; 413 414 if( elements[ currentIndex ] != null ) 415 return elements[ currentIndex ]; 416 } 417 418 return null; 419 } 420 421 @Override 422 public boolean hasNext() 423 { 424 return nextElement != null; 425 } 426 427 @Override 428 public T next() 429 { 430 if( !hasNext() ) 431 throw new NoSuchElementException(); 432 433 T current = nextElement; 434 435 nextElement = findNext(); 436 437 return current; 438 } 439 440 @Override 441 public void remove() 442 { 443 throw new UnsupportedOperationException( "Not supported" ); 444 } 445 446 public void reset() 447 { 448 currentIndex = -1; 449 nextElement = findNext(); 450 } 451 } 452 453 static class KeySetView<Key> extends AbstractSet<Key> 454 { 455 private final Set<Map.Entry<Key, ?>> parent; 456 457 public KeySetView( Set<Map.Entry<Key, ?>> parent ) 458 { 459 this.parent = parent; 460 } 461 462 @Override 463 public Iterator<Key> iterator() 464 { 465 return new KeyIterator<Key>( parent.iterator() ); 466 } 467 468 @Override 469 public int size() 470 { 471 return parent.size(); 472 } 473 } 474 475 static class ValueCollection<Value> extends AbstractCollection<Value> 476 { 477 private Set<Entry<?, Value>> parent; 478 479 ValueCollection( Set<Entry<?, Value>> parent ) 480 { 481 this.parent = parent; 482 } 483 484 @Override 485 public Iterator<Value> iterator() 486 { 487 return new ValueIterator<Value>( parent.iterator() ); 488 } 489 490 @Override 491 public int size() 492 { 493 return parent.size(); 494 } 495 } 496 497 static class KeyIterator<Key> implements Iterator<Key> 498 { 499 private final Iterator<Map.Entry<Key, ?>> parent; 500 501 public KeyIterator( Iterator<Map.Entry<Key, ?>> parent ) 502 { 503 this.parent = parent; 504 } 505 506 @Override 507 public Key next() 508 { 509 return parent.next().getKey(); 510 } 511 512 @Override 513 public boolean hasNext() 514 { 515 return parent.hasNext(); 516 } 517 518 @Override 519 public void remove() 520 { 521 parent.remove(); 522 } 523 } 524 525 static class ValueIterator<Value> implements Iterator<Value> 526 { 527 private final Iterator<Map.Entry<?, Value>> parent; 528 529 public ValueIterator( Iterator<Map.Entry<?, Value>> parent ) 530 { 531 this.parent = parent; 532 } 533 534 @Override 535 public Value next() 536 { 537 return parent.next().getValue(); 538 } 539 540 @Override 541 public boolean hasNext() 542 { 543 return parent.hasNext(); 544 } 545 546 @Override 547 public void remove() 548 { 549 parent.remove(); 550 } 551 } 552 }