Java容器系列(三)LinkedList

                                                    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<E>

      java.util.AbstractList<E>

          java.util.AbstractSequentialList<E>

              java.util.LinkedList<E>

类型参数: 

E - 在此 collection 中保持的元素的类型

All Implemented Interfaces:

  Serializable, Cloneable, Iterable<E>, Collection<E>, Deque<E>, List<E>, Queue<E>

三、根据源码  简写其原理

由于是根据自己的理解写的,可能跟源码中有些差异,但实现原理相同,易于理解,重点说明LinkedList中的几个重要方法;

  1. 先写LinkedList的底层Node节点类,Node也是实现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 <size>>1){
                // 假设容器的第一个节点为需要查找的节点
                temp = first;
                // 遍历容器中位置从0到index的节点
                for(int i=0;i<index;i++) {
                    temp = temp.next;
                }
            }
            // 传入的参数位置在容器的下半部分
            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的源码实现过程。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值