跳表查找和数组二分查找 对比
动机:
对比效率的想法源于公司的一次压测, 经过一轮排除下来 发现是由于排行榜引起 占了大部分CPU
对比代码
平台windows 10 x64
IDE IntelliJ IDEA Community Edition 2020.3
Java jdk 1.8.0_201
Kotlin 1.3.21
首先贴一份kotlin实现的链表
interface SkipList<E : RankElement> {val SKIPLIST_MAXLEVEL: Intget() = 32val SKIPLIST_P: Doubleget() = 0.25fun insert(element: E)fun delete(element: E)fun getRank(element: E): Longfun between(from: E, to: E): List<E>fun range(element: E, leftCount: Int, rightCount: Int): List<E>fun getElementByRank(rank: Long): E?fun createNode(level: Int, element: E?): SkipListNode<E> = SkipListNode(Array(level) { SkipListLevel<E>() }, element)fun firstRange(size: Int): List<E>/**根据排位来查找*/fun findWithRange(from: Long, to: Long, filter: (element: E) -> Boolean = { true }): List<E>fun toList(): ArrayList<E>;}interface RankElement : Comparable<RankElement> {fun ownerId(): Long}class SkipListImpl<E : RankElement> : SkipList<E> {var level: Int = 1private setvar length: Int = 0private setvar header: SkipListNode<E>? = nullprivate setvar tail: SkipListNode<E>? = nullprivate setinit {this.header = createNode(SKIPLIST_MAXLEVEL, null)}//随机一个levelfun randomLevel(): Int {var level = 1while (random() and 0xFFFF < SKIPLIST_P * 0xFFFF) level += 1return if (level < SKIPLIST_MAXLEVEL) level else SKIPLIST_MAXLEVEL}private fun random(): Int {return ThreadLocalRandom.current().nextInt()}override fun insert(element: E) {val update = arrayOfNulls<SkipListNode<E>>(SKIPLIST_MAXLEVEL)val rank = IntArray(SKIPLIST_MAXLEVEL)var x = this.headerfor (i in this.level - 1 downTo 0) {rank[i] = if (i == this.level - 1) 0 else rank[i + 1]var tempNode = x?.let { it.level[i].forward }while (tempNode != null) {val cmp = cmp(tempNode.element, element)if (cmp == 0) {//同一个元素throw IllegalStateException("insert same element ${tempNode.element} $element")}if (cmp < 0) {rank[i] += requireNotNull(x?.let { it.level[i].span })x = tempNodetempNode = x.let { it.level[i].forward }}else{break}}update[i] = x}val level = randomLevel()if (level > this.level) {for (i in this.level until level) {rank[i] = 0;update[i] = this.headerupdate[i]?.let { it.level[i].span = this.length }}this.level = level}val newNode = createNode(level, element)for (i in 0 until level) {update[i]?.let {newNode.level[i].forward = it.level[i].forwardit.level[i].forward = newNodenewNode.level[i].span = it.level[i].span - (rank[0] - rank[i])it.level[i].span = (rank[0] - rank[i]) + 1}}for (i in level until this.level) {update[i]?.let { it.level[i].span++ }}newNode.backward = if (update[0] == this.header) null else update[0]if (newNode.level[0].forward != null) {newNode.level[0].forward?.backward = newNode} else {this.tail = newNode}this.length++}private fun cmp(element1: E?, element2: E): Int {if (element1 == null) {throw IllegalAccessException("元素不该为空")}return element1.compareTo(element2)}override fun delete(element: E) {val update = arrayOfNulls<SkipListNode<E>>(SKIPLIST_MAXLEVEL)var x: SkipListNode<E>? = this.headerfor (i in level - 1 downTo 0) {var forward = x?.let { it.level[i].forward }while (forward != null && cmp(forward.element, element) < 0) {x = forwardforward = x.level[i].forward}update[i] = x}x = x?.let { it.level[0].forward }//找到哦了x?.let {if (cmp(x.element, element) == 0) {for (i in 0 until this.level) {update[i]?.let {if (it.level[i].forward == x) {it.level[i].span += x.level[i].span - 1it.level[i].forward = x.level[i].forward} else {it.level[i].span -= 1}}}if (x.level[0].forward != null) {x.level[0].forward?.backward = x.backward} else {this.tail = x.backward}while (this.level > 1 && this.header?.let { it.level[this.level - 1].forward } == null) {this.level--}this.length--}}}override fun getRank(element: E): Long {var x = this.headervar rank: Long = 0for (i in (this.level - 1) downTo 0) {var forward = x?.let { it.level[i].forward }while (forward != null && cmp(forward.element, element) <= 0) {rank += (x?.let { it.level[i].span } ?: 0)x = forwardforward = x.level[i].forward}}if (x?.element?.let { cmp(it, element) == 0 } == true) {return rank}return 0}override fun between(from: E, to: E): List<E> {var x = this.headervar rank: Long = 0for (i in this.level - 1 downTo 0) {var forward = x?.let { it.level[i].forward }while (forward != null && cmp(forward.element, from) <= 0) {rank += (x?.let { it.level[i].span } ?: 0)x = forwardforward = x.level[i].forward}}val elements = arrayListOf<E>()x?.let {var temp = it.level[0].forwardwhile (temp != null) {val cmp = cmp(temp.element, to)if (cmp >= 0) {return@let}elements.add(temp.element!!)temp = temp.level[0].forward}}return elements}override fun range(element: E, leftCount: Int, rightCount: Int): List<E> {var x = this.headervar rank: Long = 0for (i in this.level - 1 downTo 0) {var forward = x?.let { it.level[i].forward }while (forward != null && cmp(forward.element, element) <= 0) {rank += (x?.let { it.level[i].span } ?: 0)x = forwardforward = x.level[i].forward}}//找到自己的位置,朝前朝后找val list = LinkedList<E>()x?.let { target ->var temp = target.backwardrepeat(leftCount) {temp?.let {list.addFirst(it.element)temp = it.backward} ?: return@repeat}temp = target.level[0].forwardrepeat(rightCount) {temp?.let {list.addLast(it.element)temp = it.level[0].forward} ?: return@repeat}}return list}override fun getElementByRank(rank: Long): E? {if (this.header == null || this.length < rank) {throw IllegalAccessException("排名不存在 $rank")}var x: SkipListNode<E>? = this.headervar traversed: Long = 0for (i in this.level - 1 downTo 0) {while (x?.let { it.level[i].forward != null && traversed + it.level[i].span <= rank } == true) {traversed += x.level[i].spanx = x.level[i].forward}if (traversed == rank) {return x?.element}}return null}override fun firstRange(size: Int): List<E> {if (this.length == 0) {throw java.lang.IllegalStateException("元素为空")}val element = header?.level?.get(0)?.forward?.elementreturn element?.let { range(it, 0, size) }!!}override fun findWithRange(from: Long, to: Long, filter: (element: E) -> Boolean): List<E> {if (this.header == null || this.length < from) {throw IllegalAccessException("排名不存在 $from")}val list = arrayListOf<E>()var x: SkipListNode<E>? = this.headervar traversed: Long = 0for (i in this.level - 1 downTo 0) {while (x?.let { it.level[i].forward != null && traversed + it.level[i].span <= from } == true) {traversed += x.level[i].spanx = x.level[i].forward}if (traversed == from.toLong()) {repeat((to - from + 1).toInt()) {x?.let {list.add(requireNotNull(it.element))x = it.level[0].forward}}}}return list}override fun toList(): ArrayList<E> {val arrayList = arrayListOf<E>()var x: SkipListNode<E>? = this.headerrepeat(this.length) {x?.let {arrayList.add(requireNotNull(it.level[0].forward?.element))x = it.level[0].forward}}return arrayList}}class SkipListLevel<E> {//同level下一个元素var forward: SkipListNode<E>? = null//同level跨越节点的数量var span = 0}class SkipListNode<E>(//不同level指向的下一个元素和spanvar level: Array<SkipListLevel<E>>,var element: E? = null) {//元素 或集成 compareble//上一个元素var backward: SkipListNode<E>? = null}class Rank<E : RankElement>(val skipList: SkipList<E> = SkipListImpl(),val elementMap: HashMap<Long, E> = hashMapOf()) {fun insert(element: E) {val remove = elementMap.remove(element.ownerId())remove?.let { skipList.delete(it) }elementMap[element.ownerId()] = elementskipList.insert(element)}fun remove(element: E) {elementMap.remove(element.ownerId())?.let {skipList.delete(it)}}fun toList(): ArrayList<E> {return skipList.toList()}}
对比测试的代码一
fun testSkipList(){//初始化跳表排行榜val skipList: SkipList<testElement> = SkipListImpl()val elementMap: HashMap<Long, testElement> = hashMapOf()val rank = Rank(skipList, elementMap)//准备跳表数据repeat(10000){rank.insert(testElement(it.toLong()))}//准备二维数组数据val arrayList = arrayListOf<Int>()repeat(10000){arrayList.add(it)}//测试跳表耗时val t1 = System.nanoTime()repeat(1000) {//测试找单个rank.skipList.getElementByRank((300+it).toLong())//测试找范围//rank.skipList.range(testElement(5000),10, 10)}val t2 = System.nanoTime()//测试二维数组耗时val t3 = System.nanoTime()repeat(1000){//测试找单个arrayList.binarySearch(300+it)//测试找范围// repeat(20){// arrayList.binarySearch(4990+it)// }}val t4 = System.nanoTime()println("skiplistSearch time ${(t2-t1)/1000000.0} ms")println("binarySearch time ${(t4-t3)/1000000.0} ms")}
结果:
skiplistSearch time 1.2985 ms
binarySearch time 15.4235 ms
明显 跳表查找比二分查找还是有很大优势
对比测试的代码二
fun testSkipList(){//初始化跳表排行榜val skipList: SkipList<testElement> = SkipListImpl()val elementMap: HashMap<Long, testElement> = hashMapOf()val rank = Rank(skipList, elementMap)//准备跳表数据repeat(10000){rank.insert(testElement(it.toLong()))}//准备二维数组数据val arrayList = arrayListOf<Int>()repeat(10000){arrayList.add(it)}//测试跳表耗时val t1 = System.nanoTime()repeat(1000) {//测试找单个//rank.skipList.getElementByRank((300+it).toLong())//测试找范围rank.skipList.range(testElement(300),10, 10)}val t2 = System.nanoTime()//测试二维数组耗时val t3 = System.nanoTime()repeat(1000){//测试找单个//arrayList.binarySearch(300+it)//测试找范围repeat(20){arrayList.binarySearch(300+it)}}val t4 = System.nanoTime()println("skiplistSearch time ${(t2-t1)/1000000.0} ms")println("binarySearch time ${(t4-t3)/1000000.0} ms")}
结果:
skiplistSearch time 3.2909 ms
binarySearch time 16.2339 ms
跳表依然比 二叉查找快
截图得时间 都是多次运行之后 取得平均出现次数比较高的
结论: 不管在单次查询 或者连续多次查询中 跳表查询 都比 二维数组的二分查询 有优势
写到这里肯定有有细心的读者 注意到为什么之前
arrayList.binarySearch(300+it)
和
repeat(20){arrayList.binarySearch(300+it)}
几乎没有时间差距 一个是 15.4235 ms 一个是16.2339 ms
这是因为CPU发生了 缓存命中
什么是缓存命中 下一篇文章讲
