一:先看看集合框架接口图
(图片来源于网络)
从图中可以看到List实现了Collection接口。
二:Collection接口是什么?
在java类库中,Collection接口是集合类的基本接口,这个接口有两个基本的方法:
public interface Collection<E> extends Iterable<E>
{
boolean add(E element);
Iterator<E> iterator();
...
}
add方法用于向集合中添加元素。如果添加元素确实改变了集合就返回true,如果集合没有发生改变就返回false。列如,向一个 集合中添加一个已经存在的对象,这个添加请求就没有效果会返回false,因为Set集合不允许重复对象。从代码中可以看到, Collection接口还包括了一个iterator()方法,返回类型为Iterator接口对象,那么什么Iterator接口?
Collection接口中的其它方法:
//添加方法:
add(Object o) //添加指定元素
addAll(Collection c) //添加指定集合
//删除方法:
remove(Object o) //删除指定元素
removeAll(Collection c) //输出两个集合的交集
retainAll(Collection c) //保留两个集合的交集
clear() //清空集合
//查询方法:
size() //集合中的有效元素个数
toArray() //将集合中的元素转换成Object类型数组
//判断方法:
isEmpty() //判断是否为空
equals(Object o) //判断是否与指定元素相同
contains(Object o) //判断是否包含指定元素
containsAll(Collection c) //判断是否包含指定集合
List接口中的方法
//添加方法:
add(int index, Object o) //向指定位置添加元素
addAll(int index, Collection c) //向指定位置添加集合
//删除方法
remove(int index) //删除指定元素
//查询方法:
get(int index) //获取指定位置的元素
indexOf(Object o) //获取指定元素的位置
lastIndexOf(Object o) //获取指定元素最后一次出现的位置
//修改方法:
subList(int fromIndex, int toIndex) //截取子集合从fromIndex到toIndex,要头不要尾
set(int index, Object o) //修改指定位置的元素
三:迭代器
翻看Iterator接口源码
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
很明显的可以看到,这个Iterator接口就像当于链表中的结点,只不过在C语言里结点里的指针在java中变成了对象的引用。那么 我们就应该知道,通过反复的调用nest()就可以逐个的访问集合中的每个元素,但是当到达了集合的末尾,nest方法将抛出一个NoSuchElementException。因此,每次都用next方法前都应该调用hasNext方法进行判断。hasNext方法的作用是判断对象是否还有下一个元素,有就返回true,否则返回false。remove方法会删除上次调用next方法时返回的元素。就像是删除一个元素之前先看下它是很有必要的,remove方法就是按照这个理念设计的。举一个访问集合中所有元素的案例:
Collection<String> s = new ArrayList<String>();
s.add("xiaohong");
s.add("xionming");
s.add("wanger");
Iterator<String> iterator = s.iterator();
while(iterator.hasNext()) {
String element = iterator.next();
System.out.println(element);
}
在Java SE8版本中,新加入了for each循环遍历,编译器简单地将“for each”循环翻译为带有迭代器的循环。
for (String string : s)
{
System.out.println(string);
}
显然,通过"for each"遍历使得代码更加简洁,所有实现了Iterable接口的对象都可以使用"for each"循环。Collection接口扩展了Iterable接口。
四:具体的实现类
1.AbstractList
如果实现了Collection接口的每一个类都要实现它的所有方法,那么将是一件很烦的事情。此时,AbstractList应运而生。
它将基础的iterator抽象化,其它的方法给实现了,此时一个具体的集合类就可以扩展AbstractList类,并且只需提供Iterator方法,当然如果不满意AbstractList类实现的方法也可以在子类重写它的方法。
2.ArrayList
ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacity
操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。
它继承于 AbstractList,实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口。
在我们学数据结构的时候就知道了线性表的顺序存储,插入删除元素的时间复杂度为O(n),求表长以及增加元素,取第 i 元素的时间复杂度为O(1)
- ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
- ArrayList 实现了RandomAccess 接口, RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持快速随机访问的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。
- ArrayList 实现了Cloneable 接口,即覆盖了函数 clone(),能被克隆。
- ArrayList 实现java.io.Serializable 接口,这意味着ArrayList支持序列化,能通过序列化去传输。
和 Vector 不同,ArrayList 中的操作不是线程安全的!所以,建议在单线程中才使用 ArrayList,而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList。
ArrayList中特有的方法
ensureCapacity(int minCapactiy) //判断当前数组中的元素个数是否大于指定的minCapacity
trimToSize() //修改数组容量为当前数组有效元素个数
3.LinkedList
LinkedList是一个实现了List接口和Deque接口的双端链表。 LinkedList底层的链表结构使它支持高效的插入和删除操作,另外它实现了Deque接口,使得LinkedList类也具有队列的特性; LinkedList不是线程安全的,如果想使LinkedList变成线程安全的,可以调用静态类Collections类中的synchronizedList方法.。
需要提一下的是,LinkedList类中有一个ListIterator<E> listIterator方法,listIterator接口中包含一个add方法:
public interface ListIterator<E> extends Iterator<E> {
boolean hasNext();
E next();
boolean hasPrevious();
void set(E e);
void add(E e);
}
因为链表是一个有序的集合,每个对象的位置就显得十分重要。LinkedList中的add方法只能将对象添加到链表尾部,而经常却要将对象添加到链表的中间,迭代器就是用于描述集合中的位置的,所以这种依赖位置的方法就交由迭代器来完成。因为Set集合是无序的,所以在Iterator接口中就没有add方法,而是扩展了一个LinkIterator接口来实现。
值得一提的是:大家都知道,链表是不支持快速随机访问的。如果要查看链表中的第n个元素,就必须从头开始,越过n-1个元素,没有捷径可走,但尽管如此,LinkedList还是提供了一个用来访问某个特定元素的get方法,当然这个方法的效率并不高,如果在使用这个方法,那么可能对于所要解决的问题使用了错误的数据结构。LinkedList类中get方法所谓的随机访问都是需要从列表的头部开始搜索,效率极低。使用链表的唯一理由是尽可能的减少在链表中间插入或删除元素所付出的代价。
LinkedList中特有的方法
//查询方法:
getFirst() //获取集合中的第一个元素
getLast() //获取集合中的最后一个元素
//添加方法:
addFirst(Object o) //在集合的第一个位置添加指定元素
addLast(Object o) //在集合的最后一个位置添加指定元素
//删除方法:
removeFirst() //删除集合中的第一个元素
removeLast() //删除集合中的最后一个元素
下面程序简单的创建了两个链表,将它们合并在一起,然后从第二个链表中每隔一个元素删除一个元素,最后测试removeAll()方 法 :
package listdemo;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
public class LinkedListTest {
public static void main(String[] args) {
List<String> a = new LinkedList<>();
a.add("aaa");
a.add("bbb");
a.add("eee");
List<String> b = new LinkedList<>();
b.add("AAA");
b.add("BBB");
b.add("EEE");
ListIterator<String> aIter = a.listIterator();
ListIterator<String> bIter = b.listIterator();
//a集合合并b集合
while(bIter.hasNext()) {
if(aIter.hasNext())
aIter.next();
aIter.add(bIter.next());
}
System.out.println(a);
//从b链表中每间隔一个元素删除一个元素
while(bIter.hasNext()) {
bIter.next();//跳过一个元素
if(bIter.hasNext()) {
bIter.next();
bIter.remove();//先查后删
}
}
System.out.println(b);
//测试删除所有
a.removeAll(a);
System.out.println(a);
}
}
五:最后来看看ArrayList与LinkedList的区别
-
1. 是否保证线程安全:
ArrayList
和LinkedList
都是不同步的,也就是不保证线程安全; -
2. 底层数据结构:
Arraylist
底层使用的是Object
数组;LinkedList
底层使用的是 双向链表 数据结构(JDK1.6之前为循环链表,JDK1.7取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!) -
3. 插入和删除是否受元素位置的影响: ①
ArrayList
采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e)
方法的时候,ArrayList
会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element)
)时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ②LinkedList
采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)而数组为近似 O(n)。 -
4. 是否支持快速随机访问:
LinkedList
不支持高效的随机元素访问,而ArrayList
支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index)
方法)。 - 5. 内存空间占用: ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体 现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。
推荐阅读:
参考引用: