自己动手写写:LinkedList源码浅析
2012-04-18 10:57
267 查看
http://boy00fly.iteye.com/blog/1138904
上篇文章浅析了ArrayList的源码相关内容!这篇文章将介绍LinkedList相关的内容!
二. LinkedList
先来看看LinkedList的类结构!
Java代码
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
1. 几个重要的成员变量
Java代码
private transient Entry<E> header = new Entry<E>(null, null, null);
private transient int size = 0;//LinkedList的长度<span style="white-space: normal;"> ,初始化为0</span>
这里先来说一下header这个变量,这个很重要哦!
首先看一下Entry是个什么东西!
Java代码
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的一个内部静态类!我们知道LinkedList的内部数据结构采用的链式存储方式,链式存储决定了它插入的速度会相对会快,而索引的速度慢!链式存储最主要的有三个元素:当前元素、前一个元素地址、后一个元素地址。
Entry类中element表示当前元素; next表示后一个元素;previous表示前一个元素;这样的话一个链式的存储的模型就有了。
2. 两个构造函数
Java代码
/**
* Constructs an empty list.
*/
public LinkedList()
{
header.next = header.previous = header;
}
无参构造函数初始化的时候header的前一个、后一个均指向本身。
Java代码
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public LinkedList(Collection<? extends E> c)
{
this();
addAll(c);
}
根据已有的Collection初始化LinkedList,我们来看一个addAll(int index, Collection<? extends E> c)这个方法(addAll(Collection<? extends E> c)本身也是调用的此方法,其中index的参数值为size而已)。
Java代码
/**
* Inserts all of the elements in the specified collection into this
* list, starting at the specified position. Shifts the element
* currently at that position (if any) and any subsequent elements to
* the right (increases their indices). The new elements will appear
* in the list in the order that they are returned by the
* specified collection's iterator.
*
* @param index index at which to insert the first element
* from the specified collection
* @param c collection containing elements to be added to this list
* @return <tt>true</tt> if this list changed as a result of the call
* @throws IndexOutOfBoundsException {@inheritDoc}
* @throws NullPointerException if the specified collection is null
*/
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;
}
前面的我就不说了,都是一些预判断。我们来分析下下面这段代码的含义
Java代码
Entry<E> successor = (index == size ? header : entry(index));
Entry<E> predecessor = successor.previous;
粗略的看一个successor和predecessor
英文含义分别表示为后继者和前辈;对呀,我们想啊,LinkedList本身是一个链式存储结构,你要将一些内容插入进来,首先必须要在链上找到一个入口,然后将此入口掐断,接着将你插入进来的内容放进去,最后再将这个链给接起来,你要将链给接起来要接对啊,不能接错地方啊!successor、predecessor就是为了将链接对所用,分别指向链断裂后,后一段和前一段的链接地址。
我们来看下面这两张图:
图一
图二
上面我描述的过程就类似于从图一到图二的过程。
初步感性的分析玩之后我们来详细分析一下!
这里做了一个判断:如果当前插入的index的值等于size则返回header,否则返回entry(index);
1. index == size时,其实就是插入到链尾处,因为链尾处的元素的next比较特殊,它指向了链首header。
2. index != size时,我们先来看一下entry(int index)这个方法的源码:
Java代码
/**
* Returns the indexed entry.
*/
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;
}
其实就是找到找到索引值为index的Entry,判断index值大小是否已经超过了size值的一半,如果没超过就从链表头部开始遍历,否则从链表尾部开始遍历。
ps:看得出来,按索引找个Entry这么麻烦,真慢,不像ArrayList来得那么直接。这就是为什么LinkedList索引慢的原因,存储结构决定的,基因问题,呵呵。
3. Entry<E> predecessor = successor.previous; 鉴于上面的分析,这句也就不难理解了。
4.
Java代码
for (int i = 0; i < numNew; i++)
{
Entry<E> e = new Entry<E>((E)a[i], successor, predecessor);
predecessor.next = e;
predecessor = e;
}
这段代码就是将初始化的Collection数据一个一个链接上去。
Java代码
successor.previous = predecessor;
size += numNew;
最后将这个链给接回去,形成了一个闭环链! LinkedList的大小也要增加一下嘛!
3. 几个常用方法浅析
Java代码
/**
* Inserts the specified element at the specified position in this list.
* Shifts the element currently at that position (if any) and any
* subsequent elements to the right (adds one to their indices).
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element)
{
addBefore(element, (index == size ? header : entry(index)));
}
有些代码是不是很熟悉啊?呵呵,还是看内部的addBefore(E e, Entry<E> entry)方法吧。
Java代码
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;
}
这个还用我说吗? 是不是比在构造方法里面的要简单得多啊。
Java代码
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index)
{
return entry(index).element;
}
获取指定索引值index的元素,呵呵,不解释了!
Java代码
/**
* Returns the first element in this list.
*
* @return the first element in this list
* @throws NoSuchElementException if this list is empty
*/
public E getFirst()
{
if (size == 0)
throw new NoSuchElementException();
return header.next.element;
}
/**
* Returns the last element in this list.
*
* @return the last element in this list
* @throws NoSuchElementException if this list is empty
*/
public E getLast()
{
if (size == 0)
throw new NoSuchElementException();
return header.previous.element;
}
分别为获取第一个和最后一个元素的值,也很简单啊,第一个元素就是header的next的element的值,最后一个元素就是header的previous的element的值。
ps:真实的内部情况是这样的,整个链表的内容是包含header的Entry和LinkedList存储的所有Entry两个部分共同组成的。
Java代码
/**
* Returns the index of the first occurrence of the specified element
* in this list, or -1 if this list does not contain the element.
* More formally, returns the lowest index <tt>i</tt> such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,
* or -1 if there is no such index.
*
* @param o element to search for
* @return the index of the first occurrence of the specified element in
* this list, or -1 if this list does not contain the element
*/
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;
}
返回第一个包含对象o的索引值。也很简单吧,对吧!
Java代码
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;
}
这是一个private方法,就是删除链表中的一个Entry,思路如下:将要移除的对象e的previous的next指向e的next,对象e的next的previous指向e的previous,就是将链表的一个节点删掉,在将这个链表接起来。
Java代码
/**
* Replaces the element at the specified position in this list with the
* specified element.
*
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element)
{
Entry<E> e = entry(index);
E oldVal = e.element;
e.element = element;
return oldVal;
}
这个更简单,就是替换索引index的Entry的element的值。
好吧,基本常用的方法也都分析完了,重点已经照顾到了,其他的就不再累述了。
这里就简单描述一下Vector吧!
Java代码
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
Vector用得并不多,它和ArrayList很相似,但是它本身是线程安全的,看源码就能够看得出来,很多方法都是synchronized,但是在jdk1.6上就已经有java.util.concurrent了,对于多线程编程的话,Vector的用武之地也很少了,这里就不再讲了!
ps:附件中我上传了一个比较不错的Data Structure Visualzation工具,是一个jar包运行java -jar 执行。对于分析与理解数据结构方面的问题相当有帮助。
visualization.jar (572.9 KB)
下载次数: 4
上篇文章浅析了ArrayList的源码相关内容!这篇文章将介绍LinkedList相关的内容!
二. LinkedList
先来看看LinkedList的类结构!
Java代码
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, java.io.Serializable
1. 几个重要的成员变量
Java代码
private transient Entry<E> header = new Entry<E>(null, null, null);
private transient int size = 0;//LinkedList的长度<span style="white-space: normal;"> ,初始化为0</span>
这里先来说一下header这个变量,这个很重要哦!
首先看一下Entry是个什么东西!
Java代码
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的一个内部静态类!我们知道LinkedList的内部数据结构采用的链式存储方式,链式存储决定了它插入的速度会相对会快,而索引的速度慢!链式存储最主要的有三个元素:当前元素、前一个元素地址、后一个元素地址。
Entry类中element表示当前元素; next表示后一个元素;previous表示前一个元素;这样的话一个链式的存储的模型就有了。
2. 两个构造函数
Java代码
/**
* Constructs an empty list.
*/
public LinkedList()
{
header.next = header.previous = header;
}
无参构造函数初始化的时候header的前一个、后一个均指向本身。
Java代码
/**
* Constructs a list containing the elements of the specified
* collection, in the order they are returned by the collection's
* iterator.
*
* @param c the collection whose elements are to be placed into this list
* @throws NullPointerException if the specified collection is null
*/
public LinkedList(Collection<? extends E> c)
{
this();
addAll(c);
}
根据已有的Collection初始化LinkedList,我们来看一个addAll(int index, Collection<? extends E> c)这个方法(addAll(Collection<? extends E> c)本身也是调用的此方法,其中index的参数值为size而已)。
Java代码
/**
* Inserts all of the elements in the specified collection into this
* list, starting at the specified position. Shifts the element
* currently at that position (if any) and any subsequent elements to
* the right (increases their indices). The new elements will appear
* in the list in the order that they are returned by the
* specified collection's iterator.
*
* @param index index at which to insert the first element
* from the specified collection
* @param c collection containing elements to be added to this list
* @return <tt>true</tt> if this list changed as a result of the call
* @throws IndexOutOfBoundsException {@inheritDoc}
* @throws NullPointerException if the specified collection is null
*/
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;
}
前面的我就不说了,都是一些预判断。我们来分析下下面这段代码的含义
Java代码
Entry<E> successor = (index == size ? header : entry(index));
Entry<E> predecessor = successor.previous;
粗略的看一个successor和predecessor
英文含义分别表示为后继者和前辈;对呀,我们想啊,LinkedList本身是一个链式存储结构,你要将一些内容插入进来,首先必须要在链上找到一个入口,然后将此入口掐断,接着将你插入进来的内容放进去,最后再将这个链给接起来,你要将链给接起来要接对啊,不能接错地方啊!successor、predecessor就是为了将链接对所用,分别指向链断裂后,后一段和前一段的链接地址。
我们来看下面这两张图:
图一
图二
上面我描述的过程就类似于从图一到图二的过程。
初步感性的分析玩之后我们来详细分析一下!
这里做了一个判断:如果当前插入的index的值等于size则返回header,否则返回entry(index);
1. index == size时,其实就是插入到链尾处,因为链尾处的元素的next比较特殊,它指向了链首header。
2. index != size时,我们先来看一下entry(int index)这个方法的源码:
Java代码
/**
* Returns the indexed entry.
*/
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;
}
其实就是找到找到索引值为index的Entry,判断index值大小是否已经超过了size值的一半,如果没超过就从链表头部开始遍历,否则从链表尾部开始遍历。
ps:看得出来,按索引找个Entry这么麻烦,真慢,不像ArrayList来得那么直接。这就是为什么LinkedList索引慢的原因,存储结构决定的,基因问题,呵呵。
3. Entry<E> predecessor = successor.previous; 鉴于上面的分析,这句也就不难理解了。
4.
Java代码
for (int i = 0; i < numNew; i++)
{
Entry<E> e = new Entry<E>((E)a[i], successor, predecessor);
predecessor.next = e;
predecessor = e;
}
这段代码就是将初始化的Collection数据一个一个链接上去。
Java代码
successor.previous = predecessor;
size += numNew;
最后将这个链给接回去,形成了一个闭环链! LinkedList的大小也要增加一下嘛!
3. 几个常用方法浅析
Java代码
/**
* Inserts the specified element at the specified position in this list.
* Shifts the element currently at that position (if any) and any
* subsequent elements to the right (adds one to their indices).
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public void add(int index, E element)
{
addBefore(element, (index == size ? header : entry(index)));
}
有些代码是不是很熟悉啊?呵呵,还是看内部的addBefore(E e, Entry<E> entry)方法吧。
Java代码
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;
}
这个还用我说吗? 是不是比在构造方法里面的要简单得多啊。
Java代码
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index)
{
return entry(index).element;
}
获取指定索引值index的元素,呵呵,不解释了!
Java代码
/**
* Returns the first element in this list.
*
* @return the first element in this list
* @throws NoSuchElementException if this list is empty
*/
public E getFirst()
{
if (size == 0)
throw new NoSuchElementException();
return header.next.element;
}
/**
* Returns the last element in this list.
*
* @return the last element in this list
* @throws NoSuchElementException if this list is empty
*/
public E getLast()
{
if (size == 0)
throw new NoSuchElementException();
return header.previous.element;
}
分别为获取第一个和最后一个元素的值,也很简单啊,第一个元素就是header的next的element的值,最后一个元素就是header的previous的element的值。
ps:真实的内部情况是这样的,整个链表的内容是包含header的Entry和LinkedList存储的所有Entry两个部分共同组成的。
Java代码
/**
* Returns the index of the first occurrence of the specified element
* in this list, or -1 if this list does not contain the element.
* More formally, returns the lowest index <tt>i</tt> such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,
* or -1 if there is no such index.
*
* @param o element to search for
* @return the index of the first occurrence of the specified element in
* this list, or -1 if this list does not contain the element
*/
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;
}
返回第一个包含对象o的索引值。也很简单吧,对吧!
Java代码
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;
}
这是一个private方法,就是删除链表中的一个Entry,思路如下:将要移除的对象e的previous的next指向e的next,对象e的next的previous指向e的previous,就是将链表的一个节点删掉,在将这个链表接起来。
Java代码
/**
* Replaces the element at the specified position in this list with the
* specified element.
*
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E set(int index, E element)
{
Entry<E> e = entry(index);
E oldVal = e.element;
e.element = element;
return oldVal;
}
这个更简单,就是替换索引index的Entry的element的值。
好吧,基本常用的方法也都分析完了,重点已经照顾到了,其他的就不再累述了。
这里就简单描述一下Vector吧!
Java代码
public class Vector<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
Vector用得并不多,它和ArrayList很相似,但是它本身是线程安全的,看源码就能够看得出来,很多方法都是synchronized,但是在jdk1.6上就已经有java.util.concurrent了,对于多线程编程的话,Vector的用武之地也很少了,这里就不再讲了!
ps:附件中我上传了一个比较不错的Data Structure Visualzation工具,是一个jar包运行java -jar 执行。对于分析与理解数据结构方面的问题相当有帮助。
visualization.jar (572.9 KB)
下载次数: 4
相关文章推荐
- 自己动手写写:关于jvm的理解(3)
- 自己动手写写:LinkedHashMap源码浅析
- 自己动手写写:HashMap源码浅析
- 自己动手写写:ArrayList源码浅析
- 自己动手写写:关于jvm的理解(1)
- 自己动手写写:关于jvm的理解(2)
- 自己动手丰衣足食-自己动手修改GBA ROM游戏文件
- KVO实现机制 & 如何自己动手实现 KVO
- LNMP被挂马自己动手解决
- 连“霍金”都想学习的“人工智能”---【自己动手写神经网络】小白入门连载开始了(1)
- 自己动手,实现在kernel函数中printf()!(转)
- 自己动手写路由器之ioctl获取网络接口信息
- 用.net自己动手开发【操作系统】
- 自己动手写操作系统(二)——搭建bochs环境
- 自己动手之使用反射和泛型,动态读取XML创建类实例并赋值
- [翻]如何自己动手栽一棵菠萝树
- 自己动手写编译器、链接器作者自序
- 自己动手修改Robotium代码
- 动手写自己的cuda遇到的问题1