All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.carrotsearch.sizeof.IdentityHashSet Maven / Gradle / Ivy

The newest version!
package com.carrotsearch.sizeof;

import java.util.*;

/**
 * An identity hash set implemented using open addressing. No null keys are allowed.
 */
final class IdentityHashSet implements Iterable {
  /**
   * Default load factor.
   */
  public final static float DEFAULT_LOAD_FACTOR = 0.75f;

  /**
   * Minimum capacity for the set.
   */
  public final static int MIN_CAPACITY = 4;

  /**
   * All of set entries. Always of power of two length.
   */
  public Object[] keys;
  
  /**
   * Cached number of assigned slots.
   */
  public int assigned;
  
  /**
   * The load factor for this set (fraction of allocated or deleted slots before
   * the buffers must be rehashed or reallocated).
   */
  public final float loadFactor;
  
  /**
   * Cached capacity threshold at which we must resize the buffers.
   */
  private int resizeThreshold;
  
  /**
   * Creates a hash set with the default capacity of 16.
   * load factor of {@value #DEFAULT_LOAD_FACTOR}. `
   */
  public IdentityHashSet() {
    this(16, DEFAULT_LOAD_FACTOR);
  }
  
  /**
   * Creates a hash set with the given capacity, load factor of
   * {@value #DEFAULT_LOAD_FACTOR}.
   */
  public IdentityHashSet(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
  }
  
  /**
   * Creates a hash set with the given capacity and load factor.
   */
  public IdentityHashSet(int initialCapacity, float loadFactor) {
    initialCapacity = Math.max(MIN_CAPACITY, initialCapacity);
    
    assert initialCapacity > 0 : "Initial capacity must be between (0, "
        + Integer.MAX_VALUE + "].";
    assert loadFactor > 0 && loadFactor < 1 : "Load factor must be between (0, 1).";
    this.loadFactor = loadFactor;
    allocateBuffers(roundCapacity(initialCapacity));
  }
  
  /**
   * Adds a reference to the set. Null keys are not allowed.
   */
  public boolean add(KType e) {
    assert e != null : "Null keys not allowed.";
    
    if (assigned >= resizeThreshold) expandAndRehash();
    
    final int mask = keys.length - 1;
    int slot = rehash(e) & mask;
    Object existing;
    while ((existing = keys[slot]) != null) {
      if (e == existing) {
        return false; // already found.
      }
      slot = (slot + 1) & mask;
    }
    assigned++;
    keys[slot] = e;
    return true;
  }

  /**
   * Checks if the set contains a given ref.
   */
  public boolean contains(KType e) {
    final int mask = keys.length - 1;
    int slot = rehash(e) & mask;
    Object existing;
    while ((existing = keys[slot]) != null) {
      if (e == existing) {
        return true;
      }
      slot = (slot + 1) & mask;
    }
    return false;
  }

  /** Rehash via MurmurHash. */
  private static int rehash(Object o) {
    return MurmurHash3.hash(System.identityHashCode(o));
  }
  
  /**
   * Expand the internal storage buffers (capacity) or rehash current keys and
   * values if there are a lot of deleted slots.
   */
  private void expandAndRehash() {
    final Object[] oldKeys = this.keys;
    
    assert assigned >= resizeThreshold;
    allocateBuffers(nextCapacity(keys.length));
    
    /*
     * Rehash all assigned slots from the old hash table.
     */
    final int mask = keys.length - 1;
    for (int i = 0; i < oldKeys.length; i++) {
      final Object key = oldKeys[i];
      if (key != null) {
        int slot = rehash(key) & mask;
        while (keys[slot] != null) {
          slot = (slot + 1) & mask;
        }
        keys[slot] = key;
      }
    }
    Arrays.fill(oldKeys, null);
  }
  
  /**
   * Allocate internal buffers for a given capacity.
   * 
   * @param capacity
   *          New capacity (must be a power of two).
   */
  private void allocateBuffers(int capacity) {
    this.keys = new Object[capacity];
    this.resizeThreshold = (int) (capacity * DEFAULT_LOAD_FACTOR);
  }
  
  /**
   * Return the next possible capacity, counting from the current buffers' size.
   */
  protected int nextCapacity(int current) {
    assert current > 0 && Long.bitCount(current) == 1 : "Capacity must be a power of two.";
    assert ((current << 1) > 0) : "Maximum capacity exceeded ("
        + (0x80000000 >>> 1) + ").";
    
    if (current < MIN_CAPACITY / 2) current = MIN_CAPACITY / 2;
    return current << 1;
  }
  
  /**
   * Round the capacity to the next allowed value.
   */
  protected int roundCapacity(int requestedCapacity) {
    // Maximum positive integer that is a power of two.
    if (requestedCapacity > (0x80000000 >>> 1)) return (0x80000000 >>> 1);
    
    int capacity = MIN_CAPACITY;
    while (capacity < requestedCapacity) {
      capacity <<= 1;
    }

    return capacity;
  }
  
  public void clear() {
    assigned = 0;
    Arrays.fill(keys, null);
  }
  
  public int size() {
    return assigned;
  }
  
  public boolean isEmpty() {
    return size() == 0;
  }

  @Override
  public Iterator iterator() {
    return new Iterator() {
      int pos = -1;
      Object nextElement = fetchNext();

      @Override
      public boolean hasNext() {
        return nextElement != null;
      }

      @SuppressWarnings("unchecked")
      @Override
      public KType next() {
        Object r = this.nextElement;
        if (r == null) {
          throw new NoSuchElementException();
        }
        this.nextElement = fetchNext();
        return (KType) r;
      }

      private Object fetchNext() {
        pos++;
        while (pos < keys.length && keys[pos] == null) {
          pos++;
        }

        return (pos >= keys.length ? null : keys[pos]);
      }

      @Override
      public void remove() {
        throw new UnsupportedOperationException();
      }
    };
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy