blob: 695e91db928d5c19bd185b54e58797a1e0d3c809 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.ignite.internal.util;
import java.util.Arrays;
/**
* Lightweight identity hash table which maps objects to integer handles,
* assigned in ascending order.
*/
public class GridHandleTable {
/** Initial size. */
private final int initCap;
/** Factor for computing size threshold. */
private final float loadFactor;
/** Number of mappings in table/next available handle. */
private int size;
/** Size threshold determining when to expand hash spine. */
private int threshold;
/** Maps hash value -> candidate handle value. */
private int[] spine;
/** Maps handle value -> next candidate handle value. */
private int[] next;
/** Maps handle value -> associated object. */
private Object[] objs;
/**
* Creates new HandleTable with given capacity and load factor.
*
* @param initCap Initial capacity.
* @param loadFactor Load factor.
*/
public GridHandleTable(int initCap, float loadFactor) {
this.initCap = initCap;
this.loadFactor = loadFactor;
init(initCap, initCap);
}
/**
* Initialize hash table.
*
* @param spineLen Spine array length.
* @param size Hash table length.
*/
private void init(int spineLen, int size) {
spine = new int[spineLen];
next = new int[size];
objs = new Object[size];
Arrays.fill(spine, -1);
threshold = (int)(spineLen * loadFactor);
}
/**
* Looks up and returns handle associated with the given object. If the given object was not found,
* puts it into the table and returns {@code -1}.
*
* @param obj Object.
* @return Object's handle or {@code -1} if it was not in the table.
*/
public int putIfAbsent(Object obj) {
int idx = hash(obj) % spine.length;
if (size > 0) {
for (int i = spine[idx]; i >= 0; i = next[i])
if (objs[i] == obj)
return i;
}
if (size >= next.length)
growEntries();
if (size >= threshold) {
growSpine();
idx = hash(obj) % spine.length;
}
insert(obj, size, idx);
size++;
return -1;
}
/**
* Resets table to its initial (empty) state.
*/
public void clear() {
if (size < objs.length) {
if (shrink()) {
size = 0;
return;
}
}
Arrays.fill(spine, -1);
Arrays.fill(objs, 0, size, null);
size = 0;
}
/**
* @return Returns objects that were added to handles table.
*/
public Object[] objects() {
return objs;
}
/**
* Inserts mapping object -> handle mapping into table. Assumes table
* is large enough to accommodate new mapping.
*
* @param obj Object.
* @param handle Handle.
* @param idx Index.
*/
private void insert(Object obj, int handle, int idx) {
objs[handle] = obj;
next[handle] = spine[idx];
spine[idx] = handle;
}
/**
* Expands the hash "spine" - equivalent to increasing the number of
* buckets in a conventional hash table.
*/
private void growSpine() {
int size = (spine.length << 1) + 1;
spine = new int[size];
threshold = (int)(spine.length * loadFactor);
Arrays.fill(spine, -1);
for (int i = 0; i < this.size; i++) {
Object obj = objs[i];
int idx = hash(obj) % spine.length;
insert(objs[i], i, idx);
}
}
/**
* Increases hash table capacity by lengthening entry arrays.
*/
private void growEntries() {
int newLen = (next.length << 1) + 1;
int[] newNext = new int[newLen];
System.arraycopy(next, 0, newNext, 0, size);
next = newNext;
Object[] newObjs = new Object[newLen];
System.arraycopy(objs, 0, newObjs, 0, size);
objs = newObjs;
}
/**
* Tries to gradually shrink hash table by factor of two when it's cleared.
*
* @return {@code true} if shrinked the table, {@code false} otherwise.
*/
private boolean shrink() {
int newSize = Math.max((objs.length - 1) / 2, initCap);
if (newSize >= size && newSize < objs.length) {
int newSpine = spine.length;
if (spine.length > initCap) {
int prevSpine = (spine.length - 1) / 2;
int prevThreshold = (int)(prevSpine * loadFactor);
if (newSize < prevThreshold)
newSpine = prevSpine;
}
init(newSpine, newSize);
return true;
}
return false;
}
/**
* Returns hash value for given object.
*
* @param obj Object.
* @return Hash value.
*/
private int hash(Object obj) {
return System.identityHashCode(obj) & 0x7FFFFFFF;
}
}