A variable length Double Array: expanding and contracting its internal storage array as elements are added and removed. : Auto Grow Array « Collections « Java Tutorial






/*
 * 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.
 */

import java.io.Serializable;

/**
 * 
 * A variable length {@link DoubleArray} implementation that automatically
 * handles expanding and contracting its internal storage array as elements are
 * added and removed.
 * 
 * 
 * The internal storage array starts with capacity determined by the
 * <code>initialCapacity</code> property, which can be set by the constructor.
 * The default initial capacity is 16. Adding elements using
 * {@link #addElement(double)} appends elements to the end of the array. When
 * there are no open entries at the end of the internal storage array, the array
 * is expanded. The size of the expanded array depends on the
 * <code>expansionMode</code> and <code>expansionFactor</code> properties.
 * The <code>expansionMode</code> determines whether the size of the array is
 * multiplied by the <code>expansionFactor</code> (MULTIPLICATIVE_MODE) or if
 * the expansion is additive (ADDITIVE_MODE -- <code>expansionFactor</code>
 * storage locations added). The default <code>expansionMode</code> is
 * MULTIPLICATIVE_MODE and the default <code>expansionFactor</code> is 2.0.
 * 
 * 
 * The {@link #addElementRolling(double)} method adds a new element to the end
 * of the internal storage array and adjusts the "usable window" of the internal
 * array forward by one position (effectively making what was the second element
 * the first, and so on). Repeated activations of this method (or activation of
 * {@link #discardFrontElements(int)}) will effectively orphan the storage
 * locations at the beginning of the internal storage array. To reclaim this
 * storage, each time one of these methods is activated, the size of the
 * internal storage array is compared to the number of addressable elements (the
 * <code>numElements</code> property) and if the difference is too large, the
 * internal array is contracted to size <code>numElements + 1.</code> The
 * determination of when the internal storage array is "too large" depends on
 * the <code>expansionMode</code> and <code>contractionFactor</code>
 * properties. If the <code>expansionMode</code> is
 * <code>MULTIPLICATIVE_MODE</code>, contraction is triggered when the ratio
 * between storage array length and <code>numElements</code> exceeds
 * <code>contractionFactor.</code> If the <code>expansionMode</code> is
 * <code>ADDITIVE_MODE,</code> the number of excess storage locations is
 * compared to <code>contractionFactor.</code>
 * 
 * 
 * To avoid cycles of expansions and contractions, the
 * <code>expansionFactor</code> must not exceed the
 * <code>contractionFactor.</code> Constructors and mutators for both of these
 * properties enforce this requirement, throwing IllegalArgumentException if it
 * is violated.
 * 
 * 
 * @version $Revision: 618057 $ $Date: 2008-02-03 11:58:54 -0700 (Sun, 03 Feb
 *          2008) $
 */
public class ResizableDoubleArray implements Serializable {

  /** Serializable version identifier */
  private static final long serialVersionUID = -3485529955529426875L;

  /** additive expansion mode */
  public static final int ADDITIVE_MODE = 1;

  /** multiplicative expansion mode */
  public static final int MULTIPLICATIVE_MODE = 0;

  /**
   * The contraction criteria determines when the internal array will be
   * contracted to fit the number of elements contained in the element array +
   * 1.
   */
  protected float contractionCriteria = 2.5f;

  /**
   * The expansion factor of the array. When the array needs to be expanded, the
   * new array size will be <code>internalArray.length * expansionFactor</code>
   * if <code>expansionMode</code> is set to MULTIPLICATIVE_MODE, or
   * <code>internalArray.length + expansionFactor</code> if
   * <code>expansionMode</code> is set to ADDITIVE_MODE.
   */
  protected float expansionFactor = 2.0f;

  /**
   * Determines whether array expansion by <code>expansionFactor</code> is
   * additive or multiplicative.
   */
  protected int expansionMode = MULTIPLICATIVE_MODE;

  /**
   * The initial capacity of the array. Initial capacity is not exposed as a
   * property as it is only meaningful when passed to a constructor.
   */
  protected int initialCapacity = 16;

