IntObjectHashMap和HashMap的区别?

简介: IntObjectHashMap和HashMap的区别?

  源码来源地址:https://github.com/netty/netty/tree/4.0

不恰当的数据结构导致内存在占用过大。这个问题,就完全可以使用 Netty 的IntObjectHashMap 数据结构来解决,只需要换个类,就可以节省非常多的资源。

相同点:

IntObjectHashMap和HashMap都是以键值对的形式,储存对象。

不同点:

IntObjectHashMap的键只能是int类型,值是Object类型。

HashMap的键和值都是Object类型。

目录

一、IntObjectMap.java

二、IntObjectHashMap.java


一、IntObjectMap.java

package com;
import java.util.Collection;
/**
 * Interface for a primitive map that uses {@code int}s as keys.
 *
 * @param <V> the value type stored in the map.
 */
public interface IntObjectMap<V> {
  /**
   * An Entry in the map.
   *
   * @param <V> the value type stored in the map.
   */
  interface Entry<V> {
    /**
     * Gets the key for this entry.
     */
    int key();
    /**
     * Gets the value for this entry.
     */
    V value();
    /**
     * Sets the value for this entry.
     */
    void setValue(V value);
  }
  /**
   * Gets the value in the map with the specified key.
   *
   * @param key the key whose associated value is to be returned.
   * @return the value or {@code null} if the key was not found in the map.
   */
  V get(int key);
  /**
   * Puts the given entry into the map.
   *
   * @param key   the key of the entry.
   * @param value the value of the entry.
   * @return the previous value for this key or {@code null} if there was no
   *         previous mapping.
   */
  V put(int key, V value);
  /**
   * Puts all of the entries from the given map into this map.
   */
  void putAll(IntObjectMap<V> sourceMap);
  /**
   * Removes the entry with the specified key.
   *
   * @param key the key for the entry to be removed from this map.
   * @return the previous value for the key, or {@code null} if there was no
   *         mapping.
   */
  V remove(int key);
  /**
   * Returns the number of entries contained in this map.
   */
  int size();
  /**
   * Indicates whether or not this map is empty (i.e {@link #size()} ==
   * {@code 0]).
   */
  boolean isEmpty();
  /**
   * Clears all entries from this map.
   */
  void clear();
  /**
   * Indicates whether or not this map contains a value for the specified key.
   */
  boolean containsKey(int key);
  /**
   * Indicates whether or not the map contains the specified value.
   */
  boolean containsValue(V value);
  /**
   * Gets an iterable collection of the entries contained in this map.
   */
  Iterable<Entry<V>> entries();
  /**
   * Gets the keys contained in this map.
   */
  int[] keys();
  /**
   * Gets the values contained in this map.
   */
  V[] values(Class<V> clazz);
  /**
   * Gets the values contatins in this map as a {@link Collection}.
   */
  Collection<V> values();
}

image.gif

二、IntObjectHashMap.java

package com;
/*
 * Copyright 2014 The Netty Project
 *
 * The Netty Project 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.lang.reflect.Array;
import java.util.AbstractCollection;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
 * A hash map implementation of {@link IntObjectMap} that uses open addressing
 * for keys. To minimize the memory footprint, this class uses open addressing
 * rather than chaining. Collisions are resolved using linear probing. Deletions
 * implement compaction, so cost of remove can approach O(N) for full maps,
 * which makes a small loadFactor recommended.
 *
 * @param <V> The value type stored in the map.
 */
public class IntObjectHashMap<V> implements IntObjectMap<V>, Iterable<IntObjectMap.Entry<V>> {
  /** Default initial capacity. Used if not specified in the constructor */
  private static final int DEFAULT_CAPACITY = 11;
  /** Default load factor. Used if not specified in the constructor */
  private static final float DEFAULT_LOAD_FACTOR = 0.5f;
  /**
   * Placeholder for null values, so we can use the actual null to mean available.
   * (Better than using a placeholder for available: less references for GC
   * processing.)
   */
  private static final Object NULL_VALUE = new Object();
  /** The maximum number of elements allowed without allocating more space. */
  private int maxSize;
  /** The load factor for the map. Used to calculate {@link #maxSize}. */
  private final float loadFactor;
  private int[] keys;
  private V[] values;
  private Collection<V> valueCollection;
  private int size;
  public IntObjectHashMap() {
    this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
  }
  public IntObjectHashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
  }
  public IntObjectHashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 1) {
      throw new IllegalArgumentException("initialCapacity must be >= 1");
    }
    if (loadFactor <= 0.0f || loadFactor > 1.0f) {
      // Cannot exceed 1 because we can never store more than capacity elements;
      // using a bigger loadFactor would trigger rehashing before the desired load is
      // reached.
      throw new IllegalArgumentException("loadFactor must be > 0 and <= 1");
    }
    this.loadFactor = loadFactor;
    // Adjust the initial capacity if necessary.
    int capacity = adjustCapacity(initialCapacity);
    // Allocate the arrays.
    keys = new int[capacity];
    @SuppressWarnings({ "unchecked", "SuspiciousArrayCast" })
    V[] temp = (V[]) new Object[capacity];
    values = temp;
    // Initialize the maximum size value.
    maxSize = calcMaxSize(capacity);
  }
  private static <T> T toExternal(T value) {
    return value == NULL_VALUE ? null : value;
  }
  @SuppressWarnings("unchecked")
  private static <T> T toInternal(T value) {
    return value == null ? (T) NULL_VALUE : value;
  }
  @Override
  public V get(int key) {
    int index = indexOf(key);
    return index == -1 ? null : toExternal(values[index]);
  }
  @Override
  public V put(int key, V value) {
    int startIndex = hashIndex(key);
    int index = startIndex;
    for (;;) {
      if (values[index] == null) {
        // Found empty slot, use it.
        keys[index] = key;
        values[index] = toInternal(value);
        growSize();
        return null;
      }
      if (keys[index] == key) {
        // Found existing entry with this key, just replace the value.
        V previousValue = values[index];
        values[index] = toInternal(value);
        return toExternal(previousValue);
      }
      // Conflict, keep probing ...
      if ((index = probeNext(index)) == startIndex) {
        // Can only happen if the map was full at MAX_ARRAY_SIZE and couldn't grow.
        throw new IllegalStateException("Unable to insert");
      }
    }
  }
  private int probeNext(int index) {
    return index == values.length - 1 ? 0 : index + 1;
  }
  @Override
  public void putAll(IntObjectMap<V> sourceMap) {
    if (sourceMap instanceof IntObjectHashMap) {
      // Optimization - iterate through the arrays.
      IntObjectHashMap<V> source = (IntObjectHashMap<V>) sourceMap;
      for (int i = 0; i < source.values.length; ++i) {
        V sourceValue = source.values[i];
        if (sourceValue != null) {
          put(source.keys[i], sourceValue);
        }
      }
      return;
    }
    // Otherwise, just add each entry.
    for (Entry<V> entry : sourceMap.entries()) {
      put(entry.key(), entry.value());
    }
  }
  @Override
  public V remove(int key) {
    int index = indexOf(key);
    if (index == -1) {
      return null;
    }
    V prev = values[index];
    removeAt(index);
    return toExternal(prev);
  }
  @Override
  public int size() {
    return size;
  }
  @Override
  public boolean isEmpty() {
    return size == 0;
  }
  @Override
  public void clear() {
    Arrays.fill(keys, 0);
    Arrays.fill(values, null);
    size = 0;
  }
  @Override
  public boolean containsKey(int key) {
    return indexOf(key) >= 0;
  }
  @Override
  public boolean containsValue(V value) {
    V v1 = toInternal(value);
    for (V v2 : values) {
      // The map supports null values; this will be matched as
      // NULL_VALUE.equals(NULL_VALUE).
      if (v2 != null && v2.equals(v1)) {
        return true;
      }
    }
    return false;
  }
  @Override
  public Iterable<Entry<V>> entries() {
    return this;
  }
  @Override
  public Iterator<Entry<V>> iterator() {
    return new IteratorImpl();
  }
  @Override
  public int[] keys() {
    int[] outKeys = new int[size()];
    int targetIx = 0;
    for (int i = 0; i < values.length; ++i) {
      if (values[i] != null) {
        outKeys[targetIx++] = keys[i];
      }
    }
    return outKeys;
  }
  @Override
  public V[] values(Class<V> clazz) {
    @SuppressWarnings("unchecked")
    V[] outValues = (V[]) Array.newInstance(clazz, size());
    int targetIx = 0;
    for (V value : values) {
      if (value != null) {
        outValues[targetIx++] = value;
      }
    }
    return outValues;
  }
  @Override
  public Collection<V> values() {
    Collection<V> valueCollection = this.valueCollection;
    if (valueCollection == null) {
      this.valueCollection = valueCollection = new AbstractCollection<V>() {
        @Override
        public Iterator<V> iterator() {
          return new Iterator<V>() {
            final Iterator<Entry<V>> iter = IntObjectHashMap.this.iterator();
            @Override
            public boolean hasNext() {
              return iter.hasNext();
            }
            @Override
            public V next() {
              return iter.next().value();
            }
            @Override
            public void remove() {
              throw new UnsupportedOperationException();
            }
          };
        }
        @Override
        public int size() {
          return size;
        }
      };
    }
    return valueCollection;
  }
  @Override
  public int hashCode() {
    // Hashcode is based on all non-zero, valid keys. We have to scan the whole keys
    // array, which may have different lengths for two maps of same size(), so the
    // capacity cannot be used as input for hashing but the size can.
    int hash = size;
    for (int key : keys) {
      // 0 can be a valid key or unused slot, but won't impact the hashcode in either
      // case.
      // This way we can use a cheap loop without conditionals, or hard-to-unroll
      // operations,
      // or the devastatingly bad memory locality of visiting value objects.
      // Also, it's important to use a hash function that does not depend on the
      // ordering
      // of terms, only their values; since the map is an unordered collection and
      // entries can end up in different positions in different maps that have the
      // same
      // elements, but with different history of puts/removes, due to conflicts.
      hash ^= key;
    }
    return hash;
  }
  @Override
  public boolean equals(Object obj) {
    if (this == obj) {
      return true;
    }
    if (!(obj instanceof IntObjectMap)) {
      return false;
    }
    @SuppressWarnings("rawtypes")
    IntObjectMap other = (IntObjectMap) obj;
    if (size != other.size()) {
      return false;
    }
    for (int i = 0; i < values.length; ++i) {
      V value = values[i];
      if (value != null) {
        int key = keys[i];
        Object otherValue = other.get(key);
        if (value == NULL_VALUE) {
          if (otherValue != null) {
            return false;
          }
        } else if (!value.equals(otherValue)) {
          return false;
        }
      }
    }
    return true;
  }
  /**
   * Locates the index for the given key. This method probes using double hashing.
   *
   * @param key the key for an entry in the map.
   * @return the index where the key was found, or {@code -1} if no entry is found
   *         for that key.
   */
  private int indexOf(int key) {
    int startIndex = hashIndex(key);
    int index = startIndex;
    for (;;) {
      if (values[index] == null) {
        // It's available, so no chance that this value exists anywhere in the map.
        return -1;
      }
      if (key == keys[index]) {
        return index;
      }
      // Conflict, keep probing ...
      if ((index = probeNext(index)) == startIndex) {
        return -1;
      }
    }
  }
  /**
   * Returns the hashed index for the given key.
   */
  private int hashIndex(int key) {
    // Allowing for negative keys by adding the length after the first mod
    // operation.
    return (key % keys.length + keys.length) % keys.length;
  }
  /**
   * Grows the map size after an insertion. If necessary, performs a rehash of the
   * map.
   */
  private void growSize() {
    size++;
    if (size > maxSize) {
      // Need to grow the arrays. We take care to detect integer overflow,
      // also limit array size to ArrayList.MAX_ARRAY_SIZE.
      rehash(adjustCapacity((int) Math.min(keys.length * 2.0, Integer.MAX_VALUE - 8)));
    } else if (size == keys.length) {
      // Open addressing requires that we have at least 1 slot available. Need to
      // refresh
      // the arrays to clear any removed elements.
      rehash(keys.length);
    }
  }
  /**
   * Adjusts the given capacity value to ensure that it's odd. Even capacities can
   * break probing.
   */
  private static int adjustCapacity(int capacity) {
    return capacity | 1;
  }
  /**
   * Removes entry at the given index position. Also performs opportunistic,
   * incremental rehashing if necessary to not break conflict chains.
   *
   * @param index the index position of the element to remove.
   */
  private void removeAt(int index) {
    --size;
    // Clearing the key is not strictly necessary (for GC like in a regular
    // collection),
    // but recommended for security. The memory location is still fresh in the cache
    // anyway.
    keys[index] = 0;
    values[index] = null;
    // In the interval from index to the next available entry, the arrays may have
    // entries
    // that are displaced from their base position due to prior conflicts. Iterate
    // these
    // entries and move them back if possible, optimizing future lookups.
    // Knuth Section 6.4 Algorithm R, also used by the JDK's IdentityHashMap.
    int nextFree = index;
    for (int i = probeNext(index); values[i] != null; i = probeNext(i)) {
      int bucket = hashIndex(keys[i]);
      if (i < bucket && (bucket <= nextFree || nextFree <= i) || bucket <= nextFree && nextFree <= i) {
        // Move the displaced entry "back" to the first available position.
        keys[nextFree] = keys[i];
        values[nextFree] = values[i];
        // Put the first entry after the displaced entry
        keys[i] = 0;
        values[i] = null;
        nextFree = i;
      }
    }
  }
  /**
   * Calculates the maximum size allowed before rehashing.
   */
  private int calcMaxSize(int capacity) {
    // Clip the upper bound so that there will always be at least one available
    // slot.
    int upperBound = capacity - 1;
    return Math.min(upperBound, (int) (capacity * loadFactor));
  }
  /**
   * Rehashes the map for the given capacity.
   *
   * @param newCapacity the new capacity for the map.
   */
  private void rehash(int newCapacity) {
    int[] oldKeys = keys;
    V[] oldVals = values;
    keys = new int[newCapacity];
    @SuppressWarnings({ "unchecked", "SuspiciousArrayCast" })
    V[] temp = (V[]) new Object[newCapacity];
    values = temp;
    maxSize = calcMaxSize(newCapacity);
    // Insert to the new arrays.
    for (int i = 0; i < oldVals.length; ++i) {
      V oldVal = oldVals[i];
      if (oldVal != null) {
        // Inlined put(), but much simpler: we don't need to worry about
        // duplicated keys, growing/rehashing, or failing to insert.
        int oldKey = oldKeys[i];
        int index = hashIndex(oldKey);
        for (;;) {
          if (values[index] == null) {
            keys[index] = oldKey;
            values[index] = toInternal(oldVal);
            break;
          }
          // Conflict, keep probing. Can wrap around, but never reaches startIndex again.
          index = probeNext(index);
        }
      }
    }
  }
  /**
   * Iterator for traversing the entries in this map.
   */
  private final class IteratorImpl implements Iterator<Entry<V>>, Entry<V> {
    private int prevIndex = -1;
    private int nextIndex = -1;
    private int entryIndex = -1;
    private void scanNext() {
      for (;;) {
        if (++nextIndex == values.length || values[nextIndex] != null) {
          break;
        }
      }
    }
    @Override
    public boolean hasNext() {
      if (nextIndex == -1) {
        scanNext();
      }
      return nextIndex < keys.length;
    }
    @Override
    public Entry<V> next() {
      if (!hasNext()) {
        throw new NoSuchElementException();
      }
      prevIndex = nextIndex;
      scanNext();
      // Always return the same Entry object, just change its index each time.
      entryIndex = prevIndex;
      return this;
    }
    @Override
    public void remove() {
      if (prevIndex < 0) {
        throw new IllegalStateException("next must be called before each remove.");
      }
      removeAt(prevIndex);
      prevIndex = -1;
    }
    // Entry implementation. Since this implementation uses a single Entry, we
    // coalesce that
    // into the Iterator object (potentially making loop optimization much easier).
    @Override
    public int key() {
      return keys[entryIndex];
    }
    @Override
    public V value() {
      return toExternal(values[entryIndex]);
    }
    @Override
    public void setValue(V value) {
      values[entryIndex] = toInternal(value);
    }
  }
  @Override
  public String toString() {
    if (size == 0) {
      return "{}";
    }
    StringBuilder sb = new StringBuilder(4 * size);
    for (int i = 0; i < values.length; ++i) {
      V value = values[i];
      if (value != null) {
        sb.append(sb.length() == 0 ? "{" : ", ");
        sb.append(keyToString(keys[i])).append('=').append(value == this ? "(this Map)" : value);
      }
    }
    return sb.append('}').toString();
  }
  /**
   * Helper method called by {@link #toString()} in order to convert a single map
   * key into a string.
   */
  protected String keyToString(int key) {
    return Integer.toString(key);
  }
}

