Java实现单向双向链表原理分析

何为链表

链式结构是一种使用对象引用变量来创建对象间的链的数据结构。

对象引用变量可用于创建链式结构

对象引用变量所存储的特定地址一般无关紧要。换句话说,重要的是能够使用引用变量来访问对象,而对象在内存中的特定存储位置并不重要。因此,我们一般将引用变量描述为一个“指向”对象的名称,而不是显示其地址,按照这种理解,引用变量有时称为“指针”


一个指向对象的对象引用变量

下面的这个类就是一个链式结构:

123456
public class Person {    private String name;    private String address;

private Person next;	// Person对象的引用}

这种类型的对象有时称为自引用对象。
这种关系构成了链表的基础。链表是一种链式结构,该结构中的每个对象都指向下一个对象,从而构成了链表中对象的线性次序。。链表中存储的对象一般称为链表的节点。


链表

注意,链表单独需要一个引用变量,用以表示其第一个结点。如果某个结点的next引用为null,则表示链表在该结点处终止

实现一个单向链表

插入结点

可能会在链表的任何位置插入一个结点:在链表的首部,或在链表中任何内部结点之间,或在链表的尾部。

(1)在首部插入结点
第一步,设置所添加结点的next引用,使其指向链表中当前的第一个结点。
第二步,重新设置指向链表首部的引用,使其指向这个新添加的结点。

在链表的首部插入一个结点

注意:如果上述这些步骤颠倒了,则将会遇到困难。如果首先重置front引用,则将失去这个唯一指向现有链表的引用,而且再也不能获取它。

(2)在其他部分插入结点
首先,设置新节点的next引用,使其指向current锁引用结点的下一个结点。然后,重新设置当前结点的next引用,使其指向这个新的结点。

在链表的中部插入一个结点

删除结点

对链表中的第一个结点的处理一般也比较特殊。
要删除链表的第一个结点,则要重新设置指向链表首部的引用,使其指向链表中当前的第二个结点。

删除链表的第一个结点

一旦找到索要删除的结点,则重新设置前一个结点的next引用,使其指向的结点与当前结点的next引用所指向的结点相同。然后,可以根据需要使用这个删除的结点。

删除链表内部结点

代码实现

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
package chain;

/** * Created by benjamin on 1/18/16. * 单向链表的实现 */public class NodeList<E> {

private static class Node<E> {        E data;        Node<E> next;        public Node(E data, Node<E> next) {            this.data = data;            this.next = next;        }    }

/** 首元素的引用 **/    private Node<E> head;

/** 尾元素的引用 **/    private Node<E> last;

/** 其他元素的引用 **/    private Node<E> other;

/** list的元素个数 **/    private int size = 0;

/**     * 默认构造     */    public NodeList() {        head = null;        last = head;    }

/**     * 构造一个有参数的list     * @param data  初始化参数     */    public NodeList(E data) {        Node<E> d = new Node<>(data, null);        head = d;        last = head;        size++;    }

/**     * 增加一个数据到list中     * @param data 新增的数据     */    public void add(E data) {        Node<E> d = new Node<>(data, null);        if (isEmpty()) {            head = d;            last = head;        } else {            last.next = d;            last = d;        }        size++;    }

/**     * 在index处添加元素data,其他元素后移     * @param index 元素索引,从0开始.     * @param data 新增元素     */    public void add(int index, E data) {        checkIndex(index);

other = head;        if (index == 0) {   //如果加在第一个,就把head的引用设为新元素对象            Node<E> d = new Node<>(data, other);            head = d;        } else {            for (int i = 0; i < index; i++) {                other = other.next;            }            Node<E> d = new Node<>(data, other.next);            other.next = d;        }        size++;    }

/**     * 替换原有的值为newValue     * @param oldValue 旧值     * @param newValue 新值     * @return 如果替换成功返回true, 否则返回false     */    public boolean set(E oldValue, E newValue) {        other = head;        while (other != null) {            if (other.data.equals(oldValue)) {                other.data = newValue;                return true;            }            other = other.next;        }        return false;    }

/**     * 在指定的值后面插入一个新值     * @param specidiedData 指定的值     * @param newData 新值     * @return 如果插入成功返回true,否则返回false     */    public boolean addAfter(E specidiedData, E newData) {        other = head;        while (other != null) {            if (other.data.equals(specidiedData)) {                Node<E> d = new Node<>(newData, other.next);                other.next = d;                size++;                return true;            }            other = other.next;        }        return false;    }

/**     * 根据索引, 获得此处的数据     * @param index 索引     * @return 获得的数据     */    public E get(int index) {        checkIndex(index);        other = head;        for (int i = 0; i < index; i ++) {            other = other.next;        }        return other.data;    }

/**     * 删除list中存在的data     * @param data 要删除的数据     * @return  如果删除成功返回true,否则返回false     */    public boolean remove(E data) {        other = head;        Node<E> pre = other;        for (int i = 0; i < size; i++) {            if (other.data.equals(data)) {                if (i == 0) {                    head = other.next;                    return true;                }                pre.next = other.next;                return true;            }            pre = other;            other = other.next;        }        return false;    }

/**     * 是否包含传入的元素     * @param data 传入的数据     * @return 如果存在返回true, 否则false     */    public boolean contains(E data) {        other = head;        for (int i = 0; i < size; i++) {            if (other.data.equals(data)) {                return true;            }        }        return false;    }

private boolean isEmpty() {        return size == 0 || head == null;    }

/**     * 清空链表     */    public void clear() {        head = null;        size = 0;    }

public void checkIndex(int index) {        if (index < 0 || index > size-1)            throw new IndexOutOfBoundsException("传入的索引无效: " + index);    }

/**     * 打印全部链表数据     */    public void print() {        if (head == null || size == 0)            System.out.println("链表为空");        else {            other = head;            while (other != null) {                System.out.print(other.data + " ");                other = other.next;            }            System.out.println();        }    }

}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
package chain;

/** * Created by piqiu on 1/18/16. */public class NodeListTest {

public static void main(String[] args) {        NodeList<String> nodeList = new NodeList<>("first");        nodeList.add("a");        nodeList.add("b");        nodeList.add("c");        nodeList.print();

nodeList.add(2, "addNew");        nodeList.print();

nodeList.set("c", "C");        nodeList.print();

nodeList.addAfter("addNew", "addNew2");        nodeList.print();

System.out.println(nodeList.get(2));

nodeList.remove("addNew2");        nodeList.print();        nodeList.remove("first");        nodeList.print();

System.out.println(nodeList.contains("a"));        System.out.println(nodeList.contains("A"));

nodeList.clear();        nodeList.print();

/**         * first a b c           first a b addNew c           first a b addNew C           first a b addNew addNew2 C           b           first a b addNew C           a b addNew C           true           false           链表为空         */    }}

上面只是单向链表的简单实现。

实现一个双向链表

链式结构的另一种实现就是双向链表。双向链表维护两个引用:一个指向链表的第一个结点,另一个指向最后一个结点。链表中的每个结点都存储两个引用:一个指向下一个结点,另一个指向前一个结点。

下面是我仿着LinkedList写的双向链表。自己写完后发现很多地方原作者写的真是精妙,把增加元素、移除元素抽离出来,以及写法的精简、效率的考虑(利用索引构造结点处判断index是否小于size>>1),自己受益匪浅。

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
package chain;

import java.io.Serializable;import java.util.NoSuchElementException;

/** * Created by benjamin on 1/18/16. */public class LinkedList<E> implements Serializable {

private static final long serialVersionUID = -5635851059340344485L;

/**     * 结点类     * @param <E>     */    private static class Node<E> {        E item;        Node<E> next;        Node<E> prev;

Node(Node<E> prev, E item, Node<E> next) {            this.prev = prev;            this.item = item;            this.next = next;        }    }

/** 第一个结点的引用 **/    transient Node<E> first;

/** 最后一个结点的引用 **/    transient Node<E> last;

/** list中元素的数目 **/    transient int size = 0;

/** 操作次数 **/    transient int modCount = 0;

public LinkedList() {    }

/**     * 把传入的元素变为第一个元素     * @param e     */    private void linkFirst(E e) {        Node<E> f = first;        Node<E> newNode = new Node<>(null, e, f);        if (f == null)            last = newNode;        else            f.prev = newNode;        first = newNode;        size++;        modCount++;    }

/**     * 在最后面插入元素     * @param e     */    private void linkLast(E e) {        Node<E> l = last;        Node<E> newNode = new Node<>(l, e, null);        if (l == null)            first = newNode;        else            l.next = newNode;        last = newNode;        size++;        modCount++;    }

/**     * 在指定结点之前插入元素     * @param e     * @param succ 指定结点     */    private void linkBefore(E e, Node<E> succ) {        final Node<E> pred = succ.prev;        final Node<E> newNode = new Node<>(pred, e, succ);        succ.prev = newNode;        if (pred == null)            first = newNode;        else            pred.next = newNode;        size++;        modCount++;    }

/**     * 取消第一个结点的引用     */    private E unlinkFirst(Node<E> f) {        final E element = f.item;        final Node<E> next = f.next;        f.item = null;        f.next = null;        first = next;        if (next == null)            last = null;        else            next.prev = null;        size--;        modCount++;        return element;    }

/**     * 取消最后一个结点的引用     * @param l     * @return 最后一个结点的值     */    private E unlinkLast(Node<E> l) {        final E element = l.item;        final Node<E> pred = l.prev;        l.item = null;        l.prev = null;        last = pred;        if (pred == null)            first = null;        else            pred.next = null;        size--;        modCount++;        return element;    }

/**     * 取消结点x的引用     * @param x     * @return 取消的结点元素     */    E unlink(Node<E> x) {        final E element = x.item;        final Node<E> next = x.next;        final Node<E> pred = x.prev;

// 如果是第一个        if (pred == null)            first = next;        else {            pred.next = next;            x.prev = null;        }

//如果是最后一个        if (next == null)            last = pred;        else {            next.prev = pred;            x.next = null;        }

x.item = null;        size--;        modCount++;        return element;    }

/**     * 取得第一个元素     * @return 第一个元素     */    public E getFirst() {        final Node<E> f = first;        if (f == null)            throw new NoSuchElementException();        return f.item;    }

/**     * 取得最后一个元素     * @return 最后一个元素     */    public E getLast() {        final Node<E> l = last;        if (last == null)            throw new NoSuchElementException();        return l.item;    }

/**     * 删除第一个元素     * @return 第一个被删除的元素     */    public E removeFirst() {        final Node<E> f = first;        if (f == null)            throw new NoSuchElementException();        return unlinkFirst(f);    }

/**     * 删除最后一个元素     * @return 最后一个被删除的元素     */    public E removeLast() {        final Node<E> l = last;        if (l == null)            throw new NoSuchElementException();        return unlinkLast(l);    }

/**     * 增加一个元素到list的第一个位置     * @param e     */    public void addFirst(E e) {        linkFirst(e);    }

/**     * 增加一个元素到list的结尾     * @param e     */    public void addLast(E e) {        linkLast(e);    }

/**     * 增加一个元素(默认增加在结尾)     * @param e     * @return true     */    public boolean add(E e) {        linkLast(e);        return true;    }

/**     * 删除list中存在传入的对象     * @param o     * @return 如果改变了list,返回true,否则false     */    public boolean remove(Object o) {        if (o == null) {            for (Node<E> x = first; x != null; x = x.next) {                if (x.item == null) {                    unlink(x);                    return true;                }            }        } else {            for (Node<E> x = first; x != null; x = x.next) {                if (o.equals(x.item)) {                    unlink(x);                    return true;                }            }        }        return false;    }

/**     * 清空list,所有引用对象置为null     */    public void clear() {        for (Node<E> x = first; x != null; ) {            Node<E> next = x.next;            x.item = null;            x.prev = null;            x.next = null;            x = next;        }        first = null;        last = null;        size = 0;        modCount++;    }

/**     * 获取索引处的元素值     * @param index 索引     * @return 元素值     */    public E get(int index) {        checkIndex(index);        return node(index).item;    }

/**     * 替换索引处的值为element     * @param index 索引     * @param element 新值     * @return 旧值     */    public E set(int index, E element) {        checkIndex(index);        Node<E> oldNode = node(index);        E oldValue = oldNode.item;        oldNode.item = element;        return oldValue;    }

/**     * 在指定索引的地方插入元素element,原来的元素以及之后的元素后移     * @param index 插入元素的索引     * @param element 插入的元素     */    public void add(int index, E element) {        checkIndex(index);

if (index == size)            linkLast(element);        else            linkBefore(element, node(index));    }

/**     * 移除索引处的元素     * @param index 索引     * @return 删除的元素     */    public E remove(int index) {        checkIndex(index);        return unlink(node(index));    }

/**     * 获取对象在list中的索引     * @param o 要查找的对象     * @return 如果找到了对象,返回对应的索引值,否则返回-1     */    public int indexOf(Object o) {        int index = 0;        if (o == null) {            for (Node<E> x = first; x != null; x = x.next) {                if (x.item == null)                    return index;                index++;            }        } else {            for (Node<E> x = first; x != null; x = x.next) {                if (o.equals(x.item))                    return index;                index++;            }        }        return -1;    }

/**     * 是否包含元素     * @param o     * @return 如果包含返回true     */    public boolean contains(Object o) {        return indexOf(o) != -1;    }

/**     * 返回list中元素的大小     * @return 元素的大小     */    public int getSize() {        return size;    }

/**     * 根据索引得到结点     * @param index 索引     * @return 结点     */    Node<E> node(int index) {

// 如果是大小的一半之前,正序查找,否则倒序查找,提高效率        if (index < (size >> 1)) {            Node<E> f = first;            for (int i = 0; i < index; i++)                f = f.next;            return f;        } else {            Node<E> l = last;            for (int i = size - 1; i > index; i--)                l = l.prev;            return l;        }    }

/**     * 检查索引是否正确,不正确抛出 IndexOutOfBoundsException 异常     * @param index     */    private void checkIndex(int index) {        if (!isElementIndex(index))            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));    }

/**     * 越界的错误信息     * @param index 越界的错误索引     * @return 越界的msg     */    private String outOfBoundsMsg(int index) {        return "Index: " + index + ", Size: " + size;    }

/**     * 检查索引是否没有溢出     * @param index 索引     * @return 如果索引正确返回true     */    private boolean isElementIndex(int index) {        return index >= 0 && index < size;    }}
时间: 2024-09-20 18:21:57

Java实现单向双向链表原理分析的相关文章

java中HashMap的原理分析_java

我们先来看这样的一道面试题: 在 HashMap 中存放的一系列键值对,其中键为某个我们自定义的类型.放入 HashMap 后,我们在外部把某一个 key 的属性进行更改,然后我们再用这个 key 从 HashMap 里取出元素,这时候 HashMap 会返回什么? 文中已给出示例代码与答案,但关于HashMap的原理没有做出解释. 1. 特性 我们可以用任何类作为HashMap的key,但是对于这些类应该有什么限制条件呢?且看下面的代码: public class Person { priva

Java阻塞队列实现原理分析

Java中的阻塞队列接口BlockingQueue继承自Queue接口. BlockingQueue接口提供了3个添加元素方法: add:添加元素到队列里,添加成功返回true,由于容量满了添加失败会抛出IllegalStateException异常; offer:添加元素到队列里,添加成功返回true,添加失败返回false; put:添加元素到队列里,如果容量满了会阻塞直到容量不满. 3个删除方法: poll:删除队列头部元素,如果队列为空,返回null.否则返回元素; remove:基于对

Java中的ConcurrentHashMap原理分析节选

集合是编程中最常用的数据结构.而谈到并发,几乎总是离不开集合这类高级数据结构的支持.比如两个线程需要同时访问一个中间临界区Queue,比如常会用缓存作为外部文件的副本HashMap.这篇文章主要分析jdk1.5的3种并发集合类型concurrent,copyonright,queue中的ConcurrentHashMap,让我们从原理上细致的了解它们,能够让我们在深度项目开发中获益非浅.      在tiger之前我们使用得最多的数据结构之一就是HashMap和Hashtable.大HashMa

Java 框架 Netty 实现原理分析

文将主要分析Netty实现方面的东西,由于精力有限,本人并没有对其源码做了极细致的研 究.如果下面的内容有错误或不严谨的地方,也请指正和谅解.对于Netty使用者来说,Netty提供了几个典型的example,并有详尽的API doc和guide doc,本文的一些内容及图示也来自于Netty的文档,特此致谢. 1.总体结构 先放上一张漂亮的Netty总体结构图,下面的内容也主要围绕该图上的一些核心功能做分析,但对如Container Integration及Security Support等高

深入掌握Java技术EJB调用原理分析二

Home接口的Weblogic实现类的stub类 ((Hello Bean))_HomeImpl_WLStub(部署的时候动态生成字节码) Home接口的Weblogic实现类的skeleton类 ((Hello Bean))_HomeImpl_WLSkeleton(部署的时候动态生成字节码) Remote接口:Hello (用户编写) Remote接口的Weblogic实现类 ((Hello Bean))_EOImpl(EJBC生成) Remote接口的Weblogic实现类的stub类 ((

深入掌握Java技术EJB调用原理分析一

一个远程对象至少要包括4个class文件:远程对象:远程对象的接口:实现远程接口的对象的stub:对象的skeleton这4个class文件. 在EJB中则至少要包括10个class: Bean类,特定App Server的Bean实现类,Bean的remote接口,特定App Server的remote接口实现类,特定App Server的remote接口的实现类的stub类和skeleton类. Bean的home接口,特定App Server的home接口实现类,特定App Server的

Java8 HashMap的实现原理分析_java

前言:Java8之后新增挺多新东西,在网上找了些相关资料,关于HashMap在自己被血虐之后痛定思痛决定整理一下相关知识方便自己看.图和有些内容参考的这个文章:http://www.jb51.net/article/80446.htm HashMap的存储结构如图:一个桶(bucket)上的节点多于8个则存储结构是红黑树,小于8个是单向链表. 1:HashMap的一些属性 public class HashMap<k,v> extends AbstractMap<k,v> impl

java的nio之:java的nio的原理

转载:http://weixiaolu.iteye.com/blog/1479656         Java NIO原理图文分析及代码实现 前言: 最近在分析hadoop的RPC(Remote Procedure Call Protocol ,远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议.可以参考:http://baike.baidu.com/view/32726.htm )机制时,发现hadoop的RPC机制的实现主要用到了两个技术:动态代理(

ToyBricks简介以及原理分析

ToyBricks背景 我始终认为,在高内聚,低耦合的原则下,进行组件化,模块化,插件化都是移动应用开发的趋势. 为什么这么说呢?下面我们举个栗子:大家都知道,以前Android应用开发中,可以使用HttpClient或者HttpUrlConnection来进行http访问.这里假设有一个耦合严重,但代码量巨大的项目,使用了基于HttpClient封装的loopj/android-async-http来进行http访问.但是,后来,Google明确支持使用HttpUrlConnection.此时