  /**
   * The internal storage array.
   */
  protected double[] internalArray;

  /**
   * The number of addressable elements in the array. Note that this has nothing
   * to do with the length of the internal storage array.
   */
  protected int numElements = 0;

  /**
   * The position of the first addressable element in the internal storage
   * array. The addressable elements in the array are <code>
   * internalArray[startIndex],...,internalArray[startIndex + numElements -1]
   * </code>
   */
  protected int startIndex = 0;

  /**
   * Create a ResizableArray with default properties.
   * <ul>
   * <li><code>initialCapacity = 16</code></li>
   * <li><code>expansionMode = MULTIPLICATIVE_MODE</code></li>
   * <li><code>expansionFactor = 2.5</code></li>
   * <li><code>contractionFactor = 2.0</code></li>
   * </ul>
   */
  public ResizableDoubleArray() {
    internalArray = new double[initialCapacity];
  }

  /**
   * Create a ResizableArray with the specified initial capacity. Other
   * properties take default values:
   * <ul>
   * <li><code>expansionMode = MULTIPLICATIVE_MODE</code></li>
   * <li><code>expansionFactor = 2.5</code></li>
   * <li><code>contractionFactor = 2.0</code></li>
   * </ul>
   * 
   * @param initialCapacity
   *          The initial size of the internal storage array
   * @throws IllegalArgumentException
   *           if initialCapacity is not > 0
   */
  public ResizableDoubleArray(int initialCapacity) {
    setInitialCapacity(initialCapacity);
    internalArray = new double[this.initialCapacity];
  }

  /**
   * 
   * Create a ResizableArray with the specified initial capacity and expansion
   * factor. The remaining properties take default values:
   * <ul>
   * <li><code>expansionMode = MULTIPLICATIVE_MODE</code></li>
   * <li><code>contractionFactor = 0.5 + expansionFactor</code></li>
   * </ul>
   * 
   * 
   * Throws IllegalArgumentException if the following conditions are not met:
   * <ul>
   * <li><code>initialCapacity > 0</code></li>
   * <li><code>expansionFactor > 1</code></li>
   * </ul>
   * 
   * 
   * @param initialCapacity
   *          The initial size of the internal storage array
   * @param expansionFactor
   *          the array will be expanded based on this parameter
   * @throws IllegalArgumentException
   *           if parameters are not valid
   */
  public ResizableDoubleArray(int initialCapacity, float expansionFactor) {
    this.expansionFactor = expansionFactor;
    setInitialCapacity(initialCapacity);
    internalArray = new double[initialCapacity];
    setContractionCriteria(expansionFactor + 0.5f);
  }

  /**
   * 
   * Create a ResizableArray with the specified initialCapacity,
   * expansionFactor, and contractionCriteria. The <code>expansionMode</code>
   * will default to <code>MULTIPLICATIVE_MODE.</code>
   * 
   * 
   * Throws IllegalArgumentException if the following conditions are not met:
   * <ul>
   * <li><code>initialCapacity > 0</code></li>
   * <li><code>expansionFactor > 1</code></li>
   * <li><code>contractionFactor >= expansionFactor</code></li>
   * </ul>
   * 
   * 
   * @param initialCapacity
   *          The initial size of the internal storage array
   * @param expansionFactor
   *          the array will be expanded based on this parameter
   * @param contractionCriteria
   *          The contraction Criteria.
   * @throws IllegalArgumentException
   *           if parameters are not valid
   */
  public ResizableDoubleArray(int initialCapacity, float expansionFactor, float contractionCriteria) {
    this.expansionFactor = expansionFactor;
    setContractionCriteria(contractionCriteria);
    setInitialCapacity(initialCapacity);
    internalArray = new double[initialCapacity];
  }

