java之list源码浅析

三大数据结构链表、树和图,顺序表作为其中的一种,可以说是平时编程中最长使用到的。List接口是顺序表在java中的实现,它有很多子接口和实现类,平时的编程中使用起来非常方便。但是更进一步,我们有必要对其实现和原理进行理解,并和数据结构中所学比较,并应用于平时的编程中,编写出高效率的代码。

首先看下list接口的层次关系,下图由本人根据jdk的类结构简单画的:

从上图可以看出,list接口有Collection接口衍生而出,那么首先了解下Collection接口。

Collection

collection 是集合框架的根,他定义的集合操作的通用行为,如添加元素、批量添加、删除、批量删除、集合大小、包含、迭代器等。它的接口定义这里不再贴,在关系上,Collection是继承于Iterable接口的,其只有一个方法:

public interface Iterable<T> {

    /**
     * Returns an {@link Iterator} for the elements in this object.
     *
     * @return An {@code Iterator} instance.
     */
    Iterator<T> iterator();
}

其中Iterator如下:

public interface Iterator<E> {

    public boolean hasNext();

    public E next();

    public void remove();
}

AbstractCollection

AbstractCollection 是Collection接口的抽象实现,实现了其中大部分的功能。如下所示,我们实现自己的Collection只需要实现三个方法即可:

Collection<String> c =
           /**
        *
        *自定义实现示例,在AbstractCollection 中的一些方法的实现中,如Clear,调用了
        *iterator()方法,而在这些方法在子类中,实现这是典型的template模式。
        *
        */
       new AbstractCollection<String>() {

           /* *
            * 迭代器
            */
           @Override
           public Iterator<String> iterator() {
              // TODO Auto-generated method stub
              return null;
           }

           /*
            * 大小
            */
           @Override
           public int size() {
              // TODO Auto-generated method stub
              return 0;
           }

           /*
            * 抽象实现是抛出异常,我们需要自己实现
            */
           @Override
           public boolean add(String e) {
              return true;
           }
       };

如下代码片段摘自AbstractCollection,调用了iterator方法,其中有很多类似代码,如addAll、removeAll、contains、toArray()等,这些实现只是基本的实现,子类中有更有效率的就会覆盖它。典型的可见后面的Arraylist的toArray()。

public void clear() {
    Iterator<E> e = iterator();
    while (e.hasNext()) {
        e.next();
        e.remove();
    }
    }

List

List接口表示数据结构中的链表,其继承collection接口,又往里面添加了一些链表操作的方法,主要是随机访问、删除、查找、专用的迭代器等,如下所示:

/**
   随机获取
*/
E get(int index);

    /**
     * 随机设置值
     */
    E set(int index, E element);

    /**
     * 随机添加
     */
    void add(int index, E element);

    /**
     *随机移除
     */
    E remove(int index);

    // Search Operations

    /**
     * 查找
     */
    int indexOf(Object o);

    /**
     * 从后查找
     */
    int lastIndexOf(Object o);

    // List Iterators

    /**
     *专用迭代
     */
    ListIterator<E> listIterator();

    /**
     * 从某个位置迭代
     */
    ListIterator<E> listIterator(int index);

    // View

    /**
     * 子列表
     */
    List<E> subList(int fromIndex, int toIndex);

AbstractList

这是List接口的抽象实现,和AbstractCollection类似,实现了基本的功能而把关键的方法延迟到子类中实现。如下所示一个示意子类:

List<String>  l = /**
        *
        *示例实现
        */
       new AbstractList<String>() {

           /*
            * 随机获取
            */
           @Override
           public String get(int index) {
              return null;
           }

           /*
            * 大小
            */
           @Override
           public int size() {
              return 0;
           }

           /*
            * 超类中实现抛出异常,表示不可变list
            *
            * 自己实现后变为可变
            */
           @Override
           public String set(int index, String element) {
              return null;
           }

           /*
            * 默认实现抛出异常
            */

           @Override
           public void add(int index, String element) {
           }

           /**
            * 默认实现抛出异常
            */
           @Override
           public String remove(int index) {

              return null;
           }

       };

ListIterator

List接口添加了新的方法,其中一个方法就是返回ListIterator,其扩展了iterator,增加了向前遍历的方法。主要链表是有序的。

