Java Collection框架(一)Iterable与Spliterator

​Java Collection框架 顶级接口Iterable与Spliterator,forEach与forEachRemaining

2018拍摄于京都音羽山清水寺

微信公众号
 

王皓的GitHub:https://github.com/TenaciousDWang

 

今天开始复习Java 集合框架,多线程部分还差同步容器会在Java集合最后后面填坑。

 

在说Java集合框架之前,先简单看看数据结构。

 

程序=数据结构+算法,数据结构指的是数据组织的形式及处理方式,在Java2及以后的版本中使用了船新的集合框架体系Collection来作为数据结构的载体,用来存储和处理各种各样的对象。

 

处理方式如,增加,修改,查找,删除,遍历。

 

以下类是Java遗留下来的,基本不再使用,而是开始使用集合框架Collection,感兴趣的朋友,可以去自行百度或google。

 

  • 枚举(Enumeration)
  • 位集合(BitSet)
  • 向量(Vector)
  • 栈(Stack)
  • 字典(Dictionary)
  • 哈希表(Hashtable)
  • 属性(Properties)

 

常用数据组织方式:

 

线性结构,循序表,链表,栈,队列等,其中栈为LIFO,Last in,First out,后进先出,单门旅游大巴,队列为FIFO,First in,First Out先进先出,公交车前门上,后门下。

 

树形结构,有层次非线性结构,像我们平常的看到的树一样,有树根,树杈,由一个个节点构成树的模样,二叉树,B树,B+树,红黑树。

 

哈希结构,是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

 

不同的数据结构,即不同的数据组织方式与处理方式的性能问题,我们通常分为两个维度去考量,一个是空间维度,一个是时间维度,空间维度暂时我们不会考虑,因为现在存储空间越来越便宜,2019年4TB的机械硬盘只需要500元左右,并且已经出现了16TB的怪物,固态硬盘也越来越便宜,所以我们这里只考虑时间维度,即程序执行时间或者查找时间随输入规模而增长的量级,可以在很大程度上反映数据结构的性能。

 

最好到最坏的常用算法时间复杂度:

 

常数级O(1),最低复杂度,不管输入规模多大,耗时不变,例如哈希结构,在不考虑冲突的情况下,只计算一次即可找到对应的目标。

 

对数级O(log n),当数据增大n倍时,复杂度只增加log n倍,这里的log n以2为底数,即增加256倍输入量时,时间复杂度只增加8倍。例如二分查找法,每次排除一半可能,256个元素,只需要找8次即可。例如1-100随机查找一个数字,使用二分法,执行一亿次,平均查找次数为7.47.接近log 100即5.8

 

线性级O(n),输入数据增大几倍,查找耗时也增加几倍,例如遍历算法。256个元素,找256次。

 

线性对数级O(nlog n),n乘以log n,256倍输入量,查找时间为256*8=2048倍,这个复杂度高于线性低于平方级。

 

平方级O(n^2)。如冒泡排序,需要计算n*n次。

 

立方级O(n^3)。

 

指数级O(2^n)。

 

在实际编码中,我们应该根据实际使用场景,来确定使用什么类型的数据结构或者说使用哪种类型的集合,数据结构没有好坏之分,需要与场景与数据量结合起来考虑,优秀的程序不会因为随着输入量的增大,而使执行时间急剧上升。

 

常见数据结构时间复杂度。

 

 

接下来我们来看一下Java的常用集合框架图:

 

图片摘自《码出高效》

 

Java集合框架是用于存储对象的容器,实现了常用的数据结构,提供公开增加,修改,删除,查找,遍历的方法,集合的种类较多,形成了一个Java集合框架,主要分类两类,一个是Collection,set与List都实现了Collection接口,一个是Map接口,按照key-Value存储的数据结构。

 

这里借用《码出高效》的Java集合框架图,网上大部分框架图没有包含并发包中的类,最主要是我懒了不想画了。。。红色为接口,蓝色为抽象类,绿色为并发包中的类,灰色为早期线程安全类,由于性能低下现在基本弃用。

 

首先我们先从顶层接口来看,Iterable在java.lang中,而并不是java.util,网上很多文章都写错了。Java集合类的基本接口是Collection接口。而Collection接口必须继承java.lang.Iterable接口。

 

 