  /**
   * 
   * Create a ResizableArray with the specified properties.
   * 
   * 
   * Throws IllegalArgumentException if the following conditions are not met:
   * <ul>
   * <li><code>initialCapacity > 0</code></li>
   * <li><code>expansionFactor > 1</code></li>
   * <li><code>contractionFactor >= expansionFactor</code></li>
   * <li><code>expansionMode in {MULTIPLICATIVE_MODE, ADDITIVE_MODE}</code>
   * </li>
   * </ul>
   * 
   * 
   * @param initialCapacity
   *          the initial size of the internal storage array
   * @param expansionFactor
   *          the array will be expanded based on this parameter
   * @param contractionCriteria
   *          the contraction Criteria
   * @param expansionMode
   *          the expansion mode
   * @throws IllegalArgumentException
   *           if parameters are not valid
   */
  public ResizableDoubleArray(int initialCapacity, float expansionFactor,
      float contractionCriteria, int expansionMode) {
    this.expansionFactor = expansionFactor;
    setContractionCriteria(contractionCriteria);
    setInitialCapacity(initialCapacity);
    setExpansionMode(expansionMode);
    internalArray = new double[initialCapacity];
  }

  /**
   * Adds an element to the end of this expandable array.
   * 
   * @param value
   *          to be added to end of array
   */
  public synchronized void addElement(double value) {
    numElements++;
    if ((startIndex + numElements) > internalArray.length) {
      expand();
    }
    internalArray[startIndex + (numElements - 1)] = value;
    if (shouldContract()) {
      contract();
    }
  }

  /**
   * 
   * Adds an element to the end of the array and removes the first element in
   * the array. Returns the discarded first element. The effect is similar to a
   * push operation in a FIFO queue.
   * 
   * 
   * Example: If the array contains the elements 1, 2, 3, 4 (in that order) and
   * addElementRolling(5) is invoked, the result is an array containing the
   * entries 2, 3, 4, 5 and the value returned is 1.
   * 
   * 
   * @param value
   *          the value to be added to the array
   * @return the value which has been discarded or "pushed" out of the array by
   *         this rolling insert
   */
  public synchronized double addElementRolling(double value) {
    double discarded = internalArray[startIndex];

    if ((startIndex + (numElements + 1)) > internalArray.length) {
      expand();
    }
    // Increment the start index
    startIndex += 1;

    // Add the new value
    internalArray[startIndex + (numElements - 1)] = value;

    // Check the contraction criteria
    if (shouldContract()) {
      contract();
    }
    return discarded;
  }

  /**
   * Checks the expansion factor and the contraction criteria and throws an
   * IllegalArgumentException if the contractionCriteria is less than the
   * expansionCriteria
   * 
   * @param expansionFactor
   *          factor to be checked
   * @param contractionCritera
   *          critera to be checked
   * @throws IllegalArgumentException
   *           if the contractionCriteria is less than the expansionCriteria.
   */
  protected void checkContractExpand(float contractionCritera, float expansionFactor) {

    if (contractionCritera < expansionFactor) {
      String msg = "Contraction criteria can never be smaller than "
          + "the expansion factor.  This would lead to a never "
          + "ending loop of expansion and contraction as a newly "
          + "expanded internal storage array would immediately "
          + "satisfy the criteria for contraction";
      throw new IllegalArgumentException(msg);
    }

    if (contractionCriteria <= 1.0) {
      String msg = "The contraction criteria must be a number larger "
          + "than one.  If the contractionCriteria is less than or "
          + "equal to one an endless loop of contraction and "
          + "expansion would ensue as an internalArray.length "
          + "== numElements would satisfy the contraction criteria";
      throw new IllegalArgumentException(msg);
    }

    if (expansionFactor <= 1.0) {
      String msg = "The expansion factor must be a number greater than 1.0";
      throw new IllegalArgumentException(msg);
    }
  }

  /**
   * Clear the array, reset the size to the initialCapacity and the number of
   * elements to zero.
   */
  public synchronized void clear() {
    numElements = 0;
    internalArray = new double[initialCapacity];
  }

