Spark自定义集合简单介绍

集合浅析

AppendOnlyMap

简单介绍

AppendOnlyMap是一个只能追加的Map,它的key永远不能删除,但是它的value可以被新的值替换。

定义

class AppendOnlyMap[K, V](initialCapacity: Int = 64)
  extends Iterable[(K, V)] with Serializable 

它用来存储数据的地方其实是一个数组,数组的大小为容量的两倍,数组的类型为引用类型的最大类型AnyRef,这样里面的值就可以存储任意类型的,然后将这个数组包装为一个Map

private var data = new Array[AnyRef](2 * capacity)
  • 容量为2的倍数,这样就方便扩容

  • 负载均衡因子为0.7比HashMap的复杂均衡因子少了0.05

  • 它的每两个数据看作一组key和value,低偏移量为key高偏移量为value

  • Hash采用的为自带的hash之后再使用guava的Hashing.murmur3_32算法计一遍得到的hash值

  • 寻址采用的是hash值和一个容量减一的掩码取与(&),然后将结果乘以2,如果已经有值了则偏移量依次向上加1(也就是解决Hash冲突采用开放地址定位法),每次添加计算是否超过负载因子,如果是则扩容

  • 扩容为将数组扩容为原来的2倍,然后将旧数组的内容新增到新的数组内。

BitSet

简单介绍

固定大小的位图,他没有安全校验,因此比较快

定义

class BitSet(numBits: Int) extends Serializable 

它的数据存储使用的其实为一个Long类型的数组,数组大小为将给的位图大小减去一然后除以64(此处采用位运算右移6位)加一

private val words = new Array[Long](bit2words(numBits))
private def bit2words(numBits: Int) = ((numBits - 1) >> 6) + 1

对元素的定位时采用&的方法取出在long类型种占用了哪一位,也就是对63取余,但是他写的为 与63位操作会更快捷,然后将1左移,这样就可以得到在long中占用的那一位。然后再计算在数组内的偏移量就可以得到改元素的准确定位。

  def set(index: Int): Unit = {
    val bitmask = 1L << (index & 0x3f)  // mod 64 and shift
    words(index >> 6) |= bitmask        // div by 64 and mask
  }

CompactBuffer

简单介绍

它是一个只能追加的缓冲区,类似于ArrayBuffer,但是对于较小的缓冲区,内存效率更高效。ArrayBuffer总是分配一个Object 数组去存储数据,默认16个entries,因此它有80-100字节的对象头,相反,如果内容很少CompactBuffer里面有两个属性可以保存最多两个对象,当entries多于2个的情况下才会申请Array[AnyRef],当我们希望一些key有很少的元素时使用它让group by类似的操作更加高效

定义

private[spark] class CompactBuffer[T: ClassTag] extends Seq[T] with Serializable 

它的实现其实还是数组,在内部补充了两个对象element0element1用来存储缓冲区的前两个元素,如果元素少于两个则可以不适用数组对象,因此会比较快。大家看一下它的部分代码就可以明白。

  // First two elements
  private var element0: T = _
  private var element1: T = _
  // Number of elements, including our two in the main object
  private var curSize = 0
  // Array for extra elements
  private var otherElements: Array[T] = null
  def apply(position: Int): T = {
    if (position < 0 || position >= curSize) {
      throw new IndexOutOfBoundsException
    }
    if (position == 0) {
      element0
    } else if (position == 1) {
      element1
    } else {
      otherElements(position - 2)
    }
  }

SizeTrackingAppendOnlyMap

简单介绍

一个仅仅追加的Map,跟踪估计它的bytes大小

private[spark] class SizeTrackingAppendOnlyMap[K, V]
  extends AppendOnlyMap[K, V] with SizeTracker

它充分地体现了组合模式的使用思想,它是由AppendOnlyMap类和SizeTracker混合而成,存储主要使用的时AppendOnlyMap的方法,而大小跟踪则是通过SizeTracker实现。

SizeTracker

private[spark] trait SizeTracker

简单介绍

它是通过一个mutable.Queue[Sample]来保存数据样本,内部通过SizeEstimator评估内存占用情况。

ExternalAppendOnlyMap

class ExternalAppendOnlyMap[K, V, C](
    createCombiner: V => C,
    mergeValue: (C, V) => C,
    mergeCombiners: (C, C) => C,
    serializer: Serializer = SparkEnv.get.serializer,
    blockManager: BlockManager = SparkEnv.get.blockManager,
    context: TaskContext = TaskContext.get(),
    serializerManager: SerializerManager = SparkEnv.get.serializerManager)
  extends Spillable[SizeTracker](context.taskMemoryManager())
  with Serializable
  with Logging
  with Iterable[(K, C)] 

简单介绍

一个只追加的Map,如果没有足够的空间,将会把排序后的内容溢出到磁盘上。

该map对数据进行了两次传递,

  1. 将值合并到合并器中,并且根据需要对其进行排序并溢出到磁盘
  2. 从磁盘读取合并器并且合并到一起

他的内存数据数据实际存储在SizeTrackingAppendOnlyMap里面。当内存需要刷写到磁盘时会刷写到磁盘,并且记录本次刷写大小,可以多次刷写,每次刷写都会使用FileSegment记录大小。

它的刷写是通过DiskBlockObjectWriter实现的,这个类对OutputStream类进行了包装,装饰者模式。通过序列化器对这个流进行序列化和反序列化就完成了对数据的读和写。

MedianHeap

