Scala中HashSet的实现原理详解

Scala中HashSet的实现原理
本文通过一个简单的示例详细解析了Scala语言中HashSet的工作原理,重点介绍了add和remove方法的具体实现过程。

HashSet是平时常用到的数据结构之一,其保证元素是不重复的。
本文将用一个简单的例子来解释下scala语言中HashSet内部的工作原理,看下add和remove到底是怎样工作的。

用法示例

   val s = mutable.HashSet[String]()
    s.add("a")
    s.add("a")
    s.add("b")
    println(s)

HashSet实现原理

1、add方法

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

    protected def addElem(elem: A) : Boolean = {
      addEntry(elemToEntry(elem))
    }

    /**
         * Elems have type A, but we store AnyRef in the table. Plus we need to deal with
         * null elems, which need to be stored as NullSentinel
         */
        protected final def elemToEntry(elem : A) : AnyRef =
          if (null == elem) NullSentinel else elem.asInstanceOf[AnyRef]


     protected def addEntry(newEntry : AnyRef) : Boolean = {
         var h = index(newEntry.hashCode)
         var curEntry = table(h)
         while (null != curEntry) {
           if (curEntry == newEntry) return false
           h = (h + 1) % table.length
           curEntry = table(h)
           //Statistics.collisions += 1
         }
         table(h) = newEntry
         tableSize = tableSize + 1
         nnSizeMapAdd(h)
         if (tableSize >= threshold) growTable()
         true

       }

首先需要根据元素NewEntry计算出table数组的下标h,
然后找到table中的第h元素e,
当e为空时直接添加元素到table[h];
当e不为空并且e和要添加的元素相同时, 说明已存在,返回false;
当e不为空且和添加的元素不同时,每次使h加一(达到上限则从0开始)直到table[h]为空,将NewEntry添加至此,

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

 private def growTable() {
     val oldtable = table
     table = new Array[AnyRef](table.length * 2)
     tableSize = 0
     nnSizeMapReset(table.length)
     seedvalue = tableSizeSeed
     threshold = newThreshold(_loadFactor, table.length)
     var i = 0
     while (i < oldtable.length) {
       val entry = oldtable(i)
       if (null != entry) addEntry(entry)
       i += 1
     }
     if (tableDebug) checkConsistent()
   }
2、remove方法

理解了add操作则remove方法就会简单得多。

  /**
     * Removes an elem from the hash table returning true if the element was found (and thus removed)
     * or false if it didn't exist.
     */
    protected def removeElem(elem: A) : Boolean = {
      if (tableDebug) checkConsistent()
      def precedes(i: Int, j: Int) = {
        val d = table.length >> 1
        if (i <= j) j - i < d
        else i - j > d
      }
      val removalEntry = elemToEntry(elem)
      var h = index(removalEntry.hashCode)
      var curEntry = table(h)
      while (null != curEntry) {
        if (curEntry == removalEntry) {
          var h0 = h
          var h1 = (h0 + 1) % table.length
          while (null != table(h1)) {
            val h2 = index(table(h1).hashCode)
            //Console.println("shift at "+h1+":"+table(h1)+" with h2 = "+h2+"? "+(h2 != h1)+precedes(h2, h0)+table.length)
            if (h2 != h1 && precedes(h2, h0)) {
              //Console.println("shift "+h1+" to "+h0+"!")
              table(h0) = table(h1)
              h0 = h1
            }
            h1 = (h1 + 1) % table.length
          }
          table(h0) = null
          tableSize -= 1
          nnSizeMapRemove(h0)
          if (tableDebug) checkConsistent()
          return true
        }
        h = (h + 1) % table.length
        curEntry = table(h)
      }
      false
    }

对于要删除的元素removalEntry,首先计算其哈希值得到table的下标h,
然后如果table[h]为空,返回false,否则
比较table[h]和removalEntry是否相同,相同则删除,不相同则h逐一递增,直到table[h]为空返回false。

总结

在java中HashSet与TreeSet都是基于Set接口的实现类。
其中TreeSet是Set的子接口SortedSet的实现类。Set接口及其子接口、实现类的结构如下所示:

                  |——SortedSet接口——TreeSet实现类

  Set接口——|——HashSet实现类                

                  |——LinkedHashSet实现类

HashSet有以下特点
 不能保证元素的排列顺序,顺序有可能发生变化
 不是同步的
 集合元素可以是null,但只能放入一个null

TreeSet类型是J2SE中唯一可实现自动排序的类型

TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。
TreeSet支持两种排序方式,自然排序 和定制排序,其中自然排序为默认的排序方式。
向 TreeSet中加入的应该是同一个类的对象。

LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。
这样使得元素看起 来像是以插入顺 序保存的,也就是说,当遍历该集合时候,
LinkedHashSet将会以元素的添加顺序访问集合的元素。
LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。

<think>好的,用户想了解ScalaHashSet数据结构定义及用法。首先,我需要回忆Scala的集合体系,特别是Set的部分。Scala有可变和不可变集合,默认是不可变的,这点用户可能需要注意。根据引用[1]和[4],HashSet属于可变集合,需要导入scala.collection.mutable包。 接下来,数据结构定义方面,HashSet基于哈希表实现,类似Java的HashSet,但Scala的不可变和可变集合有不同的包路径。需要说明如何定义可变HashSet,比如使用new或者工厂方法。还要提到不可变的HashSet可能性能不如可变的,因为每次修改都会生成新实例,这点可能对用户有用。 在用法部分,应该包括初始化、添加元素(add、+=)、删除元素(remove、-=)、遍历(foreach)、转换类型(如toSet转不可变)等操作。引用[4]中的示例代码可以借用,但需要确保语法正确,比如箭头符号是否正确。 另外,用户可能关心性能特点和适用场景。比如HashSet的查询和插入时间复杂度是O(1),但哈希冲突可能影响性能。应用场景如去重、快速查找等。引用[3]提到Bloom Filter近似HashSet,但存在误判,这里可以对比说明。 需要注意用户提供的引用中的信息,比如引用[1]提到可变集合的使用方法和转换到不可变集合的例子,引用[4]展示了可变HashSet的基本操作。需要整合这些信息到回答中,并正确标注引用来源。 最后,生成的相关问题需要围绕HashSet的更多细节,比如初始化方式、性能、线程安全、不可变版本以及与其他数据结构的比较。这些问题应该帮助用户深入理解HashSet的使用和选择。 检查是否所有数学表达式和独立公式正确使用$和$$,但根据问题内容,可能数学表达式较少,主要关注代码块和文字描述。确保代码示例正确,如使用+=和-=操作符,以及正确的方法调用。 总结回答结构:定义、初始化、常用方法、性能特点、应用场景、注意事项,最后相关问题。确保各部分清晰,引用正确,代码示例无误。</think>### ScalaHashSet数据结构定义及用法 #### 一、数据结构定义 Scala的`HashSet`是基于哈希表实现的无序集合,属于`scala.collection.mutable`包(可变集合)[^1][^4]。其特点包括: 1. **元素唯一性**:自动去重,不保留重复元素。 2. **高效性**:插入、删除、查询操作的平均时间复杂度为$O(1)$[^3]。 3. **可变性**:支持原地修改(需显式导入可变包)。 #### 二、初始化方法 ```scala // 导入可变HashSet包 import scala.collection.mutable.HashSet // 方式1:使用工厂方法 val set1 = HashSet(1, 2, 3) // 方式2:显式构造 val set2 = new HashSet[Int]() ``` #### 三、常用操作方法 | 方法/操作符 | 示例 | 功能说明 | |-------------|------------------------|------------------------------| | `add` | `set.add(4)` | 添加元素 | | `+=` | `set += 5` | 添加元素(运算符形式) | | `remove` | `set.remove(1)` | 删除指定元素 | | `-=` | `set -= 2` | 删除元素(运算符形式) | | `contains` | `set.contains(3)` | 检查元素是否存在 | | `size` | `set.size` | 返回集合大小 | | `foreach` | `set.foreach(println)` | 遍历集合 | | `toSet` | `set.toSet` | 转换为不可变集合[^1] | #### 四、性能特点 - **优势**:适合高频增删改查场景,内存占用与元素数量线性相关。 - **局限**:哈希冲突可能导致性能退化,极端情况下时间复杂度退化为$O(n)$[^3]。 #### 五、应用场景 1. **数据去重**:快速过滤重复元素。 2. **集合运算**:如交集、并集(需配合`&`、`|`操作符)。 3. **缓存实现**:基于内存的快速查找表。 #### 六、注意事项 1. **可变性控制**:默认不可变集合,使用可变版本需显式导入包。 2. **线程安全**:非线程安全,多线程环境需同步或使用并发集合。 3. **不可变转换**:调用`toSet`会生成不可变集合,原集合仍可变。 ```scala // 完整示例 import scala.collection.mutable.HashSet val mutableSet = HashSet(1, 2, 3) mutableSet.add(4) mutableSet += 5 mutableSet -= 2 println(mutableSet) // 输出: Set(1, 5, 3, 4) ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值