org.jboss.marshalling.util.IntKeyMap Maven / Gradle / Ivy
The newest version!
/*
* JBoss, Home of Professional Open Source.
* Copyright 2014 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* 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.
*/
package org.jboss.marshalling.util;
import java.io.IOException;
import java.io.Serializable;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* An integer-keyed map, optimized for fast copying. Based on {@code FastCopyHashMap} by Jason T. Greene.
*
* @author Jason T. Greene
* @author David M. Lloyd
*/
public final class IntKeyMap implements Cloneable, Serializable, Iterable> {
/**
* Same default as HashMap, must be a power of 2
*/
private static final int DEFAULT_CAPACITY = 8;
/**
* MAX_INT - 1
*/
private static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* 67%, just like IdentityHashMap
*/
private static final float DEFAULT_LOAD_FACTOR = 0.67f;
/**
* The open-addressed table
*/
private transient Entry[] table;
/**
* The current number of key-value pairs
*/
private transient int size;
/**
* The next resize
*/
private transient int threshold;
/**
* The user defined load factor which defines when to resize
*/
private final float loadFactor;
private static final long serialVersionUID = -6864280848239317243L;
public IntKeyMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Can not have a negative size table!");
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (!(loadFactor > 0F && loadFactor <= 1F))
throw new IllegalArgumentException("Load factor must be greater than 0 and less than or equal to 1");
this.loadFactor = loadFactor;
init(initialCapacity, loadFactor);
}
@SuppressWarnings("unchecked")
public IntKeyMap(IntKeyMap extends V> map) {
table = (Entry[]) map.table.clone();
loadFactor = map.loadFactor;
size = map.size;
threshold = map.threshold;
}
@SuppressWarnings("unchecked")
private void init(int initialCapacity, float loadFactor) {
int c = 1;
for (; c < initialCapacity; c <<= 1) ;
threshold = (int) (c * loadFactor);
// Include the load factor when sizing the table for the first time
if (initialCapacity > threshold && c < MAXIMUM_CAPACITY) {
c <<= 1;
threshold = (int) (c * loadFactor);
}
table = (Entry[]) new Entry[c];
}
public IntKeyMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public IntKeyMap() {
this(DEFAULT_CAPACITY);
}
private int nextIndex(int index, int length) {
index = (index >= length - 1) ? 0 : index + 1;
return index;
}
private static boolean eq(Object o1, Object o2) {
return o1 == o2 || (o1 != null && o1.equals(o2));
}
private static int index(int hashCode, int length) {
return hashCode & (length - 1);
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public V get(int key) {
int length = table.length;
int index = index(key, length);
for (int start = index; ;) {
Entry e = table[index];
if (e == null)
return null;
if (e.key == key)
return e.value;
index = nextIndex(index, length);
if (index == start) // Full table
return null;
}
}
public boolean containsKey(int key) {
int length = table.length;
int index = index(key, length);
for (int start = index; ;) {
Entry e = table[index];
if (e == null)
return false;
if (e.key == key)
return true;
index = nextIndex(index, length);
if (index == start) // Full table
return false;
}
}
public boolean containsValue(Object value) {
for (Entry e : table)
if (e != null && eq(value, e.value))
return true;
return false;
}
public V put(int key, V value) {
Entry[] table = this.table;
int hash = key;
int length = table.length;
int index = index(hash, length);
for (int start = index; ;) {
Entry e = table[index];
if (e == null)
break;
if (e.key == key) {
table[index] = new Entry(key, value);
return e.value;
}
index = nextIndex(index, length);
if (index == start)
throw new IllegalStateException("Table is full!");
}
table[index] = new Entry(key, value);
if (++size >= threshold)
resize(length);
return null;
}
@SuppressWarnings("unchecked")
private void resize(int from) {
int newLength = from << 1;
// Can't get any bigger
if (newLength > MAXIMUM_CAPACITY || newLength <= from)
return;
Entry[] newTable = new Entry[newLength];
Entry[] old = table;
for (Entry e : old) {
if (e == null)
continue;
int index = index(e.key, newLength);
while (newTable[index] != null)
index = nextIndex(index, newLength);
newTable[index] = e;
}
threshold = (int) (loadFactor * newLength);
table = newTable;
}
public V remove(int key) {
Entry[] table = this.table;
int length = table.length;
int start = index(key, length);
for (int index = start; ;) {
Entry e = table[index];
if (e == null)
return null;
if (e.key == key) {
table[index] = null;
relocate(index);
size--;
return e.value;
}
index = nextIndex(index, length);
if (index == start)
return null;
}
}
private void relocate(int start) {
Entry[] table = this.table;
int length = table.length;
int current = nextIndex(start, length);
for (; ;) {
Entry e = table[current];
if (e == null)
return;
// A Doug Lea variant of Knuth's Section 6.4 Algorithm R.
// This provides a non-recursive method of relocating
// entries to their optimal positions once a gap is created.
int prefer = index(e.key, length);
if ((current < prefer && (prefer <= start || start <= current))
|| (prefer <= start && start <= current)) {
table[start] = e;
table[current] = null;
start = current;
}
current = nextIndex(current, length);
}
}
public void clear() {
Entry[] table = this.table;
for (int i = 0; i < table.length; i++)
table[i] = null;
size = 0;
}
@SuppressWarnings("unchecked")
public IntKeyMap clone() {
try {
IntKeyMap clone = (IntKeyMap) super.clone();
clone.table = table.clone();
return clone;
}
catch (CloneNotSupportedException e) {
// should never happen
throw new IllegalStateException(e);
}
}
public void printDebugStats() {
int optimal = 0;
int total = 0;
int totalSkew = 0;
int maxSkew = 0;
for (int i = 0; i < table.length; i++) {
Entry e = table[i];
if (e != null) {
total++;
int target = index(e.key, table.length);
if (i == target)
optimal++;
else {
int skew = Math.abs(i - target);
if (skew > maxSkew) maxSkew = skew;
totalSkew += skew;
}
}
}
System.out.println(" Size: " + size);
System.out.println(" Real Size: " + total);
System.out.println(" Optimal: " + optimal + " (" + (float) optimal * 100 / total + "%)");
System.out.println(" Average Distance: " + ((float) totalSkew / (total - optimal)));
System.out.println(" Max Distance: " + maxSkew);
}
@SuppressWarnings("unchecked")
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
int size = s.readInt();
init(size, loadFactor);
for (int i = 0; i < size; i++) {
int key = s.readInt();
V value = (V) s.readObject();
putForCreate(key, value);
}
this.size = size;
}
@SuppressWarnings("unchecked")
private void putForCreate(int key, V value) {
Entry[] table = this.table;
int length = table.length;
int index = index(key, length);
Entry e = table[index];
while (e != null) {
index = nextIndex(index, length);
e = table[index];
}
table[index] = new Entry(key, value);
}
private void writeObject(java.io.ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
s.writeInt(size);
for (Entry e : table) {
if (e != null) {
s.writeInt(e.key);
s.writeObject(e.value);
}
}
}
/**
* Iterate over the entries. Read-only operation.
*
* @return the entry iterator
*/
public Iterator> iterator() {
return new Iterator>() {
int i = 0;
public boolean hasNext() {
final Entry[] table = IntKeyMap.this.table;
final int len = table.length;
if (i == len) {
return false;
}
while (table[i] == null) {
if (++i == len) {
return false;
}
}
return false;
}
public Entry next() {
if (! hasNext()) {
throw new NoSuchElementException();
}
return table[i++];
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
/**
* A map entry.
*
* @param the value type
*/
public static final class Entry {
private final int key;
private final V value;
private Entry(int key, V value) {
this.key = key;
this.value = value;
}
public int getKey() {
return key;
}
public V getValue() {
return value;
}
}
}