Iterator为传统迭代器,位于java.util.Iterator中。

 

JDK1.8在Iterable中添加默认方法for-each循环可以与任何实现了Iterable接口的对象一起工作。而java.util.Collection接口继承java.lang.Iterable,故标准类库中的任何集合都可以使用for-each循环。大家注意JDK1.8中已经可以在接口类中定义非抽象普通方法了。

 

Spliterator为JDK1.8最新添加的分割迭代器,位于java.util.Spliterator。

 

Iterator

 

首先我们先说一下迭代器Iterator:是一个接口—Iterator接口,其作用:用于取集合中的元素。

 

 

hasNext()如果仍有元素可以迭代,则返回true。

 

next()返回迭代的下一个元素。

 

remove()从迭代器指向的 collection 中移除迭代器返回的最后一个元素(可选操作)这里注意一下使用方式,迭代时使用时,注意写法,例子中查看。

 

这里需要注意:

 

迭代器绑定前对数据集合的修改可以反应在迭代器遍历结果中。

 

迭代器绑定后对数据集合的修改会报ConcurrentModificationException异常。 


 

forEachRemaining()是Iterator接口在1.8的时候引入的一个默认方法,与Iterable中forEach()对比,forEach可以调用多次,来遍历元素,forEachRemaining调用第一次与forEach相同,但是调用第二次则没有迭代,因为没有下一个元素了,现在写一个例子,演示一下。


 

 

运行结果:

 

 

大家可以看到两次forEachRemaining什么也没迭代出来,因为我们上面已经用传统方法迭代过一次了,已经不存在元素了,接下来我们注释掉转筒迭代器Iterator,再来运行一下,看一下结果。

 

 

第一次forEachRemaining可以迭代,第二次迭代剩余元素已经不存在,所以没有迭代出值。

 

Spliterator

 

 

接下来我们看一下Spliterator接口,我们主要看三个方法:

 

 

 int characteristics();

 

返回特征值,根据每个容器不同的性质,这个迭代器也对应了相应的特征量。

 

 

例如一个Collection的spliterator会返回一个Spliterator.SIZED特征。

 

例如一个Set及其实例的spliterator会返回一个Spliterator.DISTINCT特征。

 

例如ArrayList为Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED。

 

例如TreeMap为Spliterator.DISTINCT | Spliterator.SORTED | Spliterator.ORDERED。

 

long estimateSize();

 

返回当前Spliterator实例中将要迭代的元素的数量的估计大小,如果无限,未知,或者计算成本太高,则返回Long.MAX_VALUE。

 

trySplit()

 

分割迭代器,每调用一次,将原来的迭代器等分为两份,并返回索引靠前的那一个子迭代器。

 

接下来我们来简单看一下Spliterator的一个实现例子,以最常用的ArrayList为例。

 

 

 

ArrayList提供获取Spliterator的方法,返回一个接下来是ArrayListSpliterator。ArrayListSpliterator为ArrayList的内部类实现了Spliterator。

 

        private final ArrayList<E> list;// 数据集合

        private int index; // 起始位置所有,会被advance()和split()函数修改。

        private int fence; // fence则代表当前结束位置的最后一个下标,-1表示第一次使用,然后指最后一个索引

        private int expectedModCount; // expectedModCount存放了当该迭代器所对应的ArrayList的modCount来保证迭代器在迭代数据中原本数组中的数据并没有发生变化,该变量会被fence更改。

 

 

 

estimateSize(),用来估算将要迭代的元素的数量。characteristics()返回特征值。之前已经在Spliterator接口中提到过了。

 

 

getFence(),会确定当前的迭代器的最后分隔下标,如果是-1,则代表此次是第一次使用,更新当前迭代器的expectedModCount为对应容器的modCount,同时更新fence为对应容器的size。

 

trySplit()每次分割,都将原来的迭代器等分为两个,并返回索引靠前的那个,除非实在太小,正常是二分。

 

 

tryAdvance()数组的ModCount进行验证。

 

forEachRemaining(),这里是一次性对所有数据进行操作。

 

接下来写一个例子来看一下使用方法:

 

 

运行结果为:

 

 

ListIterator

 

Iterator只为我们提供remove方法,如果要添加元素,可以使用ListIterator,它继承了Iterator接口,提供add与set操作。ListIterator是List的特有迭代器。

 

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值