目录
2、了解完之前ArrayList的一些特点之后,简单学习LinkList集合
JDK集合源码学习-LinkedList集合
在java开发中List集合被常常使用,对于上一篇文章简单记录了ArrayList源码的学习笔记。
从ArrayList源码分析中我们不难总结出以下几点:
1、使用ArrayList集合优缺点
1.1 ArrayList集合底层使用的数组实现,我们都知道数组容量是固定大小,所以我们在使用的时候需要尽量预估我们数据的多少。
1.2 由于底层是数组实现,操作时寻址很快,但是ArrayList操作相关方法如 add(),add(int index ,Obj o),remove()等都可能会造成数组之间的拷贝和扩容问题。如果增删数据较少影像不大,如果数据量大,造成批量copy数据无疑会增加时耗。
1.3 ArrayList虽然增删操作慢,但get数据和寻址非常快。使用for遍历ArrayList集合效率非常快。
2、了解完之前ArrayList的一些特点之后,简单学习LinkList集合
这里先介绍一下linked数据结构
LinkedList是基于双向链表实现,每个节点中有pre指针,element值,next指针;
pre指针指向上一个Node节点,item存放element的值,next指针指向下一个元素,依次类推;
具数据体结构简易图如下:
2.1 不啰嗦,直接看源码
2.1.1 尾部增加一个元素-add(E e)和addLast(E e)一样
public boolean add(E e) { linkLast(e); // linkedList 直接add方法 就是在尾部创建一个节点并赋值增加指针等 return true; }
public void addLast(E e) { linkLast(e); //最终调用linkLast方法 }
if (l == null)
first = newNode; //当linkedList是空集合的时候,新创建的newNode就作为第一个节点
2.1.2 在中间增加一个元素 addFirst(E e) 和 add(int index,E e)类似
public void addFirst(E e) {
linkFirst(e);
}
private void linkFirst(E e) { final Node<E> f = first; final Node<E> newNode = new Node<>(null, e, f); //addFirst调用,pred指向空,next指向原来集合中第一个元素 first = newNode; if (f == null) last = newNode; else f.prev = newNode; size++; modCount++; }
2.1.3 set(int index,E e)方法
public E set(int index, E element) { checkElementIndex(index); //检查set的index是否越界 Node<E> x = node(index); //遍历链表直到下标为index的节点 E oldVal = x.item; //获取原来节点的item x.item = element; //给原来节点的赋值item=element; return oldVal; }
2.1.4 get(int index)方法
public E get(int index) {
checkElementIndex(index);
return node(index).item; // node(index)方法
}
Node<E> node(int index) { // assert isElementIndex(index); //判断index位置是在前半部分还是后半部分,size>>1 相当于size/2 if (index < (size >> 1)) { //要获取的元素在集合的前半部分 Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; //从first挨个向后遍历 直到index-1的下一个元素 即是要获取的Node return x; } else { //要获取的元素在集合的后半部分 Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; //从last往前遍历 直到index+1的前一个元素 即是要获取的Node return x; } }
2.1.5 LinkedList的remove操作
remove的几种方法:remove()、remove(Obj o)、remove(int index) 、removeLast()等
public E remove() { return removeFirst(); //移除头结点 }
public E removeFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return unlinkFirst(f); //具体的删除头结点代码 与 删除last结点类似都是极端 }
public boolean remove(Object o) { //该方法与remove(int index)类似,remove(int index) 先调用node(index)从头遍历直到找到index位置的节点; //该方法中遍历与remove(int index)区别:该方法只能从头挨个遍历,如果要删除的位置在最后需要遍历整个链表,而remove(int indet)方法中调用node(index)可以通过index判断在链表的位置,最多遍历半个链表 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; }
E unlink(Node<E> x) { //移除最终调用的方法 简单来说就是让移除的节点 pre指针,next指针都指null,同时之前pred节点的next指针指向next节点,next节点的pre指针指向pred节点。item也等于null,被孤立起来的节点最终会被删除、GC清理 // assert x != null; final E element = x.item; // 取出将被移除节点存放的数据 final Node<E> next = x.next; //原先将被移除节点next指针指向的节点 final Node<E> prev = x.prev; //原先将被移除节点prev指针指向的节点 if (prev == null) { first = next; } else { prev.next = next; //pred节点的next指针指向next节点 x.prev = null; //将被移除的节点prev指针指null } if (next == null) { last = prev; } else { next.prev = prev; //next节点的prev指针指向原来的pred节点 x.next = null; // 将被移除的节点的next指针 指向null } x.item = null; // 将被移除的节点的item值设置null size--; modCount++; return element; //返回被移除的节点数据(第一行:element = x.item) }
移除前后两张图
2.1.6 LinkedList也可以作为简单的队列使用
方法有offer,offerFirst,offerLast等,源码调用的方法是add相关方法
public boolean offer(E e) {
return add(e);
}
public boolean offerFirst(E e) {
addFirst(e);
return true;
}
public boolean offerLast(E e) {
addLast(e);
return true;
}
2.1.6 peek方法,poll方法
public E peek() {
final Node<E> f = first; //直接获取头结点的item
return (f == null) ? null : f.item;
}
public E peekFirst() {
final Node<E> f = first;
return (f == null) ? null : f.item; //直接获取头结点的item
}
public E peekLast() {
final Node<E> l = last;
return (l == null) ? null : l.item; //直接获取最后结点的item
}
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f); // 获取元素后删除该节点
}
public E pollFirst() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f); // 获取头结点元素后删除该节点
}
public E pollLast() {
final Node<E> l = last;
return (l == null) ? null : unlinkLast(l); //获取最后节点元素后删除该节点
}
队列中使用的方法基本都于之前方法类似,peek和poll方法会判断空,为空返回空值。getFirst,getLast,removeFirst和removeLast方法不判断空,如果为空抛出异常NoSuchElementException.
总结:
LinkedList的特点
linkedList底层使用的是双向链表
通过前面的分析之后,我们可以了解到大量add和remove操作的时候不会导致扩容问题和大量拷贝数组的问题,这是LinkedList的强项。
get操作,首先会判断要获取的index位置在链表的前半部分还是下半部分,如果是在上半部分从First节点开始遍历直到找出获取的节点数据;如果index>size>>1 那么要获取的节点在链表后半部分,则从Last节点开始往前遍历。所以遍历数据LinkedList弱项,遍历数据是ArrayList的强项,可以随机获取。
addFirst,addLast等在两个段增删操作时性能要比在中间操作效率高,因为在中间操作比如add(index,e)或者remove(index),remove(e)等方法会先去走一个遍历的操作。
相对ArrayList来说,LinkedList需要更多的内存,因为ArrayList的每个索引的位置是实际的数据,而LinkedList中的每个节点中存储的是实际的数据和前后节点的位置。
使用场景:
linkedList的使用场景
综合上述特点,如果应用程序对数据有较多的随机访问,ArrayList对象要优于LinkedList对象;
如果应用程序有更多的插入或者删除操作,较少的随机访问,LinkedList对象要优于ArrayList对象;不过ArrayList的插入,删除操作也不一定比LinkedList慢,如果在List靠近末尾的地方插入,那么ArrayList只需要移动较少的数据,而LinkedList中remove(Obj o)移除时则需要一直查找到列表尾部,反而耗费较多时间,这时ArrayList就比LinkedList要快。