其实现不多说,需要注意的就是迭代器失效问题,在list实现中,维护了一个字段modCount,当此list进行改变操作,如add/remove等的时候,此值会进行改变,当构造迭代器的时候,此值会在迭代器中保留一个副本,使用迭代器中的方法都会检查副本和list中的modCount是否一致,如果不一致,抛出迭代器异常。需要注意的是,java中的for each语法,经过编译后,是使用的迭代器。

如下部分源码示例:

private class Itr implements Iterator<E> {
    /** 此类是个内部类,直接访问abstractList的字段
     * Index of element to be returned by subsequent call to next.
     */
    int cursor = 0;

    /**
     * Index of element returned by most recent call to next or
     * previous.  Reset to -1 if this element is deleted by a call
     * to remove.
     */
    int lastRet = -1;

    /**
     * 注意这点
     */
    int expectedModCount = modCount;

/**
     *迭代器的next方法
     */

    public E next() {

/**
     *操作前进行检查
     */
            checkForComodification();
        try {
       E next = get(cursor);
       lastRet = cursor++;
       return next;
        } catch (IndexOutOfBoundsException e) {
       checkForComodification();
       throw new NoSuchElementException();
        }
    }
/**
     *检查迭代器失效问题。
     */
    final void checkForComodification() {
        if (modCount != expectedModCount)
       throw new ConcurrentModificationException();
    }
    }

ArrayList

这是一般编程中最长使用的数据结构,基于数组实现的顺序表,但是数组可自动扩容。它直接继承于abstractList,故只研究其关键实现和方法。

数组存储

ArrayList是基于数组的存储,默认构造初始大小是10,后面我们会看到这个初始大小会一定程度上影响其性能:

/**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer.
     */
private transient Object[] elementData;
public ArrayList(int initialCapacity) {
    super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
    this.elementData = new Object[initialCapacity];
    }

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
    this(10);
    }

Add方法

List接口有两个add的重载方法,第一个是在list的末尾添加元素,第二个是随机位置添加元素,其自动扩展数据容量的方法是ensureCapacity(int),保证大小:

/**
     * 此方法在父类中已经实现,但是arralylist覆盖了其实现,采用更加有效率的实现
     */
    public boolean add(E e) {
       ensureCapacity(size + 1);  // Increments modCount!!
       elementData[size++] = e;
       return true;
        }

        /**
         * 任意位置添加元素,相应的元素后移,保持链表的特性。
         * 其中System.arrayCopy效率较高。
         *
         */
        public void add(int index, E element) {
       if (index > size || index < 0)
           throw new IndexOutOfBoundsException(
           "Index: "+index+", Size: "+size);

       ensureCapacity(size+1);  // Increments modCount!!
       System.arraycopy(elementData, index, elementData, index + 1,
               size - index);
       elementData[index] = element;
       size++;
        }

        /**
         * 保证list链表的大小,如果不足则扩容。
         * 这个方法比较消耗性能,因此,如果实现可以知道或者预估AyyayList的大小,那么
         * 可以在构造的时候设定合适的初始容量。
         */
        public void ensureCapacity(int minCapacity) {
       modCount++;
       //获取旧的数组大小
       int oldCapacity = elementData.length;
       //比较,如果不足则扩容
       if (minCapacity > oldCapacity) {
           Object oldData[] = elementData;
           int newCapacity = (oldCapacity * 3)/2 + 1;
            if (newCapacity < minCapacity)
           newCapacity = minCapacity;
                // 调用效率较高的arraycopy
                elementData = Arrays.copyOf(elementData, newCapacity);
       }
        }

Vector

实现和ArraaylIst基本一致,只是在方法上加了同步操作,但需要注意其线程安全是相对的,比如一个线程进行add操作,另外一个线程迭代,肯定会出异常。

另外有个Stack继承Vector,添加了栈的相关方法,如push,和pop。

LinkedList

基于链表的顺序表,和ArrayList相比,其随机插入和删除的效率较高,因为其不需要扩容和移动元素操作,但是随机访问效率较低(随机访问需要从头节点遍历)。

AbstractSequentialList

LinkedList其继承于AbstractSequentialList,而其中AbstractSequentialList实现了abstractlist中的抽象方法,都是基于迭代器实现的,它同时把返回迭代器的方法覆盖为抽象方法。故LinkedList实现的关键在于迭代器,如下所示:

