您的位置:首页 > 编程语言 > Java开发

Java源码之LinkedList

2012-11-04 16:07 429 查看
看完了ArrayList想到了其兄弟LinkedList,当时上学的时候就听老师给我们说,针对ArrayList和LinkedList两个容器进行选择的时候如果查询很多,添加和修改不多的情况下就用ArrayList,如果查询不是很多,但添加和修改很多的情况下就用LinkedList。为什么LinkedList添加数据较慢?为什么添加和删除数据较快呢?携带着这些疑问,现在开始在源码中寻找答案。

当用到一个对象时首先会new一个对象,因此第一个执行的就是构造,那就先从无参构造开始看起

源码:

public LinkedList() {

header.next = header.previous = header;

}

private transient Entry<E> header = new Entry<E>(null, null, null);

初始化后进行把对应空对象和值赋给第一个元素。为了能够更清楚的明白LinkedList底层存储数据方式,先看看对应Entry实体对象

private static class Entry<E> {

E element;

Entry<E> next;

Entry<E> previous;

Entry(E element, Entry<E> next, Entry<E> previous) {

this.element = element;

this.next = next;

this.previous = previous;

}

}

Entry是一个静态的私有类,主要作用就是在LinkedList中进行存放数据。进行记录当前值和对应前一个以及最后一个值。通过这些可以看出对应存储数据时双向链表方式。

既然通过默认构造创建了对应的对象,那下一步就是向对象中放入数据了,那就从最简单的add方法看起

public boolean add(E e) {

addBefore(e, header);

return true;

}

添加一个对象,本身调用addBefore方法

private Entry<E> addBefore(E e, Entry<E> entry) {

Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);

newEntry.previous.next = newEntry;

newEntry.next.previous = newEntry;

size++;

modCount++;

return newEntry;

}

通过addBefore方法可以看出通过传入对象和对应上一个元素。

下一步看看在指定位置进行开始添加元素,又是如何进行的

public void add(int index, E element) {

addBefore(element, (index==size ? header : entry(index)));

}

同样是调用对应addBefore方法进行设定其中元素值。 (index==size
? header : entry(index))通过三目运算符进行获得制定index位置的Entry对象。如果index和对应LinkedList 的大小是一致的,那对应的直接就是header对象。如果不是进行调用entry(index)方法

private Entry<E> entry(int index) {

if (index < 0 || index >= size)

throw new IndexOutOfBoundsException("Index: "+index+

", Size: "+size);

Entry<E> e = header;

if (index < (size >> 1)) {

for (int i = 0; i <= index; i++)

e = e.next;

} else {

for (int i = size; i > index; i--)

e = e.previous;

}

return e;

}

通过entry方法可以看出。对应传入值后进行判定值是否大于对应总大小一半,如果小于一半就从前向后进行获取对应对象,如果大于一半就从后向前进行获取对象

既然普通的添加一个元素的操作时如此进行的那批量进行插入数据又是如何进行操作的呢?那我们就看看你对应addAll方法是内部是如何实现的

public boolean addAll(int index, Collection<? extends E> c) {

if (index < 0 || index > size)

throw new IndexOutOfBoundsException("Index: "+index+

", Size: "+size);

Object[] a = c.toArray();

int numNew = a.length;

if (numNew==0)

return false;

modCount++;

Entry<E> successor = (index==size ? header : entry(index));

Entry<E> predecessor = successor.previous;

for (int i=0; i<numNew; i++) {

Entry<E> e = new Entry<E>((E)a[i], successor, predecessor);

predecessor.next = e;

predecessor = e;

}

successor.previous = predecessor;

size += numNew;

return true;

}

通过这段可以看出,对应先根据传入集合对象获得对应Object[]数组 然后对传入index索引号进行获得对应指定位置的对象successor,然后进行对数组Object[]进行循环遍历,进行设定指定对象。在看看对应重载方法addAll(Collection c)

public boolean addAll(Collection<? extends E> c) {

return addAll(size, c);

}