  /**
   * Contracts the storage array to the (size of the element set) + 1 - to avoid
   * a zero length array. This function also resets the startIndex to zero.
   */
  public synchronized void contract() {
    double[] tempArray = new double[numElements + 1];

    // Copy and swap - copy only the element array from the src array.
    System.arraycopy(internalArray, startIndex, tempArray, 0, numElements);
    internalArray = tempArray;

    // Reset the start index to zero
    startIndex = 0;
  }

  /**
   * Discards the <code>i<code> initial elements of the array.  For example,
   * if the array contains the elements 1,2,3,4, invoking 
   * <code>discardFrontElements(2)</code> will cause the first two elements 
   * to be discarded, leaving 3,4 in the array.  Throws illegalArgumentException
   * if i exceeds numElements.
   * 
   * @param i  the number of elements to discard from the front of the array
   * @throws IllegalArgumentException if i is greater than numElements.
   */
  public synchronized void discardFrontElements(int i) {
    if (i > numElements) {
      String msg = "Cannot discard more elements than are" + "contained in this array.";
      throw new IllegalArgumentException(msg);
    } else if (i < 0) {
      String msg = "Cannot discard a negative number of elements.";
      throw new IllegalArgumentException(msg);
    } else {
      // "Subtract" this number of discarded from numElements
      numElements -= i;
      startIndex += i;
    }
    if (shouldContract()) {
      contract();
    }
  }

  /**
   * Expands the internal storage array using the expansion factor.
   * 
   * if <code>expansionMode</code> is set to MULTIPLICATIVE_MODE, the new
   * array size will be <code>internalArray.length * expansionFactor.</code>
   * If <code>expansionMode</code> is set to ADDITIVE_MODE, the length after
   * expansion will be <code>internalArray.length + expansionFactor</code>
   * 
   */
  protected synchronized void expand() {

    // notice the use of Math.ceil(), this gaurantees that we will always
    // have an array of at least currentSize + 1. Assume that the
    // current initial capacity is 1 and the expansion factor
    // is 1.000000000000000001. The newly calculated size will be
    // rounded up to 2 after the multiplication is performed.
    int newSize = 0;
    if (expansionMode == MULTIPLICATIVE_MODE) {
      newSize = (int) Math.ceil(internalArray.length * expansionFactor);
    } else {
      newSize = internalArray.length + Math.round(expansionFactor);
    }
    double[] tempArray = new double[newSize];

    // Copy and swap
    System.arraycopy(internalArray, 0, tempArray, 0, internalArray.length);
    internalArray = tempArray;
  }

  /**
   * Expands the internal storage array to the specified size.
   * 
   * @param size
   *          Size of the new internal storage array
   */
  private synchronized void expandTo(int size) {
    double[] tempArray = new double[size];
    // Copy and swap
    System.arraycopy(internalArray, 0, tempArray, 0, internalArray.length);
    internalArray = tempArray;
  }

  /**
   * The contraction criteria defines when the internal array will contract to
   * store only the number of elements in the element array. If the
   * <code>expansionMode</code> is <code>MULTIPLICATIVE_MODE</code>,
   * contraction is triggered when the ratio between storage array length and
   * <code>numElements</code> exceeds <code>contractionFactor</code>. If
   * the <code>expansionMode</code> is <code>ADDITIVE_MODE</code>, the
   * number of excess storage locations is compared to
   * <code>contractionFactor.</code>
   * 
   * @return the contraction criteria used to reclaim memory.
   */
  public float getContractionCriteria() {
    return contractionCriteria;
  }

  /**
   * Returns the element at the specified index
   * 
   * @param index
   *          index to fetch a value from
   * @return value stored at the specified index
   * @throws ArrayIndexOutOfBoundsException
   *           if <code>index</code> is less than zero or is greater than
   *           <code>getNumElements() - 1</code>.
   */
  public synchronized double getElement(int index) {
    if (index >= numElements) {
      String msg = "The index specified: " + index
          + " is larger than the current number of elements";
      throw new ArrayIndexOutOfBoundsException(msg);
    } else if (index >= 0) {
      return internalArray[startIndex + index];
    } else {
      String msg = "Elements cannot be retrieved from a negative array index";
      throw new ArrayIndexOutOfBoundsException(msg);
    }
  }

  /**
   * Returns a double array containing the elements of this
   * <code>ResizableArray</code>. This method returns a copy, not a reference
   * to the underlying array, so that changes made to the returned array have no
   * effect on this <code>ResizableArray.</code>
   * 
   * @return the double array.
   */
  public synchronized double[] getElements() {
    double[] elementArray = new double[numElements];
    System.arraycopy(internalArray, startIndex, elementArray, 0, numElements);
    return elementArray;
  }

  /**
   * The expansion factor controls the size of a new aray when an array needs to
   * be expanded. The <code>expansionMode</code> determines whether the size
   * of the array is multiplied by the <code>expansionFactor</code>
   * (MULTIPLICATIVE_MODE) or if the expansion is additive (ADDITIVE_MODE --
   * <code>expansionFactor</code> storage locations added). The default
   * <code>expansionMode</code> is MULTIPLICATIVE_MODE and the default
   * <code>expansionFactor</code> is 2.0.
   * 
   * @return the expansion factor of this expandable double array
   */
  public float getExpansionFactor() {
    return expansionFactor;
  }

  /**
   * The <code>expansionMode</code> determines whether the internal storage
   * array grows additively (ADDITIVE_MODE) or multiplicatively
   * (MULTIPLICATIVE_MODE) when it is expanded.
   * 
   * @return Returns the expansionMode.
   */
  public int getExpansionMode() {
    return expansionMode;
  }

  /**
   * Notice the package scope on this method. This method is simply here for the
   * JUnit test, it allows us check if the expansion is working properly after a
   * number of expansions. This is not meant to be a part of the public
   * interface of this class.
   * 
   * @return the length of the internal storage array.
   */
  synchronized int getInternalLength() {
    return (internalArray.length);
  }

  /**
   * Returns the number of elements currently in the array. Please note that
   * this is different from the length of the internal storage array.
   * 
   * @return number of elements
   */
  public synchronized int getNumElements() {
    return (numElements);
  }

  /**
   * Returns the internal storage array. Note that this method returns a
   * reference to the internal storage array, not a copy, and to correctly
   * address elements of the array, the <code>startIndex</code> is required
   * (available via the {@link #start} method). This method should only be used
   * in cases where copying the internal array is not practical. The
   * {@link #getElements} method should be used in all other cases.
   * 
   * 
   * @return the internal storage array used by this object
   */
  public synchronized double[] getValues() {
    return (internalArray);
  }

  /**
   * Sets the contraction criteria for this ExpandContractDoubleArray.
   * 
   * @param contractionCriteria
   *          contraction criteria
   */
  public void setContractionCriteria(float contractionCriteria) {
    checkContractExpand(contractionCriteria, getExpansionFactor());
    this.contractionCriteria = contractionCriteria;
  }

  /**
   * Sets the element at the specified index. If the specified index is greater
   * than <code>getNumElements() - 1</code>, the <code>numElements</code>
   * property is increased to <code>index +1</code> and additional storage is
   * allocated (if necessary) for the new element and all (uninitialized)
   * elements between the new element and the previous end of the array).
   * 
   * @param index
   *          index to store a value in
   * @param value
   *          value to store at the specified index
   * @throws ArrayIndexOutOfBoundsException
   *           if <code>index</code> is less than zero.
   */
  public synchronized void setElement(int index, double value) {
    if (index < 0) {
      String msg = "Cannot set an element at a negative index";
      throw new ArrayIndexOutOfBoundsException(msg);
    }
    if (index + 1 > numElements) {
      numElements = index + 1;
    }
    if ((startIndex + index) >= internalArray.length) {
      expandTo(startIndex + (index + 1));
    }
    internalArray[startIndex + index] = value;
  }

