1. 介绍
- 实现List、Deque接口,内部使用双向链表实现的列表集合,允许存储任意类型的元素(包括null)。
- 和ArrayList一样都是
不同步
的,注意多线程环境下保证在外部同步。
和ArrayList一样,也实现了Cloneable、Serializable接口,用于标记可被克隆和序列化。并实现了List接口,表示具备List集合的规范。
和ArrayList不同的是,LinkedList实现了Deque接口,这是一个双向列表,即从该列表的两端都可以进行增删操作。
2. 字段
头节点和尾节点都是一个Node对象,Node是LinkedList的一个内部类,用于记录当前节点存储的元素和记录上个和下个节点。用于构建一条双向链表。
所以LinkedList内部就是使用这种方式构成的双向链表。
3. 构造器
- 无参构造
什么都没有。。
- 参数为集合的构造器
addAll()的具体实现等后面添加元素的时候加。
我们可以看到,LinkedList构造器在初始化的时候没做什么初始化操作,这是因为它内部是由链表实现的,不需要刻意指定一段连续的内存来存储,它内部两个节点直接通过next、prev的指向来确定的。
4. 添加元素(重点)
在遇到链表相关的操作的时候,最好可以自己画个图辅助理解(上学的时候老师教的)
4.1. 添加到链表尾部
这两个方法都可以将元素插入到链表的尾部,区别是一个有返回值,一个没返回值。
具体操作我们来看linkLast()方法。
分别讨论,假设是第一次添加元素,最简单就是初始化node节点并把该节点设为头节点和尾节点。
如果不是第一次添加元素,我们来画个图看一下过程:
4.2. 添加到链表头部
和添加到链表尾部基本一致
4.3. 添加到指定位置
注意:这里因为需要将新节点插入到两个节点之间,所以双向链表需要有4条链。
我们来看下如何获取到指定索引位置的节点:
4.4. 将集合添加到链表中
这里和将元素添加到指定索引位置基本相似。
如果我们将需要添加的整个集合看作1个节点,则只需要考虑“3个节点”绑定双链即可。
再解决如何将集合链在一起。
5. 删除元素
5.1. 删除最后一个元素
5.2. 删除第一个元素
总结:删除头节点时,看头节点是否有下一个节点,有的话设为头节点,否则链表为空。
5.3. 删除指定索引位置的元素
5.4. 删除指定元素
上述方法删除的是第一次出现的的节点。如果需要删除最后一次出现的,可以使用removeLastOccurrence()方法。
6. 修改元素
7. 查找元素
7.1. 根据索引查找节点
7.2. 根据节点查找索引
- 第一个出现的
- 最后一个出现的
8. 遍历
8.1. 普通for循环
LinkedList list = new LinkedList();
list.add(1);
list.add(2);
list.add(3);
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
8.2. 迭代器 & 增强for循环
LinkedList list = new LinkedList();
list.add(1);
list.add(2);
list.add(3);
Iterator it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
注:这里的迭代器实现方式和ArrayList的基本相同。
区别是获取节点的时候:ArrayList通过数组索引,LinkedList通过记录下个节点。
关于增强for循环,我们知道增强for循环的底层实现就是通过创建迭代器。
LinkedList list = new LinkedList();
list.add(1);
list.add(2);
list.add(3);
for (Object o : list) {
System.out.println(o);
}
8.3. 效率对比
LinkedList list = new LinkedList();
for (int i = 0; i < 100000; i++) {
list.add(i);
}
long b1 = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
list.get(i);
}
System.out.println("普通for耗时:" + (System.currentTimeMillis() - b1));
long b2 = System.currentTimeMillis();
Iterator it = list.iterator();
while (it.hasNext()) {
it.next();
}
System.out.println("迭代器耗时:" + (System.currentTimeMillis() - b2));
我们发现耗时差距极大,那么这种问题是怎么导致的呢?我们来分析一下。
在上面我们知道了、在普通for循环中使用get(index)获取元素的时候,是需要将其前面的所有元素都要走一遍的。
而使用迭代器,是使用游标记录位置,用next记录记录下个节点,也就是不会再访问前面的元素。
所以,差距才会这么大。