单向链表
双向链表
LinkedList适用于什么场景?
适用于需要频繁插入和删除元素的场景,例如消息聊天系统,一开始并不明确有多少记录,可以在空间满足的情况下不断增加数据
LinkedList的特点有哪些?
- LinkedList的底层采用了双向链表数据结构(好处是随即增删改元素快速,不涉及到元素位移)
- 检索效率低(每次需要节点开始逐个往下进行检索,直到找到位置)
- 在空间存储上内存地址不连续
链表的优缺点有哪些?
链表的优点:
增删效率比较高。由于链表上的元素在空间存储上内存地址不连续,所以随即增删元素的时候不会有大量元素位移,只需要修改元素指针域指向的位置就行
链表的缺点:
检索效率低。不能通过数学表达式计算被查找元素的内存地址,所以每次查找的时候都是从头节点开始遍历,直到找到位置
底层原理
测试用例:
package List;
import java.util.LinkedList;
public class LinkedListTest {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
//添加元素
list.add("张三1");
list.add("张三2");
list.add("张三3");
list.add("张三4");
list.add("张三5");
//删除元素
list.remove(); //删除第一个节点
list.remove("张三2"); //删除指定元素的节点
list.remove(3); //删除指定位置的节点
list.removeFirst(); //删除第一个节点
list.removeLast(); //删除最后一个节点
//更新元素
list.set(2, "下标为3的元素更新");
//查找元素
list.indexOf("下标为3的元素更新"); //查找某个元素的位置
list.get(3); //查找指定位置的元素
}
}
添加元素——add
在LinkedList的底层维护了一个内部类Node,链表中的每一个节点就是一个一个的Node节点组成
获取last节点、创建新节点
- 判断last节点是否为空,为空说明此时并没有节点,也就不存在first和last指向任何一个节点
- 如果last节点不为空,将新节点链接到链表的尾部
删除元素——remove
①、删除第一个元素——remove()
当使用remove方法不传任何参数的时候默认是删除链表的第一个元素,需要修改的内容:
如果是空链表,直接报异常
如果不是空链表:
- 获取要删除节点的下一个节点
- 使first指向要删除节点的下一个节点
- 将要删除节点的下一个节点的prev为null(前面没有任何节点需要指向)
- 对删除节点的item、next、prev置空
②、删除指定元素的节点——remove(Object o)
按照元素内容删除,这一步涉及到遍历链表,需要去查找要删除元素所在的节点,查找之后会调用专门用于删除节点的unlink方法
③、删除指定位置的节点——remove(int index)
unlink和上述相同,不做解释
④、删除第一个节点——removeFirst
⑥、删除最后一个节点——removeLast
从上面的例子结果发现:如果删除第一第一个和最后一个节点都不需要遍历
删除第一个节点:修改first指向为第二个节点,第二个节点的前驱指针为null
删除最后一个节点:修改last指向为倒数第二个节点
更新元素——set
在执行Node<E> node(int index) 方法的时候内部会对链表长度右移一位,这是为什么?
其实是一种分治的思想,右移一位相当于链表的长度除以2,根据传入的索引号确定要更新的元素是在链表的前半部分还是后半部分,如果是在链表的前半部分则链表的后半部分不需要在进行遍历,只需要遍历前半部分就可以,能够大大提高效率。
所以我们可以发现当我们要遍历的元素月靠近链表的中间位置,遍历所需要花费的时间越长
查找元素——indexOf、get
在实际场景中如何选择是使用LinkedList还是ArrayList?
- 如果涉及到随机增删元素的业务比较多时,使用LinkedList
- 如果涉及到检索,使用ArrayList
注:不同的数据结构在某些方面发挥的效果不一样