Java集合框架为不同的集合类型定义了大量接口,如下图所示:
集合框架的接口如下图所示:
集合框架中与List接口有关的类如下图所示:
本文中我们来学习LinkedList和ArrayList的相关知识。
由上图可以看出:在集合框架中,除了以Map结尾的类之外,其他类都实现了Collection接口,而以Map结尾的类实现了Map接口。
List接口的常用实现有LinkedList和ArrayList。
LinkedList的功能可以简述为:可以在任意位置高效插入和删除的一个有序序列。
ArrayList的功能可以简述为:可以动态增长和缩减的一个索引序列。
ArrayList
以往当人们需要处理一些类型相同的数据时,就会使用到数组,但是数组在使用之前就必须声明其大小,对于未知大小的数据,使用数组是不合适的;因此,Java提供了ArrayList类以弥补传统数组的缺陷。
ArrayList的底层是基于数组实现的,因此它支持提供一个索引来进行快速的随机访问;不过,数组和数组列表都有一个重大的缺陷。就是从数组中间删除或新增一个元素的开销很大,其原因是数组中位于被删除元素之后的所有元素都要向数组的前端移动或位于新增元素之后的所有元素都要向数组的后端移动。
需要注意的是:ArrayList和LinkedList都是List接口的实现类,但二者的特点却大不相同:
- ArrayList可以随机访问,但是增删慢;
- LinkedList增删快,但随机访问很慢;
因此,在使用List接口声明对象变量时,要注意实现是ArrayList还是LinkedList。
为了避免对链表执行随机访问操作,Java1.4引入了一个标记接口RandomAccess,这个接口不包含任何方法,不过可以用它来测试一个特定的集合是否支持高效的随机访问:
if(c instanceof RandomAccess){
//进行随机存取操作
}else{
//进行连续存取操作
}
if(c instanceof RandomAccess)
这句话的含义是:如果c支持高效的的随机访问。
LinkedList
在Java程序设计语言中,所有的链表实际上都有双向链接,即每个链接还存储着其前驱的引用。
从链表中间删除一个元素是一个很轻松的操作,只需要更新所删除元素周围的链接即可。
关于LinkedList增加元素的方法,与ArrayList有一些区别。LinkedList.add
方法会将对象添加到链表的尾部。
但是在实际中,往往需要将元素添加到链表的中间。因为迭代器描述了集合中的位置,所以这时就必须通过迭代器来向链表中间添加元素。
需要注意的是:
Java中提供了一个子接口ListIterator,ListIterator接口扩展了Iterator接口,ListIterator接口中包含了一个add方法,可以通过这个方法将元素添加到LinkedList集合中的指定位置。
例如:
var staff = new LinkedList<String>();
staff.add("a");
staff.add("b");
staff.add("c");
//此时链表中元素的顺序为:a b c
//接下来获得staff的迭代器
ListIterator<String> iter = staff.listIterator();
iter.next();//翻过第一个元素,此时迭代器在第一个元素和第二个元素之间
iter.add("x");//在迭代器位置之前添加一个元素
//此时链表中元素的顺序为:a x b c
通过迭代器操作时要注意,add方法将会在迭代器位置之前添加一个新对象。如果使用一个刚由listIterator
方法返回并指向链表表头的迭代器来调用add方法时,新添加的对象将变成新表头。
ListIterator中还有反向遍历链表的方法:
E previous();//对应next,迭代器调用此方法会向前移动一位,并返回刚才越过的元素
boolean hasPrevious();//对应hasNext,判断当前位置是否还有前驱
在调用next之后,remove操作会删除迭代器左侧的元素;但是,如果调用了previous方法,remove操作会删除迭代器右侧的元素。
大致可以理解为:add方法只依赖迭代器的位置,而remove方法不同,它依赖于迭代器的状态。