HashMap学习

Map 接口以及主要实现类

Map接口提供三类集合视图

  • 键(key) 集合
  • 值(value) 集合
  • 键值对(key-value) 集合
public interface Map<K, V> {
  Set<K> keySet();
  Collection<V> values();
  Set<Map.Entry<K, V>> entrySet();
}

AbstractMap类提供了Map接口的框架实现

大多数Map具体实现类均基于 AbstractMap 类扩展其所需要的方法实现。

// Example
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
	// methods
}

Map接口主要方法介绍

  • int size() : 返回Map中键值对个数
  • boolean isEmpty(): 返回Map是否为空
  • boolean containsKey(Object key) : 返回Map是否包含指定键key
  • boolean containsValue(Object value) : 返回Map是否包含指定值value
  • V get(Object key) : 返回指定key对应的值,若key不存在则返回null
  • V put(K key, V value) : 插入键值对,若指定key原本就在Map中,则更新key的值,并返回旧的值;若指定key原本不在Map中,则插入该键值对,并返回null
  • V remove(Object key) : 移除键值对,若Map中存在指定key,则移除该键值对,并返回对应的值value;若Map中不存在指定key,则返回null
  • void clear() : 删除Map中所有键值对
  • boolean equals(Object o) : 返回两个Map是否相同
  • int hashCode() : 计算当前Map的哈希值,hash(map) = sum(hash(key))
public interface Map<K,V> {
  int size();
  boolean isEmpty();
  boolean containsKey(Object key);
  boolean containsValue(Object value);
  V get(Object key);
  V put(K key, V value);
  V remove(Object key);
  
  // 拷贝m中的键值对到当前Map,每个元素执行put操作
  void putAll(Map<? extends K, ? extends V> m);
  
  void clear();
  boolean equals(Object o);
  int hashCode();
  
  /**
   * 默认方法 Default
   * 1.可以写方法内容
   * 2.不强制实现类重写
   * 3.接口的实现可以直接调用default方法
   */
  
  // 查找指定key的值,若key不存在则返回默认值
  default V getOrDefault(Object key, V defaultValue) {
    V v;
    return (((v = get(key)) != null) || containsKey(key))
      ? v
      : defaultValue;
  }
  
  // 使用指定action.accept()方法处理所有键值对
  default void forEach(BiConsumer<? super K, ? super V> action) {
    Objects.requireNonNull(action);
    for (Map.Entry<K, V> entry : entrySet()) {
      K k;
      V v;
      try {
        k = entry.getKey();
        v = entry.getValue();
      } catch(IllegalStateException ise) {
        // this usually means the entry is no longer in the map.
        throw new ConcurrentModificationException(ise);
      }
      action.accept(k, v);
    }
  }
  
  // 使用指定function.apply()方法更新所有键值对
  default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
    Objects.requireNonNull(function);
    for (Map.Entry<K, V> entry : entrySet()) {
      K k;
      V v;
      try {
        k = entry.getKey();
        v = entry.getValue();
      } catch(IllegalStateException ise) {
        // this usually means the entry is no longer in the map.
        throw new ConcurrentModificationException(ise);
      }

      // ise thrown from function is not a cme.
      v = function.apply(k, v);

      try {
        entry.setValue(v);
      } catch(IllegalStateException ise) {
        // this usually means the entry is no longer in the map.
        throw new ConcurrentModificationException(ise);
      }
    }
  }
  
  // 如果Map中不包含指定key,则插入该键值对;否则返回指定key对应的值
  default V putIfAbsent(K key, V value) {
    V v = get(key);
    if (v == null) {
      v = put(key, value);
    }

    return v;
  }
  
  // 移除指定键值对
  default boolean remove(Object key, Object value) {
    Object curValue = get(key);
    if (!Objects.equals(curValue, value) ||
        (curValue == null && !containsKey(key))) {
      return false;
    }
    remove(key);
    return true;
  }
  
  // 更新指定键值对
  default boolean replace(K key, V oldValue, V newValue) {
    Object curValue = get(key);
    if (!Objects.equals(curValue, oldValue) ||
        (curValue == null && !containsKey(key))) {
      return false;
    }
    put(key, newValue);
    return true;
  }
  
  // 更新指定键值对并返回旧的值value
  default V replace(K key, V value) {
    V curValue;
    if (((curValue = get(key)) != null) || containsKey(key)) {
      curValue = put(key, value);
    }
    return curValue;
  }
}