传递最大位置给addAll方法,进行把对象在最后位置进行添加集合数据

LinkedList本身提供特殊两个添加方法对应addFirst 和addLast这个两个方法分别标明在最前面进行添加对象、在最后进行添加对象重点说一下 addLast方法,源码如下

public void addLast(E e) {

addBefore(e, header);

}

和对应add方法代码移植唯一区别是add方法可以返回一个boolean类型值

看完了对应添加方法,下一个看看对应修改方法

public E set(int index, E element) {

Entry<E> e = entry(index);

E oldVal = e.element;

e.element = element;

return oldVal;

}

set方法首先根据指定的index进行找到指定位置的Entry对象,然后进行对对象值进行重新赋值操作。并返回修改之前该位置的值

修改看完了,下一步就是对应数据删除了。

既然有那么多方式进行添加数据,那对应删除数据也是有多中多样的。就从简单的一一看起吧

普通的remove(Object o)

public boolean remove(Object o) {

if (o==null) {

for (Entry<E> e = header.next; e != header; e = e.next) {

if (e.element==null) {

remove(e);

return true;

}

}

} else {

for (Entry<E> e = header.next; e != header; e = e.next) {

if (o.equals(e.element)) {

remove(e);

return true;

}

}

}

return false;

}

调用该方法进行从第一个元素进行循环遍历判断元素值相等,进行删除第一个与其相等的对象,如果含有多一个一个对象想同时进行删除,需要进行遍历对应LinkedList

真正删除动作时在对应Entry类中的remove方法

private E remove(Entry<E> e) {

if (e == header)

throw new NoSuchElementException();

E result = e.element;

e.previous.next = e.next;

e.next.previous = e.previous;

e.next = e.previous = null;

e.element = null;

size--;

modCount++;

return result;

}

可以看出是根据传来的对象进行获得上一个和对应下一个对象,并把null赋给当期对象值,并且重新制定该元素的下一个元素,使当前的对象能够释放空间(GC执行)

public E remove(int index) {

return remove(entry(index));

}

对应的根据index进行获得制定位置对象,然后进行删除,并且返回对应要删除对应对象值

public E removeFirst() {

return remove(header.next);

}

public E removeLast() {

return remove(header.previous);

}

public E remove() {

return removeFirst();

}

这三个方法就不说了,但非常好奇,为什么remove方法和removeFirst()两个方法需要同时存在?

删除的方法就告一段落,既然数据都放里面了,并且可以删除修改,那对应想获得对应数据又是如何操作的呢?

public E get(int index) {

return entry(index).element;

}

根据位置进行获得制定对象,然后进行获得对应值

public E getFirst() {

if (size==0)

throw new NoSuchElementException();

return header.next.element;

}

public E getLast() {

if (size==0)

throw new NoSuchElementException();

return header.previous.element;

}

这两个方法一个获得第一个值一个获得最后一个值

在LinkedList中普通的增删改查都搞定了,下一步就看看其他有用的方法了

public int indexOf(Object o) {

int index = 0;

if (o==null) {

for (Entry e = header.next; e != header; e = e.next) {

if (e.element==null)

return index;

index++;

}

} else {

for (Entry e = header.next; e != header; e = e.next) {

if (o.equals(e.element))

return index;

index++;

}

}

return -1;

}

indexOf方法根据传入对象进行查找对象所在位置,返回的索引值是第一次进行查询得到的值,如果查不到就进行返回-1

public boolean contains(Object o) {

return indexOf(o) != -1;

}

contains方法进行判定指定对象是否在linkedList容器中,如果在进行返回true如果不在进行返回false。底层调用indexOf方法

public int size() {

return size;

}

size返回对应linkedList容器的大小,拥有对象的个数

public void clear() {

Entry<E> e = header.next;

while (e != header) {

Entry<E> next = e.next;

e.next = e.previous = null;

e.element = null;

e = next;

}

header.next = header.previous = header;

size = 0;

modCount++;

}

