1.基本概念
Java容器类类库的用途是“保存对象”,并将其划分为两个不同的概念。
1.Collection。一个独立元素的序列,这些元素都服从一条或多条规则。List
必须按照插入的顺序保存元素,而Set
不能有重复元素。Queue
按照排队规则来确定对象产生的顺序(通常与它们被插入的顺序相同)
2.Map。一组成对的“键值对”对象,允许你使用键来查找值。ArrayList
允许你使用数字来查找值,因此在某种意义上,它将数字与对象关联在了一起。而Map
允许我们使用另外一个对象来查找对象,我们称之为“关联数组”,或者与在Python中类似,也可称之为“字典”。
容器分类图如下所示:
2.List
List
承诺可以将元素维护在特定的序列中。List
是Collection
的一个子接口,它在Collection
的基础上增加了大量的方法,使得可以在List
的中间插入和移除元素。
有两种类型的List
:
- 基本的ArrayList
,它长于随机访问元素,但是在ArrayList
的中间插入和移除元素时较慢。
- LinkedList
,它通过代价较低的在List中间进行的插入和删除操作,提供了优化的顺序访问。LinkedList
在随机访问方面相对比较慢,但是它的特性集较ArrayList
更大。
List
接口中定义的一些常用的抽象方法有:
序号 | 方法 | 描述 |
---|---|---|
1 | boolean add(E e) | 将指定的元素添加到此列表的尾部 |
2 | void add(int index, E element) | 将指定的元素插入此列表中的指定位置 |
3 | boolean addAll(Collection<? extends E> c) | 按照指定 collection 的迭代器所返回的元素顺序,将该 collection 中的所有元素添加到此列表的尾部 |
4 | boolean addAll(int index, Collection<? extends E> c) | 从指定的位置开始,将指定 collection 中的所有元素插入到此列表中 |
5 | E set(int index, E element) | 用指定的元素替代此列表中指定位置上的元素 |
6 | E get(int index) | 返回此列表中指定位置上的元素 |
7 | int indexOf(Object o) | 返回此列表中首次出现的指定元素的索引,或如果此列表不包含元素,则返回 -1 |
8 | int lastIndexOf(Object o) | 返回此列表中最后一次出现的指定元素的索引,或如果此列表不包含索引,则返回 -1 |
9 | E remove(int index) | 移除此列表中指定位置上的元素 |
10 | boolean remove(Object o) | 移除此列表中首次出现的指定元素(如果存在) |
11 | boolean removeAll(Collection<?> c) | 从列表中移除指定 collection 中包含的其所有元素 |
12 | void clear() | 移除此列表中的所有元素 |
13 | boolean isEmpty() | 如果此列表中没有元素,则返回 true |
14 | int size() | 返回此列表中的元素数 |
15 | Object[] toArray() | 按适当顺序(从第一个到最后一个元素)返回包含此列表中所有元素的数组 |
16 | List<E> subList(int fromIndex, int toIndex) | 返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图 |
17 | boolean retainAll(Collection<?> c) | 仅在列表中保留指定 collection 中所包含的元素(可选操作) |
18 | Iterator<E> iterator() | 返回按适当顺序在列表的元素上进行迭代的迭代器 |
19 | ListIterator<E> listIterator() | 返回此列表元素的列表迭代器 |
20 | ListIterator<E> listIterator(int index) | 返回列表中元素的列表迭代器,从列表的指定位置开始 |
也就是说,List
接口的两个实现类ArrayList
和LinkedList
中,这些方法都是有的。
2.1 ArrayList
通过查看源代码,可以看到ArrayList
底层采用数组实现,并且默认的初始长度为10。由于通过数组实现,故ArrayList
长于随机访问也就不难理解了。
2.1.1 构造方法
ArrayList
中的常用方法与List
接口中定义的方法基本一致。它有3个不同的构造方法,如下:
序号 | 构造方法 | 描述 |
---|---|---|
1 | public ArrayList() | 构造一个初始容量为 10 的空列表 |
2 | public ArrayList(int initialCapacity) | 构造一个具有指定初始容量的空列表。 |
3 | ArrayList(Collection<? extends E> c) | 构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。 |
2.1.2 ArrayList的遍历方式
(1)随机访问,通过索引值去遍历
由于ArrayList
实现了RandomAccess
接口,它支持通过索引值去随机访问元素。
ArrayList<Integer> array = new ArrayList<>();
array.addAll(Arrays.asList(1, 2, 3));
for (int i = 0; i < array.size(); i++) {
System.out.println(array.get(i));
}
(2)foreach循环
ArrayList<Integer> array = new ArrayList<>();
array.addAll(Arrays.asList(1, 2, 3));
for(Integer item:array){
System.out.println(item);
}
(3)迭代器
1.Iterator
通常意义上的Iterator
,对Collection
进行迭代的迭代器。它也是一个对象,它的工作时遍历并选择序列中的对象,而程序员不必知道或关心该序列底层的结构。
Java的Iterator
只能单向移动,这个Iterator
只能用来:
1.使用方法
iterator()
要求容器返回一个Iterator
。Iterator
将准备好返回序列的第一个元素。
2.使用next()
获得序列的下一个元素。
3.使用hasNext()
检查序列中是否还有元素。
4.使用remove()
将迭代器新近返回的元素删除。
使用迭代器遍历ArrayList
如下:
ArrayList<Integer> array = new ArrayList<>();
array.addAll(Arrays.asList(1, 2, 3));
Iterator<Integer> iterator = array.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
2.ListIterator
ListIterator
是一个更加强大的Iterator
的子类型,它只能用于各种List
类的访问。Iterator
只能单向移动,但是ListIterator
可以双向移动。它还可以产生相对于迭代器在列表中指向的当前的前一个和后一个元素的索引,并且可以使用set()
方法替换它访问过的最后一个元素。我们可以通过调用listIterator()
方法产生一个指向List
开始位置的ListIterator
,并且还可以通过调用listIterator(n)
方法创建一个一开始就指向列表索引为n
的元素处的ListIterator
。
ArrayList<Integer> array = new ArrayList<>();
array.addAll(Arrays.asList(1, 2, 3));
// 先从前向后遍历
ListIterator<Integer> listIt = array.listIterator();
while(listIt.hasNext()){
System.out.println(listIt.next()+", "+listIt.previousIndex()+", "+listIt.nextIndex());
}
System.out.println();
// 再从后向前遍历
while(listIt.hasPrevious()){
System.out.println(listIt.previous());
}
2.2 LinkedList
LinkedList
也像ArrayList
一样实现了基本的List
接口,但是它底层采用链表实现,所以执行插入和删除的操作比ArrayList
更高效,在随机访问操作方面要逊色一些。
LinkedList
还添加了可以使其用作栈、队列或者双端队列的方法。这些方法中有些彼此之间只是名称有些诧异,或者只存在些许诧异。比如:
- (1)
getFirst()
和element()
完全一样,它们都返回列表的头(第一个元素),并不移除它,如果List为空,则抛出NoSuchElementException
。peek()
方法与这两个方式稍有诧异,它在列表为空时返回null
。 - (2)
removeFirst()
和remove()
也是完全一样的,它们移除并返回列表的头,而在列表为空时抛出NoSuchElementException
。poll()
稍有差异,它在列表为空时返回null
。 - (3)
add()
和addLast()
相同,它们都将某个元素插入到列表的尾部。
我个人的习惯,不管是将LinkedList
作为队列Queue
来使用,还是作为栈Stack
来使用,我都使用getFirst()
、getLast()
、addFirst()
、addLast()
、removeFirst()
和removeLast()
六个方法来实现相关操作,因为这些方法不仅好记,而且意义很明显,可以根据自己需要搭配使用。
另外,LinkedList
的遍历方式与ArrayList
完全一致,这里不再赘述。
对于另外的两个List
的实现类:Vector
和Stack
,根据《Think in Java》中所述,我们在现在的新程序中,应该抛弃这两个过时的类。
但是要知道的是,Vector
非常类似于ArrayList
,两者的用法几乎一致,但是Vector
是线程安全的,而ArrayList
是非线程安全的。