HashMap源码阅读

HashMap是使用最频繁的Map实现类之一,主要用于存放键值对,允许 key 和 value 为null值,不保证元素顺序,线程不安全,使用内部类 Node <K, V> 存在元素。

  • HashMap使用 数组 + 链表/红黑树 的结构存储元素
  • 当链表长度大于阈值(默认值8)时:
    • 当前数组长度小于 MIN_TREEIFY_CAPACITY,则对数组扩容(2倍)
    • 当前数组长度不小于 MIN_TREEIFY_CAPACITY,则转换为红黑树

HashMap主要属性介绍

public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable {
  // 序列号,用于Java序列化
  private static final long serialVersionUID = 362498820763181265L;
  
  // 默认初始化容量 16
  static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
  
  // HashMap最大容量 2^30 - 1
  static final int MAXIMUM_CAPACITY = 1 << 30;
  
  /**
   * 默认负载因子 0.75
   * count(hash_map) > load_factor * capacity: 触发扩容
   */
  static final float DEFAULT_LOAD_FACTOR = 0.75f;
  
  // 对于哈希冲突产生的链表结构,将其转换为树结构的阈值
  static final int TREEIFY_THRESHOLD = 8;
  
  // 上面转换操作的逆操作阈值
  static final int UNTREEIFY_THRESHOLD = 6;
  
  /**
   * 使用树结构的最小容量
   * 如果table容量小于64且链表长度超过设置的阈值,则优先对table扩容
   */
  static final int MIN_TREEIFY_CAPACITY = 64;
  
  // 存储键值对的数组,容量总是2的幂次方
  transient Node<K,V>[] table;
  
  // 存储键值对的集合
  transient Set<Map.Entry<K,V>> entrySet;
  
  // map中键值对的数量
  transient int size;
  
  // map扩容和结构改变的次数
  transient int modCount;
  
  // 扩容阈值 LOAD_FACTOR * CAPACITY
  int threshold;
  
  // 加载因子
  final float loadFactor;
}

HashMap构造方法介绍

/**
 * 默认构造函数
 * 使用默认的载入因子 0.75
 * 使用默认容量 16
 * put操作时才初始化table数组
 */
public HashMap() {
  this.loadFactor = DEFAULT_LOAD_FACTOR;
}

/**
 * 自定义初始容量
 */
