天天看点

Java集合---List

List接口

List接口,成为有序的Collection也就是序列。该接口可以对列表中的每一个元素的插入位置进行精确的控制,同时用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜索列表中的元素。例如List接口定义如下方法,实现元素指定位置的插入和访问。

  • add(int index, E element)
  • get(int index)

AbstractList 抽象类

List 接口的骨干实现,以最大限度地减少实现“随机访问”数据存储(如数组)支持的该接口所需的工作。

List具体实现类

Vector

类声明如下

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

           

构造方法如下

Constructor 1
public Vector() {
    this(10);
}
           
Constructor 2
public Vector(int initialCapacity) {
    this(initialCapacity, 0);
}
           
Constructor 
public Vector(int initialCapacity, int capacityIncrement) {
    super();
    if (initialCapacity < )
        throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);                                             
    this.elementData = new Object[initialCapacity];
    this.capacityIncrement = capacityIncrement;
}
           
Constructor 4
public Vector(Collection<? extends E> c) {
    elementData = c.toArray();
    elementCount = elementData.length;
    if (elementData.getClass() != Object[].class)
        elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}
           

Constructor 1:创建一个容量为 10,增量为 0 的 Vector 容器,容量和增量的概念接下来会介绍。

Constructor 2:创建一个容量为形参 initialCapacity,增量为 0 的 Vector 容器。

Constructor 3:创建一个容量为形参 initialCapacity,增量为 capacityIncrement的Vector 容器。

Constructor 4:创建一个容量为集合 c 长度,增量为 0 的 Vector 容器。

上面四个构造方法中使用到了Vector定义的三个成员变量:

protected Object[] elementData;
           

Object类型的数组,Vector底层是数组来实现的,所有的元素都在 elementData 数组中存放

protected int elementCount;
           

elementCount 属性用来记录Vector容器中的元素个数,也就是数组 elementData 中元素的个数,而不是 elementData 数组的长度,下面是Vector size()方法的定义,直接返回elementCount 的值。

public synchronized int size() {
    return elementCount;
}
           
protected int capacityIncrement;
           

capacityIncrement 属性记录容器的增量,即容器自动扩展的时候一次性扩展的大小。

接下来看一下 Vector 类元素增加、删除、访问和迭代实现的具体细节

1 增加元素

public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + );
        elementData[elementCount++] = e;
        return true;
    }
           

1 synchronized:Vector在多线程环境下提供了同步机制。

2 add 方法并没有进行null判断,允许null元素。

modCount++:modCount 属性在 AbstractList 中定义

protected transient int modCount = ;
           

modCount 是用来记录容器被结构性修改的次数,所谓结构性修改是对容器进行了元素增加和删除等操作,下面是Vector修改和获取元素的代码都没有进行 modCount++ 的操作。

public synchronized E get(int index) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);
        return (E) elementData[index];
}
public synchronized E set(int index, E element) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);
        Object oldValue = elementData[index];
        elementData[index] = element;
        return (E)oldValue;
}
           

那设置这个属性的作用是什么呢?是为了在多线程环境下以一种叫做“快速失败”的方式通知程序迭代遍历容器失败。下面是调用 Vector 容器的 iterator()方法返回的迭代器对象的具体类。

private class Itr implements Iterator<E> {
            int cursor = ;
            int lastRet = -;
            int expectedModCount = modCount;
            public boolean hasNext() {
                    return cursor != size();
            }
            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();
            }
     }
           

此类通过 expectedModCount 保存了一个 Vector 结构化修改的次数,而在迭代过程中会不断的调用 checkForComodification 方法,一旦发现容器被结构化修改就会抛出ConcurrentModificationException 异常。可以联想,当线程 t1 正在迭代遍历 Vector 容器时,线程 t2 结构性修改了容器,那么线程 t1 将立刻抛出异常,这也是所谓的“快速失败”。

接着上面讲 Vector add 方法的第二行代码:

ensureCapacityHelper(elementCount + );
           

在往容器中添加元素之间需要确保容器有足够的空间来存储元素,即 elementData 数组还有空间存储元素,ensureCapacityHelper 方法代码如下:

private void ensureCapacityHelper(int minCapacity) {
        int oldCapacity = elementData.length;
        if (minCapacity > oldCapacity) {
            Object[] oldData = elementData;
            int newCapacity = (capacityIncrement > ) ? (oldCapacity + capacityIncrement)
                    : (oldCapacity * );
            if (newCapacity < minCapacity) {
                newCapacity = minCapacity;
            }
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    }
           

elementCount + 1 代表容器存放下当前元素需要的最小空间,如果最小空间尚且大于数组的长度,那么必须进行动态扩容,扩容的方法如上所示。

接着 Vector add 方法的第三行代码

这个代码完成了元素的添加。

2 删除元素

public synchronized E remove(int index) {
        modCount++;
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);
        Object oldValue = elementData[index];
        int numMoved = elementCount - index - ;
        if (numMoved > )
            System.arraycopy(elementData, index+, elementData, index,numMoved);
        elementData[--elementCount] = null; 
        return (E)oldValue;
     }
           

此处关键的操作,是 System.arraycopy 方法,此方法是一个 native 方法,主要完成数组元素的移动,即从 index+1 开始,每个元素往前移动一位。

因为index后面的元素都往前移动了一位,所以需要将最后一位制空,elementCount 属性值减一。

3 访问元素

public synchronized E get(int index) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);
        return (E)elementData[index];
    }
           

代码简单易懂,此处不再介绍。

4 迭代元素

Vector 没有重写 iterator 方法,直接继承 AbstractList 类的iterator方法,定义如下:

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

内部类 Itr 定义如下所示

private class Itr implements Iterator<E> {

        int cursor = ;
        int lastRet = -;
        int expectedModCount = modCount;
        public boolean hasNext() {
            return cursor != size();
        }
        public E next() {
            checkForComodification();
            try {
                E next = get(cursor);
                lastRet = cursor++;
                return next;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        public void remove() {
            if (lastRet == -)
                throw new IllegalStateException();
            checkForComodification();

            try {
                AbstractList.this.remove(lastRet);
                if (lastRet < cursor)
                    cursor--;
                lastRet = -;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException e) {
                throw new ConcurrentModificationException();
            }
        }

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

Iterator remove 操作不会引起 当前线程迭代遍历失败,因为删除元素之后重新设置了 expectedModCount 的值。

next 和 hasNext 方法简单易懂。

3 Vector迭代遍历,按照加入元素的顺序输出所有元素。

4 Vector加入元素时并没有进行排序。

List<Integer> vector = new Vector<Integer>();
        vector.add();
        vector.add();
        vector.add();
        vector.add();
        vector.add();
        for(Integer i: vector){
            System.out.println(i);
        }
           

所以代码输出结果为:

5

4

3

2

1

Vector#总结

  1. 提供同步机制
  2. 底层数组实现
  3. 可以加入null元素
  4. 元素加入时没有进行排序
  5. 元素可以重复加入

Stack

类声明如下

Stack通过五个操作对Vector进行扩展,允许将向量视为堆栈

  1. empty() 测试堆栈是否为空
  2. peek() 查看堆栈顶部的对象,但不从堆栈中移除它
  3. pop() 移除堆栈顶部的对象,并作为此函数的值返回该对象
  4. push(E item) 把项压入堆栈顶部
  5. search(Object o) 返回对象在堆栈中的位置,以 1 为基数

Stack构造函数

public Stack() {
 }
           

只有一个空构造函数

因为 Stack 继承自 Vector ,所以兼备Vector所有的特性,下面我们看一下 Stack 扩展的 5 中基本操作。

1 push(E item) 将元素推入栈顶

public E push(E item) {
    addElement(item);
    return item;
}
 public synchronized void addElement(E obj) {
    modCount++;
    ensureCapacityHelper(elementCount + );
    elementData[elementCount++] = obj;
}
           

push 方法调用 Vector 中定义的方法将当前元素添加到Statck容器中数组的末尾,也就是栈结构对应的栈顶。

2 peek() 查看堆栈顶部的对象,但不从堆栈中移除它

public synchronized E peek() {
        int len = size();
        if (len == )
            throw new EmptyStackException();
        return elementAt(len - );
}

public synchronized E elementAt(int index) {
        if (index >= elementCount) {
            throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
        }
        return (E) elementData[index];
    }
           

peek 方法调用 Vector 中定义的 elementAt 方法返Statck容器中数组最后一个元素,即栈顶元素。

3 pop() 移除堆栈顶部的对象,并作为此函数的值返回该对象

public synchronized E pop() {
        E obj;
        int len = size();
        obj = peek();
        removeElementAt(len - );
        return obj;
}
           

pop 方法调用 Vector 方法中定义的 removeElementAt 方法删除并返回Statck容器中数组的最后一个元素,即栈顶元素。

4 empty() 测试堆栈是否为空

public boolean empty() {
    return size() == ;
}
           

5 search(Object o) 返回对象在堆栈中的位置,以 1 为基数

public synchronized int search(Object o) {
        int i = lastIndexOf(o);
        if (i >= ) {
            return size() - i;
        }
        return -;
}
           

从栈顶处开始检索,即栈顶元素的返回 1,次栈顶元素返回 2 。

Stack<Integer> stack = new Stack<Integer>();
        stack.push();
        stack.push();
        stack.push();
        stack.push();
        stack.push();
        System.out.println(stack.search());
        System.out.println(stack.search());
           

1

5

Stack总结

  1. 继承自 Vector
  2. 提供同步机制
  3. 扩展了 push、pop、 empty、peek、search 五个方法

ArrayList

类声明如下

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

构造方法

Constructor 1
public ArrayList() {
    this(10);
}

           
Constructor 
public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < )
            throw new IllegalArgumentException("Illegal Capacity: "
                    + initialCapacity);
        this.elementData = new Object[initialCapacity];
}
           
Constructor 3
public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        size = elementData.length;
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
}
           