private[spark] class MedianHeap(implicit val ord: Ordering[Double]) 

简单介绍

中值堆用来快速跟组可能包含重复项的一组数字的中位数,插入数据的复杂度为o(logn)计算中位数时间复杂度为o(1)。

基本思想是维护两个堆(使用优先队列存储PriorityQueue),一个大堆,一个小堆, 小堆存储小的一半数据,大堆存储大的一半数据,新数据插入时两个数据需要平衡,它们的大小不会相差1,每次获取数据都检查两个堆的大小,如果一样就取平均值,如果不一样就去取元素多的堆的顶部的值。

OpenHashMap

class OpenHashMap[K : ClassTag, @specialized(Long, Int, Double) V: ClassTag](
    initialCapacity: Int)
  extends Iterable[(K, V)]
  with Serializable 

k一个key可以为空的map,支持插入和更新不支持删除,比hashmap快5倍而且占用空间更小。内部使用OpenHashSet存储key数据,使用数组存储value值。key的类型只能为(Long, Int, Double)

OpenHashSet

class OpenHashSet[@specialized(Long, Int, Double, Float) T: ClassTag](
    initialCapacity: Int,
    loadFactor: Double)
  extends Serializable 

类型只能为(Long, Int, Double, Float)的hashset,它不能进行删除,它比hashset占用小,用来构建高级的数据结构。

内部使用位图BitSet存储,对数据求hash,然后根据hash值向位图添加元素,如果hash冲突就采用位图偏移量加一的方式,开放地址定位法。如果超过负载因子就扩容。

PartitionedAppendOnlyMap

private[spark] class PartitionedAppendOnlyMap[K, V]
  extends SizeTrackingAppendOnlyMap[(Int, K), V] with WritablePartitionedPairCollection[K, V] 

添加分区的信息,实际上也就是调用SizeTrackingAppendOnlyMap的update方法,并且将key封装为了tuple2类型,key上为一个分区。

  def insert(partition: Int, key: K, value: V): Unit = {
    update((partition, key), value)
  }

PartitionPairBuffer

private[spark] class PartitionedPairBuffer[K, V](initialCapacity: Int = 64)
  extends WritablePartitionedPairCollection[K, V] with SizeTracker

简单介绍

一个只能追加的键值对缓冲区,每个键值对都有自己的分区ID,便于追踪评估字节大小,缓冲区最多支持1073741819个元素。

它存储数据采用的为数组

private var data = new Array[AnyRef](2 * initialCapacity)

数组的内容存储的key为一个tuple2类型为(partition,key),value类型存储的为value,key和value相邻存储,key存储在低偏移量value存储在高偏移量。当容量满了的时候再去扩容(这一点不像map采用负载因子的形式,而是满了之后再扩容)

PrimitiveKeyOpenHashMap

private[spark]
class PrimitiveKeyOpenHashMap[@specialized(Long, Int) K: ClassTag,
                              @specialized(Long, Int, Double) V: ClassTag](
    initialCapacity: Int)
  extends Iterable[(K, V)]
  with Serializable 

借助OpenHashSet实现的原始的map,可添加、更新不可删除。使用OpenHashSet存储key,使用数组存储value。

PrimitiveVector

class PrimitiveVector[@specialized(Long, Int, Double) V: ClassTag](initialSize: Int = 64) 

只追加的,非线程安全的,支持数组向量的,为原始类型优化的类型。

内部使用数组存储数据,其实也就是对数组进行了封装。

可添加、更新不可删除。使用OpenHashSet存储key,使用数组存储value。

PrimitiveVector

class PrimitiveVector[@specialized(Long, Int, Double) V: ClassTag](initialSize: Int = 64) 

只追加的,非线程安全的,支持数组向量的,为原始类型优化的类型。

内部使用数组存储数据,其实也就是对数组进行了封装。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Spark内核设计的艺术是指在Spark计算引擎的设计中,涉及了许多精心的考量和优化来提高性能和可扩展性。 首先,Spark内核设计中充分考虑了分布式计算的特点。Spark使用弹性分布式数据集(RDD)作为基本的计算模型,RDD是可分区的、可并行操作的数据集合,有助于将数据分布在集群中的不同节点上进行并发处理。这种设计能够更好地适应大规模数据处理的需求。 其次,Spark内核设计充分利用了内存计算的优势。Spark引入了内存计算的概念,并提供了内存管理机制,使得数据可以驻留在内存中,加速了数据处理的速度,尤其是对于迭代计算和交互式查询等工作负载。 此外,Spark内核设计还考虑到了任务调度和资源管理的问题。Spark使用了多阶段的任务调度器,将任务划分为不同的阶段,并优化了数据的本地性以减少数据传输的开销。同时,Spark还提供了动态资源分配的功能,根据任务的需求动态地分配和回收资源,提高资源利用率。 最后,Spark内核设计还注重了用户友好性和可扩展性。Spark提供了易用的API,如Spark SQL、Spark Streaming、Spark MLlib等,使得用户可以方便地进行大数据处理和机器学习等任务。同时,Spark还支持丰富的扩展功能,如自定义的数据源和函数,以及与其他大数据生态系统的无缝集成,使得Spark可以灵活地适应各种应用场景。 综上所述,Spark内核设计的艺术体现在它在分布式计算、内存计算、任务调度和资源管理、用户友好性和可扩展性等方面的优化和创新,使得Spark成为了当前大数据处理领域最受欢迎和广泛应用的计算引擎之一。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天心有情

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值