java集合框架学习笔记 Collection之List、Set及Queue详解

        上一篇我们简要地介绍了集合框架的接口体系,厘清java的集合接口实现分离原理,让我们该框架有个整体的认识,下面我们继续讲解下Collection之List、Set及Queue。下图为其接口实现体系:

       List接口是一个有序集合,元素可以精确地添加到容器中的特定位置。它可以采用两种方式访问元素: 使用迭代器访问, 或者使用一个整数索引来访问。后一种方法称为随机访问,可以按任意顺序访问数组,利用迭代器访问时,便只能顺序地访问元素。(索引访问操作可能在和某些实现(例如 LinkedList 类)的索引值成比例的时间内执行。因此,如果调用者不知道实现,那么在列表元素上迭代通常优于用索引遍历列表。)

       List 接口提供了 4 种对列表元素进行定位(索引)访问方法:(注意:列表像 Java 数组一样是基于 0 的)

void add(int index, E element)
void remove(int index)
E get(int index)
E set(int index, E element)

        同时List提供了特殊的迭代器,称为 ListIterator(是Iterator 的一个子接口),除了允许 Iterator 接口提供的正常操作外,该迭代器还允许元素插入和替换,以及双向访问。还提供了一个方法来获取从列表中指定位置开始的列表迭代器。

其主要方法有:

boolean hasNext();    E next();
boolean hasPrevious();E previous();
int nextIndex();      int previousIndex();
void remove();        void set(E e);
void add(E e);

其中next()为指针前移遍历(hasNext()为判断是否还有“下”一个元素),相对应地previous()为指针后移遍历(hasPrevious()为判断是否还有“下”一个元素),previousIndex()及nextIndex()返回相对应的指针。

        在这里我们可以做个小实验以便于对Iterator遍历时指针的理解:(值得注意的是,当交替调用next()和previous()时,会返回同一个元素,原理很简单,稍作理解即可)

下面来简单说一下其两大实现ArrayList及LinkList。

        ArrayList是List 接口的大小可变数组的实现,其每个实例都有一个容量,该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向 ArrayList 中不断添加元素,其容量也自动增长。由于其基于数组这种简单的结构,也不做过多的解析,更详细的信息可以参考:API文档

        LinkList是List 接口的链接列表实现,不过,其还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列及双端队列。此类还实现了 Deque 接口,为 add、poll 提供先进先出队列操作,以及其他堆栈和双端队列操作。这种基于“指针”的操作在上面的小实验便得到了很好的体现。(实际上collection许多类似这样的方法均是在这里得到了体现)

        ArrayList及LinkList的区别也正是数组和链表的区别,其优劣势也是老生常谈的话题,在访问及修改上ArrayList占优势,而在增删方面明显LinkList性能更好,我们应当根据我们对数据的处理特点选用合适的实现类(其实数据量不大的话完全可以使用ArrayList)注意:ArrayList及LinkList都不是线程安全的,多线程环境下建议使用java并发集合包java.util.concurrent

