有个疑惑之处,有没有老师们帮忙解答一下。。。
“offer()在队列容量满时进行操作会返回false,而add(obj)会直接抛出异常”看了很多博客都是这样说的,但是看源码并无法理解是怎么得出来的这个结论。。
目录
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在前面添加