Java集合源码 -- List列表

List概述

List是一个有序,可重复的集合,可以在List的中间插入和移除元素,根据整数索引访问元素

下图是List集合的框架图

下面是对上图的简单介绍

AbstractCollection: 提供 Collection 接口的骨干实现

Iterator: 迭代器
ListIterator:列表迭代器

Queue:队列
Deque:一个线性 collection,支持在两端插入和移除元素

AbstractSequentialList:提供了 List 接口的骨干实现
LinkedList:链表的实现

ArrayList:大小可变数组的实现

Vector:实现可增长的对象数组
Stack:后进先出(LIFO)的对象堆栈

ArrayList

底层存储

属性 elementData 存储集合中的内容

 /**  缓存区:存储元素 */
 private transient Object[] elementData;

将元素添加到指定位置

将元素添加到指定位置后, 把原数组中 该位置和之后的元素 向后移动一位

/** 在指定位置添加元素 */
public void add(int index, E element) {
        rangeCheckForAdd(index);          //判断索引位置是否正确

        ensureCapacityInternal(size + 1);  // 扩容
        
        //对原数组进行复制处理(位移),从index + 1到size-index
        //即向右移动当前位于该位置的元素以及所有后续元素。 
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;  //插入
        size++;
    }

介绍下 System.arraycopy(......)

/**
即从指定源数组中复制一个数组,
复制从指定的位置开始,到目标数组的指定位置结束。
将原数组src从srcPos位置开始复制到dest数组中,到dest的destPos位置开始,复制的长度为length
*/
public static native void arraycopy(Object src,  int  srcPos, Object dest, int destPos, int length);

删除指定位置的元素

删除元素后, 把原数组中 该位置之后的元素 向前移动一位

public E remove(int index) {
        rangeCheck(index);     //判断移动位置

        modCount++;    //记录改变次数
        E oldValue = elementData(index);   //要删除的元素

        int numMoved = size - index - 1;   //移动的长度
        
        //向左移动numMoved个长度
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; //把最后一个元素设为null

        return oldValue;
    }

扩容

首先扩容为原来的1.5倍, 在检查新容量,  最大容量为Integer.MAX_VALUE

private void grow(int minCapacity) { 
    ...
    int newCapacity = oldCapacity + (oldCapacity >> 1); //将新的容量变为原来容量的1.5倍。
    ...
    hugeCapacity(int minCapacity);
}

//对扩容后的容量进行检查
private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) //得到的容量<0,为什么会有这种情况,内存溢出了。
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?  //如果需要的容量比规定的最大容量大,那么最大容量只能是 Integer.MAX_VALUE。
            Integer.MAX_VALUE :                     
            MAX_ARRAY_SIZE;
}

动态扩容,其实是一个新数组; 在清楚业务,插入的数据量大于扩容后的1.5倍, 应该自己设置一个合适容量

Vector 和 Stack

概述

Vector可以看做ArrayList的同步实现, 具体可参考ArrayList的分析

Stack类提供了栈的结构, 表示先进后出的操作方法 

Stack 继承 Vector , 它定义了5个方法并且调用的都是父类中的方法, 下面是方法定义

E push(E item)    //把对象压入栈顶部, 内部调用是父类的 addElement(item)
E pop()           //移除栈顶对象,内部调用是父类的 removeElementAt(int index)
E peek()          //查看栈顶的对象,内部调用是父类的 elementAt(int index)
int search(Object o) // 返回对象在堆栈中的位置,内部调用父类lastIndexOf(Object o)
empty()             // 测试堆栈是否为空

自定义Stack

/**
 * 自定义Stack
 */
public class MyStack {
    private static final int CAPACITY = 3;
    private static Object[] array = new Object[CAPACITY];
    private static int top = -1;
    
    /** 把对象压入栈顶  */
    void push(Object o) throws Exception {
        if(getSize() == CAPACITY) {
            throw new ExceptionStack("stack is full");
        }
        
        array[++top] = o;
    }
    
    /** 移除栈顶元素 */
    Object pop() throws Exception {
        if(isEmpty()) {
            throw new ExceptionStack("stack is empty");
        }
        return array[top--];
        
    }
    
    /** 查看栈顶元素  */
    Object peek() throws Exception {
        if(isEmpty()){
            throw new ExceptionStack("Stack is empty");
        }
        return array[top];
    }
    
    /** 判断尺寸已满 */
    int  getSize() {
        if(isEmpty()){
            return 0;
        }else{
            return top + 1;
        }
    }
    
    /** 判断为空 */
    boolean isEmpty() {
        return (top < 0);
    }
    
    public static void main(String[] args) throws Exception {
        MyStack s = new MyStack();
        System.out.println(s.isEmpty());
        
        s.push("1");
        s.push("2");
        s.push("3");
        
        System.out.println(s.isEmpty());
        System.out.println(s.getSize());
        System.out.println(s.pop());
        System.out.println(s.peek());
    }
}

