scala中HashMap的实现原理解析

本文介绍了Scala语言中HashMap的数据结构及其内部工作原理,详细解析了put和get方法的实现过程,包括hashMap的键值对存储方式、数组和链表结合的结构特点以及扩容机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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实现的

<think>好的,现在用户问的是如何用Java实现协同过滤。之前他们问过Python的实现,现在换到Java,可能需要对比或者迁移的经验。首先,我得考虑Java在推荐系统中的应用场景,可能用户所在的公司或项目主要使用Java技术栈,所以需要相应的解决方案。 用户可能已经了解协同过滤的基本概念,但需要具体的Java实现方法。他们可能想知道有哪些库或框架可用,或者如何从零开始实现。需要区分基于用户和物品的协同过滤,以及内存和分布式处理的不同场景。 接下来要思考Java生态中的相关工具。比如,Apache Mahout曾经是机器学习的重要库,虽然现在转向Scala和Spark,但仍有Java支持。还有Elasticsearch的推荐功能,以及Weka这样的数据挖掘工具。此外,自研实现的话,可能需要处理矩阵运算、相似度计算等。 用户可能关心实现步骤,比如数据准备、相似度计算、预测评分等,和Python类似的流程,但用Java代码如何表达。需要注意Java在矩阵运算上的不足,可能需要引入第三方库如ND4J或Apache Commons Math。 另外,用户可能遇到性能问题,特别是处理大数据时的扩展性。需要提到分布式框架如Spark MLlib,虽然它是Scala写的,但Java可以调用。或者使用Elasticsearch进行实时推荐,适合高并发的场景。 还要考虑冷启动、数据稀疏性等问题,是否需要在Java实现中加入特殊处理。比如通过混合推荐或者结合其他算法来优化。 最后,总结Java实现的适用场景,比如企业级应用、需要与现有Java系统集成、高性能需求等,帮助用户判断是否选择Java作为实现语言。</think>### Java实现协同过滤的完整解析 我们将从原理分析、工具选择到代码实现三个层面进行说明,重点关注Java生态中的特殊实现方式。 --- #### 一、实现原理对比 $$协同过滤核心步骤 = \begin{cases} 1. 数据加载 \& 矩阵构建 \\ 2. 相似度计算 \\ 3. 评分预测 \end{cases}$$ 与Python的主要差异: 1. **矩阵处理**:Java缺少原生矩阵库,需使用`Apache Commons Math`或`ND4J` 2. **分布式计算**:Java生态更依赖`Spark MLlib`进行大规模计算 3. **实时推荐**:可通过`Elasticsearch`插件实现 --- #### 二、工具选择矩阵 | 场景 | 推荐工具 | 特点 | |------|----------|------| | 中小规模内存计算 | Apache Mahout | 经典机器学习库,含User/Item CF实现 | | 大数据分布式计算 | Spark MLlib | 支持ALS矩阵分解算法 | | 实时推荐系统 | Elasticsearch | 内置协同过滤插件 | | 快速原型开发 | Weka Java API | 集成基础相似度计算 | --- #### 三、核心代码实现(基于Mahout) ##### 1. 数据准备 ```java // 创建数据模型 FileDataModel model = new FileDataModel(new File("ratings.csv")); // 构建用户相似度计算器 UserSimilarity similarity = new PearsonCorrelationSimilarity(model); // 定义邻居选择器 UserNeighborhood neighborhood = new NearestNUserNeighborhood(50, similarity, model); ``` ##### 2. 推荐器配置 ```java // 创建推荐引擎 Recommender recommender = new GenericUserBasedRecommender( model, neighborhood, similarity); // 生成推荐结果 List<RecommendedItem> recommendations = recommender.recommend(123, 10); // 为用户123推荐10个物品 ``` ##### 3. 自定义相似度算法 ```java // 实现余弦相似度 public class CustomCosineSimilarity extends AbstractSimilarity { @Override protected double computeResult(int n, double sumXY, double sumX2, double sumY2) { return sumXY / Math.sqrt(sumX2 * sumY2); } } ``` --- #### 四、分布式实现(Spark MLlib) ```java // 创建Spark会话 SparkSession spark = SparkSession.builder() .appName("CollaborativeFiltering") .master("local[*]") .getOrCreate(); // 加载评分数据 JavaRDD<Rating> ratingsRDD = spark.read() .textFile("hdfs://ratings.dat") .javaRDD() .map(line -> { String[] fields = line.split("::"); return new Rating( Integer.parseInt(fields[0]), Integer.parseInt(fields[1]), Double.parseDouble(fields[2]) ); }); // 训练ALS模型 ALS als = new ALS() .setRank(10) .setMaxIter(15) .setRegParam(0.01) .setUserCol("userId") .setItemCol("itemId") .setRatingCol("rating"); ALSModel model = als.fit(ratingsRDD); ``` --- #### 五、性能优化技巧 $$吞吐量提升 = \begin{cases} 内存优化:使用Trove库替代Java集合 \\ 并行计算:利用ForkJoinPool进行相似度矩阵并行计算 \\ 稀疏矩阵存储:采用CRS格式存储评分矩阵 \end{cases}$$ **代码示例(内存优化)**: ```java // 使用Trove的TIntDoubleHashMap替代HashMap TIntDoubleHashMap userRatings = new TIntDoubleHashMap(); userRatings.put(101, 4.5); userRatings.put(205, 3.0); ``` --- #### 六、企业级实现方案 **架构图示例**: ``` 用户请求 ↓ [API Gateway] → [实时推荐引擎(Elasticsearch)] ↓ [批处理推荐系统(Spark)] → [HDFS存储] ↓ [监控系统] ← [A/B测试平台] ``` **组件选择建议**: 1. **实时推荐**:Elasticsearch的`rescore`功能 2. **离线训练**:Spark MLlib ALS算法 3. **特征存储**:Redis缓存用户最近行为 --- #### 七、特殊场景解决方案 **冷启动问题**: ```java // 混合推荐策略 public List<RecommendedItem> hybridRecommend(int userId) { if (isNewUser(userId)) { return contentBasedRecommend(); // 调用内容过滤 } else { return cfRecommend(userId); // 协同过滤 } } ``` **代码级优化**: ```java // 使用Eclipse Collections加速迭代 MutableList<UserSimilarity> similarities = Lists.mutable.empty(); userPairs.forEachWithIndex((pair, i) -> similarities.add(computeSimilarity(pair.getOne(), pair.getTwo())) ); ``` --- #### 八、Java实现适用场景 $$选择Java当:\begin{cases} 1. 需要与企业级Java系统集成 \\ 2. 要求高吞吐低延迟(如金融交易系统) \\ 3. 已有Hadoop/Spark大数据平台 \end{cases}$$ **推荐工具链**: - 中小规模:Mahout + Spring Boot - 大规模:Spark MLlib + Flink - 实时场景:Elasticsearch + Redis 实际开发中建议优先使用Spark MLlib实现,兼顾开发效率和扩展性。对于需要深度定制算法的场景,可基于`Apache Commons Math`自研核心计算模块。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值