List集合代表一个元素有序、可重复的集合,集合中每个元素都有其对应的顺序索引。
List作为Collection接口的子接口,当然可以使用Collection接口里的全部方法。而且由于List是有序的集合,因此List集合里增加了一些根据索引来操作集合元素的方法。
将元素element插入到List集合的index处
void add(int index, E element);
将集合c所包含的所有元素都插入到List集合的index处
boolean addAll(int index, Collection<? extends E> c);
返回集合index索引处的元素
E get(int index);
返回对象o在List集合中第一次出现的位置索引
int indexOf(Object o);
返回对象o在List集合中最后一次出现的位置索引
int lastIndexOf(Object o);
删除并返回index索引处的元素
E remove(int index);
将index索引处的元素替换成element对象,返回新元素
E set(int index, E element);
返回从索引fromIndex(包含)到索引toIndex(不包含)处所有集合元素组成的子集合
List<E> subList(int fromIndex, int toIndex);
public class ListTest {
public static void main(String[] args) {
List books = new ArrayList<>();
//向books中添加三个元素
books.add(new String("轻量级Java EE企业应用实战"));
books.add(new String("疯狂Java讲义"));
books.add(new String("疯狂Android讲义"));
System.out.println(books);
//将新字符串对象插入在第二个位置
books.add(1, new String("疯狂Ajax讲义"));
for (int i=0; i < books.size(); i++){
System.out.println(books.get(i));
}
//删除第三个元素
books.remove(2);
System.out.println(books);
//判断指定元素在List集合中的位置;输出1表明位于第二位
System.out.println(books.indexOf(new String("疯狂Ajax讲义")));
//将第二个元素替换成新的字符串对象
books.set(1, new String("疯狂Java讲义"));
System.out.println(books);
//将books集合的第二个元素(包括)到第三个元数(不包括)截取成子集合
System.out.println(books.subList(1, 2));
}
}
[轻量级Java EE企业应用实战, 疯狂Java讲义, 疯狂Android讲义]
轻量级Java EE企业应用实战
疯狂Ajax讲义
疯狂Java讲义
疯狂Android讲义
[轻量级Java EE企业应用实战, 疯狂Ajax讲义, 疯狂Android讲义]
1
[轻量级Java EE企业应用实战, 疯狂Java讲义, 疯狂Android讲义]
[疯狂Java讲义]
与Set只提供了一个iterator()方法不同,List还额外提供了一个listIterator()方法,该方法返回一个ListIterator对象,ListIterator接口继承了Iterator接口,提供了专门操作List的方法。ListIterator接口在Iterator接口基础上增加了如下方法。
返回该迭代器关联的集合是否还有上一个元素
boolean hasPrevious();
返回该迭代器的上一个元素
E previous();
在指定位置插入一个元素
void add(E e);
拿ListIterator与普通的Iterator进行对比,不难发现ListIterator增加了向前迭代的功能(Iterator只能向后迭代),而且ListIterator还可以通过add方法向List集合中添加元素(Iterator只能删除元素).
public class ListIteratorTest {
public static void main(String[] args) {
String[] books = {"疯狂Java讲义","轻量级Java EE企业应用实战"};
List bookList = new ArrayList<>();
for (int i=0; i<books.length; i++){
bookList.add(books[i]);
}
ListIterator lit = bookList.listIterator();
while (lit.hasNext()){
System.out.println(lit.next());
lit.add("-----------分割符-------------");
}
System.out.println("============下面开始反向迭代=============");
while (lit.hasPrevious()){
System.out.println(lit.previous());
}
}
}
疯狂Java讲义
轻量级Java EE企业应用实战
============下面开始反向迭代=============
-----------分割符-------------
轻量级Java EE企业应用实战
-----------分割符-------------
疯狂Java讲义
从上面程序中可以看出,使用ListIterator迭代List集合时,开始也需要采用正向迭代,即先使用next()方法进行迭代,在迭代过程中可以使用add()方法向上一次迭代元素的后面添加一个新元素。
ArrayList和Vector类都是基于数组实现的List类,所以ArrayList和Vector类封装了一个动态的、允许再分配的Object[]数组。ArrayList或Vector对象使用initialCapacity参数来设置该数组的长度,当向ArrayList或Vector中添加元素超出该数组的长度时,它们的initialCapacity会自动增加。
对于通常的编程场景,我们无须关心ArrayList和Vector的initialCapacity。但如果向ArrayList或Vector集合中添加大量元素时,可使用ensureCapacity(int minCapacity)方法一次性地增加initialCapacity。这可以减少重分配的次数,从而提高性能。
如果开始就知道ArrayList或Vector集合需要保存多少个元素,则可以在创建它们时就指定initialCapacity大小。如果创建空的ArrayList或Vector集合时不指定initialCapacity参数,则Object[]数组的长度默认为10.
除此之外,ArrayList和Vector还提供了如下两个方法来重新分配Object[]数组。
将ArrayList或Vector集合的Object[]数组长度增加minCapacity
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// any size if not default element table
? 0
// larger than default for default empty table. It's already
// supposed to be at default size.
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
调整ArrayList或Vector集合的Object[]数组长度为当前元素的个数。程序可调用该方法来减少ArrayList或Vector集合对象占用的存储空间。
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
Vector里有一些功能重复的方法,这些方法中方法名更短的方法属于后来新增的方法,方法名更长的方法则是Vector原有的方法。Java改写了Vector原有的方法,将其方法名缩短是为了简化编程。而ArrayList开始就作为List的主要实现类。实际上,Vector具有很有缺点,通常尽量少用Vector实现类。
ArrayList和Vector的显著区别是:ArrayList是线程不安全的,当多个线程访问同一个ArrayList集合时,如果有超过一个线程修改了ArrayList集合,则程序必须手动保证该集合的同步性;但Vector集合则是线程安全的,无须程序保证该集合的同步性。因为Vector是线程安全的,所以Vector的性能比ArrayList的性能要低。实际上,即使需要保证List集合线程安全,也通用不推荐使用Vector实现类。后面会介绍一个Collections工具类,它可以将一个ArrayList变成线程安全的。
Vector还提供了一个Stack子类,它用于模拟“栈”这种数据结构,“栈”通常是指“后进先出”(LIFO)的容器。最后“push”进栈的元素,将最先被“pop”出栈。Stack类里提供了如下几个方法。
返回“栈”的第一个元素,但并不将该元素“pop”出栈
public synchronized E peek() {
int len = size();
if (len == 0)
throw new EmptyStackException();
return elementAt(len - 1);
}
返回“栈”的第一个元素,并将该元素“pop”出栈
public synchronized E pop() {
E obj;
int len = size();
obj = peek();
removeElementAt(len - 1);
return obj;
}
将一个元素“push”进栈,最后一个进“栈”的元素总是位于“栈”顶
public E push(E item) {
addElement(item);
return item;
}
public class VectorTest {
public static void main(String[] args) {
Stack v = new Stack();
//依次将3个元素push入栈
v.push("疯狂Java讲义");
v.push("轻量级Java EE企业应用实战");
v.push("疯狂Android讲义");
System.out.println(v);
//访问第一个元素,但并不将其pop出栈
System.out.println(v.peek());
System.out.println(v);
System.out.println(v.pop());
System.out.println(v);
}
}
[疯狂Java讲义, 轻量级Java EE企业应用实战, 疯狂Android讲义]
疯狂Android讲义
[疯狂Java讲义, 轻量级Java EE企业应用实战, 疯狂Android讲义]
疯狂Android讲义
[疯狂Java讲义, 轻量级Java EE企业应用实战]
需要指出的是,由于Stack继承了Vector,因此它也是一个非常古老的Java集合类,它是线程安全的,性能比较差,因此现在的程序中一般比较少使用Stack类。如果程序需要使用“栈”这种数据结构,则可以考虑使用LinkedList。
LinkedList也是List的实现类,它是一个基于链表实现的List类,对于顺序访问集合中的元素进行了优化,特别是插入、删除元素时速度非常快。LinkedList既实现了List接口,也实现了Deque接口,由于实现了Deque接口,因此可以作为栈来使用。
Arrays工具类里提供了asList(T… a)方法,该方法可以把一个数组或指定个数的对象转换成一个List集合,这个List集合既不是ArrayList实现类的实例,也不是Vector实现类的实例,而是Arrays的内部类ArrayList的实例。
Arrays.Arraylist是一个固定长度的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());
for (int i=0; i<fixedList.size(); i++){
System.out.println(fixedList.get(i));
}
//试图增加、删除元素都会引发异常
fixedList.add("疯狂Android讲义");
fixedList.remove("疯狂Java讲义");
}
}
class java.util.Arrays$ArrayList
Exception in thread "main" java.lang.UnsupportedOperationException
疯狂Java讲义
轻量级Java EE企业应用实战
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
at com.sunrise.eHealth.day0612.FixedSizeList.main(FixedSizeList.java:18)
Queue用于模拟队列这种数据结构,队列通常是指先进先出(FIFO)
的容器。通常,队列不允许随机访问队列中的元素。
Queue接口中定义了如下几个方法。
将指定元素加入此队列的尾部
boolean add(E e);
获取队列头部的元素,但是不删除该元素
E element();
将指定元素加入此队列的尾部。当使用有容量限制的队列时,此方法通常比add方法更好
boolean offer(E e);
获取队列头部的元素,但是不删除该元素,如果此队列为空,则返回null
E peek();
获取队列头部的元素,并删除该元素。如果此队列为空,则返回null
E poll();
获取队列头部的元素,并删除该元素
E remove();
Queue接口有一个PriorityQueue实现类。除此之外,Queue还有一个Deque接口,Deque代表一个双端队列
,双端队列可以同时从两端来添加、删除元素,因此Deque的实现类既可当成队列使用,也可当成栈使用。Java为Deque提供了ArrayDeque和LinkedList两个实现类。
PriorityQueue是一个比较标准的队列实现类。之所以说它是比较标准的队列实现,而不是绝对标准的队列实现,是因为PriorityQueue保存队列元素的顺序并不是按加入队列的顺序,而是按队列元素的大小进行重新排序。因此当调用peek方法或者poll方法取出队列中的元素时,并不是取出最先进入队列的元素,而是取出队列中最小的元素。从这个意义上来看,PriorityQueue已经违反了队列的最基本规则:先进先出(FIFO)。
Deque接口是Queue接口的子接口,它代表一个双端队列,Deque接口里定义了一些双端队列的方法,这些方法允许从两端来操作队列的元素。
将指定元素插入该双端队列的开头
void addFirst(E e);
将指定元素插入该双端队列的末尾
void addLast(E e);
返回该双端队列对应的迭代器,该迭代器将以逆向顺序来迭代队列中的元素
Iterator<E> descendingIterator();
获取但不删除双端队列的第一个元素
E getFirst();
获取但不删除双端队列的最后一个元素
E getLast();
将指定元素插入该双端队列的开头
boolean offerFirst(E e);
将指定元素插入该双端队列的末尾
boolean offerLast(E e);
获取但不删除该双端队列的第一个元素;如果此双端队列为空,则返回null
E peekFirst();
获取但不删除该双端队列的最后一个元素;如果此双端队列为空,则返回null
E peekLast();
获取并删除该双端队列的第一个元素;如果此双端队列为空,则返回null
E pollFirst();
获取并删除该双端队列的最后一个元素;如果此双端队列为空,则返回null
E pollLast();
ArrayList和ArrayDeque两个集合类的实现机制基本相似,它们的底层都采用一个动态的,可重分配的Object[]数组来存储集合元素,当集合元素超出了该数组的容量时,系统会在底层重新分配一个Object[]数组来存储集合元素。
LinkedList类是一个List接口的实现类,这意味着它是一个List集合,可以根据索引来随机访问集合中的元素。除此之外,LinkedList还实现了Deque接口,因此它可以被当成双端队列来使用,自然也可以被当成栈来使用了。
public class LinkedListTest {
public static void main(String[] args) {
LinkedList books = new LinkedList();
//将字符串元素加入队列的尾部
books.offer("疯狂Java讲义");
//将一个字符串元素加入栈的顶部
books.push("轻量级Java EE企业应用实战");
//将字符串元素添加到队列的头部(相当于栈的顶部)
books.offerFirst("疯狂Android讲义");
for (int i=0; i<books.size(); i++){
System.out.println(books.get(i));
}
//访问但不删除栈顶的元素
System.out.println(books.peekFirst());
//访问但不删除队列的最后一个元素
System.out.println(books.peekLast());
//将栈顶的元素弹出栈
System.out.println(books.pop());
//下面输出将看到队列中第一个元素被删除
System.out.println(books);
//访问并删除队列的最后一个元素
System.out.println(books.pollLast());
//下面输出将看到队列中只剩下中间一个元素
System.out.println(books);
}
}
疯狂Android讲义
轻量级Java EE企业应用实战
疯狂Java讲义
疯狂Android讲义
疯狂Java讲义
疯狂Android讲义
[轻量级Java EE企业应用实战, 疯狂Java讲义]
疯狂Java讲义
[轻量级Java EE企业应用实战]
LinkedList与ArrayList、ArrayDeque的实现机制完全不同,ArrayList、ArrayDeque内部以数组的形式来保存集合中的元素,因此随机访问集合元素时有较好的性能;而LinkedList内部以链表的形式来保存集合中的元素,因此随机访问集合元素时性能较差,但在插入、删除元素时性能非常出色(只需要改变指针所指的地址即可)。
Java提供的List就是一个线性表接口,而ArrayList、LinkedList又是线性表的两种典型实现:基于数组的线性表和基于链的线性表。Queue代表了队列,Deque代表了双端队列(既可作为队列使用,也可作为栈使用)。
因为数组以一块连续内存区来保存所有的数组元素,所以数组在随机访问时性能最好。所有的内部以数组作为底层实现的集合在随机访问时性能较好;而内部以链表作为底层实现的集合在执行插入、删除操作时有很好的性能;进行迭代操作时,以链表作为底层实现的集合比以数组作为底层实现的集合性能好。
public class PerformanceTest {
public static void main(String[] args) {
//创建一个字符串数组
String[] tst1 = new String[900000];
//动态初始化数组元素
for (int i=0; i<900000; i++){
tst1[i] = String.valueOf(i);
}
ArrayList al = new ArrayList();
//将所有的数组元素加入ArrayList集合中
for (int i=0; i<tst1.length; i++){
al.add(tst1[i]);
}
LinkedList ll = new LinkedList();
//将所有的数组元素加入LinkedList集合中
for (int i=0; i<tst1.length; i++){
ll.add(tst1[i]);
}
//迭代访问ArrayList集合的所有元素,并输出迭代时间
long start = System.currentTimeMillis();
for (Iterator it = al.iterator(); it.hasNext();){
it.next();
}
System.out.println("迭代ArrayList集合元素的时间:" + (System.currentTimeMillis() - start));
//迭代访问LinkedList集合的所有元素,并输出迭代时间
start = System.currentTimeMillis();
for (Iterator it = ll.iterator(); it.hasNext();){
it.next();
}
System.out.println("迭代LinkedList集合元素的时间:" + (System.currentTimeMillis() - start));
}
}
迭代ArrayList集合元素的时间:31
迭代LinkedList集合元素的时间:16
由运行结果可知,迭代ArrayList集合的时间略大于迭代LinkedList集合的时间。
关于使用List集合有如下建议