public abstract class AbstractSequentialList<E> extends AbstractList<E> {

    /**
          *基于迭代器实现的get
     */
    public E get(int index) {
        try {
            return listIterator(index).next();
        } catch (NoSuchElementException exc) {
            throw new IndexOutOfBoundsException("Index: "+index);
        }
    }

    /**
     *基于迭代器实现的set
     */
    public E set(int index, E element) {
    try {
        ListIterator<E> e = listIterator(index);
        E oldVal = e.next();
        e.set(element);
        return oldVal;
    } catch (NoSuchElementException exc) {
        throw new IndexOutOfBoundsException("Index: "+index);
    }
    }

/*
基于迭代器实现的add

     */
    public void add(int index, E element) {
    try {
        listIterator(index).add(element);
    } catch (NoSuchElementException exc) {
        throw new IndexOutOfBoundsException("Index: "+index);
    }
    }

    /**
     * 基于迭代器实现的remove
     *
     */
    public E remove(int index) {
    try {
        ListIterator<E> e = listIterator(index);
        E outCast = e.next();
        e.remove();
        return outCast;
    } catch (NoSuchElementException exc) {
        throw new IndexOutOfBoundsException("Index: "+index);
    }
}

    public Iterator<E> iterator() {
        return listIterator();
    }

    /**
     * Returns a list iterator over the elements in this list (in proper
     * sequence).
     *
     */
    public abstract ListIterator<E> listIterator(int index);
}

LinkedList

Linkedlist除了实现了List接口,还实现了队列的相关接口,这里略过不提。由listiterator接口,可知linkedList也是一个可双向遍历的顺序表。

只需研究每个链表节点的结构和迭代器实现。

链表节点如下所示,是一个静态内部类:

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;
    }
    }

其迭代器实现是一个内部类,接下来以其add操作说明,其他类似:

private class ListItr implements ListIterator<E> {
           private Entry<E> lastReturned = header;
           private Entry<E> next;
           private int nextIndex;
           private int expectedModCount = modCount;

           /**
            * 构造的时候采用一个优化技巧,根据index决定从前还是从后遍历。
            */
           ListItr(int index) {
               if (index < 0 || index > size)
              throw new IndexOutOfBoundsException("Index: "+index+
                                ", Size: "+size);
               if (index < (size >> 1)) {
              next = header.next;
               for (nextIndex=0; nextIndex<index; nextIndex++)
                  next = next.next;
               } else {
              next = header;
              for (nextIndex=size; nextIndex>index; nextIndex--)
                  next = next.previous;
               }
           }

           /*
            * 链表插入,在构造此迭代器的时候,index就是插入的位置,故直接插入元素即可
            *
            * addbefor是ArrayList的方法,就是链表插入,指针改变的过程,这里不赘述。
            */
           public void add(E e) {
               checkForComodification();
               lastReturned = header;
               addBefore(e, next);
               nextIndex++;
               expectedModCount++;
           }

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