image.gif


目录
相关文章
|
4月前
|
存储 缓存 安全
面试题-HashMap底层原理与HashTable的区别
字节跳动面试题-HashMap底层原理与HashTable的区别
50 0
|
11月前
|
存储 Java
每日一道面试题之谈一谈HashMap和HashSet的区别
每日一道面试题之谈一谈HashMap和HashSet的区别
|
1月前
|
存储 安全 Java
一天十道Java面试题----第二天(HashMap和hashTable的区别--------》sleep、wait、join)
这篇文章是关于Java面试的第二天笔记,涵盖了HashMap与HashTable的区别、ConcurrentHashMap的实现原理、IOC容器的实现方法、字节码的概念和作用、Java类加载器的类型、双亲委派模型、Java异常体系、GC如何判断对象可回收、线程的生命周期及状态,以及sleep、wait、join、yield的区别等十道面试题。
一天十道Java面试题----第二天(HashMap和hashTable的区别--------》sleep、wait、join)
|
1月前
|
存储 Java
【Java集合类面试七】、 JDK7和JDK8中的HashMap有什么区别?
JDK7中的HashMap使用数组加链表解决冲突,而JDK8增加了红黑树结构以优化链表过长时的性能,提高查找效率。
|
1月前
|
安全 Java
【Java集合类面试十五】、说一说HashMap和HashTable的区别
HashMap和Hashtable的主要区别在于Hashtable是线程安全的,不允许null键和值,而HashMap是非线程安全的,允许null键和值。
|
1月前
|
安全 Java
【Java集合类面试十六】、HashMap与ConcurrentHashMap有什么区别?
HashMap是非线程安全的,而ConcurrentHashMap通过减少锁粒度来提高并发性能,检索操作无需锁,从而提供更好的线程安全性和性能。
|
29天前
|
存储 安全 Java
Hashtable 和 HashMap 的区别
【8月更文挑战第22天】
59 0
|
10月前
|
安全 Java
HashMap和Hashtable的区别
HashMap和Hashtable的区别
48 2
|
2月前
|
存储 安全 Java
Java面试题:请解释Java内存模型,并说明如何在多线程环境下使用synchronized关键字实现同步,阐述ConcurrentHashMap与HashMap的区别,以及它如何在并发环境中提高性能
Java面试题:请解释Java内存模型,并说明如何在多线程环境下使用synchronized关键字实现同步,阐述ConcurrentHashMap与HashMap的区别,以及它如何在并发环境中提高性能
26 0
|
3月前
|
消息中间件 存储 缓存
面试题--HashMap和TreeMap的区别和应用场景有啥区别?
然后底层调用key的hashCode()方法得出hash值; 过哈希表哈希算法,将hash值转换成数组的下标(注1),下标位置上如果没有任何元素,就把Node添加到这个位置上。如果说下标对应的位置上有值。此时,就会拿着key和链表上每个节点的key进行equal。如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true,那么这个节点的value将会被覆盖,如果最终长度大于8就会转成红黑树,红黑树插入;
35 3