带你手写LinkedList
前言
LinkedList,顾名思义,链表!是List接口下的一个实现类。看过jdk6,jdk7,jdk8的LinkedList源码,稍有差异。但大体实现细节都是相同的。或者说,链表本身是属于一种数据结构,任何语言的链表的实现套路都是大同小异。
本文所写的LinkedList也只是实现的一种简易版双向链表帮助大家理解。在命名方面有部分参考过jdk的命名规则!
关注点
不只是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慢在寻址
- LinkedList方法get速度没有ArrayList相对快,但是如果你确定你的需求中你get都是靠前或靠后的元素,那么get方法也是极快的,因为寻址快。
- 删除和插入的速度比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() { } } }
============================================================================
如果不觉得一年前的自己是个傻子,那说明这一年你没有任何进步!
============================================================================

浙公网安备 33010602011771号