clear方法进行清空所有对象

public int lastIndexOf(Object o) {

int index = size;

if (o==null) {

for (Entry e = header.previous; e != header; e = e.previous) {

index--;

if (e.element==null)

return index;

}

} else {

for (Entry e = header.previous; e != header; e = e.previous) {

index--;

if (o.equals(e.element))

return index;

}

}

return -1;

}

lastIndexOf进行倒叙查询数据,并且返回第一次查询到的制定位置

在jdk1.5引入的方法,主要是队列操作(Queue operations.)

public E peek() {

if (size==0)

return null;

return getFirst();

}

返回第一个元素,如果对应linkedList中没有元素进行返回为null,如果直接调用getFirst方法会抛出异常信息。

public E element() {

return getFirst();

}

返回第一个元素值.

public E poll() {

if (size==0)

return null;

return removeFirst();

}

poll方法是移除第一个元素。如果linkedList里面没有值进行返回null。

public E remove() {

return removeFirst();

}

public boolean offer(E e) {

return add(e);

}

在jdk1.6引入的双向队列操作方法(Deque operations)

ublic boolean offerFirst(E e) {

addFirst(e);

return true;

}

添加一个元素放到首位置

public boolean offerLast(E e) {

addLast(e);

return true;

}

public E peekFirst() {

if (size==0)

return null;

return getFirst();

}

public E peekLast() {

if (size==0)

return null;

return getLast();

}

public E pollFirst() {

if (size==0)

return null;

return removeFirst();

}

public E pollLast() {

if (size==0)

return null;

return removeLast();

}

public void push(E e) {

addFirst(e);

}

public E pop() {

return removeFirst();

}

public boolean removeFirstOccurrence(Object o) {

return remove(o);

}

public boolean removeLastOccurrence(Object o) {

if (o==null) {

for (Entry<E> e = header.previous; e != header; e = e.previous) {

if (e.element==null) {

remove(e);

return true;

}

}

} else {

for (Entry<E> e = header.previous; e != header; e = e.previous) {

if (o.equals(e.element)) {

remove(e);

return true;

}

}

}

return false;

}

本身1.5和1.6引入的方法对数据操作性是一直的,目前还没有明白为什么会引入对应方法?

最后看一个LinkedList中一个有用方法

public ListIterator<E> listIterator(int index) {

return new ListItr(index);

}

本身可以调用listIterator进行获得一个迭代对象,然后进行操作

在jdk1.6引入

public Iterator<E> descendingIterator() {

return new DescendingIterator();

}

降迭代器,作用和名称一样,集合从后往前进行迭代遍历。

public Object clone() {

LinkedList<E> clone = null;

try {

clone = (LinkedList<E>) super.clone();

} catch (CloneNotSupportedException e) {

throw new InternalError();

}

// Put clone into "virgin" state

clone.header = new Entry<E>(null, null, null);

clone.header.next = clone.header.previous = clone.header;

clone.size = 0;

clone.modCount = 0;

// Initialize clone with our elements

for (Entry<E> e = header.next; e != header; e = e.next)

clone.add(e.element);

return clone;

}

通过调用clone进行克隆一个对象,并且拥有对象中制定的结构和对应值

public Object[] toArray() {

Object[] result = new Object[size];

int i = 0;

for (Entry<E> e = header.next; e != header; e = e.next)

result[i++] = e.element;

return result;

}

toArray方法是把对应链表中实体对应值进行放入obj数组当中

public <T> T[] toArray(T[] a) {

if (a.length < size)

a = (T[])java.lang.reflect.Array.newInstance(

a.getClass().getComponentType(), size);

int i = 0;

Object[] result = a;

for (Entry<E> e = header.next; e != header; e = e.next)

result[i++] = e.element;

if (a.length > size)

a[size] = null;

return a;

}

把对应值返回到制定数组类型和大小中。

到这里LinkedList容器类就告一段落了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: