Java容器系列(三)LinkedList
一、简介
LinkedList 是一个继承于AbstractSequentialList的双向链表。它可以被当作堆栈、队列或双端队列进行操作;LinkedList类的底层是双向链表,链表中的每个节点都包含了对前一个和后一个元素的引用。
LinkedList实现 List 接口,能对它进行队列操作; 实现 Deque 接口,即能将LinkedList当作双端队列使用;实现了Cloneable接口,即覆盖了函数clone(),能隆;实现java.io.Serializable接口,即LinkedList支持序列化,能通过序列化去传输。 LinkedList相对于Arraylist来说,get,方法即查询比较慢,但add和remove会比较快;LinkedList是非线程安全的,不同步。
下面就双向链表的底层原理,详细说明:
链表有双向链表和单向链表之分,而LinkedList的底层是双向链表,
其底层就是由多个节点类Node手拉手链接到一块的,每个节点如图2-1所示,由三部分组成,Object表示当前节点的内容,previous处表示存放前一个节点,next处表示存放下一个节点。
二、具体继承关系及直接子类
java.lang.Object
java.util.AbstractCollection
java.util.AbstractList
java.util.AbstractSequentialList
java.util.LinkedList
类型参数:
E - 在此 collection 中保持的元素的类型
All Implemented Interfaces:
Serializable, Cloneable, Iterable
三、根据源码 简写其原理
由于是根据自己的理解写的,可能跟源码中有些差异,但实现原理相同,易于理解,重点说明LinkedList中的几个重要方法;
// 节点类,双向链表所需节点,由三部分组成,
class Node {
// 前一个节点
Node previous;
// 当前节点存储的内容
Object obj;
// 下一个节点
Node next;
public Node () {
}
public Node(Node previous, Object obj, Node next) {
this.previous = previous;
this.obj = obj;
this.next = next;
}
public Node getPrevious() {
return previous;
}
public Object getObj() {
return obj;
}
public Node getNext() {
return next;
}
public void setPrevious(Node previous) {
this.previous = previous;
}
public void setObj(Object obj) {
this.obj = obj;
}
public void setNext(Node next) {
this.next = next;
}
}
2、重点,自定义的LinkedList实现类,重点说明其实现原理
public class MyLinkedList {
// 双向链表的第一个节点
private Node first;
//双向链表的最后一个节点
private Node last;
// 容器包含的节点数
private int size;
/**
* 说明:新增方法的思路:
* 1、首先判断容器中是否存在元素,即判断容器中的
* 第一个节点是否为空;
* 2、若第一个节点为空,则说明容器中暂无节点元素,
* 可将此时添加进来
* 的元素放置到容器的第一个节点处(此时第一节点
* 和最后一个节为同一个);
* 3、若第一个节点不为空,则说明容器中已有节点元素存在,
* 可将此时添加进来的元素放置到容器的最后一个节点处;
* @param obj 参数暂时都是视作Object对象,
* 目的只是了解原理,不考虑泛型等细节的处理
*
* 其实现过程如图2-3所示
*/
public void add(Object obj) {
// 创建一个节点对象
Node n = new Node();
if(first==null) { // 容器中暂无元素
// 第一节点的上一个节点置空
n.setPrevious(null);
// 存储添加进来的元素;
n.setObj(obj);
// 第一节点的下一个元素暂时也置空
n.setNext(null);
// 由于容器中只有一个元素,故当前创建的节点
// 既是容器的第一节点,也是容器的最后一个节点
first = n;
last = n;
}else { // 容器中已经存在元素
// 当前创建的节点的 上一个元素
// 即为容器原来的最后一个节点
n.setPrevious(last);
// 存放当前添加的元素
n.setObj(obj);
// 当前创建的节点的下一个元素暂时没有,置空
n.setNext(null);
// 将当前创建的节点连接到 容器原来的
// 最后一个节点的下一个元素的位置
last.setNext(n);
// 更换容器的最后一个节点为当前节点
last = n;
}
// 容器中包含的节点个数递增1
size++;
};
// 返回容器中包含的节点个数
public int size () {
return size;
};
/**
* 方法说明:容器的移除方法(根据位置)
* 思路:
* 1、先判断传入的参数是否合法;
* 2、找到容器中对应位置的节点;
* 3、移除此位置的节点,(即将此位置之前的节点和
* 此位置之后的节点直接链接起来,如图2-2);
* @param index
*
* 其实现过程如图2-4所示
*/
public void remove(int index) {
// 检查传入的位置参数是否合法
rangeCheck(index);
// 找到参数所对应的容器中的节点
Node temp =node(index);
if(temp !=null) { // 若找到的节点不为null
// 当前节点的上一个节点
Node up = temp.previous;
// 当前节点的下一个节点
Node down = temp.next;
// 将down节点赋值给up节点的下一个元素
up.next = down;
// 将up节点赋值给down节点的上一个元素
down.previous = up;
// 节点中包含的节点个数减1
size--;
}
};
// 参数合法性检查
private void rangeCheck(int index) {
if(index<0 || index>=size) {
try {
// 不作详细的异常处理
throw new Exception();
} catch (Exception e) {
e.printStackTrace();
}
}
};
/**
* 方法说明:获取容器中指定位置的节点
* 思路:
* 1、判断容器中包含的节点数是否大于零;
* 2、由于根据位置查找节点,需要遍历整个容器,
* 为提高遍历效率,将容器中包含的节点从
* 中间分成两份,
* 3、若传入的参数在上半部分,在从小到大遍历
* 在上半部分中查找;若传入的参数在下半部分,
* 则从大到小遍历,在下半部分中查找;
* @param index
* @return
*/
private Node node(int index) {
Node temp = null;
if(null != first) { // 容器中包含节点
// 传入的参数位置在容器的上半部分
if(index
// 假设容器的第一个节点为需要查找的节点
temp = first;
// 遍历容器中位置从0到index的节点
for(int i=0;i
}
}
// 传入的参数位置在容器的下半部分
else {
// 假设容器的最后一个节点为需要查找的节点
temp = last;
// 遍历容器中位置从size-1到index的节点
for(int i=size-1;i>index;i--) {
temp = temp.previous;
}
}
}
return temp;
}
/**
* 方法说明:获取指定节点位置的元素内容
* 思路:
* 1、判断容器中包含的节点数是否大于零;
* 2、由于根据位置查找节点,需要遍历整个容器,
* 为提高遍历效率,将容器中包含的节点从中间分成两份,
* 3、根据找到的节点,返回该节点的内容即可
*
* @param index
* @return
*/
public Object get (int index) {
//检查异常
rangeCheck(index);
Node temp =node(index);
return temp.obj;
}
// 测试环节
public static void main(String[] args) {
List l = new LinkedList();
MyLinkedList list = new MyLinkedList();
list.add("aaaaaaaaaa");
list.add("bbbbbbbbbbbb");
list.add("cccccccccccc");
list.remove(1);
System.out.println(list.get(1));
}
}
四、总结
总之,LinkedList的底层是一个双向链表,把人可以形象的比作其底层的一个Node节点类,身体就是Node中的object,左右手就可以表示Node中的previous和next,好多人手拉手链接起来,就形成了一个类似LinkedList的人形双向链表。
以上代码如有错误之处,请各位仁兄批评指正,下一节我会总结HashMap的源码实现过程。