com.gemstone.gemfire.internal.ObjIdMap Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gemfire-core Show documentation
Show all versions of gemfire-core Show documentation
SnappyData store based off Pivotal GemFireXD
/*
* Copyright (c) 2010-2015 Pivotal Software, Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you
* may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License. See accompanying
* LICENSE file.
*/
package com.gemstone.gemfire.internal;
import com.gemstone.gemfire.internal.i18n.LocalizedStrings;
import java.lang.Math;
import java.lang.ref.*;
/**
* An ObjIdMap
maps GemFire object ids to an
* Object
. This is an optimization because using a
* {@link java.util.HashMap} for this purposed proved to be too slow
* because of all of the {@link Integer}s that had to be created.
*/
public class ObjIdMap {
/** The contents of the map */
protected Entry[] table;
/** The total number of mappings in the map */
private int count;
/** Once the number of mappings in the map exceeds the threshold,
* the map is rehashed. The threshold is the
* (capacity * loadFactor). capacity is table.length
*/
private int threshold;
/** The load factor of the map */
private float loadFactor;
public final Object rehashLock = new Object();
//////////////////// Constructors ////////////////////
/**
* Creates a new, empty map with the given initial capacity (number
* of buckets) and load factor.
*/
public ObjIdMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0) {
throw new IllegalArgumentException(LocalizedStrings.ObjIdMap_ILLEGAL_INITIAL_CAPACITY_0.toLocalizedString(Integer.valueOf(initialCapacity)));
}
if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
throw new IllegalArgumentException(LocalizedStrings.ObjIdMap_ILLEGAL_LOAD_FACTOR_0.toLocalizedString(new Float(loadFactor)));
}
if (initialCapacity==0) {
initialCapacity = 1;
}
this.loadFactor = loadFactor;
table = new Entry[initialCapacity];
threshold = (int)(initialCapacity * loadFactor);
}
/**
* Creates a new, empty map with the default initial capacity (11
* buckets) and load factor (0.75).
*/
public ObjIdMap() {
this(11, 0.75f);
}
/**
* Create a new map which will contain all the contents of the oldMap
* and then add the specified key and value.
*/
public ObjIdMap(ObjIdMap oldMap, int addKey, Object addValue) {
this.loadFactor = oldMap.loadFactor;
// we do a +2 to make enough room for one more entry
// since we think the loadFactory is 0.5
rehash(oldMap.table, oldMap.count, oldMap.count + 2);
put(addKey, addValue);
}
/**
* Create a new map which will contain all the contents of the oldMap.
*/
public ObjIdMap(ObjIdMap oldMap) {
this.table = new Entry[oldMap.table.length];
System.arraycopy(oldMap.table, 0, this.table, 0, this.table.length);
this.count = oldMap.count;
this.threshold = oldMap.threshold;
this.loadFactor = oldMap.loadFactor;
}
//////////////////// Instance Methods ////////////////////
/**
* Returns the number of mappings in this map
*/
public int size() {
return this.count;
}
/**
* Returns true
if this map contains a mapping for the
* given key.
*
* @throws IllegalArgumentException
* key
is less than zero
*/
public boolean containsKey(int key) {
Entry[] table = this.table;
int bucket = Math.abs(key) % table.length;
for (Entry e = table[bucket]; e != null; e = e.next) {
if (e.key == key) {
return true;
}
}
return false;
}
/**
* Returns the object to which the given key is mapped. If no
* object is mapped to the given key, null
is returned.
*
* @throws IllegalArgumentException
* key
is less than zero
*/
public Object get(int key) {
Entry[] table = this.table;
int bucket = Math.abs(key) % table.length;
for (Entry e = table[bucket]; e != null; e = e.next) {
if (e.key == key) {
return e.value;
}
}
return null;
}
/**
* Rehashes this map into a new map with a large number of buckets.
* It is called when the number of entries in the map exceeds the
* capacity and load factor.
*/
private void rehash() {
rehash(this.table, this.count, this.count * 2 + 1);
}
private void rehash(Entry[] oldMap, int newCount, int newCapacity) {
int oldCapacity = oldMap.length;
Entry newMap[] = new Entry[newCapacity];
synchronized (rehashLock) {
for (int i = oldCapacity ; i-- > 0 ;) {
for (Entry old = oldMap[i] ; old != null ; ) {
Entry e = old;
old = old.next;
if (e.value != null && e.value instanceof WeakReference) {
WeakReference r = (WeakReference)e.value;
if (r.get() == null) {
// don't copy this one into the new table since its value was gc'd
newCount--;
continue;
}
}
int index = Math.abs(e.key) % newCapacity;
e.next = newMap[index];
newMap[index] = e;
}
}
threshold = (int)(newCapacity * loadFactor);
count = newCount;
table = newMap;
}
}
/**
* Creates a mapping between the given key (object id) and an
* object. Returns the previous value, or null
if
* there was none.
*
* @throws IllegalArgumentException
* key
is less than zero
*/
public Object put(int key, Object value) {
// Is the key already in the table?
int bucket = Math.abs(key) % table.length;
for (Entry e = table[bucket]; e != null; e = e.next) {
if (e.key == key) {
Object old = e.value;
e.value = value;
return old;
}
}
// Adjust the table, if necessary
if (this.count >= this.threshold) {
rehash();
// table = this.table; assignment has no effect
bucket = Math.abs(key) % table.length;
}
Entry e = new Entry();
e.key = key;
e.value = value;
e.next = table[bucket];
table[bucket] = e;
count++;
return null;
}
/**
* Removes the mapping for the given key. Returns the object to
* which the key was mapped, or null
otherwise.
*/
public Object remove(int key) {
Entry[] table = this.table;
int bucket = Math.abs(key) % table.length;
for (Entry e = table[bucket], prev = null; e != null;
prev = e, e = e.next) {
if (key == e.key) {
if (prev != null)
prev.next = e.next;
else
table[bucket] = e.next;
count--;
Object oldValue = e.value;
e.value = null;
return oldValue;
}
}
return null;
}
/**
* Returns all of the objects in the map
*/
public Object[] values() {
Object[] values = new Object[this.size()];
Entry[] table = this.table;
int i = 0;
for (int bucket = 0; bucket < table.length; bucket++) {
for (Entry e = table[bucket]; e != null; e = e.next) {
values[i++] = e.value;
}
}
return values;
}
/**
* Returns an iterator over the {@link Entry}s of this map. Note
* that this iterator is not fail-fast. That is, it is the
* user's responsibility to ensure that the map does not change
* while he is iterating over it.
*/
public EntryIterator iterator() {
return new EntryIterator();
}
/////////////////////// Inner Classes ///////////////////////
/**
* Inner class that represents an entry in the map
*/
public static class Entry {
/** The key of the entry */
int key;
/** The value of the entry */
Object value;
/** The next entry in the collision chain */
Entry next;
public int getKey() {
return this.key;
}
public Object getValue() {
return this.value;
}
}
/**
* A class for iterating over the contents of an
* ObjIdMap
*/
public class EntryIterator {
/** The current collision chain we're traversing */
private int index = 0;
/** The next Entry we'll iterate over */
private Entry next = null;
//////////////////// Instance Methods ////////////////////
/**
* Returns the next Entry to visit. Will return null
* after we have iterated through all of the entries.
*/
public Entry next() {
while (this.next == null && this.index < table.length) {
if (table[index] != null) {
this.next = table[index];
}
this.index++;
}
Entry oldNext = this.next;
if (oldNext != null) {
this.next = oldNext.next;
}
return oldNext;
}
}
}