最近在梳理一些关于java的概念,这篇文章是最近笔记中关于基础数据结构的部分,因为记录笔记的时候思路比较天马行空,所以不知道这篇文章的思路能不能清晰,姑且总结下将要涉及到的方面(jdk1.8)(另外毕竟是自己的理解,如果能指出错误,不胜感激):
-
基础数据结构继承关系图
-
相关接口的一些解读
-
iterable和iterator的异同
-
map接口中值的注意的地方
-
collection类族和map类族
基础数据结构接口继承关系图
关于接口的一些解读
我们就解释和基础数据结构关系比较密切的接口iterable、iterator、map。除了这三个接口之外,其实还有比较重要的接口比如comparator、enumeration、spliterator,在一些具体的基础数据结构中为了满足不同的功能需求,有选择得继承了这些接口,他们的功能就和他们得名字一样,值得一提的是spliterator接口,这是在1.8中新加入的一个接口,基本数据结构基本上都继承了这个接口,该接口的功能是产生一组分割后的可迭代对象,这些对象是原来数据集的一个子集,这样做的好处在与对原数据集的处理可以等价为对这些分割后数据集的处理,而对这些数据集的处理是可以并行进行的,从而实现了对多线程或者多核或者分布式编程的支持(当然这是一个非常复杂的部分,也是java8中最亮眼的新特性)。还需要注意的是Collection接口直接继承了iterable接口,以实现collection类族的可迭代,但是map是没有继承iterable接口的,也就是说不能通过iterable中的方法遍历map类族(map类族有相应的遍历方法,在后面会提到),下面详细介绍一些接口。
iterable接口
该接口在文档中的说明是:
实现此接口允许对象成为“for-each loop”语句的目标
可以看到其中比较重要的一个方法iterator(),该方法返回一个类型为T的迭代器,所以实际上iterable接口并没有直接实现对象的可迭代,而是通过iterator的逻辑来实现对象的迭代。
iterator
可以看到两个比较重要的函数,hasNext()和next()通过这两个函数就可以实现一个对象的可迭代化。至于他的实现逻辑在下面部分将做详细阐述。
iterator和iterable的关系和区别
如果说iterable接口的目标是实现对象的可迭代,那么iterator就是iterable选择的一种方法,也就是说iterator是一种普适的、与具体集合松耦合的迭代逻辑。比如说对于一个arrayList,该类继承了iterable接口,为了实现该接口,我们需要实现arrayList的迭代方式,如果我们使用以下代码:
在这段代码中使用了arraylist.length,即与具体对象是紧耦合的,但是我们无从得知将来实例化的对象是怎么样的,也就是说arraylist参数是变化且未知的,而如果采用固定值会造成数组访问越界或者数组访问不全的问题,这就导致了无法实现arraylist的可迭代。但是换种方式来思考,我们是如何遍历链表的,在遍历链表的时候我们可以无需关心链表的具体结构,我们只需要问程序,还有下一个吗,如果程序回答有,那么我们就进行处理,如果回答没有,那么我们就结束处理,而iterator则采用了与这个相似的逻辑,通过hasNext()和next()即可以实现集合的遍历,这种遍历逻辑摆脱了对于具体对象的依耐,所以iterable采用了这种逻辑来实现对象的可遍历性。那么为什么collection接口不直接继承iterator接口而继承iterable接口呢,这就不得不佩服sun的大佬们,前面提到了iterable中有一个方法为iterator()方法,该方法是一个工厂方法,返回一个类型为T的迭代器,采用的是iterator的迭代逻辑,这样做有什么好处呢,在实际代码环境中,我们对一个可迭代对象的迭代访问不管是时间还是次数都是随机的,可能我们需要对一个可迭代对象同时进行迭代访问,如果我们只是实现iterator接口,iterator会在原始数据上使用指针进行标记,表示我们访问的位置,但是当我们并行迭代访问的时候,指针的移动会受并行迭代的影响,造成指针标记位置的错误,而继承iterable使用iterator()方法则可以解决这个问题,iterator()是一个工厂方法,会返回一个迭代器,当需要对对象迭代访问的时候,即生成一个对应的迭代器,该次迭代访问所有操作对应该次迭代器,当需要并行迭代访问的时候,生成多个迭代器,每个迭代访问的操作在其对应的迭代器上被记录,指针由该迭代器维护,从而实现了并行迭代访问。
map接口
在map接口中已经定义了绝大部分map的操作,具体的功能可以看看上面的图,有些注意的地方放在后面具体的实现类中解释。
具体类继承关系图
首先从图中可以看到,java基础数据结构主要由两个基础的类,分别是AbstractCollection和AbstractMap,他们是java共同基类Object的子类,分别是collection结构和map类结构的基类。
collection类族
AbstractCollection:
其继承了Iterable和Collection接口,并实现了绝大部分的功能,但是没有实现iterator和size方法,所以他依旧是一个抽象类,他为整个collection类族定义了一个基调,在他的基础上如果要实现一个不可修改的集合类,只需要实现iterator和size方法即可,如果要实现一个可修改类,只需要额外覆盖方法add并实现iterator中的remove方法(这些方法在接口中已经被定义了)。该类提供了操作集合的基础方法(注意其中的两个abstract声明的方法):
在AbstractCollection的基础上,衍生出了AbstractLIst,AbstractQueue,AbstractSet,ArrayDeque
AbstractList为整个List类族定义了基调,collection没有对集合的顺序作具体的要求,但是list指明了集合中元素都是由顺序的,提供了List类族访问的基础函数:
后面实现具体的list的时候,如果该list不可变,则只需要实现get和size就好,如果列表元素可变,则需要额外实现set,如果列表大小可变,则需要实现add和remove。下面看看具体实现的list的异同。
ArrayList是最常用的List实现类,允许所有元素,包括null。内部是通过数组实现的,它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要讲已经有数组的数据复制到新的存储空间中。当从ArrayList的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。
Vector与ArrayList一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,访问它比访问ArrayList慢。statck是对vector的扩展,使其支持使一个vector对象成为一个堆栈对象(先进先出)
LinkedList是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,他还提供了List接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。
AbstractQueue:是队列的抽象定义,其定义了一些队列的基本操作,和AbstractCollection相比,AbstractQueue不允许元素为null,如果不能满足这个要求,则应该使用AbstractCollection而不是AbstractQueue,目前继承并实现了该抽象类的类只有PriorityQueue,这是一个带有优先级的队列,除了满足元素不能是null之外,还需要满足元素具有可比性,因为要维持元素的优先级,就需要对元素进行比较,确定顺序。如果没有指定优先级顺序,则默认最小值为头部。
然后使AbstractSet,该抽象类定义的set与数学上的集合相似,需要满足一个比较重要的特性,互异性,就是说set中不能存在两个一样的值(拥有相同的hashcode),没有对无序性作要求(treeset就是一个有序的set,但是这和我们常说的顺序不同,treeset的顺序指的是按照一定的排序算法排出的顺序,而不是我们插入数据时的顺序,所以在set中时没有get(i)方法的,即不能按照索引进行访问的,本来set是没有索引这个概念的。)此类不会覆盖AbstractCollection类中的任何实现。 它只是添加了equals和hashCode的实现,这是为了实现AbstractSet规定的互异性,通过将比较需要加入的元素的hasocode值是否和set中其他元素的hashcode值相等,如果相等,则拒绝插入,抛出异常。
实现AbstractSet的有EnumSet、HashSet(它有一个子类LinkedHashSet),TreeSet,下面比较一下他们的异同点:
EnumSet比较少见,文档上是这样描述的:是一个与枚举类型一起使用的专用 Set 实现。枚举set中所有元素都必须来自单个枚举类型(即必须是同类型,且该类型是Enum的子类)。 枚举类型在创建 set 时显式或隐式地指定。枚举 set 在内部表示为位向量。 此表示形式非常紧凑且高效。此类的空间和时间性能应该很好,足以用作传统上基于 int 的“位标志”的替换形式,具有高品质、类型安全的优势。也就是说比其他的set类型效率高上很多。而HashSet作用类似与arrayList,只是元素不能重复(没有顺序,如果要保持插入时迭代顺序需要使用LinkedHashSet,需要按要求实现排序需要使用TreeSet),当然他们的底层实现也是不同的。
ArrayDeque是更高效的stack,java推荐的堆栈使用对象由stack变为了ArrayDeque。
下面是一些常用数据结构的一些性质中介,treeset我也不知道到底支不支持null
Map类族
AbstractMap
其定义的方法如下:
先介绍以下map的底层实现,map是一个数组和链表混合使用实现的一种数据结构,所以他在增删改查方面的性能比较平衡,map是一个键值对集合,大部分map是没有顺序的,所以没有按索引访问的方法,但是treeMap是有顺序的,但是这个顺序不是数据插入的顺序,而是按我们指定的排序方式所产生的顺序,如果需要map有数据插入的顺序,需要使用linkedHashMap。
下面介绍以下使用比较多的hashMap以及它和hashTable的关系:
hashTable是线程安全但不支持null的hashMap。
hashMap主要有以下方法:
map写的比较少,主要它和collection可以类比,之后有机会再补充吧。
学习过程中遇到什么问题或者想获取学习资源的话,欢迎加入Java学习交流群346942462,我们一起学Java!