Memory-efficient map of keys to values with list-style random-access semantics. : Random « Development Class « Java






Memory-efficient map of keys to values with list-style random-access semantics.

  
/*
 * Copyright (c) 2010 Google Inc.
 *
 * 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 com.google.api.client.util;

import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;

/**
 * Memory-efficient map of keys to values with list-style random-access semantics.
 * <p>
 * Conceptually, the keys and values are stored in a simpler array in order to minimize memory use
 * and provide for fast access to a key/value at a certain index (for example {@link #getKey(int)}).
 * However, traditional mapping operations like {@link #get(Object)} and
 * {@link #put(Object, Object)} are slower because they need to look up all key/value pairs in the
 * worst case.
 *
 * @since 1.0
 * @author Yaniv Inbar
 */
public class ArrayMap<K, V> extends AbstractMap<K, V> implements Cloneable {
  int size;
  private Object[] data;
  private EntrySet entrySet;

  /**
   * Returns a new instance of an array map with initial capacity of zero. Equivalent to calling the
   * default constructor, except without the need to specify the type parameters. For example:
   * {@code ArrayMap<String, String> map = ArrayMap.create();}.
   */
  public static <K, V> ArrayMap<K, V> create() {
    return new ArrayMap<K, V>();
  }

  /**
   * Returns a new instance of an array map of the given initial capacity. For example: {@code
   * ArrayMap<String, String> map = ArrayMap.create(8);}.
   */
  public static <K, V> ArrayMap<K, V> create(int initialCapacity) {
    ArrayMap<K, V> result = create();
    result.ensureCapacity(initialCapacity);
    return result;
  }

  /**
   * Returns a new instance of an array map of the given key value pairs in alternating order. For
   * example: {@code ArrayMap<String, String> map = ArrayMap.of("key1", "value1", "key2", "value2",
   * ...);}.
   * <p>
   * WARNING: there is no compile-time checking of the {@code keyValuePairs} parameter to ensure
   * that the keys or values have the correct type, so if the wrong type is passed in, any problems
   * will occur at runtime. Also, there is no checking that the keys are unique, which the caller
   * must ensure is true.
   */
  public static <K, V> ArrayMap<K, V> of(Object... keyValuePairs) {
    ArrayMap<K, V> result = create(1);
    int length = keyValuePairs.length;
    if (1 == length % 2) {
      throw new IllegalArgumentException(
          "missing value for last key: " + keyValuePairs[length - 1]);
    }
    result.size = keyValuePairs.length / 2;
    Object[] data = result.data = new Object[length];
    System.arraycopy(keyValuePairs, 0, data, 0, length);
    return result;
  }

  /** Returns the number of key-value pairs set. */
  @Override
  public final int size() {
    return this.size;
  }

  /** Returns the key at the given index or {@code null} if out of bounds. */
  public final K getKey(int index) {
    if (index < 0 || index >= this.size) {
      return null;
    }
    @SuppressWarnings("unchecked")
    K result = (K) this.data[index << 1];
    return result;
  }

  /** Returns the value at the given index or {@code null} if out of bounds. */
  public final V getValue(int index) {
    if (index < 0 || index >= this.size) {
      return null;
    }
    return valueAtDataIndex(1 + (index << 1));
  }

  /**
   * Sets the key/value mapping at the given index, overriding any existing key/value mapping.
   * <p>
   * There is no checking done to ensure that the key does not already exist. Therefore, this method
   * is dangerous to call unless the caller can be certain the key does not already exist in the
   * map.
   *
   * @return previous value or {@code null} for none
   * @throws IndexOutOfBoundsException if index is negative
   */
  public final V set(int index, K key, V value) {
    if (index < 0) {
      throw new IndexOutOfBoundsException();
    }
    int minSize = index + 1;
    ensureCapacity(minSize);
    int dataIndex = index << 1;
    V result = valueAtDataIndex(dataIndex + 1);
    setData(dataIndex, key, value);
    if (minSize > this.size) {
      this.size = minSize;
    }
    return result;
  }

