List集合介绍
List集合概述
List集合是一个元素有序(每个元素都有对应的顺序索引,第一个元素索引为0)、且可重复的集合。
List集合常用方法
List是Collection接口的子接口,拥有Collection所有方法外,还有一些对索引操作的方法。
void add(int index, E element);
:将元素element插入到List集合的index处;boolean addAll(int index, Collection<? extends E> c);
:将集合c所有的元素都插入到List集合的index起始处;E remove(int index);
:移除并返回index处的元素;int indexOf(Object o);
:返回对象o在List集合中第一次出现的位置索引;int lastIndexOf(Object o);
:返回对象o在List集合中最后一次出现的位置索引;E set(int index, E element);
:将index索引处的元素替换为新的element对象,并返回被替换的旧元素
;E get(int index);
:返回集合index索引处的对象;List<E> subList(int fromIndex, int toIndex);
:返回从索引fromIndex(包含)到索引toIndex(不包含)所有元素组成的子集合;void sort(Comparator<? super E> c)
:根据Comparator参数对List集合元素进行排序;void replaceAll(UnaryOperator<E> operator)
:根据operator指定的计算规则重新设置集合的所有元素。ListIterator<E> listIterator();
:返回一个ListIterator对象,该接口继承了Iterator接口,在Iterator接口基础上增加了以下方法,具有向前迭代功能且可以增加元素:
bookean hasPrevious()
:返回迭代器关联的集合是否还有上一个元素;
E previous();
:返回迭代器上一个元素;
void add(E e);
:在指定位置插入元素;
示例
1)运行主类
public class DemoApplication {
public static void main(String[] args) {
List<String> list = new ArrayList();
list.add(new String("book001"));
list.add(new String("book002"));
list.add(new String(" book003 "));
System.out.println("原列表:" + list);
//将新字符串插入第二个位置
list.add(1, new String("newBook002"));
System.out.println("新增第二个位置元素后列表:" + list);
//删除第三个元素
list.remove(2);
System.out.println("删除第三个元素后列表:" + list);
//判断指定元素在List集合的位置
System.out.println("判断newBook002的位置:" + list.indexOf(new String("newBook002")));
//将第二元素替换新的字符串
System.out.println("替换的旧值:" + list.set(1, new String("book002")));
System.out.println("替换第二个元素后的列表:" + list);
//返回第二个元素
System.out.println("回第二个元素:" + list.get(1));
List<String> newList = new ArrayList<>();
newList.add("book001");
newList.add("book004");
newList.add("book002");
//新增集合
list.addAll(1, newList);
System.out.println("新增一个集合后的列表:" + list);
//返回元素最后一次出现的位置索引
System.out.println("返回"book001"最后一次出现的位置:" + list.lastIndexOf("book001"));
//截取子集合
System.out.println("返回一个范围子集合列表:" + list.subList(0, 3));
list.sort(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
//逆序
return o2.compareTo(o1);
}
});
//lambda表达式输出
list.forEach(book -> System.out.println(book));
list.replaceAll(String::trim);
System.out.println("replaceAll去除两端空格" + list);
list.replaceAll(t -> t.replace("book00", "书籍系列"));
System.out.println("replaceAll替换字符串:" + list);
System.out.println("正向迭代输出:");
ListIterator listIterator = list.listIterator();
while (listIterator.hasNext()) {
System.out.println(listIterator.next());
//添加元素,会影响list元素
listIterator.add("book");
}
System.out.println("反向迭代输出:");
while(listIterator.hasPrevious()) {
System.out.println(listIterator.previous());
}
System.out.println(list);
}
}
2)运行结果:
原列表:[book001, book002, book003 ]
新增第二个位置元素后列表:[book001, newBook002, book002, book003 ]
删除第三个元素后列表:[book001, newBook002, book003 ]
判断newBook002的位置:1
替换的旧值:newBook002
替换第二个元素后的列表:[book001, book002, book003 ]
回第二个元素:book002
新增一个集合后的列表:[book001, book001, book004, book002, book002, book003 ]
返回"book001"最后一次出现的位置:1
返回一个范围子集合列表:[book001, book001, book004]
book004
book002
book002
book001
book001
book003
replaceAll去除两端空格[book004, book002, book002, book001, book001, book003]
replaceAll替换字符串:[书籍系列4, 书籍系列2, 书籍系列2, 书籍系列1, 书籍系列1, 书籍系列3]
正向迭代输出:
书籍系列4
书籍系列2
书籍系列2
书籍系列1
书籍系列1
书籍系列3
反向迭代输出:
book
书籍系列3
book
书籍系列1
book
书籍系列1
book
书籍系列2
book
书籍系列2
book
书籍系列4
[书籍系列4, book, 书籍系列2, book, 书籍系列2, book, 书籍系列1, book, 书籍系列1, book, 书籍系列3, book]
从上述运行结果看出,System.out.println("判断newBook002的位置:" + list.indexOf(new String("newBook002")));
我们重新new一个"newBook002"进行判断索引位置时,还是可以返回索引位置,List集合判断两个对象相当只通过equals()
方法,所以如果重写对象的equals()
方法都是true,则存入List集合中的对象其实都是相等的。
ArrayList
ArrayList概述
ArrayList 是一个数组队列,相当于动态数组。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了List
, RandomAccess
(随机访问), Cloneable
(克隆), java.io.Serializable
(可序列化)这些接口。
ArrayList 继承了AbstractList
,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
ArrayList 实现了RandmoAccess
接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问
。
ArrayList 实现了Cloneable
接口,即覆盖了函数clone()
,能被克隆。
ArrayList 实现java.io.Serializable
接口,这意味着ArrayList支持序列化
,能通过序列化去传输。
和Vector不同,ArrayList中的操作不是线程安全
的!所以,建议在单线程
中才使用ArrayList
,而在多线程
中可以选择Vector
或者CopyOnWriteArrayList
。
ArrayList源码解析
- ArrayList包含了两个重要的对象:
elementData
和size
。
elementData
是"Object[]类型的数组",它保存了添加到ArrayList中的元素。实际上,elementData是个动态数组,我们能通过构造函数 ArrayList(int initialCapacity)来执行它的初始容量为initialCapacity;如果通过不含参数的构造函数ArrayList()来创建ArrayList,则elementData的容量默认是10。elementData数组的大小会根据ArrayList容量的增长而动态的增长
size
则是动态数组的实际大小。
// 默认初始化容量为10
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
// DEFAULTCAPACITY_EMPTY_ELEMENTDATA默认为空数组
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 才开始构造的时候是一个空list,只有当第一个元素add的时候,扩展到DEFAULT_CAPACITY值,即长度为10.
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
// 构造成10长度的空list;
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
- ArrayList 实际上是通过一个数组去保存数据的。当我们构造ArrayList时;若使用默认构造函数,则ArrayList的默认容量大小是
10
。 - 当ArrayList容量不足以容纳全部元素时,ArrayList会重新设置容量:
新的容量=“(原始容量x3)/2 + 1
”。 - ArrayList的克隆函数,即是将全部元素克隆到一个数组中。
- ArrayList实现
java.io.Serializable
的方式。当写入到输出流时,先写入“容量”,再依次写入“每一个元素”;当读出输入流时,先读取“容量”,再依次读取“每一个元素”。
ArrayList的遍历方式
3种方式
- 第一种,通过迭代器遍历。即通过
Iterator
去遍历。
Integer value = null;
Iterator iter = list.iterator();
while (iter.hasNext()) {
value = (Integer)iter.next();
}
- 第二种,
随机访问index
,通过索引值去遍历。
由于ArrayList实现了RandomAccess接口,它支持通过索引值去随机访问元素。
Integer value = null;
int size = list.size();
for (int i=0; i<size; i++) {
value = (Integer)list.get(i);
}
- 第三种,
增强for循环遍历
。如下:
Integer value = null;
for (Integer integ:list) {
value = integ;
}
遍历ArrayList时,使用随机访问(即,通过索引序号访问)效率最高,而使用迭代器的效率最低
LinkedList
LinkedList概述
LinkedList 是一个继承于AbstractSequentialList
的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。LinkedList的本质是双向链表。1)LinkedList继承于AbstractSequentialList,并且实现了Dequeue接口。2) LinkedList包含两个重要的成员:header 和 size。
header
是双向链表的表头,它是双向链表节点所对应的类Entry的实例。Entry中包含成员变量:previous, next, element。(其中,previous
是该节点的上一个节点,next
是该节点的下一个节点,element
是该节点所包含的值。)
size
是双向链表中节点的个数。
LinkedList 实现 List接口
,能对它进行队列操作。
LinkedList 实现 Deque接口
,即能将LinkedList当作双端队列使用。
LinkedList 实现了Cloneable接口
,即覆盖了函数clone()
,能克隆。
LinkedList 实现java.io.Serializable接口
,这意味着LinkedList支持序列化
,能通过序列化去传输。
LinkedList 是非同步的
。(若要实现同步 List list = Collections.synchronizedList(new LinkedList(...))
;)
LinkedList源码分析
- 访问性
LinkedList实际上是通过双向链表
去实现的。既然是双向链表,那么它的顺序访问会非常高效
,而随机访问效率比较低
。 - 根据索引值操作
既然LinkedList是通过双向链表的,但是它也实现了List接口,也就是说,它实现了get(int index)
、remove(int index)
等根据索引值来获取、删除节点的函数。 - LinkedList是如何实现List的这些接口的,如何将双向链表和索引值联系起来的?其实,它是通过一个
计数索引值
来实现的。例如,当程序调用get(int index)
方法时,首先会比较location
和双向链表长度的1/2
;如果前者大,则从链表头开始向后
查找,直到location位置
;否则,从链表末尾开始向前
查找,直到location位置
。
总结:
- LinkedList 实际上是通过
双向链表
去实现的。包含一个非常重要的内部类:Entry
。Entry是双向链表节点所对应的数据结构,它包括的属性有:当前节点所包含的值
,上一个节点
,下一个节点
。 - LinkedList的克隆函数,即是将全部元素克隆到一个新的LinkedList对象中。
- LinkedList实现
java.io.Serializable
。当写入到输出流时,先写入“容量”,再依次写入“每一个节点保护的值”;当读出输入流时,先读取“容量”,再依次读取“每一个元素”。 - 由于LinkedList实现了
Deque
,而Deque接口定义了在双端队列两端访问元素的方法。提供插入、移除和检查元素的方法。每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个特殊值(null 或 false,具体取决于操作)。
LinkedList遍历方式
支持多种遍历方式。建议不要采用随机访问的方式去遍历LinkedList,而采用逐个遍历的方式。
- 第一种,通过迭代器遍历。即通过
Iterator
去遍历。
for(Iterator iter = list.iterator(); iter.hasNext();)
iter.next();
- 通过
快速随机index
访问遍历LinkedList
int size = list.size();
for (int i=0; i<size; i++) {
list.get(i);
}
- 通过另外一种
增强版for循环
来遍历LinkedList
for (Integer ele: list) {
}
- 通过
pollFirst()
来遍历LinkedList,获取并移除此列表的第一个元素
;如果此列表为空,则返回 null
while(list.pollFirst() != null){
}
- 通过
pollLast()
来遍历LinkedList,获取并移除此列表的最后一个元素
;如果此列表为空,则返回 null。
while(list.pollLast() != null) {
}
- 通过
removeFirst()
来遍历LinkedList,移除并返回此列表的第一个元素
。 NoSuchElementException - 如果此列表为空。
try {
while(list.removeFirst() != null) {
}
} catch (NoSuchElementException e) {
}
- 通过
removeLast()
来遍历LinkedList,移除并返回此列表的最后一个元素
。NoSuchElementException - 如果此列表为空。
try {
while(list.removeLast() != null) {
}
} catch (NoSuchElementException e) {
}
QA
ArrayList底层动态扩容的原理?
ArrayList底层采用数组实现,当使用不带参数的构造方法生成ArrayList对象时,底层实际会生成一个长度为10
的Object类型数组
,如果增加的元素个数超过10个,则ArrayList底层会新生成一个数组,长度为原数组的1.5倍+1
,然后将原数组的内容复制到新数组中去,兵器后续增加的内容都会放入新数组中,当新数组无法容纳新元素时,又会重复上述步骤。
ArrayList和LinkedList的区别?
- 底层实现:ArrayList实现是基于动态数组的数据结构(新建一个数组进行扩容,然后copy原来数组中内容,实现数组可增长);LinkedList是基于双向链表的数据结构,其每个对象除了数据本身外,还有两个引用,分别指向前一个元素和后一个元素。
- 查询:对于随机访问get和set,ArrayList支持;LinkedList不支持,因为LinkedList要移动指针。
- 增删:对于新增和删除操作add和remove,在ArrayList的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的。
- 应用场景:ArrayList适合一列数据的后面添加数据而不是在前面或中间,且需要随机访问元素;LinkedList适合在一列数据的前面或中间添加或删除数据,且按照顺序访问其中的元素。
- 消耗内存:LinkedList比ArrayList消耗更多的内存,因为LinkedList中的每个节点都存储前后节点的引用。(双向链表)
ArrayList和Vector的区别?
- 线程安全性:ArrayList是非线程安全的,Vector是线程安全的,如果需要再迭代的时候对列表进行改变,使用CopyOnWriteArrayList。
- 效率:ArrayList是非同步的,效率高;Vector是同步的,效率低;
ArrayList和CopyOnWriteArrayList的区别
- 和ArrayList继承于AbstractList不同,CopyOnWriteArrayList没有继承于AbstractList,它仅仅只是实现了List接口。
- ArrayList的iterator()函数返回的Iterator是在AbstractList中实现的;而CopyOnWriteArrayList是自己实现Iterator。
- ArrayList的Iterator实现类中调用next()时,会“调用checkForComodification()比较'expectedModCount'和'modCount'的大小”;但是,CopyOnWriteArrayList的Iterator实现类中,没有所谓的checkForComodification(),更不会抛出ConcurrentModificationException异常!
Iterater和ListIterator区别
- 遍历目标:可以使用Iterator来遍历Set和List集合;而ListIterator只能遍历List。
- 遍历方向:Iterator只可以后向顺序遍历;而ListIterator可以双向遍历。
- 功能区别:ListIterator从Iterator接口继承,然后添加了一些额外的功能,比如添加一个元素、替换一个元素、获取前面或后面元素的索引位置。