  /**
   * Sets the expansionFactor. Throws IllegalArgumentException if the the
   * following conditions are not met:
   * <ul>
   * <li><code>expansionFactor > 1</code></li>
   * <li><code>contractionFactor >= expansionFactor</code></li>
   * </ul>
   * 
   * @param expansionFactor
   *          the new expansion factor value.
   * @throws IllegalArgumentException
   *           if expansionFactor is <= 1 or greater than contractionFactor
   */
  public void setExpansionFactor(float expansionFactor) {
    checkContractExpand(getContractionCriteria(), expansionFactor);
    // The check above verifies that the expansion factor is > 1.0;
    this.expansionFactor = expansionFactor;
  }

  /**
   * Sets the <code>expansionMode</code>. The specified value must be one of
   * ADDITIVE_MODE, MULTIPLICATIVE_MODE.
   * 
   * @param expansionMode
   *          The expansionMode to set.
   * @throws IllegalArgumentException
   *           if the specified mode value is not valid
   */
  public void setExpansionMode(int expansionMode) {
    if (expansionMode != MULTIPLICATIVE_MODE && expansionMode != ADDITIVE_MODE) {
      throw new IllegalArgumentException("Illegal expansionMode setting.");
    }
    this.expansionMode = expansionMode;
  }

  /**
   * Sets the initial capacity. Should only be invoked by constructors.
   * 
   * @param initialCapacity
   *          of the array
   * @throws IllegalArgumentException
   *           if <code>initialCapacity</code> is not positive.
   */
  protected void setInitialCapacity(int initialCapacity) {
    if (initialCapacity > 0) {
      synchronized (this) {
        this.initialCapacity = initialCapacity;
      }
    } else {
      String msg = "The initial capacity supplied: " + initialCapacity
          + "must be a positive integer";
      throw new IllegalArgumentException(msg);
    }
  }

  /**
   * This function allows you to control the number of elements contained in
   * this array, and can be used to "throw out" the last n values in an array.
   * This function will also expand the internal array as needed.
   * 
   * @param i
   *          a new number of elements
   * @throws IllegalArgumentException
   *           if <code>i</code> is negative.
   */
  public synchronized void setNumElements(int i) {

    // If index is negative thrown an error
    if (i < 0) {
      String msg = "Number of elements must be zero or a positive " + "integer";
      throw new IllegalArgumentException(msg);
    }

    // Test the new num elements, check to see if the array needs to be
    // expanded to accomodate this new number of elements
    if ((startIndex + i) > internalArray.length) {
      expandTo(startIndex + i);
    }

    // Set the new number of elements to new value
    numElements = i;
  }

  /**
   * Returns true if the internal storage array has too many unused storage
   * positions.
   * 
   * @return true if array satisfies the contraction criteria
   */
  private synchronized boolean shouldContract() {
    if (expansionMode == MULTIPLICATIVE_MODE) {
      return (internalArray.length / ((float) numElements)) > contractionCriteria;
    } else {
      return (internalArray.length - numElements) > contractionCriteria;
    }
  }

  /**
   * Returns the starting index of the internal array. The starting index is the
   * position of the first addressable element in the internal storage array.
   * The addressable elements in the array are <code>
   * internalArray[startIndex],...,internalArray[startIndex + numElements -1]
   * </code>
   * 
   * @return starting index
   */
  public synchronized int start() {
    return startIndex;
  }

}








9.10.Auto Grow Array
9.10.1.A variable length Double Array: expanding and contracting its internal storage array as elements are added and removed.
9.10.2.Simple object pool. Based on ThreadPool and few other classes
9.10.3.Your own auto-growth Array
9.10.4.The character array based string
9.10.5.ByteArray wraps java byte arrays (byte[]) to allow byte arrays to be used as keys in hashtables.
9.10.6.Adds all the elements of the given arrays into a new double-type array.
9.10.7.A writer for char strings
9.10.8.Array-List for integer objects.
9.10.9.Simple object pool
9.10.10.Concatenates two arrays of strings
9.10.11.Puts the entire source array in the target array at offset offset.
9.10.12.Lazy List creation
9.10.13.Stores a list of int