scala中HashMap的实现原理解析

HashMap是平时最常用到的数据结构之一,查找和增删元素具有O(1)的时间复杂度。
本文将用一个简单的例子来解释下scala语言中HashMap内部的工作原理,看下get和put到底是怎样工作的。

用法示例

  def main(args: Array[String]): Unit = {
    val m = mutable.HashMap[String,Int]()
    m.put("a",1)
    m.put("b",2)
    println("m:"+m)
    println("a="+m.get("a"))

    for( (k,v) <- m){ //遍历方式
      print(k,v)
    }
  }

  输出结果:
   m:Map(b -> 2, a -> 1)
   a=Some(1)
   (b,2)(a,1)

HashMap实现原理

1、数据结构

HashMap是数组和链表相结合,其中key的hash值是数组的下标,同样hash值的key以链表方式存储。

其中数组用table, 其中table数组存储了Entry类的对象,
Entry的key即HashMap的键,value即HashMap的值,next为下一个Entry元素

@transient protected var table: Array[HashEntry[A, Entry]] = new Array(initialCapacity)

trait HashEntry [A, E] {
  val key: A
  var next: E = _
}
final class DefaultEntry[A, B](val key: A, var value: B)
      extends HashEntry[A, DefaultEntry[A, B]] with Serializable
{
  override def toString = chainString

  def chainString = {
    "(kv: " + key + ", " + value + ")" + (if (next != null) " -> " + next.toString else "")
  }
}
2、put方法

我们看一下put的代码实现:

  override def put(key: A, value: B): Option[B] = {
    val e = findOrAddEntry(key, value)
    if (e eq null) None
    else { val v = e.value; e.value = value; Some(v) } //如果key存在则将其value值进行更新
  }

  /** Find entry with given key in table, or add new one if not found.
     *  May be somewhat faster then `findEntry`/`addEntry` pair as it
     *  computes entry's hash index only once.
     *  Returns entry found in table or null.
     *  New entries are created by calling `createNewEntry` method.
     */
    protected def findOrAddEntry[B](key: A, value: B): Entry = {
      val h = index(elemHashCode(key))  //获取hash值计算出的数组的下标
      val e = findEntry0(key, h)  //通过下标和键查找value是否存在
      if (e ne null) e else { addEntry0(createNewEntry(key, value), h); null }
    }

    private[this] def findEntry0(key: A, h: Int): Entry = {
        var e = table(h).asInstanceOf[Entry]
        while (e != null && !elemEquals(e.key, key)) e = e.next
        e
    }

首先需要根据hashMap的键key计算出table数组的下标h,
然后找到table中的第h元素e,当e不为空并且e的key和HashMap的key相同时,将e的next赋给e
最后返回的e如果不为空,表示key存在,返回Entry对象,并将其value值进行更新
如果e为空,则进行添加

  protected def createNewEntry[B](key: A, value: B): Entry

  private[this] def addEntry0(e: Entry, h: Int) {
    e.next = table(h).asInstanceOf[Entry] //将e作为链表的头结点
    table(h) = e
    tableSize = tableSize + 1
    nnSizeMapAdd(h)
    if (tableSize > threshold)
      resize(2 * table.length)
  }

每次添加元素的时候tableSize加一,代表元素的总个数,即size方法返回的结果

override def size: Int = tableSize

   protected def elemHashCode(key: KeyType) = key.##
   其中##是一个方法
   ##():Int

返回一个对象的hasCode,和hascode方法区别是:
对于numerics,它返回与值相等一致的散列值,
对于null,返回一个hashcode,而hashCode抛出NullPointerException。

2、table扩容

当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。
所以为了提高查询的效率,就要对HashMap的数组进行扩容,
这是一个常用的操作,而在HashMap数组扩容之后,最消耗性能的点就出现了:
原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。

那么HashMap什么时候进行扩容呢?当HashMap中的元素个数超过数组大小threshold时,
就会进行数组扩容,threshold的默认值为table大小的0.75,这是一个折中的取值。
也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12的时候,
就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,


  private def resize(newSize: Int) {
    val oldTable = table
    table = new Array(newSize)
    nnSizeMapReset(table.length)
    var i = oldTable.length - 1
    while (i >= 0) {
      var e = oldTable(i)
      while (e != null) {
        val h = index(elemHashCode(e.key))
        val e1 = e.next
        e.next = table(h).asInstanceOf[Entry]
        table(h) = e
        e = e1
        nnSizeMapAdd(h)
      }
      i = i - 1
    }
    threshold = newThreshold(_loadFactor, newSize)
  }

   private[collection] final def defaultLoadFactor: Int = 750 // corresponds to 75%
    private[collection] final def loadFactorDenum = 1000 // should be loadFactorDenom, but changing that isn't binary compatible

  private[collection] final def newThreshold(_loadFactor: Int, size: Int) =
   ((size.toLong * _loadFactor) / loadFactorDenum).toInt  //新的threshold是扩容前大小两倍的75%
3、get方法

理解了put操作则get方法就会简单得多,get只是put操作其中的一步。

  def get(key: A): Option[B] = {
    val e = findEntry(key)
    if (e eq null) None
    else Some(e.value)
  }

  protected def findEntry(key: A): Entry =
     findEntry0(key, index(elemHashCode(key)))

总结

HashMap 是一个散列表,它存储的内容是键值对(key-value)映射.
HashMap 的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的。
HashMap的实现是借助于哈希表HashTable实现的

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值