构造方法与 Vector 差不多,这里不再赘述。

ArrayList 成员属性

private transient Object[] elementData;
           

Object 类型的数组,ArrayList 底层是数组来实现的,所有的元素都在 elementData 数组中存放.

private int size;
           

size 属性用来记录 ArrayList 容器中的元素个数,也就是数组 elementData 中元素的个数,而不是 elementData 数组的长度。

接下来看一下 ArrayList 类元素增加、删除、访问和迭代实现的具体细节

1 增加元素

public boolean add(E e) {
        ensureCapacity(size + ); 
        elementData[size++] = e;
        return true;
}
           

这跟 Vector 的 add 方法基本上一致,添加元素之前要确保空间够用,然后将元素加入容器,size++。

1 no synchronized:ArrayList 不是多线程安全的。

2 add 方法并没有进行null判断,允许null元素。

2 删除元素

public E remove(int index) {
        RangeCheck(index);
        modCount++;
        E oldValue = (E) elementData[index];
        int numMoved = size - index - ;
        if (numMoved > )
            System.arraycopy(elementData, index + , elementData, index,
                    numMoved);
        elementData[--size] = null; 
        return oldValue;
}
           

跟 Vector 中定义的删除方法基本一致,首先是越界检查,然后数组元素移位。

3 访问元素

public E get(int index) {
    RangeCheck(index);
    return (E) elementData[index];
}
           

代码简单明了,不再赘述。

4 迭代元素

Arraylist 没有重写 iterator 方法,同样使用 AbstractList 定义的方法返回容器迭代器,跟上述的 Vector 完全一样。

ArrayList 总结

  1. 不提供同步机制
  2. 底层数组实现
  3. 可以加入null元素
  4. 元素加入时没有进行排序
  5. 元素可以重复加入

LinkedList

LinkedList 与 ArrayList 一样实现 List 接口,只是 ArrayList 是 List接 口的大小可变数组的实现,LinkedList 是 List 接口链表的实现。基于链表实现的方式使得LinkedList在插入和删除时更优于ArrayList,而随机访问则比ArrayList逊色些。

AbstractSequentialList提供了 List 接口的骨干实现,从而最大限度地减少了实现受“连续访问”数据存储(如链接列表)支持的此接口所需的工作,从而以减少实现List接口的复杂度。

除了实现 List 接口外,LinkedList 类还为在列表的开头及结尾 get、remove 和 insert 元素提供了统一的命名方法。这些操作允许将链接列表用作堆栈、队列或双端队列。

与栈结构相关的方法

public void push(E e);
public E pop();
public E peek();
           

LinkedList 实现了 Deque 接口,Deque 接口继承自 Queue 接口,LinkedList中与单端对列相关的此方法

boolean add(E e);
E poll();
E peek();
           

LinkedList与双端队列相关的方法

双端队列:即在队列两端都可以 insert 和 remove:insertLast、insertFirst,removeLast、removeFirst 含有栈和队列的功能,如去掉 insertLast、removeLast,那就跟栈一样了;如去掉insertLast、removeFirst,那就跟队列一样了。

public void addFirst(E e) ;
public void addLast(E e);
public E removeFirst();
public E removeLast();
           
