浅谈ArrayList的removeAll方法

JAVA编程 徐 承恩 83℃ 0评论

在开发过程中,遇到一个情况,就是从所有骑手Id中过滤没有标签的骑手Id(直接查询没有标签的骑手不容易实现)。

逻辑很简单,就是取一个差集,这样子就拿到没有任何标签的骑手数据。
但是在实际开发过程中,removeAll这个动作很耗时,做测试大概要4分钟左右。查看ArrayList中removeAll的源码片段:

在循环过程中调用contains方法做比较,查一下ArrayList的contains方法,源代码片段如下:

这可以看出来,在比较的过程中,又调用了一次循环。
所以removeAll两层for循环,复杂度O(m*n),所以在操作比较大的ArrayList时,这种方法是绝对不可取的。
下面看一下最终的实现方式:

同样数量级list, 整个过程只需要几十毫秒,简直天壤之别。
回过头来,比较一下两种实现方式,为什么差距这个大。
1、外层循环
     一个是普通的for循环,一个迭代器遍历元素,二者相差不大
2、内层数据比较
     前者通过index方法把整个数组顺序遍历了一遍;
     后者调用HashSet的contains方法,实际上是调用HashMap的containKey方法,查找时是通过hash表查找,复杂度为O(1)。
接下来我们简单看一下hash表。
hash表是一种特殊的数据结构,它同数组、链表以及二叉排序树等相比较有很明显的区别,它能够快速定位到想要查找的记录,而不是与表中存在的记录的关键字进行比较来进行查找。这个源于Hash表设计的特殊性,它采用了函数映射的思想将记录的存储位置与记录的关键字关联起来,从而能够很快速地进行查找。可以简单理解为,以空间换时间,牺牲空间复杂度来换取时间复杂度。
hash表采用一个映射函数 f : key —> address 将关键字映射到该记录在表中的存储位置,从而在想要查找该记录时,可以直接根据关键字和映射关系计算出该记录在表中的存储位置,通常情况下,这种映射关系称作为hash函数,而通过hash函数和关键字计算出来的存储位置(注意这里的存储位置只是表中的存储位置,并不是实际的物理地址)称作为hash地址。
上面的图大家应该都很熟悉,hash表的一种实现方式,是由数组+链表组成的。元素放入hash表的位置通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。
另外hash表大小的确定也很关键,如果hash表的空间远远大于最后实际存储的记录个数,则造成了很大的空间浪费,如果选取小了的话,则容易造成冲突。在实际情况中,一般需要根据最终记录存储个数和关键字的分布特点来确定Hash表的大小。还有一种情况时可能事先不知道最终需要存储的记录个数,则需要动态维护Hash表的容量,此时可能需要重新计算Hash地址。
当然,关于hash表要说的话太多,先简单到此吧~~~
本篇文章转载自点我达官方技术博客

转载请注明:徐叔科技 » 浅谈ArrayList的removeAll方法

喜欢 (3)or分享 (0)
发表我的评论
取消评论
表情

嗨,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址