自定义异常类:

public class ExceptionStack extends Exception{
    
    //Define myself exception construct with parameters
    public ExceptionStack(String string){
        super(string);
    }
}

LinkedList

LinkedList 的内部实现是双向链表,它允许null的存在,它的方法是不同步的

与ArrayList相比,在插入和删除时优于ArrayLis, 而随机访问则比ArrayList逊色些

主要属性

transient int size = 0;     //集合长度
transient Node<E> first;    //头节点
transient Node<E> last;     //尾节点

介绍下静态内部类Node

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

构造方法

分析下:  LinkedList(Collection<? extends E> c)  构造一个包含指定 collection 中的元素的列表

最终调用的是  addAll(..) ,如下:

 1 public boolean addAll(int index, Collection<? extends E> c) {
 2         checkPositionIndex(index); //判断下表越界
 3 
 4         Object[] a = c.toArray();  //把c转为数组
 5         int numNew = a.length;    //numNew为数组长度
 6         if (numNew == 0)              
 7             return false;
 8 
 9         Node<E> pred, succ;   
10         
11         if (index == size) {  //在构造的调用过程中,肯定是相等的
12             succ = null;
13             pred = last;  //pred指向尾节点
14         } 
15         
16         /*  注释的是构造时不执行的
17         else {
18             succ = node(index); 
19             pred = succ.prev;   
20         }
21         */
22 
23         for (Object o : a) {
24             E e = (E) o;    //从数组中取出元素
25             Node<E> newNode = new Node<>(pred, e, null);  //创建新节点
26             
27             /*
28             if (pred == null)      
29                 first = newNode; 
30             */    
31             else
32                 pred.next = newNode;  //原尾节点的后一个节点指向新节点
33             pred = newNode;   //pred成了新节点
34         }
35 
36         if (succ == null) {
37             last = pred;   //last是添加c后的尾节点
38         } 
39         
40         /*
41         else {
42             pred.next = succ;  
43             succ.prev = pred;
44         }
45         */
46 
47         size += numNew;    //修改容量
48         modCount++;     //修改次数+1
49         return true;
50     }

 node(int index) 返回指定位置的节点

node(int index) 很重要, 添加,删除等操作都会用到, 下面就介绍它

  /**
        返回指定位置的节点
    */
    Node<E> node(int index) {

        //判断遍历的方向
        if (index < (size >> 1)) {    //  size >> 1  = siz2 /2  
            Node<E> x = first;    //从指定位置的节点开始往前遍历
            for (int i = 0; i < index; i++)
                x = x.next;    
            return x;
        } else {
            Node<E> x = last;   //从尾节点开始向指定位置遍历
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

添加

add(E e ): 把节点加到链表的最后 ,实际上调用的是linkLast(E e)方法

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++;      //链表总数+1
    modCount++;
 }    

add(int index, E element): 把数据插入指定的位置,实际上调用的是 linkBefore(element, node(index))

/**
    参数1: 代表新元素
    参数2: succ 代表指定位置的节点
*/
void linkBefore(E e, Node<E> succ) {
       
        final Node<E> pred = succ.prev;   //把指定位置的前一个节点保存在pred    
        final Node<E> newNode = new Node<>(pred, e, succ);  //创建新节点
        succ.prev = newNode;   //指定新节点的后一个节点
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;  //指定新节点的前一个节点
        size++;    
        modCount++;
}

删除

remove(Object o):  从此列表中移除首次出现的指定元素, 实际上调用的是 unlink(Node<E> x)

/**
 参数: 要移除的节点
*/    
 E unlink(Node<E> x) {
       
        final E element = x.item;
        final Node<E> next = x.next;    // 保存 移除节点 的后一个节点 在变量next中
        final Node<E> prev = x.prev;    //保存 移除节点 的前一个节点  在变量prev中

        if (prev == null) {   //true代表 移除节点 是头节点    
            first = next;        
        } else {
            prev.next = next;    // 将 prev后一个节点 指向next
            x.prev = null;      // 将 移除节点的prev设为空
        }

        if (next == null) {  //true代表移除节点是尾节点
            last = prev;
        } else {
            next.prev = prev;  //将 next前一个节点 指向prev
            x.next = null;    //将 移除节点的next 设为空
        }

        x.item = null;  //将移除节点的元素 设为空
        size--;
        modCount++;
        return element;   //返回移除的元素
    }    

总结

对LinkedList的操作实际上是对指向前节点和后节点的引用操作,所以其插入和删除效率较高,但是随机访问效率较差,因为要遍历

 

posted @ 2016-12-25 16:43  liuconglin  阅读(273)  评论(0编辑  收藏  举报