HashMap如何添加元素详解

  Map接口结构

  map接口是一个双边队列,拥有key,value两个属性,其中key在存储的集合中不允许重复,value可以重复。

  MapHashMapLinkedHashMapHashtable实现map接口实现map接口继承HashMap实现map接口MapHashMapLinkedHashMapHashtable

  HashMap特点

  存储结构在jdk1.7当中是数组加链表的结构,在jdk1.8当中改为了数组加链表加红黑树的结构。

  HashMap在多线程的环境下是不安全的,没有进行加锁措施,所以执行效率快。如果我么需要有一个线程安全的HashMap,可以使用Collections.synchronizedMap(Map m)方法获得线程安全的HashMap,也可以使用ConcurrentHashMap类创建线程安全的map。

  存储的元素在jdk1.7当中是Entry作为存储的节点,在jdk1.8当中用Node作为存储的节点,Node实现了Map.Entry接口。在map中其实是将key和value节点组合起来成为一个Node节点作为存储。

  // jdk1.8Node节点

  static class Node implements Map.Entry {

  final int hash;

  final K key;

  V value;

  Node next;

  Node(int hash, K key, V value, Node next) {

  this.hash = hash;

  this.key = key;

  this.value = value;

  this.next = next;

  }

  // 下面省略代码

  }

  在HashMap中key不允许重复,value可以允许重复,并且key和value都允许是null值,因为他们是另外存储的。

  当我们使用自定义类作为HashMap的key时我们需要重写object类的,hashCode()和equals()方法。

  在HashMap中链表部分在jdk1.7中新的节点总是在链表的头部,旧的节点在新节点的next域当中,而在jdk1.8中是新的节点总是在链表的尾部,他们的指向都是由旧节点指向新的节点,由此我们可以记成七上八下。

  HashMap当中常见的名词

  DEFAULT_INITIAL_CAPACITY 默认数组容量大小,16

  MAXIMUM_CAPACITY 最大数组容量大小,2^30

  DEFAULT_LOAD_FACTOR 默认的负载因子,0.75

  TREEIFY_THRESHOLD 链表改成红黑树存储的最小长度

  MIN_TREEIFY_CAPACITY 链表改成红黑树存储,map数组最小容量

  HashMap存储元素过程源码解析(jdk1.8)

  // 存储元素的数组,加上transient关键字代表不可以被序列化

  transient Node[] table;

  // 这个方法是HashMap对外公开的添加元素方法

  public V put(K key, V value) {

  // 实际调用里面的一个默认修饰符的方法

  return putVal(hash(key), key, value, false, true);

  }

  final V putVal(int hash, K key, V value, boolean onlyIfAbsent,

  boolean evict) {

  Node[] tab; Node p; int n, i;

  // 在jdk1.7当中,创建一个HashMap实例是直接分配一个长度为16的数组,

  // 在jdk1.8当中,创建HashMap时是分配了一个长度为0的数组,然后调用put方法时,如果数组长

  // 度为0时,则调用数组扩容方法,扩容数组到长度为16。当长度不为0时,扩容操作是扩大到原来

  // 长度的两倍

  if ((tab = table) == null || (n = tab.length) == 0)

  // 调用扩容方法,并利用n变量,记录数组的长度

  n = (tab = resize()).length;

  // 通过计算哈希散列,得出该key应该放在数组的哪一个位置上,用变量p存储该位置上的元素

  // 判断该位置上是否已经存在元素,如果不存在元素则直接将该元素放入数组中,存放

  if ((p = tab[i = (n - 1) & hash]) == null)

  tab[i] = newNode(hash, key, value, null);

  // 如果数组的该位置已经存在了元素

  else {

  Node e; K k;

  // 比较需要加入的元素,于原来的元素hash值进行比较,如果hash值和equals都为true那么

  // 判定两个元素为相同元素,用变量e来记录原来的元素,方便下面进行替换

  if (p.hash == hash &&

  ((k = p.key) == key || (key != null && key.equals(k))))

  e = p;

  // 如果上述条件不满足,但是是一个TreeNode类型,则当作树节点插入

  else if (p instanceof TreeNode)

  e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);

  else {郑州哪家人流医院好 http://www.zzzyrl120.com/

  // 当hash值不同,或者equals为false,也不是TreeNode时将新元素插入数组对应位置的

  // 链表中,遍历对应数组位置的链表

  for (int binCount = 0; ; ++binCount) {

  // 用变量e存储链表的下一个节点

  if ((e = p.next) == null) {

  // 如果该链表只有一个元素,则直接将旧节点的next域指向新的节点

  p.next = newNode(hash, key, value, null);

  // 当单条链表上元素数量大于最大数量时则按照红黑树存储8,在进行红黑树时

  // 又会进行判断数组容量是否到达红黑树最小容量64,两个条件同时满足,则该

  // 条链表改造成红黑树存储

  if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st

  treeifyBin(tab, hash);

  break;

  }

  // 比较链表的下一个节点的hash值和equals

  if (e.hash == hash &&

  ((k = e.key) == key || (key != null && key.equals(k))))

  break;

  p = e;

  }

  }

  // 如果e不等于null则证明,在链表中存在相同的元素,则进行替换

  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;

  }

  // 将链表改造成红黑树

  final void treeifyBin(Node[] tab, int hash) {

  int n, index; Node e;

  // 判断是否满足条件,数组大小达到64,没有达到则做扩容操作,达到则改成红黑树

  if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)

  resize();

  else if ((e = tab[index = (n - 1) & hash]) != null) {

  TreeNode hd = null, tl = null;

  do {

  TreeNode p = replacementTreeNode(e, null);

  if (tl == null)

  hd = p;

  else {

  p.prev = tl;

  tl.next = p;

  }

  tl = p;

  } while ((e = e.next) != null);

  if ((tab[index] = hd) != null)

  hd.treeify(tab);

  }

  }

  HashMap常见的问题

  为什么在使用自定义类型作为key需要我们重写hashCode()和equals()方法

  因为map的存值的时候是先计算hash值,然后再判断equals,通过这两个值是否都为true来判断该元素是否再map中已经存在。如果不重写这两个方法,可能会存在我们认为相同,但是他们的hash或者equals不同的元素也能存进map中,从而达不到key唯一的效果。

  在HashMap中为什么要存在DEFAULT_LOAD_FACTOR负载因子这个概念

  因为我们map的存储数据结构是数组加链表加红黑树结构,如果没有负载因子,map是不可能满的,所以加上一个负载因子的概念来判断数组是否扩容,减少链表的负担。

posted @ 2020-03-18 14:18  网管布吉岛  阅读(2797)  评论(0编辑  收藏  举报