  /**
   * Sets the value at the given index, overriding any existing value mapping.
   *
   * @return previous value or {@code null} for none
   * @throws IndexOutOfBoundsException if index is negative or {@code >=} size
   */
  public final V set(int index, V value) {
    int size = this.size;
    if (index < 0 || index >= size) {
      throw new IndexOutOfBoundsException();
    }
    int valueDataIndex = 1 + (index << 1);
    V result = valueAtDataIndex(valueDataIndex);
    this.data[valueDataIndex] = value;
    return result;
  }

  /**
   * Adds the key/value mapping at the end of the list. Behaves identically to {@code set(size(),
   * key, value)}.
   *
   * @throws IndexOutOfBoundsException if index is negative
   */
  public final void add(K key, V value) {
    set(this.size, key, value);
  }

  /**
   * Removes the key/value mapping at the given index, or ignored if the index is out of bounds.
   *
   * @return previous value or {@code null} for none
   */
  public final V remove(int index) {
    return removeFromDataIndexOfKey(index << 1);
  }

  /** Returns whether there is a mapping for the given key. */
  @Override
  public final boolean containsKey(Object key) {
    return -2 != getDataIndexOfKey(key);
  }

  /** Returns the index of the given key or {@code -1} if there is no such key. */
  public final int getIndexOfKey(K key) {
    return getDataIndexOfKey(key) >> 1;
  }

  /**
   * Returns the value set for the given key or {@code null} if there is no such mapping or if the
   * mapping value is {@code null}.
   */
  @Override
  public final V get(Object key) {
    return valueAtDataIndex(getDataIndexOfKey(key) + 1);
  }

  /**
   * Sets the value for the given key, overriding any existing value.
   *
   * @return previous value or {@code null} for none
   */
  @Override
  public final V put(K key, V value) {
    int index = getIndexOfKey(key);
    if (index == -1) {
      index = this.size;
    }
    return set(index, key, value);
  }

  /**
   * Removes the key-value pair of the given key, or ignore if the key cannot be found.
   *
   * @return previous value or {@code null} for none
   */
  @Override
  public final V remove(Object key) {
    return removeFromDataIndexOfKey(getDataIndexOfKey(key));
  }

  /** Trims the internal array storage to minimize memory usage. */
  public final void trim() {
    setDataCapacity(this.size << 1);
  }

  /**
   * Ensures that the capacity of the internal arrays is at least a given capacity.
   */
  public final void ensureCapacity(int minCapacity) {
    Object[] data = this.data;
    int minDataCapacity = minCapacity << 1;
    int oldDataCapacity = data == null ? 0 : data.length;
    if (minDataCapacity > oldDataCapacity) {
      int newDataCapacity = oldDataCapacity / 2 * 3 + 1;
      if (newDataCapacity % 2 == 1) {
        newDataCapacity++;
      }
      if (newDataCapacity < minDataCapacity) {
        newDataCapacity = minDataCapacity;
      }
      setDataCapacity(newDataCapacity);
    }
  }

  private void setDataCapacity(int newDataCapacity) {
    if (newDataCapacity == 0) {
      this.data = null;
      return;
    }
    int size = this.size;
    Object[] oldData = this.data;
    if (size == 0 || newDataCapacity != oldData.length) {
      Object[] newData = this.data = new Object[newDataCapacity];
      if (size != 0) {
        System.arraycopy(oldData, 0, newData, 0, size << 1);
      }
    }
  }

  private void setData(int dataIndexOfKey, K key, V value) {
    Object[] data = this.data;
    data[dataIndexOfKey] = key;
    data[dataIndexOfKey + 1] = value;
  }

  private V valueAtDataIndex(int dataIndex) {
    if (dataIndex < 0) {
      return null;
    }
    @SuppressWarnings("unchecked")
    V result = (V) this.data[dataIndex];
    return result;
  }

  /**
   * Returns the data index of the given key or {@code -2} if there is no such key.
   */
  private int getDataIndexOfKey(Object key) {
    int dataSize = this.size << 1;
    Object[] data = this.data;
    for (int i = 0; i < dataSize; i += 2) {
      Object k = data[i];
      if (key == null ? k == null : key.equals(k)) {
        return i;
      }
    }
    return -2;
  }

  /**
   * Removes the key/value mapping at the given data index of key, or ignored if the index is out of
   * bounds.
   */
  private V removeFromDataIndexOfKey(int dataIndexOfKey) {
    int dataSize = this.size << 1;
    if (dataIndexOfKey < 0 || dataIndexOfKey >= dataSize) {
      return null;
    }
    V result = valueAtDataIndex(dataIndexOfKey + 1);
    Object[] data = this.data;
    int moved = dataSize - dataIndexOfKey - 2;
    if (moved != 0) {
      System.arraycopy(data, dataIndexOfKey + 2, data, dataIndexOfKey, moved);
    }
    this.size--;
    setData(dataSize - 2, null, null);
    return result;
  }

  @Override
  public void clear() {
    this.size = 0;
    this.data = null;
  }

  @Override
  public final boolean containsValue(Object value) {
    int dataSize = this.size << 1;
    Object[] data = this.data;
    for (int i = 1; i < dataSize; i += 2) {
      Object v = data[i];
      if (value == null ? v == null : value.equals(v)) {
        return true;
      }
    }
    return false;
  }

  @Override
  public final Set<Map.Entry<K, V>> entrySet() {
    EntrySet entrySet = this.entrySet;
    if (entrySet == null) {
      entrySet = this.entrySet = new EntrySet();
    }
    return entrySet;
  }

  @Override
  public ArrayMap<K, V> clone() {
    try {
      @SuppressWarnings("unchecked")
      ArrayMap<K, V> result = (ArrayMap<K, V>) super.clone();
      result.entrySet = null;
      Object[] data = this.data;
      if (data != null) {
        int length = data.length;
        Object[] resultData = result.data = new Object[length];
        System.arraycopy(data, 0, resultData, 0, length);
      }
      return result;
    } catch (CloneNotSupportedException e) {
      // won't happen
      return null;
    }
  }

  final class EntrySet extends AbstractSet<Map.Entry<K, V>> {

    @Override
    public Iterator<Map.Entry<K, V>> iterator() {
      return new EntryIterator();
    }

    @Override
    public int size() {
      return ArrayMap.this.size;
    }
  }

  final class EntryIterator implements Iterator<Map.Entry<K, V>> {

    private boolean removed;
    private int nextIndex;

    public boolean hasNext() {
      return this.nextIndex < ArrayMap.this.size;
    }

    public Map.Entry<K, V> next() {
      int index = this.nextIndex;
      if (index == ArrayMap.this.size) {
        throw new NoSuchElementException();
      }
      this.nextIndex++;
      return new Entry(index);
    }

    public void remove() {
      int index = this.nextIndex - 1;
      if (this.removed || index < 0) {
        throw new IllegalArgumentException();
      }
      ArrayMap.this.remove(index);
      this.removed = true;
    }
  }

  final class Entry implements Map.Entry<K, V> {

    private int index;

    Entry(int index) {
      this.index = index;
    }

    public K getKey() {
      return ArrayMap.this.getKey(this.index);
    }

    public V getValue() {
      return ArrayMap.this.getValue(this.index);
    }

    public V setValue(V value) {
      return ArrayMap.this.set(this.index, value);
    }
  }
}
-------------------------
/*
 * Copyright (c) 2010 Google Inc.
 * 
 * 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 com.google.api.client.util;

import com.google.api.client.util.ArrayMap;

import junit.framework.TestCase;

/**
 * Tests {@link ArrayMap}.
 * 
 * @author Yaniv Inbar
 */
public class ArrayMapTest extends TestCase {

  public ArrayMapTest() {
  }

