LinkedList的底层是基于双向链表实现的,所以插入和删除效率较高,但是不易查找(因为链表在内存中存放的地址是不连续的)。
LInkedList中数据的个数。
头节点。
尾节点
构造方法:
一个无参构造方法,一个有参构造方法。有参构造方法传入一个集合,在这个方法体内部可以看到先调用了无参构造方法,然后使用addAll()方法将集合的数据添加到链表中。
添加元素
add()
add方法体内部只调用了linkLast()方法。linkLast()方法如下。
linkLast()
(这里有些啰嗦,但是很清晰,后面的头插法可以参考这里的解释自己理解更加深刻)
linkLast()方法就是将数据插入到链表的尾部。
创建一个新的节点类对象 l 存放当前链表的尾节点last,
创建一个新的节点类对象 newNode 存放传进的元素e,将它的前一个节点设置为原链表的尾节点,将它的后继节点设置为null就行,再让last指向传进来的节点newNode。
判断语句就是判断这个链表是否为空,为空的话此时传进去要尾插的元素就是第一个,也就是说它既是这个链表的头节点也是尾节点,让first指向它,不为空就是原来链表的后继节点,让原来链表的最后一个元素的next指向它。
最后让链表的元素个数加1,对链表的操作次数加1。
补充一下:
上面提到了modCount
记录对 List 操作的次数,主要使用是在 Iterator,是防止在迭代的过程中集合被修改。该变量定义在AbstractList中,被ArrayList继承。思考一下,如果我们在遍历集合时,对集合进行了修改,比如说删除了某一个元素,那么很容易导致结果出错或者下标越界。
linkFirst(),linkBefore()
linkFirst这段代码大家根据我上面说的linkLast自己服用吧。
这里linkBefore多说一嘴,可以看到传进的参数多了一个succ节点,这个节点就是存进去的e。succ.prev就是找到它的前驱节点。剩下代码请亲们自己服用。
add(int index, E element)
首先检查数组插入位置是否越界,调用checkPositionIndex(index)方法,若越界则抛出异常。判断插入的位置是否是尾部,如果是尾部直接调用 linkLast 方法,若不是则调用 linkBefore 方法。而 linkLast 方法已经在上文分析过了。 值得注意的是,对于插入位置是头结点的情况,LinkBefore 方法中进行了判断。
addAll()
首先检查要添加的位置是否越界,
将要插入的集合转型为Object[]数组,
判断数组不为空再进行下面的代码。
Node<E> pred, succ; // 用于添加操作的两个临时的指针 if (index == size) { //确定你的插入位置是否在末尾 succ = null; pred = last; } else { succ = node(index); //当你的插入位置不在末尾时,node方法用于确定你指定位置的节点。 pred = succ.prev; // 将指定节点的前向指针赋值给pred }
这里提到了node(index)方法,我们下面看看他是怎么找到插入位置元素的。
对index 进行了比较,如果小于 size 的一半(size >> 1 右移一位,相当于除2),那么是从头往后查找元素,否则从尾部往前查找元素。然后返回这个元素。
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null)
first = newNode;
else
pred.next = newNode;
pred = newNode;
}
pred存放的是前一个节点的地址 ,for循环将元素依次插入。
if (succ == null) {
last = pred;
} else {
pred.next = succ;
succ.prev = pred;
}
判断插入元素是否在末尾,将节点进行连接。
删除元素
remove(int index)
unlink是删除这个节点
移除第一个元素,这里将 item 和 next 置空是让垃圾回收器回收这部分内存。
removeFirst(),removeLast(),clear()可以参考remove(int index)。
获取元素
get(int index)
这里要遍历数组来获取对应下标位置的信息,所以其查找效率比较低。
getFirst(),getLast()
直接获取首位元素。