LinkedList(JDK1.8)底层原理

有个疑惑之处,有没有老师们帮忙解答一下。。。

“offer()在队列容量满时进行操作会返回false,而add(obj)会直接抛出异常”看了很多博客都是这样说的,但是看源码并无法理解是怎么得出来的这个结论。。

目录

LinkedList简介

LinkedList构造方法

LinkedList常用方法

添加

add

offer

push

删除

remove

poll

pop

查找

get

peek 

其他 

clone

总结 

LinkedList简介

LinkedList继承于AbstractSequentialList(继承于AbstractList),实现了List、Deque、Cloneable, Serializable。底层是双向链表,实现了Deque接口,Deque接口继承于Queue接口,所以一般实现队列可用LinkedList接口。

LinkedList构造方法

两中构造方法,一个无参(默认大小0),一个构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。

 public LinkedList()
 {
     size = 0;
 }
 public LinkedList(Collection<? extends E> collection)
 {
     this();
     addAll(collection);
 }

LinkedList常用方法

添加

add

add(Object obj)、add(int i,Object obj)、addFirst(Object obj)、addLast(Object obj)这四个方法的核心都是linkFirst(obj)、linkLast(obj)、linkBefore(obj,node(i)),其中linkFirst和linkLast方法相似,只对一个进行详细说明。add(Object obj)默认在尾端添加

 public boolean add(Object obj)
 {
     linkLast(obj);
     return true;
 }
 public void add(int i, Object obj)
 {
     checkPositionIndex(i);
     if(i == size)
         linkLast(obj);
     else
         linkBefore(obj, node(i));
 }
 public void addFirst(Object obj)
 {
     linkFirst(obj);
 }
 public void addLast(Object obj)
 {
     linkLast(obj);
 }

linkFirst(Object obj):创建出新node,前驱给null,后驱指向原本first指向的node,新的first指向新node,再判断原本的first指向的node是否是null,是则意味着原本list为空,所以last也指向新node,否则原本的first指向的node的前驱指向新node.

 private void linkFirst(Object obj)
 {
     Node node1 = first;//将first的值存放node1
     Node node2 = new Node(null, obj, node1);//首节点变为新节点的后续指向
     first = node2;//新节点成为first
     if(node1 == null)//如果node1(原本list为空)为空
         last = node2;//尾节点也是node2
     else
         node1.prev = node2;//否则,原本的首节点的前驱指向新节点
     size++;
     modCount++;
 }

示意图如下:第一次新增的时候first和last都指向添加的节点。 

LinkBefore(Object obj, Node node1)

 void linkBefore(Object obj, Node node1)
 {
//结合add(int i,Object obj)代码一起看,传入的obj是要添加进list的对象,node1是原本list中i位置的值
     Node node2 = node1.prev;//找到node1的前驱指向的对象(node2)
     Node node3 = new Node(node2, obj, node1);//将node2作为前驱创建新节点node3
     node1.prev = node3;//同时node3成为node1的前驱
     if(node2 == null)//如果node2是null(node1是首节点)
         first = node3;//node3成为首节点
     else
         node2.next = node3;//否则node3成为node2的后驱节点
     size++;
     modCount++;
 }

 在add(int i,Object obj)方法中如果不是在尾部插入则调用了LinkBefore方法,传入的值用到了node(i)方法。即根据指定位置去找对应的值,先判断i是处于list的前半段还是后半段,这种方式减少了循环次数。

 Node node(int i)
 {
     if(i < size >> 1)
     {
         Node node1 = first;
         for(int j = 0; j < i; j++)
             node1 = node1.next;

         return node1;
     }
     Node node2 = last;
     for(int k = size - 1; k > i; k--)
         node2 = node2.prev;

     return node2;
 }

addAll(int i,Collection c)

addAll(Collection c) 的本质是调用了addAll(int i,Collection c)

 public boolean addAll(Collection collection)
 {
     return addAll(size, collection);
 }

 public boolean addAll(int i, Collection collection)
 {
     checkPositionIndex(i);//校验是否越界
     Object aobj[] = collection.toArray();//转为数组aobj
     int j = aobj.length;//数组长度j
     if(j == 0)
         return false;
     Node node1;
     Node node2;
     if(i == size)//如果是在末尾添加数据
     {
         node2 = null;
         node1 = last;
     } else//否则将原本处于i位置的数据存入node2,node1存放原本处于i位置的前驱
     {
         node2 = node(i);
         node1 = node2.prev;
     }
     Object aobj1[] = aobj;
     int k = aobj1.length;
     for(int l = 0; l < k; l++)//将数组进行循环
     {
         Object obj = aobj1[l];//存放aobj1数组中的元素
         Object obj1 = obj;
         Node node3 = new Node(node1, obj1, null);//创建新节点以node1为前驱,null为后驱
         if(node1 == null)//如果node1是null,则首节点是node3
             first = node3;
         else
             node1.next = node3;//否则node1的后驱是node3
         node1 = node3;//将node3赋值给node1
     }
//在上面的代码中,首先是将要原本i位置的前驱和添加数组的首元素进行链接(用node1和node3),node3代表待链接进来的数据,node1代表待添加位置前的数据,按数组的顺序一一链接起来,数组中最后的元素的后驱为null
     if(node2 == null)//如果node2是null,即末尾添加的元素,则last指向当前的node1
     {
         last = node1;
     } else//否则node1的后驱指向node2(原本i位置的元素),node2的前驱指向node1
     {
         node1.next = node2;
         node2.prev = node1;
     }
     size += j;
     modCount++;
     return true;
 }