Set接口是一个不包含重复元素的 “collection”(即直接继承collection,没有特有的方法),只不过collection的方法做了更加严谨的定义,其add方法不允许增加重复的元素。其要适当地定义集的equals 方法:只要两个集包含同样的元素就认为是相等的,而不要求这些元素有同样的顺序。hashCode 方法的定义要保证包含相同元素的两个集会得到相同的散列码。(即注意:使用Set时,元素类必须已经重写equals方法和hashCode方法,其要求为判重要求为,若hashCode值不同则不重复,若hashCode值相同而equal值为假则仍为不重复)。详细解读可以看下这篇:HashSet要重写equals方法和hashCode方法(但对于散列表即关于数组(散列桶)与链表的组合体的介绍未涉及,下面将提及)。

        下面简要说下两个实现类HashSet(散列集)和TreeSet(树集,有序集):

 散列表结构

         首先我们先说下散列表(数组(散列桶)与链表的组合体的介绍),在Java 中, 散列表用链表数组实现。每个列表被称为桶( bucket) ( 参看图)。要想査找表中对象的位置, 就要先计算它的散列码, 然后与桶的总数取余, 所得到的结果就是保存这个元素的桶的索引。例如, 如果某个对象的散列码为76268, 并且有128 个桶, 对象应该保存在第108 号桶中( 76268除以128 余108 )。这是便可以将元素直接插入到桶中就可以了,查找时便根据索引(见前面)得到桶的头指针然后遍历得到这一对映射。

        HashSet便是实现了基于散列表的集,可以用add 方法添加元素。contains 方法已经被重新定义,用来快速地查看是否某个元素已经出现在集中。它只在某个桶中査找元素,而不必查看集合中的所有元素。散列集迭代器将依次访问所有的桶。由于散列将元素分散在表的各个位置上,所以访问它们的顺序几乎是随机的。只有不关心集合中元素的顺序时才应该使用HashSet。

        TreeSet基于TreeMapNavigableSet实现(具体算法可见红黑树或平行二叉树)。使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供Comparator 进行排序,具体取决于使用的构造方法,在对集合进行遍历时,每个值将自动地按照排序后的顺序呈现。将一个元素添加到树中要比添加到散列表中慢,但是,与检查数组或链表中的重复元素相比还是快很多。如果树中包含n 个元素, 査找新元素的正确位置平均需要l0g2 n 次比较。(注意:要使用树集, 必须能够比较元素。即元素必须实现Comparable 接口或者构造集时必须提供一个Comparator)。

         对与应当选择使用哪种集合,我们应当根据是否有排序的必要和性能的消耗做出考虑。注意:HashSet和TreeSet都不是线程安全的,多线程环境下建议使用java并发集合包java.util.concurrent

        Queue接口是设计用于在处理之前保持元素的集合。除基本Collection操作外,队列还提供额外的插入,提取和检查操作。这些方法中的每一种都以两种形式存在:一种在操作失败时抛出异常,另一种返回特殊值(null或false,具体取决于操作)。后一种形式的插入操作专门用于容量限制的队列 实现; 在大多数实现中,插入操作不会失败。(具体方法见下图)

       队列通常(但并非一定)以 FIFO(先进先出)的方式排序各个元素。不过优先级队列和 LIFO 队列(或堆栈)例外,前者根据提供的比较器或元素的自然顺序对元素进行排序,后者按 LIFO(后进先出)的方式对元素进行排序。无论使用哪种排序方式,队列的 都是调用 remove() 或 poll() 所移除的元素。在 FIFO 队列中,所有的新元素都插入队列的末尾。其他种类的队列可能使用不同的元素放置规则。每个 Queue 实现必须指定其顺序属性。

       下面简单介绍下优先级队列(priority queue):它的元素可以按照任意的顺序插人,却总是按照排序的顺序进行检索。也就是说,无论何时调用remove 方法, 总会获得当前优先级队列中最小的元素。然而, 优先级队列并没有对所有的元素进行排序。如果用迭代的方式处理这些元素,并不需要对它们进行排序。优先级队列使用了一个优雅且高效的数据结构,称为堆( heap)。堆是一个可以自我调整的二叉树,对树执行添加( add) 和删除( remore) 操作, 可以让最小的元素移动到根,而不必花费时间对元素进行排序。与TreeSet—样,一个优先级队列既可以保存实现了Comparable 接口的类对象,也可以保存在构造器中提供的Comparator 对象。使用优先级队列的典型示例是任务调度。每一个任务有一个优先级, 任务以随机顺序添加到队列中。每当启动一个新的任务时,都将优先级最高的任务从队列中删除(由于习惯上将1 设为“ 最高” 优先级,所以会将最小的元素删除)。

          那么关于Collection之List、Set及Queue的介绍就到这里了,在本文中,我们着重介绍了List的两种元素访问并就此展开了这两种方式了两个实现类ArrayList及LinkList,通过对源码的分析,进而对比出他们各自的特点和优缺点。然后,我们介绍了Set接口,对其判重原理做了一番探究,同时介绍了两个实现类HashSet和TreeSet,通过分析他们的基本原理,总结他们的特点及使用时所要注意的点。最后我们简要地介绍了一下Queue,队列通常以 FIFO的方式排序各个元素,同时提供额外l的插入,提取和检查操作以便于我们得到我们所要的结果,我们还特别介绍了一下基于堆结构的优先级队列,以扩展我们的视野。下一篇我们将学习Map接口(映射)及其实现类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值