Java集合总结(三)--List集合

List集合


1、List集合是一个元素有序、可重复的集合,集合中每个元素都有其对应的顺序索引,可以通过索引来访问指定位置的集合元素,List集合默认按元素的添加顺序设置元素的索引。

2、因为List可以通过索引来访问指定位置的集合元素,针对ArrayList和Vector这种基于数组实现的List集合类,可以使用普通的for循环来遍历集合元素。

3、List判断两个对象是否相等使用过equals()方法进行判断

1、Java 8改进的List接口

方法如下:

void add(int index,Object element):将元素element 插入到List集合的index处。

boolean addAll(int index,Collection c):将集合c所包含的所有元素都插入到List集合的index处。

Object get(int index):返回集合index索引处的元素。

int indexOf(Objecto):返回对象o在List集合中第一次出现的位置索引。

int lastIndexOf(Objecto):返回对象o在List集合中最后一次出现的位置索引。

Object remove(int index):删除并返回index索引处的元素。

Object set(int index,Object element):将index索引处的元素替换成element对象,返回被替换的旧元素。

List subList(int fromIndex,int tolndex):返回从索引fromlndex(包含)到索引tolndex(不包含)处所有集合元素组成的子集合。

所有的List实现类都可以调用这些方法来操作集合元素。与Set集合相比,List增加了根据索引来插入、替换和删除集合元素的方法。除此之外,Java8还为List接口添加了如下两个默认方法。

void replaceAll(UnaryOperator operator):根据operator指定的计算规则重新设置List集合的所有元素。

void sort(Comparator c):根据Comparator 参数对List集合的元素排序。

tips:

注意:当调用List的set(int index,Object obj)方法,来改变List集合制定索引出的元素时,制定的索引必须是List集合的有效索引。例如集合长度为4,就不能替换索引为4处的元素,也就是说set(int index,Object obj)方法不会改变List集合的长度。


2、Java 8改进的ListIterator接口

与Set只提供了一个 iterator()方法不同,List还额外提供了一个listlterator()方法,该方法返回一个 Listlterator 对象,Listlterator 接口继承了Iterator接口,提供了专门操作List的方法。Listlterator 接口在 Iterator 接口基础上增加了如下方法。

boolean hasPrevious():返回该迭代器关联的集合是否还有上一个元素。

Object previous():返回该迭代器的上一个元素。

void add(Objecto):在指定位置插入一个元素。

拿Listlterator与普通的Iterator进行对比,不难发现Listlterator增加了向前迭代的功能(Iterator只能向后迭代),而且Listlterator还可通过add()方法向List集合中添加元素(Iterator只能删除元素)。


3、ArrayList和Vector实现类

1、ArrayList和Vector是List的典型实现,完全支持List接口的全部功能。

2、ArrayList和Vector类是基于数组实现的List类,因此ArrayList和Vector类封装了一个动态的、允许再分配的Obejct[ ]数组。

3、ArrayList和Vector对象可以使用intialCapcity参数来设置该数组的长度,当向ArrayList和Vector中添加元素超出了该数组的长度时,它们的intialCapcity会自动增加

4、如果向ArrayList和Vector集合中添加大量的元素时,可以使用ensureCapacity(int miniCapacity)方法一次性地增加intialCapacity,减少重新分配的次数,提高性能。

5、ArrayList和Vector集合需要保存多少元素,可以在创建时就指定intialCapacity,如果不指定,则Object[ ]数组默认长度为10.

6、Vector是一个古老的集合,作为List的实现,其中有一些原有的方法和List接口中的方法重复了。

ArrayList和Vector的区别

ArrayList是线程不安全的,但Vector是线程安全的,所以Vector的性能比ArrayList要低


4、固定长度的List

有一个操作数组的工具类:Arrays,该工具类里提供了asList(Object… a)方法,该方法可以把一个数组或指定个数的对象转换成一个List集合,这个List集合既不是ArrayList实现类的实例,也不是Vector实现类的实例,而是Arrays的内部类ArrayList的实例。

Arrays.ArayList是一个固定长度的List集合,程序只能遍历访问该集合里的元素,不可增加、删除该集合里的元素。如下程序所示。

public class FixedSizeList
{
    public static void main(String[] args)
    {
        List fixedList = Arrays.asList("疯狂Java讲义", "轻量级Java EE企业应用实战");

        // 获取fixedList的实现类,将输出Arrays$ArrayList
        System.out.println(fixedList.getClass());

        // 使用方法引用遍历集合元素
        fixedList.forEach(System.out::println);

        // 试图增加、删除元素都会引发UnsupportedOperationException异常
        fixedList.add("疯狂Android讲义");
        fixedList.remove("疯狂Java讲义");
    }
}


5、Queue集合

Queue用于模拟队列这种数据结构,队列:先进先出。队列不允许随机访问队列中的元素。

Queue接口中定义了如下几个方法。

void add(Objecte):将指定元素加入此队列的尾部。

Object element():获取队列头部的元素,但是不删除该元素。

boolean offer(Objecte):将指定元素加入此队列的尾部。当使用有容量限制的队列时,此方法通常比add(Objecte)方法更好。

Object peek():获取队列头部的元素,但是不删除该元素。如果此队列为空,则返回null。

Object poll():获取队列头部的元素,并删除该元素。如果此队列为空,则返回null。

Object remove():获取队列头部的元素,并删除该元素。

Queue有PriorityQueue实现类还有一个Deque接口,Deque代表一个“双端队列”,双端队列可以同时从两端来添加、删除元素。因此Deque的实现类既可以当队列使用也可当(栈:后进先出)来使用。Deque有两个实现类:ArrayDeque和LinkedList。


5.1 PriorityQueue实现类(优先级队列)

1、会将元素按照大小进行重新排序,调用 peek() 方法或者 poll() 方法取出的并不是取出最先进入队列的元素,而是队列中最小的元素。

2、PriorityQueue不允许插入null元素

3、PriorityQueue实现类有两种排序方式:自然排序和定制排序

  • 自然排序:采用自然顺序的PriorityQueue集合中的元素必须实现了Comparable接口,因为自然排序需要调用 Comparable 接口中的 compareTo() 方法,对传入的对象进行排序,而且传入的对象应该是同一个类的多个实例,传入不同的类可能无法进行比较,也就无法进行排序,因而可能导致ClassCastException异常。

  • 定制排序:创建PriorityQueue队列时,传入一个Comparator对象,该对象负责对队列中的所有元素进行排序。采用定制排序时不要求队列元素实现Comparable接口。

5.2 Deque接口和ArrayDeque实现类

定义的方法:

void addFirst(Objecte):将指定元素插入该双端队列的开头。

void addLast(Objecte):将指定元素插入该双端队列的末尾。

Iterator descendinglterator():返回该双端队列对应的迭代器,该迭代器将以逆向顺序来迭代队列中的元素。

Object getFirst():获取但不删除双端队列的第一个元素。

Object getLast():获取但不删除双端队列的最后一个元素。

boolean offerFirst(Objecte):将指定元素插入该双端队列的开头。

boolean offerLast(Objecte):将指定元素插入该双端队列的末尾。

Object peekFirst():获取但不删除该双端队列的第一个元素;如果此双端队列为空,则返回null。

Object peekLast():获取但不删除该双端队列的最后一个元素;如果此双端队列为空,则返回null。

Object pollFirst():获取并删除该双端队列的第一个元素;如果此双端队列为空,则返回null。

Object pollLast():获取并删除该双端队列的最后一个元素;如果此双端队列为空,则返回null。

Object pop()(栈方法):pop出该双端队列所表示的栈的栈顶元素。相当于removeFirstO。

void push(Object e)(栈方法):将一个元素push进该双端队列所表示的栈的栈顶。相当于addFirst(e)。

Object removeFirst():获取并删除该双端队列的第一个元素。

Object removeFirstOccurrence(Objecto):删除该双端队列的第一次出现的元素o。

Object removeLast():获取并删除该双端队列的最后一个元素。

boolean removeLastOccurrence(Object o):删除该双端队列的最后一次出现的元素o。

从上面方法中可以看出,Deque不仅可以当成双端队列使用,而且可以被当成栈来使用,因为该类里还包含了pop(出栈)、push(入栈)两个方法。

tips:

ArrayList和ArrayDeque两个集合类的实现机制基本相似,它们的底层都采用一个动态的、可重分配的Object[ ]数组来存储集合元素,当集合元素超出了该数组的容量时,系统会在底层重新分配一个Objec[ ]数组来存储集合元素。


“栈”和“队“使用举例:


//下面程序示范了把ArrayDeque当成“栈”来使用

public class ArrayDequeStack
{
    public static void main(String[] args)
    {
        ArrayDeque stack = new ArrayDeque();
        // 依次将三个元素push入"栈"
        stack.push("疯狂Java讲义");
        stack.push("轻量级Java EE企业应用实战");
        stack.push("疯狂Android讲义");
        // 输出:[疯狂Android讲义, 轻量级Java EE企业应用实战, 疯狂Java讲义]
        System.out.println(stack);
        // 访问第一个元素,但并不将其pop出"栈",输出:疯狂Android讲义
        System.out.println(stack.peek());
        // 依然输出:[疯狂Android讲义, 疯狂Java讲义, 轻量级Java EE企业应用实战]
        System.out.println(stack);
        // pop出第一个元素,输出:疯狂Android讲义
        System.out.println(stack.pop());
        // 输出:[轻量级Java EE企业应用实战, 疯狂Java讲义]
        System.out.println(stack);
    }
}