public HashMap(int initialCapacity) {
  this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

/**
 * 自定义初始容量和加载因子
 */
public HashMap(int initialCapacity, float loadFactor) {
  if (initialCapacity < 0)
    throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
  if (initialCapacity > MAXIMUM_CAPACITY)
    initialCapacity = MAXIMUM_CAPACITY;
  if (loadFactor <= 0 || Float.isNaN(loadFactor))
    throw new IllegalArgumentException("Illegal load factor: " +
                                       loadFactor);
  this.loadFactor = loadFactor;
  
  // 返回2的幂次方,如initialCapacity=5,则threshold=8
  this.threshold = tableSizeFor(initialCapacity);
}

/**
 * 使用Map m初始化要构造的HashMap
 */
public HashMap(Map<? extends K, ? extends V> m) {
  this.loadFactor = DEFAULT_LOAD_FACTOR;
  putMapEntries(m, false);
}

HashMap核心方法介绍

插入键值对 —— V put(K key, V value)方法

// 计算 key 的哈希值
static final int hash(Object key) {
  int h;
  return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

public V put(K key, V value) {
  // 调用 putVal 方法插入元素
  return putVal(hash(key), key, value, false, true);
}

/**
 * 缺省 访问控制修饰符,仅供包内使用,用户无法调用
 * @param hash: key的哈希值
 * @param key: 插入的键
 * @param value: 插入的值
 * @param onlyIfAbsent: ture -> 不更新已存在的键值对
 * @param evict: 在HashMap中不做处理 
 * @return: 返回旧的value值,若不存在则返回Null
 */
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
  Node<K,V>[] tab; 
  Node<K,V> p; 
  int n, i;
  
  // 如果 table 未初始化或长度为0,则使用 resize()方法初始化和扩容
  if ((tab = table) == null || (n = tab.length) == 0)
    n = (tab = resize()).length;
  
  /**
   * tab[i = (n - 1) & hash]: 计算键值对应该存储的table位置
   * 1.若当前位置还没有元素,则直接添加
   * 2.若当前位置已有元素,则判断当前要插入的 key 和已存在元素的 key 是否一致
   * 3.如果一致,则直接更新该元素的value
   * 4.如果不一致,则插入链表尾部或插入红黑树
   */
  if ((p = tab[i = (n - 1) & hash]) == null)
    tab[i] = newNode(hash, key, value, null);
  else {
    Node<K,V> e; 
    K k;
    if (p.hash == hash &&
        ((k = p.key) == key || (key != null && key.equals(k))))
      e = p;
    else if (p instanceof TreeNode)
      e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
    else {
      for (int binCount = 0; ; ++binCount) {
        if ((e = p.next) == null) {
          p.next = newNode(hash, key, value, null);
          if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
            treeifyBin(tab, hash);
          break;
        }
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k))))
          break;
        p = e;
      }
    }
    if (e != null) { // existing mapping for key
      V oldValue = e.value;
      if (!onlyIfAbsent || oldValue == null)
        e.value = value;
      afterNodeAccess(e);
      return oldValue;
    }
  }
  ++modCount;
  if (++size > threshold)
    resize();
  afterNodeInsertion(evict);
  return null;
}

/**
 * putIfAbsent()方法
 * 在 put()方法的基础上,将onlyIfAbsent设置true调用putval()方法
 * 即map中已存在key时,不更新该元素,并返回该key在map中的value值
 */
public V putIfAbsent(K key, V value) {
  return putVal(hash(key), key, value, true, true);
}

查询键值对 —— V get(K key) 方法

public V get(Object key) {
  Node<K,V> e;
  // 调用 getNode() 方法查询元素
  return (e = getNode(hash(key), key)) == null ? null : e.value;
}

/**
 * 缺省 访问控制修饰符,仅供包内使用,用户无法调用
 * @param hash: key的哈希值
 * @param key: 键
 * @return: 返回查询到的键值对Node
 */
final Node<K,V> getNode(int hash, Object key) {
  Node<K,V>[] tab; 
  Node<K,V> first, e; 
  int n; 
  K k;
  
  // tab[(n - 1) & hash] 该位置为 key 的哈希索引位置
  if ((tab = table) != null && (n = tab.length) > 0 &&
      (first = tab[(n - 1) & hash]) != null) {
    // 比较该位置第1个元素与查询key
    if (first.hash == hash && // always check first node
        ((k = first.key) == key || (key != null && key.equals(k))))
      return first;
    
    // 如果第1个元素不是要找的key,则遍历整个链表或红黑树查找
    if ((e = first.next) != null) {
      if (first instanceof TreeNode)
        return ((TreeNode<K,V>)first).getTreeNode(hash, key);
      do {
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k))))
          return e;
      } while ((e = e.next) != null);
    }
  }
  
  // 若未找到则返回null
  return null;
}

/**
 * getOrDefault() 方法
 * 该方法在get()方法的基础上,添加了默认返回值。
 * 若 key 存在 map 中,则反回对应的 value
 * 若 key 不存在,则返回默认值 defaultValue
 */
