LinkedList是基于双向链表的集合,先看以下代码:
List<Integer> list = new LinkedList<>(); for(int i = 1;i < 6;i++){ list.add(i); }
代码执行后数据如图所示:
结合LinkedList源码:
transient int size = 0; /** * Pointer to first node. */ transient Node<E> first; /** * Pointer to last node. */ transient Node<E> last;LinkedList的三个数据成员,size是集合内元素的个数,first指向头节点,last指向尾节点。而集合内的元素是通过内部类node来存储的,定义如下:
private static class Node<E> { E item; //存储数据 Node<E> next; //后节点 Node<E> prev; //前节点 Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } }包括三个成员变量,item是存储的数据元素,next指向后一个Node节点,perv指向前一个Node节点。
1、构造方法
只有两个构造方法,一个无参构造,一个有参
public LinkedList() { } public LinkedList(Collection<? extends E> c) { this(); addAll(c); }
它和ArrayList不一样,没有指定初始化容量的构造函数。有参构造就是把集合全部加入到链表中,
2、add方法
public boolean add(E e) { linkLast(e); return true; }直接调用 linkLast方法,看一下 linkLast方法:
void linkLast(E e) { final Node<E> l = last; //把last节点赋值给l final Node<E> newNode = new Node<>(l, e, null); //根据加入的元素构造新的node节点 last = newNode; //新的node节点设置为last节点 if (l == null) //last节点不为空的时候 first = newNode; //即添加第一个节点的时候last为null,就把新节点设置为first else l.next = newNode; //把l节点(之前的last)的next指向新的node节点 size++; // 数据的size加1 modCount++; }
add方法就是把新节点设为last节点,在第三行的构造函数里面,把新节点的perv指向之前的last节点l,然后把之前last节点l的next指向新节点。最后一行modCount是集合中记录修改次数的成员变量,和快速失败机制有关。具体查看Java从入门到放弃(七)集合框架之ArrayList的坑
public void add(int index, E element) { checkPositionIndex(index); //链表越界检查 if (index == size) linkLast(element); //添加到last节点方法,如上 else linkBefore(element, node(index)); 在node(index)节点前面添加新节点 }
node(index)方法就是获取链表中index位置的node:
Node<E> node(int index) { // assert isElementIndex(index); if (index < (size >> 1)) { //如果在链表的前半部分,就从first向后遍历 Node<E> x = first; for (int i = 0; i < index; i++) 循环index次 x = x.next; return x; } else { Node<E> x = last; //如果在链表的后半部分,就从last向前遍历 for (int i = size - 1; i > index; i--) x = x.prev; return x; } }
可以看到内部做了优化,并不是直接遍历到index位置,而是进行了判断,当index在链表的前半部分就从first向后遍历,如果在后半部分就从last向前遍历。
linkBefore方法:
void linkBefore(E e, Node<E> succ) { // assert succ != null; final Node<E> pred = succ.prev; //获取succ的perv节点 final Node<E> newNode = new Node<>(pred, e, succ); //构建新节点,新节点的next指向succ,新节点的perv指向succ的perv succ.prev = newNode; //succ的perv节点指向新节点 if (pred == null) first = newNode; //如果perv节点为null,说明succ是first节点,所以把first指向新节点 else pred.next = newNode; //succ的perv节点的next节点指向新节点 size++; modCount++; }
执行如下代码后结果如图:
list.add(2,6)
3、remove方法
public boolean remove(Object o) { if (o == null) { for (Node<E> x = first; x != null; x = x.next) { 遍历节点 if (x.item == null) { unlink(x); return true; } } } else { for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) { unlink(x); return true; } } } return false; }就是遍历链表,找到对应的节点,然后用unlink方法删除,看一下 unlink方法:
E unlink(Node<E> x) { // assert x != null; final E element = x.item; //node节点存储的数据 final Node<E> next = x.next; //后一个节点 final Node<E> prev = x.prev; //前一个节点 if (prev == null) { first = next; //如果x的perv节点为null,说明x节点就是first,所以要把x的next节点设为first节点 } else { prev.next = next; //x节点的perv节点的next节点设置为x节点的next节点 x.prev = null; //x的perv节点置为null,便于gc } if (next == null) { last = prev; //如果x的next节点为null,说明x节点就是last节点,所以要把x的perv节点设为last节点 } else { next.prev = prev; //x节点的next节点的perv节点设置为x节点的perv节点 x.next = null; //x的next节点置为null,便于gc } x.item = null; //x的item置为null,便于gc size--; modCount++; return element; }如下代码执行后:
list.remove(3);
remove(index)方法:
public E remove(int index) { checkElementIndex(index); return unlink(node(index)); }方法都是调用了之前的一些方法,链表越界检查,然后根据node(index)知道对应的节点,使用unlink方法删除,这两个方法上面都有说明。