  public ArrayMapTest(String testName) {
    super(testName);
  }

  public void testOf_zero() {
    ArrayMap<String, Integer> map = ArrayMap.of();
    assertTrue(map.isEmpty());
  }

  public void testOf_one() {
    ArrayMap<String, Integer> map = ArrayMap.of("a", 1);
    assertEquals(1, map.size());
    assertEquals("a", map.getKey(0));
    assertEquals((Integer) 1, map.getValue(0));
  }

  public void testOf_two() {
    ArrayMap<String, Integer> map = ArrayMap.of("a", 1, "b", 2);
    assertEquals(2, map.size());
    assertEquals("a", map.getKey(0));
    assertEquals((Integer) 1, map.getValue(0));
    assertEquals("b", map.getKey(1));
    assertEquals((Integer) 2, map.getValue(1));
  }

  public void testRemove1() {
    ArrayMap<String, Integer> map = ArrayMap.of("a", 1, "b", 2);
    map.remove("b");
    assertEquals(ArrayMap.of("a", 1), map);
  }

  public void testRemove2() {
    ArrayMap<String, Integer> map = ArrayMap.of("a", 1, "b", 2);
    map.remove("a");
    assertEquals(ArrayMap.of("b", 2), map);
  }

  public void testRemove3() {
    ArrayMap<String, Integer> map = ArrayMap.of("a", 1);
    map.remove("a");
    assertEquals(ArrayMap.of(), map);
  }

  public void testRemove4() {
    ArrayMap<String, Integer> map = ArrayMap.of("a", 1, "b", 2, "c", 3);
    map.remove("b");
    assertEquals(ArrayMap.of("a", 1, "c", 3), map);
  }

  public void testClone_changingEntrySet() {
    ArrayMap<String, String> map = ArrayMap.of();
    assertEquals("{}", map.toString());
    ArrayMap<String, String> clone = map.clone();
    clone.add("foo", "bar");
    assertEquals("{foo=bar}", clone.toString());
  }
}

   
    
  








Related examples in the same category

1.Create random number
2.Create two random number generators with the same seed
3.Does Math.random() produce 0.0 and 1.0
4.Random numbers between 1 and 100
5.Random number between 0 AND 10
6.Random integers that range from from 0 to n
7.Random.nextInt(n) returns a distributed int value between 0 (inclusive) and n (exclusive).
8.Round Java float and double numbers using Math.round
9.Randomizer
10.nextDouble() and nextGaussian() in java.util.Random
11.Generating random numbers
12.Generate random ints by asking Random() for
13.Getting random numbers: nextGaussian
14.Generate a random array of numbers
15.Next double and next int
16.Random bytes
17.Random boolean
18.Random long type number
19.Random float type number
20.Random double type number
21.Math.random
22.A wrapper that supports all possible Random methods via the java.lang.Math#random() method and its system-wide {@link Random} object.
23.Operations for random Strings
24.Random Util with ReentrantLock
25.A Java implementation of the MT19937 (Mersenne Twister) pseudo random number generator algorithm
26.Randomizer
27.Random: Properly synchronized and can be used in a multithreaded environment
28.A predictable random number generator.
29.Random GUID
30.A random.org seeded random generator.
31.Generates a secure random word with the given length consisting of uppercase and lowercase letters and numbers.
32.A random choice maker
33.Generates a random integer inside the lo and hi interval
34.Randomizer
35.RandomGUID generates truly random GUIDs
36.A random access text file allows a text file to be accessed randomly starting at any position.
37.A garbage-free randomised quicksort
38.A linear random method based on xor shifts
39.Mersenne Twister Random
40.A GaussianHat is a random number generator that will give you numbers based on a population mean and standard deviation.
41.Randomly permutes the elements of the specified array
42.generate string consists of random characters.
43.Atomic Pseudo Random
44.Atomic Simple Random
45.Mersenne
46.Mersenne Twister
47.Mersenne Twister Fast
48.Mersenne Twister algorithm
49.Emulates a dice