[译] Kotlin Collections vs sequences ——集合与序列对比

Collections vs sequences

 使用集合完成工作是十分常见的任务,kotlin标准库也提供了很多易用的工具集合。它提供了两种工作集合:饥饿型集合和懒惰型序列。继续阅读查看两者差别,应该如何使用、什么时候使用以及两者隐藏性能开销。

Collections vs sequences

 饥饿型集合和懒惰型序列主要差别在于何时执行转换。
  Collections是饥饿的——每个操作都是在调用时执行,操作结果保存在一个新集合中。集合转换为内联函数。比如,查看map是如何实现地,我们可以看到它是一个内联函数,内部创建了一个新ArrayList:

public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
  return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}

 序列是懒惰型。它们有两种类型操作:中间操作和末端操作。中间操作不会被立即执行;它们只会被暂存。只有当末端操作被调用时,中间操作才会对连续对每一个元素触发,最终应用末端操作。中间操作会(比如 map、distinct、groupBy 等等)返回另一个序列,然而末端操作(比如 first、toList、count 等等)并不会。
 序列并不会保持持有对集合中items的引用。它们会基于原始集合迭代器被创建,持有对所有需要执行的中间操作的引用。
 和集合转换不同,序列的中间操作并不是内联函数——内联函数不能被保存而序列需要保存。查看中间操作是如何实现的,比如map,我们可以看见转换函数会被保存在序列实例中。

public fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R>{      
   return TransformingSequence(this, transform)
}

 末端操作,比如first,会迭代遍历序列元素,直到某个元素满足了判断表达式。

public inline fun <T> Sequence<T>.first(predicate: (T) -> Boolean): T {
   for (element in this) if (predicate(element)) return element
   throw NoSuchElementException(“Sequence contains no element matching the predicate.”)
}

 如果我们探究序列比如TransformingSequence(在上面map中使用到了)如何实现,我们会发现在序列迭代器调用next时就会应用所存储的转换。

internal class TransformingIndexedSequence<T, R> 
constructor(private val sequence: Sequence<T>, private val transformer: (Int, T) -> R) : Sequence<R> {
override fun iterator(): Iterator<R> = object : Iterator<R> {
   …
   override fun next(): R {
     return transformer(checkIndexOverflow(index++), iterator.next())
   }
   …
}

 无论你使用集合还是序列,kotlin标准库提供了两者的广泛操作,比如find,filter,groupBy和其他。


Collections vs sequences

Collections and sequences

 我们有一个列表,里面有不同的形状。我们需要一个黄色的然后第一个是方形的形状。


data class Shape(val edges:Int, val angle : Int=0, val color:Int)

val circle = Shape(edges=0,color=1)
val square = Shape(edges=4,color=2)
val rhombus = Shape(edges=4,angle=45,color=3)
val triangle = Shape(edges=3,color=4)
val shapes = list(circle,square,rhombus,triangle)

fun main() {
    println(shapes.map {it.color}.toList())
    
    val yellowSquareSequence = shapes.asSequence().map {
        it.copy(color = 3)
    }.first {
        it.edges == 4
    }
    println(yellowSquareSequence)
   
    val yellowSquareCollection = shapes.map {
        it.copy(color = 3)
    }.first {
        it.edges == 4
    }
    println(yellowSquareCollection)
}

 让我们看看每个操作是如何、何时应用到每一个集合或序列的。

Collections

  • map被调用——一个新ArrayList被创建。我们迭代初始集合的每一个item,通过拷贝原始对象并且改变颜色来实现转换,之后将其添加到新集合。
  • first被调用——我们迭代遍历每一个item直到找到第一个方形。

Sequences

  • asSequence —— 基于原始集合迭代器创建序列
  • 调用map —— 由sequence将转换被添加到需要执行的操作列表中,但是操作不会执行。
  • 调用first —— 这是一个末端操作,所以会对于集合中每个元素触发所有中间操作。我们迭代遍历初始集合应用map并且获取其中的第一项。因此集合的第一个元素以及满足了条件,那么就不再需要对集合的剩下元素执行map操作。

 当我们使用没有中间操作集合的序列,之后items会一个个进行评估,map操作也只会对一部分输入执行。

Collections vs sequences — eager vs lazy evaluation

Performance

Order of transformations

 无论你使用collections或者sequences,总是需要注意转换顺序。在上述例子中,first并不需要在map之后执行,因为它不是map转换的结果。如果我们反转业务逻辑顺序,首先对collection调用first,如何转换结果,那么我们就只创建一个新对象 —— 黄色方块。当我们使用sequence —— 我们就避免创建两个新对象,当我们使用集合,我们就避免创建整个新list。


Order of transformations matters — avoid unnecessary work

 因为末端操作可以提早结束处理,中间操作是懒惰的,在一些场景中,相比collection,sequence则可以帮助你避免不需要的工作。你要确保总是会检查转换顺序,还有在两者直接的依赖。

Inlining and large data sets consequences

 集合操作使用内联函数,传递进lambda的字节码操作会被内联。sequence并不使用内联函数,因此,每次操作中都会创建新函数对象。
 另一方面,集合对每一个转换都创建新list,然而,sequence仅会保持对转换函数的引用。
 当在数据量小的集合中,有1-2个操作符,就不会有比较大的性能影响。但是如果在数据量较大的集合使用中间操作符,性能开销就会比较大;在这种场景下,则使用序列。
 不幸运地是,我没有找到任何banchmarking材料帮助我们更好的理解collection和sequence的性能差异。
 Collections是饥饿型的,而sequences是懒惰型的。这取决与你的数据量,选择最合适使用方案:集合 —— 小数据量 或者 序列 —— 大数据量 , 尤其主义转换顺序。

原文地址:Collections and sequences in Kotlin

MarcinMoskala的 kotlin 性能对比 Effictive Kotlin 性能对比

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,271评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,725评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,252评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,634评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,549评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,985评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,471评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,128评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,257评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,233评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,235评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,940评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,528评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,623评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,858评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,245评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,790评论 2 339