`
huibin
  • 浏览: 754480 次
  • 性别: Icon_minigender_1
  • 来自: 郑州
社区版块
存档分类
最新评论

Java:LinkedList分析

    博客分类:
  • JAVA
阅读更多

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    private transient Entry<E> header = new Entry<E>(null, null, null);
    private transient int size = 0;

可以看到,LinkedList继承了抽象的序列链表类,并实现了List、Queue、Cloneable、Serializable接口,使LinkedList可以像队列一样进行操作,同时可以克隆和串行化,使其能够保存到文件。

值得我们注意的是,在这里面声明变量时,用到了transient关键词。那么他是什么意思啊?

transient 关键字表示在Serializable 的时候不保存该值。这两个变量在串行化时,不需要保存。

Entry类到底是如何实现的呢?

private static class Entry<E> {
E element;
Entry<E> next;
Entry<E> previous;

Entry(E element, Entry<E> next, Entry<E> previous) {
     this.element = element;
     this.next = next;
     this.previous = previous;
}

}

可以看到了,Entry里面声明了变量elment,next和previous,这就是我们在数据结构中知道的一个节点类声明。这是一个双向链表,不是吗?

public LinkedList() {
        header.next = header.previous = header;
    }

构造函数。初始化链表类。将next和previous指针指向自己。

public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
    }

用一个链表类初始化LinkedList,关于上面的红色部分,据我的理解,这个泛型类要求加入的泛型元素必须是E的子类。

这个构造函数调用了默认构造函数,addAll把c添加到LinkedList中,下面会介绍这个函数。

   public E getFirst() {
if (size==0)
     throw new NoSuchElementException();

return header.next.element;
    }

返回第一个元素。如果size==0,抛出异常,否则就返回head.next.element;

通过这句话,我们似乎明白,这里的数据结构是有头结点的。

public E getLast() {
if (size==0)
     throw new NoSuchElementException();

return header.previous.element;
    }

返回最后一个元素。如果size==0,那么抛出异常。否则返回header.previous.element。

这就是双向链表,将头结点的previous指向最后一个元素。

public E removeFirst() {
return remove(header.next);
    }

public E removeLast() {
return remove(header.previous);
    }

public E remove(int index) {
        return remove(entry(index));
    }

private E remove(Entry<E> e) {
if (e == header)
     throw new NoSuchElementException();

        E result = e.element;
e.previous.next = e.next;
e.next.previous = e.previous;
        e.next = e.previous = null;
        e.element = null;
size--;
modCount++;
        return result;
    }

移走元素。要移走的元素已经确定,不需再找到这个元素。所以直接移走就可以了。先用result指向要移走的元素。然后按照数据结构中的算法移走就可以了。移走后返回移走的元素。

modCount是在AbstractList中声明使用的。用来记录这个链表结构被修改的次数。this list has been

public void addFirst(E e) {
addBefore(e, header.next);
    }

public void addLast(E e) {
addBefore(e, header);
    }

public boolean add(E e) {
addBefore(e, header);
        return true;
    }

   public void add(int index, E element) {
        addBefore(element, (index==size ? header : entry(index)));
    }

private Entry<E> addBefore(E e, Entry<E> entry) {
Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
newEntry.previous.next = newEntry;
newEntry.next.previous = newEntry;
size++;
modCount++;
return newEntry;
    }

将元素e添加到entry前面。很简单,不用分析,相信你也明白。

public boolean contains(Object o) {
        return indexOf(o) != -1;
    }

public int indexOf(Object o) {
        int index = 0;
        if (o==null) {//如果o为null
            for (Entry e = header.next; e != header; e = e.next) {
                if (e.element==null)
                    return index;
                index++;
            }
        } else {不为空,其判断是通过equals进行判断的。
            for (Entry e = header.next; e != header; e = e.next) {
                if (o.equals(e.element))
                    return index;
                index++;
            }
        }
        return -1;
    }

首先获得元素的索引值。如果返回-1,无这个元素。为什么要分元素是不是空这个分支呢?因为如果o是空的话,判断是通过e.element==null判断的。不为空,则通过o.equals(e.element)判断。

public int size() {
return size;
    }

返回大小。

public boolean remove(Object o) {
        if (o==null) {
            for (Entry<E> e = header.next; e != header; e = e.next) {
                if (e.element==null) {
                    remove(e);
                    return true;
                }
            }
        } else {
            for (Entry<E> e = header.next; e != header; e = e.next) {
                if (o.equals(e.element)) {
                    remove(e);
                    return true;
                }
            }
        }
        return false;
    }

也比较容易理解。不做解释了。

public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }

public boolean addAll(int index, Collection<? extends E> c) {
        if (index < 0 || index > size)//如果要插入的位置>size或小于0,异常。
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+size);
        Object[] a = c.toArray();//将集合中的元素转化为数组。
        int numNew = a.length;//返回数组长度
        if (numNew==0)//如果是0的话,遗憾,无法添加了
            return false;
modCount++;//不是0的话,ok,修改计数器加1

        Entry<E> successor = (index==size ? header : entry(index));//如果index==size,那么successor=header
        Entry<E> predecessor = successor.previous;//获得前一个处理元素
for (int i=0; i<numNew; i++) {//对于数组每一个元素,生成entry类,加入到链表中。
            Entry<E> e = new Entry<E>((E)a[i], successor, predecessor);
            predecessor.next = e;
            predecessor = e;
        }
        successor.previous = predecessor;

        size += numNew;
        return true;
    }
如上注解。

public void clear() {
        Entry<E> e = header.next;
        while (e != header) {
            Entry<E> next = e.next;//记录e的next
            e.next = e.previous = null;//去掉e的next和previous
            e.element = null;//去掉自己元素。
            e = next;//处理下一个节点
        }
        header.next = header.previous = header;
        size = 0;
modCount++;
    }

将链表清空。不用解释了。很简单。不过,我们通过这个方法明白一点,要真正的销毁对象,光靠垃圾回收机制是不行的。像这里,给没有用的引用赋空值,使内存中的实体对象没有指针,而加快内存的回收。

public E get(int index) {
        return entry(index).element;
    }

private Entry<E> entry(int index) {
        if (index < 0 || index >= size)
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+size);
        Entry<E> e = header;//获得头结点
        if (index < (size >> 1)) {//如果index小于size的一半
            for (int i = 0; i <= index; i++)
                e = e.next;
        } else {//不小于的话,ok,从后面找。
            for (int i = size; i > index; i--)
                e = e.previous;
        }
        return e;
    }
多么好的算法啊。如果要找的元素在前半部分,那就从头找,如果在最后那么就从尾开始找。获得size的一半也很节省cpu消耗,采用移位的方式,值得学习。

public E set(int index, E element) {
        Entry<E> e = entry(index);
        E oldVal = e.element;
        e.element = element;
        return oldVal;
    }
不再解释。很简单。

下面的分析关系到了LinkedList的遍历、持久化、克隆等相关主题。

public ListIterator<E> listIterator(int index) {
return new ListItr(index);
    }

返回一个从index开始的遍历器。

   private class ListItr implements ListIterator<E> {
private Entry<E> lastReturned = header;//让这里面的参数初始化,最后一个返回的元素。
private Entry<E> next;下一个元素的指针
private int nextIndex;下一个元素的索引
private int expectedModCount = modCount;等于原来类中的修改次数

ListItr(int index) {//构造函数
     if (index < 0 || index > size)//如果给的index不正确
   throw new IndexOutOfBoundsException("Index: "+index+
          ", Size: "+size);
     if (index < (size >> 1)) {//如果index<size的一半
   next = header.next;//找到index所指的元素
   for (nextIndex=0; nextIndex<index; nextIndex++)
      next = next.next;
     } else {
   next = header;//从后面遍历,找到所要的元素
   for (nextIndex=size; nextIndex>index; nextIndex--)
      next = next.previous;
     }
}

public boolean hasNext() {//判断有没有下个元素
     return nextIndex != size;
}

public E next() {获得下一个元素
     checkForComodification();
     if (nextIndex == size)
   throw new NoSuchElementException();

     lastReturned = next;
     next = next.next;
     nextIndex++;
     return lastReturned.element;
}

public boolean hasPrevious() {
     return nextIndex != 0;
}

public E previous() {
     if (nextIndex == 0)
   throw new NoSuchElementException();

     lastReturned = next = next.previous;
     nextIndex--;
     checkForComodification();
     return lastReturned.element;
}

public int nextIndex() {
     return nextIndex;
}

public int previousIndex() {
     return nextIndex-1;
}

public void remove() {
            checkForComodification();
            Entry<E> lastNext = lastReturned.next;
            try {
                LinkedList.this.remove(lastReturned);
            } catch (NoSuchElementException e) {
                throw new IllegalStateException();
            }
     if (next==lastReturned)
                next = lastNext;
            else
   nextIndex--;
     lastReturned = header;
     expectedModCount++;
}

public void set(E e) {
     if (lastReturned == header)
   throw new IllegalStateException();
     checkForComodification();
     lastReturned.element = e;
}

public void add(E e) {
     checkForComodification();
     lastReturned = header;
     addBefore(e, next);
     nextIndex++;
     expectedModCount++;
}

final void checkForComodification() {//
     if (modCount != expectedModCount)
   throw new ConcurrentModificationException();
}
    }

上面的这个类是定义在LinkedList中的私有类。他实现了ListIterator具体的分析参照上面的注释。

public Iterator<E> descendingIterator() {
        return new DescendingIterator();
    }

private class DescendingIterator implements Iterator {
        final ListItr itr = new ListItr(size());
public boolean hasNext() {
     return itr.hasPrevious();
}
public E next() {
            return itr.previous();
        }
public void remove() {
            itr.remove();
        }
    }

上面的函数和类仍然返回不同的遍历器。不再解释。不过DescendingIterator 是递减的遍历器。可以注意到他的next()、hasNext()方法的不同。

public Object clone() {
        LinkedList<E> clone = null;
try {
     clone = (LinkedList<E>) super.clone();//先调用父类的克隆函数
} catch (CloneNotSupportedException e) {
     throw new InternalError();
}

        // Put clone into "virgin" state
        clone.header = new Entry<E>(null, null, null);//自己开始复制,先复制头节点
        clone.header.next = clone.header.previous = clone.header;//给头结点赋值
        clone.size = 0;
        clone.modCount = 0;

        // Initialize clone with our elements
        for (Entry<E> e = header.next; e != header; e = e.next)//把元素也复制过去
            clone.add(e.element);

        return clone;
    }

public Object[] toArray() {
Object[] result = new Object[size];
        int i = 0;
        for (Entry<E> e = header.next; e != header; e = e.next)
            result[i++] = e.element;
return result;
    }

转化成数组。

public <T> T[] toArray(T[] a) {
        if (a.length < size)如果a太小了,那么就在重新给a分配空间吧。
            a = (T[])java.lang.reflect.Array.newInstance(
                                a.getClass().getComponentType(), size);如何分配,那么就用反射机制吧。

//有地方放了,好吧数据放入数组中,呵呵。
        int i = 0;
Object[] result = a;
        for (Entry<E> e = header.next; e != header; e = e.next)
            result[i++] = e.element;

        if (a.length > size)如果a长了,a【size】=null,使不能访问后面的元素。
            a[size] = null;

        return a;
    }

private static final long serialVersionUID = 876323262645176354L;

   private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
// Write out any hidden serialization magic
s.defaultWriteObject();

        // Write out size
        s.writeInt(size);

// Write out all elements in the proper order.
        for (Entry e = header.next; e != header; e = e.next)
            s.writeObject(e.element);
    }

 

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
// Read in any hidden serialization magic
s.defaultReadObject();

        // Read in size
        int size = s.readInt();

        // Initialize header
        header = new Entry<E>(null, null, null);
        header.next = header.previous = header;

// Read in all elements in the proper order.
for (int i=0; i<size; i++)
            addBefore((E)s.readObject(), header);
    }
}

分享到:
评论

相关推荐

    源码解析jdk7.0集合:LinkedList的底层实现原理.pdf

    在深入分析JDK 7.0中LinkedList集合的底层实现原理前,我们首先需要了解LinkedList的基本概念。LinkedList,即双向链表,是一种通过双向指针相互连接的数据结构,每个节点包含数据域以及指向前驱和后继节点的指针。...

    JAVA利用LinkedList构造栈与队列

    在Java编程语言中,LinkedList是一个常用的集合类,它实现了List接口,同时也提供了双向链表的实现...在阅读和分析给定的Queue.java和Stack.java文件时,可以从实现原理、方法调用、线程安全等方面进行深入学习和理解。

    Java 中Linkedlist类的源代码

    LinkedList类的源代码分析对于理解其工作原理非常重要,特别是对于学习数据结构和算法的开发者来说。它展示了如何使用链表数据结构来实现动态列表,以及如何通过内部类和指针操作实现高效的插入、删除和遍历操作。...

    Jdk1.6 Collections Framework源码解析(2)-LinkedList

    8. 性能分析: 由于LinkedList的所有插入、删除操作都需要改变节点的引用,所以其在随机访问和修改时的性能不如ArrayList。但在需要频繁插入、删除元素且对元素顺序有特定要求的情况下,LinkedList的性能更优。 9. ...

    LinkedList:LinkedList上的程序

    LinkedList是Java中的一个类,位于java.util包下,它是List接口的一个实现,主要用于存储动态的、有序的数据集合。与数组不同,链表的元素不需要在内存中连续存储,这使得在链表中插入和删除元素具有较高的效率。 ...

    比较ArrayList、LinkedList、Vector1

    【ArrayList、LinkedList、Vector对比分析】 1. **List概述** List接口是Java集合框架中的重要组成部分,它是一个有序的集合,允许重复元素,并且保持插入顺序。List接口的实现类主要有ArrayList、LinkedList和...

    简单了解java集合框架LinkedList使用方法

    * 数据处理:LinkedList可以用来处理大量数据,例如数据统计、数据分析等。 LinkedList是Java集合框架中的一个重要组件,具有链表底层实现的特点,查询速度慢,增删速度快。通过本文的讲解,读者可以快速掌握...

    java 层次分析法

    Java层次分析法(Analytic Hierarchy Process,AHP)是一种基于多准则决策分析的方法,它通过将复杂问题分解为多个相互关联的子问题,并通过比较和综合这些子问题的相对重要性来解决整个问题。在Java编程环境中实现...

    疯狂JAVA:突破程序员基本功的16课 源代码

    通过分析和研究这些源代码,读者可以深入理解Java语言的特性和应用。 1. **Java基础**:书中源代码可能包括了Java的基础语法,如变量、数据类型、控制结构(if、for、while)、类与对象、封装、继承和多态等概念。...

    Java code Java code

    每个文件都可能包含独立的Java编程知识点,通过阅读和分析这些代码,学习者可以深入理解Java语言的不同方面。为了充分利用这些资源,可以逐个文件打开,理解并实践其中的代码,同时查阅相关的Java教程和文档以增强...

    Java中ArrayList和LinkedList区别 时间复杂度 与空间复杂度1

    6. 示例代码分析: 给定的代码中,通过二分查找测试了 ArrayList 和 LinkedList 的访问速度。由于二分查找依赖于随机访问,ArrayList 在这个例子中表现得更快。实际运行结果会显示 ArrayList 消耗的时间少于 ...

    ArrayList LinkedList Vector性能测试

    在Java编程语言中,ArrayList、LinkedList和Vector是三种常见的动态数组实现,它们都在java.util包中,用于存储和管理对象的集合。这三个类都实现了List接口,提供了多种操作方法,但它们在内部实现和性能特性上有所...

    Map+List+ArrayList+LinkedList Java源码

    Java编程语言中的`Map`, `List`, `ArrayList` 和 `LinkedList` 是四个核心的数据结构,它们在实际开发中被广泛使用。了解它们的源码对于深入理解Java集合框架的内部工作原理至关重要,尤其是对初学者而言,这有助于...

    Java LinkedList源码分析

    简介  LinkedList 是一个常用的集合类,用于顺序存储元素。 LinkedList 经常和 ArrayList 一起被提及。...本文分析 LinkedList 的具体实现。  继承关系  public class LinkedList  extends AbstractS

    2022年java语言-java语言程序设计.docx

    Java 试题解析包括对 Java 试题的分析和解释,可以帮助考生更好地准备 Java 考试。 8. Java collection 框架 Java collection 框架提供了一个通用的集合类框架,能够帮助开发者更方便地操作数据。 9. Java IO ...

    数据结构与算法分析:Java语言描述

    在《数据结构与算法分析:Java语言描述》一书中,作者深入探讨了这一领域的关键概念和技术,并通过Java编程语言进行了具体的实现和演示。 ### 数据结构 数据结构是指在计算机中存储、组织数据的方式,它不仅影响到...

    Java中ArrayList和LinkedList区别

    以下是对这两个类的详细分析: 1. 数据结构: - ArrayList是基于动态数组的数据结构,即在内存中分配一块连续的空间来存储元素。这种设计使得ArrayList支持随机访问,因为可以通过索引直接定位到元素。 - ...

    编译原理 C、Java语言词法分析器(java实现)

    在处理过程中,词法分析器会将每个识别到的记号存储在一个数据结构中,如ArrayList或LinkedList,以便后续的语法分析阶段使用。 图形用户界面(GUI)的设计使得用户能直观地看到分析结果。它可以显示源代码中的行号...

    Java应用:两种Java容器类List和Set分析

    ### Java应用:两种Java容器类List和Set分析 #### 一、概述 在Java编程语言中,集合框架(Collections Framework)是处理数据的核心组件之一,它提供了存储和操作对象的各种方式。本文将深入探讨Java中的两种重要...

    Java超市经营分析管理系统设计与实现(毕业设计).zip

    4. **Java集合框架**:在处理数据时,Java的集合框架如ArrayList、LinkedList、HashMap等会广泛应用于数据的存储和操作。 5. **JDBC**:Java数据库连接,是Java访问数据库的标准API,用于建立、配置和管理数据库...

Global site tag (gtag.js) - Google Analytics