带你手写LinkedList

前言

LinkedList,顾名思义,链表!是List接口下的一个实现类。看过jdk6,jdk7,jdk8的LinkedList源码,稍有差异。但大体实现细节都是相同的。或者说,链表本身是属于一种数据结构,任何语言的链表的实现套路都是大同小异。

本文所写的LinkedList也只是实现的一种简易版双向链表帮助大家理解。在命名方面有部分参考过jdk的命名规则!

关注点

不只是LinkedList,在任何集合中,都应关注以下几点:

LinkedList

是否可以为空

可以为空

是否有序(插入时候的顺序和读取时是否一致)

有序

是否允许重复

允许

是否线程安全

特性

 随机插入与删除快,查找慢

 

 

 

 

 

 

 

 

 

 

原理

上一篇文章讲过,ArrayList是基于数组实现的,所有的元素都存储到了数组中。在LinkedList中,是基于链表实现,所谓链表:假设一个存储单元是一个对象Node(jdk7中是Node,jdk6是Entry),就是许多对象链到一起,行成一条链。那么不同对象怎么连起来呢?

所以这个Node对象包含了3部分信息:

1、存储的实际信息,比如链表中放字符串,那次属性就放"aaa","bbb"之类的信息。

2、因为是双向链表,所以还要有属性存储上一个节点的信息与下一个节点的信息。所以单向链表只需存储下一个节点的信息即可(如HashMap即中使用了单向链表)

实现

首先,定义我们的LinkedList:

 public class HkLinkedList {
     Node first;//第一个节点
     Node last;//最后一个节点
     
     private int size=0;
    
    public int size(){
    return size;
    }


 }

 

对与里面的Node类,是我们实际存储的媒介,将此定义为一个内部类,专供外部类使用,如下:

1 private static class Node{
2      Node prev;//上一个节点信息
3      Object obj;//实际存储的对象
4      Node next;//下一个节点信息
5      //构造器   
6      public Node() {
7      }
8         
9    }

可见,每一个Node对象,都包含了上一个节点信息与下一个节点的信息,还有一个Object对象用来存储实际内容。

接下来,我们实现add(Object obj)方法,add方法的核心思想是往链表的末尾新增一个元素

 1     public boolean add(Object obj){
 2         Node n=new Node();
 3         if(null==first){
 4             n.prev=null;
 5             n.obj=obj;
 6             n.next=null;
 7             first=n;
 8             last=n;
 9         }else{
10             n.prev=last;
11             n.obj =obj;
12             n.next=null;
13             last.next=n;
14             last=n;
15         }
16         size++;
17         return true;
18     }

 可见,此方法要比ArrayList看着篇幅多一些,实际上,实现方式也较为简单:

对上述代码做出解释:

每次新增,肯定需要新new一个Node对象 n(第2行)

如果这个链表中first为空,(4~8行)表明链表是空的,此时新建的这个对象即为链表的唯一元素,需要将新对象的上下节点信息都置为空引用(表明这个链表中此时只有这1个元素,我的上一个是null,下一个也是null),所以,链表中的first也是这个n对象,last也是这个n对象。

如果这个链表不是空的,则first必不为空,此时走到(10~14行的代码),按照add方法的核心思想往链表的末尾新增一个元素,那么n元素就成为了链表的最后一个元素了。

此时n元素的上一个Node就是当前链表的last(10行),因为n要到末尾,所以n的下一个Node是null(12行)。

此时,链表中last元素还是原先的最后一个元素,需要先将last元素的下一个节点引用变为当前的n对象,然后将last对象的引用变为n对象。(13~14行)(此处可能有些绕口)

* 可见:双向链表中的核心思想就是要维护好每一个Node的prev和next。这样才能保证链表不断,一直能链起来,如add方法中13~14行所述。

获取元素,get(int index)方法

我们知道,任实现List都有按下标来取值的此方法,但我们LinkedList并不像数组那样有下标,来直接返回对象,我们刚写了add方法,没有任何涉及到下标的操作。

此时,如果来个get(2)的方法,我们去哪确定下标为2的元素是哪个呢???这就是LinkedList查找多的话,不推荐用此集合的原因所在!!!接下来,来看一下:

 1     public Object get(int index){
 2         Node temp =null;
 3         if(first!=null){
 4             temp=first;
 5             for(int i=0;i<index;i++){
 6                 temp=temp.next;
 7             }
 8         }
 9         return temp.obj;
10     }

 