offer

实现的时Queue接口中的方法,LinkedList用作队列时,一般使用offer(obj)进行元素的添加,offer(obj)是基于add(obj)实现的,但是有所不同的是,offer()在队列容量满时进行操作会返回false,而add(obj)会直接抛出异常(有没有老师们帮忙解答一下为什么?看LinkedList源码offer应该永远都只会返回true...)。offer(obj)在尾端添加元素

 public boolean offer(Object obj)
 {
     return add(obj);
 }

 public boolean offerFirst(Object obj)
 {
     addFirst(obj);
     return true;
 }

 public boolean offerLast(Object obj)
 {
     addLast(obj);
     return true;
 }

push

无返回,添加在最前面

 public void push(Object obj)
 {
     addFirst(obj);
 }

删除

remove

remove():获取并移除此列表的头(第一个元素)

remove(int index):移除此列表中指定位置处的元素。

remove(Object obj):从此列表中移除首次出现的指定元素(如果存在)。

removeFirst():移除并返回此列表的第一个元素。
removeFirstOccurrence(Object o):从此列表中移除第一次出现的指定元素(从头部到尾部遍历列表时)。
removeLast():移除并返回此列表的最后一个元素。
removeLastOccurrence(Object o):从此列表中移除最后一次出现的指定元素(从头部到尾部遍历列表时)。

 public Object remove()
 {
     return removeFirst();
 }
 public boolean remove(Object obj)
 {
     if(obj == null)
     {
         for(Node node1 = first; node1 != null; node1 = node1.next)
             if(node1.item == null)
             {
                 unlink(node1);
                 return true;
             }

     } else
     {
         for(Node node2 = first; node2 != null; node2 = node2.next)
             if(obj.equals(node2.item))
             {
                 unlink(node2);
                 return true;
             }

     }
     return false;
 }
 public Object remove(int i)
 {
     checkElementIndex(i);
     return unlink(node(i));
 }
 public Object removeFirst()
 {
     Node node1 = first;
     if(node1 == null)
         throw new NoSuchElementException();
     else
         return unlinkFirst(node1);
 }

 public Object removeLast()
 {
     Node node1 = last;
     if(node1 == null)
         throw new NoSuchElementException();
     else
         return unlinkLast(node1);
 }

poll

poll():检索并删除此列表的头(第一个元素),返回的是该列表的头,或 null如果此列表为空

pollFirst():检索并删除此列表的第一个元素,如果此列表为空,则返回 null 。 

pollLast():检索并删除此列表的最后一个元素,如果此列表为空,则返回 null 。

pop

pop():弹出第一个元素。返回的是第一个元素,如果列表为空,抛出异NoSuchElementException

查找

get

LinkedList虽然也实现了get(i),但是LinkedList没有索引,因为它本质上是链表,只能从头依次开始查找,只能顺序访问。

get(int i):返回列表中指定位置的元素,i<0||i>size抛出异常IndexOutOfBoundsException

getFirst():返回列表中的第一个元素,如果列表为空,抛出异NoSuchElementException

getLast():返回列表中的最后一个元素,如果列表为空,抛出异NoSuchElementException

peek 

peek():检索但不删除此列表的头(第一个元素),返回第一个元素,如果此列表为空返回null

peekFirst():检索但不删除此列表的第一个元素,如果此列表为空,则返回 null 。 

peekLast():检索但不删除此列表的最后一个元素,如果此列表为空,则返回 null 。

其他 

clone

先调用superClone()进行一次浅拷贝,此时新列表的每个节点都没有进行new的操作,这种情况下如果修改了新列表,原本的列表也会被修改,为了防止这种情况,将每一个节点重新进行了new的操作。

 public Object clone()
 {
     LinkedList linkedlist = superClone();
     linkedlist.first = linkedlist.last = null;
     linkedlist.size = 0;
     linkedlist.modCount = 0;
     for(Node node1 = first; node1 != null; node1 = node1.next)
         linkedlist.add(node1.item);

     return linkedlist;
 }

总结

1、LinkedList是一个双向链表,查找节点的平均时间复杂度是 O(n),首尾增加和删除节点的时间复杂度是 O(1)。

2、LinkedList 作为队列(先进先出)使用时,可以通过 offer/poll/peek 来代替 add/remove/get 等方法, 这些方法在遇到空集合或队列容量满的情况不会抛出异常。

3、LinkedList作为堆栈(先进后出)使用时,可用push/pop/peek。

4、LinkedList是线程不安全的

5、add()、offer()方法在末尾添加,push在前面添加

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值