1 LinkedList 提供了栈、队列及双端队列数据结构的实现。

LinkedList

类声明

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
           

LinedList 构造方法

Constructor1

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

Constructor2

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

在介绍构造方法之前首先看一下 LinkedList 的成员属性

private transient Entry<E> header = new Entry<E>(null, null, null);
private transient int size = ;
           

size 属性记录 LinkedList 容器的尺寸

header 属性是一个 LinkedList 内部类 Entry 类型,接下来看一下 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;
        }
}
           

element 是元素的值

next 是当前元素后继的引用

previous 是当前元素前驱的引用

接下来继续探讨 LinkedList 的构造方法

Constructor2 中显示调用 Constructor1 方法,Constructor1 执行了这样一条语句

header.next = header.previous = header;
           

初始化成员属性 header,为下面添加元素做准备

Java集合---List

接下来看一下 LinkedList 类元素增加、删除、访问和迭代实现的具体细节

1 增加元素

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

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;
}
           
Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
           

创建一个新的 元素节点。

Java集合---List

接下来的几行代码完成新元素的添加

newEntry.previous.next = newEntry;
newEntry.next.previous = newEntry;
size++;
modCount++;
           

为了更清楚的展示,在向 LinkedList 中添加一个元素 B

Java集合---List

2 LinkedList 底层使用循环双链表实现。

3 元素添加时没有进行排序。

4 元素添加时没有提供同步机制。

5 没有限制 null 元素的添加。

2 删除元素

public E remove() {
    return removeFirst();
}
public E removeFirst() {
    return remove(header.next);
}
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;
}
           

删除操作,简单明了,只不过是在循环双链表上进行元素前驱和后继引用的设置,但要明确一点,header 并不是容器中的有效元素,因为其 element 为 null。它只是一个辅助元素。

3 访问操作

public E get(int index) {
     return entry(index).element;
}
private Entry<E> entry(int index) {
   if (index <  || index >= size)
        throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+size);
    Entry<E> e = header;
    if (index < (size >> )) {
        for (int i = ; i <= index; i++)
            e = e.next;
    } else {
        for (int i = size; i > index; i--)
            e = e.previous;
    }
    return e;
}
           

get 方法调用 entry 方法来定位目标元素在LinkedList中的位置,因为 LinkedList 底层双循环链表实现的,所以可以根据 index 参数的大小从前往后检索、或者从后往前检索。

4 迭代操作

LinkedList 没有重写 iterator 方法 ,继承 AbstractSequentialList 中定义的 iterator 方法,下面看一下 AbstractSequentialList 中的 iterator 方法的定义

public Iterator<E> iterator() {
     return listIterator();
}
public ListIterator<E> listIterator(final int index) {
    if (index< || index>size())
      throw new IndexOutOfBoundsException("Index: "+index);
    return new ListItr(index);
}
           

iterator 方法调用 listIterator 方法,最后生成一个 ListItr 类型的迭代器,下面是 ListItr 类型的定义

private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            cursor = index;
        }

        public boolean hasPrevious() {
            return cursor != ;
        }

        public E previous() {
            checkForComodification();
            try {
                int i = cursor - ;
                E previous = get(i);
                lastRet = cursor = i;
                return previous;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

        public int nextIndex() {
            return cursor;
        }

        public int previousIndex() {
            return cursor - ;
        }

        public void set(E e) {
            if (lastRet == -)
                throw new IllegalStateException();
            checkForComodification();

            try {
                AbstractList.this.set(lastRet, e);
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        public void add(E e) {
            checkForComodification();

            try {
                AbstractList.this.add(cursor++, e);
                lastRet = -;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
}
           

ListItr 扩展了如下方法

public boolean hasPrevious();
public E previous();
public int nextIndex();
public int previousIndex();
public void set(E e);
public void add(E e);  
           

这里我们不在讨论上述 六种方法,只关心与迭代紧密相关的 hashNext和next方法,但是在 ListItr 类中没有发现这两个方法。因为 ListItr 继承自 Itr(在 AbstractList 中定义) 类,所以 hashNext 和 next 方法跟上面讲述 Vector 迭代时一致。

LinkedList 总结

  1. 底层是循环双联表实现
  2. 没有提供同步机制
  3. 允许 null 元素
  4. 加入元素时没有进行排序
  5. 直接继承自 AbstractSequentialList
  6. 元素可以重复加入
Java集合---List