HashMap源代码阅读理解

  前言:
  
  这个实现跟数组和链表相关。应用了他们各自的特点,进行优化。因为之前的很多工作经验都是.NET的,所以之前都研究了.NET的东西。在写了一阵子java之后,熟悉了java,就开始写一些java的东西。
  
  做一门语言,了解底层的实现一直是我的追求。毕竟了解了他们的实现,你才会了解到到底什么样的情况下,该用什么样的集合。
  
  数组的优势是,方便查找,但是新增超过加载因子的阈值的话(这部分是hashmap初始化的时候赋值进去的,如果不赋值,则会给默认值),就需要重新分配。链表查找比较麻烦,顺子链子一直找,找到位置才截止,有多少就得找多少。
  
  数组的时间复杂度为O(1),链表的时间复杂度为O(n)。因为数组是根据下标来找的,而数组的大小是确定的。而链表的大小是不确定的,是根据有多少元素进行多少次查找,找到为止。
  
  于是就会有人说hash碰撞,什么是hash碰撞,根据key(这里的key是根据散列值计算出来的)找数组的时候,发现数组的下标已经有值了,有值的情况下,会走链表的对比。所以会有碰撞的情况下,时间复杂度为O(n)的说法。
  
  不过看了源代码的收获还是蛮大的,很多写法值得借鉴。因为里面的很多变量都是一个英文字母之类的,因此可读性比较差。但是我想,为了性能考虑的话,这一切都是值得的。毕竟源代码的性能高于可阅读性。所以就造成了我们阅读源代码的时候有点吃力。
  
  文章比较枯燥,很多都是个人见解。如果哪里错了,望指出来共同进步。后面有时间了,也会写一些其他的集合类型的源码理解。因为这对于代码的优化也有借鉴作用,谢谢大家!
  
  正文:
  
  先看下构造函数部分,用来初始化的部分,其实初始化也很重要。容量和加载因子最好设置在合适的范围。过小的话,会不断的resize。过大的话又显得太过于浪费。因此根据需求来初始化一个合适的值,显得还是很重要的
  
  无初始化参数
  
  public HashMap() {
  
  this.loadFactor = DEFAULT_LOAD_FACTOR; // 加载因子默认0.75f
  
  }
  
  根据m初始化
  
  public HashMap(Map<? extends K, ? extends V> m) {//初始化并加入m的内容
  
  this.loadFactor = DEFAULT_LOAD_FACTOR; // 加载因子默认0.75f
  
  putMapEntries(m, false);//@1
  
  }
  
  public HashMap(int initialCapacity, float loadFactor) { //初始化hashmap的容量和加载因子
  
  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;
  
  this.threshold = tableSizeFor(initialCapacity); //根据容量和加载因子得到阈值 @2
  
  }
  
  看完了初始化,再来看看put
  
  public V put(K key, V value) {
  
  return putVal(hash(key), key, value, false, true);//@3 @4
  
  }
  
  再来看看取值
  
  public V get(Object key) {
  
  Node<K,V> e;
  
  return (e = getNode(hash(key), key)) == null ? null : e.value;//@5
  
  }
  
  --------------------------------------------------------------------------------方法说明--------------------------------------------------------------------------------------
  
  /**
  
  * @1把m赋值到初始化的hashMap里
  
  */
  
  final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
  
  int s = m.size();
  
  if (s > 0) {
  
  if (table == null) { //初始化的时候,哈希桶是空的,所以走这段逻辑
  
  float ft = ((float)s / loadFactor) + 1.0F; //如果我们传的m的size是6,6/0.75 + 1 = 9 为什么+1 我个人的理解就是先+1,看看是否超过了阈值,如果超过了阈值,就扩容,不加1的话,如果正好等于阈值就不会扩容。如果我们这一步不加1,那么tableSizeFor(8)求出来的就是8,那么正好等于阈值。但是没进行扩容。
  
  int t = ((ft < (float)MAXIMUM_CAPACITY) ?(int)ft : MAXIMUM_CAPACITY);//MAXIMUM_CAPACITY是最大的容量,为2的30次方,因此这里的t就是9
  
  if (t > threshold)//threshold是容量阈值,超过这个阈值后就会resize() 这个时候是0
  
  threshold = tableSizeFor(t);//求出阈值@2
  
  }
  
  else if (s > threshold) //如果不是初始化的时候,比较size是否大于阈值,大于则重新分配
  
  resize();
  
  for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
  
  K key = e.getKey();
  
  V value = e.getValue();
  
  putVal(hash(key), key, value, false, evict);
  
  }
  
  }
  
  }
  
  /**@2
  
  * 返回大于输入参数且最近的2的整数次幂的数
  
  * 这里网上查的都是大于,但是我发现,如果传入的是2的幂次方,那么就会得出2的幂次方这个数,比如8 16等,所以我个人的理解是大于等于,不知道是不是我哪里理解错了
  
  */
  
  static final int tableSizeFor(int cap) {
  
  int n = cap - 1;//9-1=8 防止9是2的幂次方的数,如果9是2的幂次方 则会
  
  n |= n >>> 1;//无符号右移,无论正负,高位补0 0000 1000|=0000 0100=0000 1100=12
  
  n |= n >>> 2;//0000 1100|=0000 0011=0000 1111=15
  
  n |= n >>> 4;//0000 1111|=0000 0000=0000 1111=15
  
  n |= n >>> 8;//0000 1111|=0000 0000 =0000 1111=15
  
  n |= n >>> 16;//0000 1111|=0000 0000=0000 1111 =15
  
  return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;//return 16
  
  }
  
  /**
  
  *@3
  
  *java里的hashMap用的是数组和链表的结合,数组寻址比较容易(下标易找),但是插入和删除不方便(需要重新排列)。链表查找不方便(需要顺着链子找),但是插入和删除比较方便(把next和pre指针变动下就可以了)
  
  *数组中存着指针,指向链表中的元素(哈希桶),而我们hash函数就负责定位到下标。
  
  *从这里我们就可以看出 如果我们没有遇到哈希碰撞,那么时间复杂度就是O(1) 如果遇到碰撞了,时间复杂度就是O(n)
  
  */
  
  static final int hash(Object key) {//hash叫做散列,就是把任意长度的输入,通过散列算法,变成固定长度的输出(散列值)
  
  int h;// 散列会有碰撞,比如我现在只有0和1两个数字,要变成1位,那么输入的组合为00 01 10 11 输出的组合为0 1,所以不同的对象就会有相同的散列。这个时候同性相斥,就打起来了
  
  return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);//返回散列值
  
  }
  
  //**@4
  
  * Implements Map.put and related methods
  
  *
  
  * @param hash 散列值
  
  * @param key hashMap的key
  
  * @param value 需要put的hashMap的value
  
  * @param onlyIfAbsent true存在key的话,就忽略不覆盖,false,相同key,覆盖原来值
  
  * @param evict if false, the table is www.boyunylpt1.com in creation mode.
  
  * @return previous value, or null if none
  
  */
  
  final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
  
  boolean evict) {
  
  Node<K,V>[] tab; Node<K,V> p; int n, i;
  
  if ((tab = table) == null || (n = tab.length) == 0)//如果哈希桶是空的,分配空间
  
  n = (tab = resize()).length;//重新分配后的length
  
  if ((p = tab[i = (n - 1) & hash]) == null)//根据最大的索引值和hash来求得下标,看是否会有碰撞
  
  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))))//如果跟放在链表第一个的对象相同的key和相同的value,则赋值给e
  
  e = p;
  
  else if (p instanceof TreeNode)//如果值是treenode类型,则走treenode的puttreevalue方法
  
  e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
  
  else {
  
  for (int binCount = 0; ; ++binCount) {
  
  if ((e = p.next) == null) {//走链表,看看下个元素是否为空,为空则放到下个元素里面去,注意,走到最后e是空的
  
  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))))//如果遇到相同的key和value(代表放进去了)则break
  
  break;
  
  p = e;//走链表,一个个顺下去
  
  }
  
  }
  
  if (e != null) { //这个key相应的value已经存在情况下,上面if else第一种情况
  
  V oldValue = e.value;
  
  if (!onlyIfAbsent || oldValue == null)//如果遇到相同的值,是否替换原值
  
  e.value = value;
  
  afterNodeAccess(e);//把e移到链表的最后一个
  
  return oldValue;
  
  }
  
  }
  
  ++modCount;
  
  if (++size > threshold)
  
  resize();
  
  afterNodeInsertion(evict);
  
  return null;
  
  }}
  
  /**@5
  
  * Implements Map.get and related methods
  
  *
  
  * @param hash hash for key
  
  * @param key the key
  
  * @return the node, or null www.gcyL157.com if none
  
  */
  
  final Node<K,V> getNode(int hash, Object key) {
  
  Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
  
  if ((tab = table) != null && (n = tab.length) > 0 &&
  
  (first = tab[(n - 1) & hash]) != null) {//我们之前putvalue的方法讲过的,(n-1)&hash是用来计算hash桶的下标的,找到下标
  
  if (first.hash == hash &&
  
  ((k = first.key) == key || (key != null && key.equals(k))))//这个putvalue也有相应的方法和解释
  
  return first;//返回
  
  if ((e = first.next) != null) {//查看下一个
  
  if (first instanceof TreeNode)//如果不是linkedHashMap,而是TreeNode,
  
  return ((TreeNode<K,V>)first).getTreeNode(hash, key);//返回TreddNode的计算方式的值
  
  do {//顺着链子找到相应的对象,返回
  
  if (e.hash == hash &&
  
  ((k = e.key) == key || (key != null && key.www.fengshen157.com/ equals(k))))
  
  return e;
  
  } while ((e = e.next) != null);
  
  }
  
  }
  
  return null;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值