          /**
         *AbstractSequentialList的方法,随机插入。首先构造从index开始的迭代器
         */
        public void add(int index, E element) {
           try {
               listIterator(index).add(element);
           } catch (NoSuchElementException exc) {
               throw new IndexOutOfBoundsException("Index: "+index);
           }
           }

CopyOnWriteArrayList

CopyOnWriteArrayList是java并发包中的一个工具类,在jdk1.5的时候被引入,单独一个它的内容足以写一篇文章,故这里不再展开,只是简要对其说明。

其基本思想从名称中可以看出,当进行写操作的时候,先复制复制出一个副本,写操作对副本进行。

读和遍历操作发生在操作发生的那一刻存在的副本上。

写操作的时候枷锁,读操作的时候不加锁。并通过java内存模型的语义,进行优雅的设计。

CopyOnWrite并发容器用于读多写少的并发场景。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。

具体可以参考一下文章:

聊聊并发-Java中的Copy-On-Write容器

Java内存模型happens-before俗解

时间: 2024-10-14 02:36:06

java之list源码浅析的相关文章

java之Map源码浅析

Map是键值对,也是常用的数据结构.Map接口定义了map的基本行为,包括最核心的get和put操作,此接口的定义的方法见下图: JDK中有不同的的map实现,分别适用于不同的应用场景,如线程安全的hashTable和非线程安全的hashMap. 如下图是JDK中map接口的子类UML类图,其中有个特例Dictionary已经不建议使用: Map接口中的方法我们需要关注的就是get.put 和迭代器相关的方法如entrySet().keySet().values()方法. Entry 在开始分析

java.util.HashMap源码浅析之解决hash冲突

HashMap是java无论是企业管理系统还是web或者其他应用层的程序开发,都是应用比较多的一种数据结构,正好最近面试有问到与HashMap解决hash冲突的方式(本人菜比没答上来),现浅析源码以解惑 且记录,将来在项目上尽量避免此类问题的出现,大家都知道HashMap为key-value存储,在HashMap中,HashMap本身拥有一个Entry数组,Entry则存有key-value,且对于Hashmap来讲一个key只能对应一个value     首先是put方法          

java的Iterator源码浅析

Iterator经常用来遍历List,如下所示: List<String> list=new ArrayList<String>(); list.add("apple"); list.add("banana"); list.add("watermelon"); for (Iterator<String> iterator=list.iterator();iterator.hasNext();) { System

java之Set源码浅析

Set的接口和实现类是最简单的,说它简单原因是因为它的实现都是基于实际的map实现的.如 hashSet 基于hashMap,TreeSet 基于TreeMap,CopyOnWriteArraySet 基于 CopyOnWriteArrayList . 故对其实现简要分析.首先看下面的类图: Set Set接口的意义是,含有相等的元素,相等由集合内元素的equals方法决定.Set接口继承Collection接口,但是其实其所含的方法和Collection接口一样. AbstractSet 从类

我对java String的理解 及 源码浅析

每天起床告诉自己,自己的目标是 ”技术 + 英语 还有生活“! -泥沙砖瓦浆木匠 一.char说起到String 这也是自己第二次回过头来啃java基础书,小生自认为愚昧无知.如果大神有好的教育,可以评论私信.以下都是我的看法:为什么说char呢,我这里先卖个关子.在java中,char是用unicode编码的,占16位(2字节).从ansi编码(1字节)到unicode编码(2字节).Java中使用Unicode的原因是,Java的Applet(网页)运行,Unicode里面包含最多最广比如:

java并发:jdk1.8中ConcurrentHashMap源码浅析

ConcurrentHashMap是线程安全的.可以在多线程中对ConcurrentHashMap进行操作. 在jdk1.7中,使用的是锁分段技术Segment.数据结构是数组+链表. 对比jdk1.7,在jdk1.8中,ConcurrentHashMap主要使用了CAS(compareAndSwap).volatile.synchronized锁. 跟jdk1.8中的HashMap一样,数据结构是数组+链表+红黑树.当链表长度过长时,会转变为红黑树. jdk1.8的HashMap源码浅析,见

Volley框架源码浅析(一)

尊重原创http://blog.csdn.net/yuanzeyao/article/details/25837897 从今天开始,我打算为大家呈现关于Volley框架的源码分析的文章,Volley框架是Google在2013年发布的,主要用于实现频繁而且粒度比较细小的Http请求,在此之前Android中进行Http请求通常是使用HttpUrlConnection和HttpClient进行,但是使用起来非常麻烦,而且效率比较地下,我想谷歌正式基于此种原因发布了Volley框架,其实出了Voll

Volley框架源码浅析(二)

尊重原创 http://write.blog.csdn.net/postedit/25921795 在前面的一片文章Volley框架浅析(一)中我们知道在RequestQueue这个类中,有两个队列:本地队列和网络队列 /** The cache triage queue. */ private final PriorityBlockingQueue<Request<?>> mCacheQueue = new PriorityBlockingQueue<Request<

转:Spring FactoryBean源码浅析

http://blog.csdn.net/java2000_wl/article/details/7410714 在Spring BeanFactory容器中管理两种bean 1.标准Java Bean 2,另一种是工厂Bean,   即实现了FactoryBean接口的bean  它不是一个简单的Bean 而是一个生产或修饰对象生成的工厂Bean 在向Spring容器获得bean时  对于标准的java Bean  返回的是类自身的实例 而FactoryBean 其返回的对象不一定是自身类的一