//下面程序示范了把ArrayDeque当成“队列”来使用
public class ArrayDequeQueue
{
    public static void main(String[] args)
    {
        ArrayDeque queue = new ArrayDeque();
        // 依次将三个元素加入队列
        queue.offer("疯狂Java讲义");
        queue.offer("轻量级Java EE企业应用实战");
        queue.offer("疯狂Android讲义");
        // 输出:[疯狂Java讲义, 轻量级Java EE企业应用实战, 疯狂Android讲义]
        System.out.println(queue);
        // 访问队列头部的元素,但并不将其poll出队列"栈",输出:疯狂Java讲义
        System.out.println(queue.peek());
        // 依然输出:[疯狂Java讲义, 轻量级Java EE企业应用实战, 疯狂Android讲义]
        System.out.println(queue);
        // poll出第一个元素,输出:疯狂Java讲义
        System.out.println(queue.poll());
        // 输出:[轻量级Java EE企业应用实战, 疯狂Android讲义]
        System.out.println(queue);
    }
}


5.3、LinkedList实现类

LinkedList类是List接口的实现类——这意味着它是一个List集合,可以根据索引来随机访问集合中的元素。除此之外,LinkedList还实现了Deque接口,可以被当成双端队列来使用。

因此既可以被当成“栈”来使用,也可以当成队列使用。下面程序简单示范了LinkedList集合的用法。

//下面程序代码分别示范了LinkedList作为List集合、双端队列、栈的用法。由此可见,LinkedList是一个功能非常强大的集合类。

public class LinkedListTest
{
    public static void main(String[] args)
    {
        LinkedList books = new LinkedList();

        // 将字符串元素加入队列的尾部
        // 将一个字符串元素加入栈的顶部
        books.push("轻量级Java EE企业应用实战");

        // 将字符串元素添加到队列的头部(相当于栈的顶部)

        // 以List的方式(按索引访问的方式)来遍历集合元素
        for (int i = 0; i < books.size() ; i++ )
        {
            System.out.println("遍历中:" + books.get(i));
        }

        // 访问、并不删除栈顶的元素

        // 访问、并不删除队列的最后一个元素
        System.out.println(books.peekLast());

        // 将栈顶的元素弹出“栈”

        // 下面输出将看到队列中第一个元素被删除
        System.out.println(books);

        // 访问、并删除队列的最后一个元素

        // 下面输出:[轻量级Java EE企业应用实战]
        System.out.println(books);
    }
}



LinkedList与ArrayList、ArrayDeque的实现机制完全不同。ArrayList、ArrayDeque内部以数组的形式来保存集合中的元素,因此随机访问集合元素时有较好的性能;

而LinkedList内部以链表的形式来保存集合中的元素,因此随机访问集合元素时性能较差,但在插入、删除元素时性能比较出色(只需改变指针所指的地址即可)。

需要指出的是,虽然Vector也是以数组的形式来存储集合元素的,但因为它实现了线程同步功能(而且实现机制也不好),所以各方面性能都比较差。

对于所有的内部基于数组的集合实现,例如ArrayList、ArrayDeque等,使用随机访问的性能比使用Iterator迭代访问的性能要好,因为随机访问会被映射成对数组元素的访问。

6、各种线性表的性能分析

Java提供的List就是一个线性表接口,而ArrayList、LinkedList又是线性表的两种典型实现:基于数组的线性表和基于链的线性表.Queue代表了队列,Deque代表了双端队列(既可作为队列使用,也可作为栈使用),接下来对各种实现类的性能进行分析。

初学者可以无须理会ArrayList和LinkedList之间的性能差异,只需要知道LinkedList集合不仅提供了List的功能,还提供了双端队列、栈的功能就行。但对于一个成熟的Java程序员,在一些性能非常敏感的地方,可能需要慎重选择哪个List实现。

一般来说,由于数组以一块连续内存区来保存所有的数组元素,所以数组在随机访问时性能最好,所有的内部以数组作为底层实现的集合在随机访问时性能都比较好;而内部以链表作为底层实现的集合在执行插入、删除操作时有较好的性能。但总体来说,ArrayList的性能比LinkedList的性能要好,因此大部分时候都应该考虑使用ArrayList。

关于使用List集合有如下建议。

  • 如果需要遍历List集合元素,对于ArrayList、Vector集合,应该使用随机访问方法(get)来遍历集合元素,这样性能更好;对于LinkedList集合,则应该采用迭代器(Iterator)来遍历集合元素。

  • 如果需要经常执行插入、删除操作来改变包含大量数据的List集合的大小,可考虑使用LinkedList集合。使用ArrayList、Vector集合可能需要经常重新分配内部数组的大小,效果可能较差。

  • 如果有多个线程需要同时访问List集合中的元素,开发者可考虑使用Collections将集合包装成线程安全的集合。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值