我们从第一个元素first开始找,如果当前元素的下标不是index的值,则将指针向后移动一位,直到for循环中的i=index值时,此时找到的Node对象就是对应index下标的对象,看起来很耗时,事实上,确实很耗时。

jdk中的LinkedList的寻址都是采用的这种for循环的方式来寻址(jdk会将集合分为2段,判断index大小来确定是从前到后寻址还是从后到前)。因此,毕业生面试时候面试官老喜欢问LinkedList和ArrayList的区别,相信在这能看出LinkedList为何查找慢,而ArrayList是数组实现,只是从对应下标取值即可。

删除元素:remove(int index)

可见,还是通过下标来删除,首先,我们将通过下标查找对象的方法封装一下,用的地方太多了,如下:

    private Node findNode(int index) {
        Node temp =null;
        if(first!=null){
            temp=first;
            for(int i=0;i<index;i++){
                temp=temp.next;
            }
        }
        return temp;
    }

 

通过此方法,我们先找到对应的下标是哪个元素,我们再执行查?删?改?等操作

 1     public Object remove(int index){
 2         Node temp = findNode(index);
 3         if(temp.prev==null){
 4             first=temp.next;
 5         }else{
 6             temp.prev.next=temp.next;
 7             temp.prev=null;
 8         }
 9         if(temp.next==null){
10             last=temp.prev;
11         }else{
12             temp.next.prev=temp.prev;
13             temp.next=null;
14         }
15         size--;
16         return temp.obj;
17     }

 

先通过第2行,找到对应下标的Node元素 ,我们定义为temp,因为每次涉及到元素的变化,我们都需要确定变化处的链表链接关系,

首先排除当前元素的上一个节点为空的情况(3~8行):

如果当前元素即是first,那么此时链表的first即为temp的下一个(第4行),因为temp我们是要删除的。

如果当前不是第一个,那么,需要将temp的上一个Node的下一个Node设置为temp的下一个Node(第6行),再将temp的上一个Node引用断开设为null(7行,因为temp是要被删除的),此时,关于temp的上一个节点的所有引用链都已完成。

此时,再将temp元素后边的引用全部链接完成,那么temp即从链表中断开了,没人能链到它,它也链不到任何人(9~14行)。原理同上

修改set(int index, Object obj)方法原理同删除!相信大家自行可以实现了。在此不在累述!

 

总结:LinkedList慢在寻址

  1. LinkedList方法get速度没有ArrayList相对快,但是如果你确定你的需求中你get都是靠前或靠后的元素,那么get方法也是极快的,因为寻址快。
  2. 删除和插入的速度比ArrayList相对快,因为ArrayList每次中间添加或删除元素,都涉及到一次数组的copy,此操作即其耗时,且新增时可能还会引发数组扩容。如果只是在ArrayList的靠后端插入元素,此时copy的元素较少,ArrayList的插入性能就要大于LinkedList了。

疑问:

 

附上源码(add,get,remove):

package main.java.collection;


public class HkLinkedList {
    Node first;
    Node last;
    
    
    private int size=0;
    
    public HkLinkedList() {
    }
    
    public int size(){
        return size;
    }

    public HkLinkedList(Node first, Node last) {
        super();
        this.first = first;
        this.last = last;
    }
    
    public boolean add(Object obj){
        Node n=new Node();
        if(null==first){
            n.prev=null;
            n.obj=obj;
            n.next=null;
            first=n;
            last=n;
        }else{
            n.prev=last;
            n.obj =obj;
            n.next=null;
            last.next=n;
            last=n;
        }
        size++;
        return true;
    }
    public Object get(int index){
        Node temp = findNode(index);
        return temp.obj;
    }
    
    public Object remove(int index){
        Node temp = findNode(index);
        if(temp.prev==null){
            first=temp.next;
        }else{
            temp.prev.next=temp.next;
            temp.prev=null;
        }
        if(temp.next==null){
            last=temp.prev;
        }else{
            temp.next.prev=temp.prev;
            temp.next=null;
        }
        size--;
        return temp.obj;
    }

    private Node findNode(int index) {
        Node temp =null;
        if(first!=null){
            temp=first;
            for(int i=0;i<index;i++){
                temp=temp.next;
            }
        }
        return temp;
    }






    private static class Node{
        Node prev;
        Object obj;
        Node next;
        
        public Node() {
        }
        
    }

}
View Code

 

posted @ 2018-05-31 16:03  HK博客  阅读(526)  评论(0)    收藏  举报