public V getOrDefault(Object key, V defaultValue) {
  Node<K,V> e;
  return (e = getNode(hash(key), key)) == null ? defaultValue : e.value;
}

删除键值对 —— V remove(Object key) 方法

public V remove(Object key) {
  Node<K,V> e;
  return (e = removeNode(hash(key), key, null, false, true)) == null ? null : e.value;
}

/**
 * 缺省 访问控制修饰符,仅供包内使用,用户无法调用
 * @param hash: key的哈希值
 * @param key: 键
 * @param value: 值
 * @param matchValue: 是否需要匹配value,true->键在map中的value值和传入的参数value匹配才会删除该键值对
 * @param movable: 是否要移动其它节点
 */
final Node<K,V> removeNode(int hash, Object key, Object value,
                           boolean matchValue, boolean movable) {
  Node<K,V>[] tab; 
  Node<K,V> p; 
  int n, index;
  if ((tab = table) != null && (n = tab.length) > 0 &&
      (p = tab[index = (n - 1) & hash]) != null) {
    Node<K,V> node = null, e; 
    K k; 
    V v;
    
    /**
     * 查询指定的Key
     */
    if (p.hash == hash &&
        ((k = p.key) == key || (key != null && key.equals(k))))
      node = p;
    else if ((e = p.next) != null) {
      if (p instanceof TreeNode)
        node = ((TreeNode<K,V>)p).getTreeNode(hash, key);
      else {
        do {
          if (e.hash == hash &&
              ((k = e.key) == key ||
               (key != null && key.equals(k)))) {
            node = e;
            break;
          }
          p = e;
        } while ((e = e.next) != null);
      }
    }
    
    /**
     * 删除指定键值对
     */
    if (node != null && (!matchValue || (v = node.value) == value ||
                         (value != null && value.equals(v)))) {
      if (node instanceof TreeNode)
        ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable);
      else if (node == p)
        tab[index] = node.next;
      else
        p.next = node.next;
      ++modCount;
      --size;
      afterNodeRemoval(node);
      return node;
    }
  }
  return null;
}

HashMap简单使用

package hash_map;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class HashMapExamle {
  public static void main(String[] args) {
    // 使用默认构造函数, 默认容量为16, 载入因子为 0.75
    HashMap<String, String> map = new HashMap<>();

    for (int i = 0; i < 20; i++) {
      // 添加第13个元素时触发扩容:16 * 0.75 = 12
      map.put(i + "", "Example " + i);
    }

    // 支持null作为key和value
    map.put(null, "Null key");
    map.put("Null value", null);

    map.remove("0");

    // get获取指定键对应的值, 若不存在则返回null
    // getOrDefault获取指定键对应的值, 若不存在在则返回默认值
    System.out.println(map.get("2")); // Example 2
    System.out.println(map.get("20") + " " + map.getOrDefault("20", "Default Value"));  // null Default Value

    System.out.println(map.containsKey("2") + " " + map.containsKey("20"));  // true false
    System.out.println(map.containsValue("Example 2") + " " + map.containsKey("Example 20"));  //true false

    // entrySet()遍历哈希表
    Set<Map.Entry<String, String>> entrySet = map.entrySet();
    for (Map.Entry<String, String> entry : entrySet) {
      System.out.println(entry.getKey() + ": " + entry.getValue());
    }

    // keySet()遍历哈希表
    Set<String> keySet = map.keySet();
    for (String key : keySet) {
      System.out.println(key);
    }

    // valueSet()获取所有value值
    Collection<String> values = map.values();
    for (String value : values) {
      System.out.println(value);
    }
  }
}

参考文章:

[1] Java HashMap - HashMap in Java

[2] HashMap源码&底层数据结构分析

posted @ 2022-07-05 18:38  ylyzty  阅读(21)  评论(0编辑  收藏  举报