JDK源码学习(3)-java.util.ArrayList与LinkedList

一、java.util.ArrayList的数据结构为数组结构,为可序列化类型、实现了List接口,类声明方式:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

JDK8中该类在初始化时的容量为0,在add时会将容量扩充到10,当容量较少时会进行扩容。

1.扩容逻辑

代码如下:

 初始化:

 private static final int DEFAULT_CAPACITY = 10;
 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
 //构造函数
  public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
 //添加数据
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
  //扩容逻辑
   private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
      private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

 可以看到,在方法ensureCapacityInternal中,选取当前size+1与默认容量10中较大的值minCapaccity,如果该值比当前容量小,则不进行扩容;如果已经大于当前容量,则进行扩容:根据当前容量oldCapacity,扩展oldCapacity的开方,并保证可以满足当前使用,否则直接使用minCapaccity的值。

2.equals

代码如下:

  public boolean equals(Object o) {
        if (o == this)
            return true;
        if (!(o instanceof List))
            return false;

        ListIterator<E> e1 = listIterator();
        ListIterator<?> e2 = ((List<?>) o).listIterator();
        while (e1.hasNext() && e2.hasNext()) {
            E o1 = e1.next();
            Object o2 = e2.next();
            if (!(o1==null ? o2==null : o1.equals(o2)))
                return false;
        }
        return !(e1.hasNext() || e2.hasNext());
    }

比较两个list对象中的所有元素的顺序、取值是否完全一致。



3.contains和indexOf

代码如下:

  //查看是否包含某元素
  public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }
  //查看某元素在list中的第一个的位置
    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }
    //查看某元素在list中的最后一个的位置
     public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

indexOf(Object o)的主要作用为:遍历arrayList中的所有元素,并返回该元素所在list中的第一个位置。如果未找到则返回-1。


4.removeRange删除list中某一范围的元素

 protected void removeRange(int fromIndex, int toIndex) {
        modCount++;
        int numMoved = size - toIndex;
        System.arraycopy(elementData, toIndex, elementData, fromIndex,
                         numMoved);

        // clear to let GC do its work
        int newSize = size - (toIndex-fromIndex);
        for (int i = newSize; i < size; i++) {
            elementData[i] = null;
        }
        size = newSize;
    }

该方法主要是从toIndex的位置开始到最后一位的数据复制到fromIndex起始的位置,然后将最后几位的位置数据设置为null。

其中System.arraycopy为系统方法,为数据拷贝的方法。elementDate为需要复制的原始数组;toIndex为原始数组的起始位置;elementDate为目标数组;fromIndex为目标数组的起始位置;numMoved为拷贝的数组数据的长度。


二、java.util.LinkedList

结构:该list为链表结构,包含first节点、last节点、size值。first节点前置节点为null,last节点后继节点为null,size初始值为0。

节点结构:

private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

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

即每个节点有个一个前置节点、有一个后继节点、有一个节点值。

1.add方法

  public boolean add(E e) {
        linkLast(e);
        return true;
    }
void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

linkLast方法:首先声明两个节点,一个节点l指向list最后一个节点(last);另一个节点newNode指向一个新节点,该节点前置节点指向l,后继节点指向null,节点值为想要插入的值。

然后,如果list为空,则list的首节点first指向newNode节点;如果list不为空,则l指向newNode,并且将list最后一个节点的后续指向newNode。并且将newNode赋值给last节点。

最后,将size加1,modCount加1。

具体modCount作用:

主要是为了检查是否有多个线程同时改变list的内容,可以查看博文的讲解:http://www.cnblogs.com/dolphin0520/p/3933551.html


2.contains和indexOf

 public boolean contains(Object o) {
        return indexOf(o) != -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;
    }

indexOf主要是查找list中的某节点值:

1)遍历list值。从first节点开始到节点不为null为止(last节点的后继节点为null),每次遍历节点的后继节点。

2)如果查找null节点,则直接使用“==”进行比较;如果查找非null节点,则使用equals()方法。

3)返回值为该节点在list中的位置值index,从0开始,每遍历一个节点加1,指导找到匹配的值位置,并返回index。如果未找到则返回-1;

contains方法主要是查看list中是否包含某个节点:

通过indexOf方法查找节点,找到(即indexOf方法结果不为-1)则返回逻辑判断结果true(boolean类型),否则返回false。

3.remove为删除第一个找到的节点值

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

remove通过遍历list,从first到last节点如果查找到匹配节点,则调用unlink方法进行释放当前节点。

具体方法如下:

E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }

        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

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

该方法主要功能为:

1)如果该节点前置节点为空,则该节点为first首节点,即将first首节点指向该节点的后续节点;

 如果前置节点不为空,则将前置节点的后续指向该节点的后续节点,并将该节点的前置节点置为空,相当于断掉该节点前面的链条。

2)继续判断后继节点,如果为空,则该节点为last尾节点,即将last为节点指向该节点的前置节点;

 如果后继节点不为空,则将后续节点的前置节点指向该节点的前置节点,并将该节点的后继节点值为空,相当于断掉该节点后面的链条。

3)该节点的前后链条已经与list断掉,但是需要释放掉该节点的空间,并且需要将list的长度size减1,修改modCount的值加1。

4)返回值为节点的内容。


java.util.ArrayList与LinkedList两种类型的区别:

1.ArrayList结构为数组结构,LinkedList为链表结构。

2.ArrayList由于根据index位数查找字段时可以直接取出数组的位置,而LinkedList则需要遍历到指定位置才可以取出节点值,因此对于根据位置查找频繁的项目建议使用ArrayList。但是根据值查询两者相差不大。

3.ArrayList删除时需要将后面所有位数的数据全部前移,而LinkedList删除某一节点只需要释放该节点。因此ArrayList速度较慢,对于由频繁插入和删除操作的需求,建议使用LinkedList。

但是由于linkedList在删除或者插入时需要定位到该数据,因此也会对效率有影响,因此具体问题也许要综合分析。

样例如下:

		List<String> sList = new ArrayList<String>();
		for (int i = 0; i < 10000000; i++) {
			sList.add(String.valueOf(i));
		}
		long begina1 = System.currentTimeMillis();
		System.out.println("根据位置查询arrayList的值==" + sList.get(999999));
		long enda1 = System.currentTimeMillis();
		System.out.println("根据位置查询arrayList时间==" + (enda1 - begina1));
		long begina2 = System.currentTimeMillis();
		System.out.println("根据值查询arrayList的位置==" + sList.indexOf("999999"));
		long enda2 = System.currentTimeMillis();
		System.out.println("根据值查询arrayList时间==" + (enda2 - begina2));
		long begina3 = System.currentTimeMillis();
		sList.remove(0);
		long enda3 = System.currentTimeMillis();
		System.out.println("删除arrayList中数据使用的时间==" + (enda3 - begina3));

		List<String> linkList = new LinkedList<String>();
		for (int i = 0; i < 10000000; i++) {
			linkList.add(String.valueOf(i));
		}
		long beginb1 = System.currentTimeMillis();
		System.out.println("根据位置查询linkList的值==" + linkList.get(999999));
		long endb1 = System.currentTimeMillis();
		System.out.println("根据位置查询linkList的时间==" + (endb1 - beginb1));
		long beginb2 = System.currentTimeMillis();
		System.out.println("根据值查询linkList的值==" + linkList.indexOf("999999"));
		long endb2 = System.currentTimeMillis();
		System.out.println("根据值查询linkList的时间==" + (endb2 - beginb2));
		long beginb3 = System.currentTimeMillis();
		linkList.remove(0);
		long endb3 = System.currentTimeMillis();
		System.out.println("删除linkList中数据使用的时间==" + (endb3 - beginb3));

结果为,数字为毫秒数:

根据位置查询arrayList的值==999999
根据位置查询arrayList时间==0
根据值查询arrayList的位置==999999
根据值查询arrayList时间==16
删除arrayList中数据使用的时间==6
根据位置查询linkList的值==999999
根据位置查询linkList的时间==12
根据值查询linkList的值==999999
根据值查询linkList的时间==17
删除linkList中数据使用的时间==0


你可能感兴趣的:(源码,jdk8,ArrayList)