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 3
public Vector(int initialCapacity, int capacityIncrement) {
    super();
    if (initialCapacity < 0)
        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定义的三个成员变量:

1 protected Object[] elementData;

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

2 protected int elementCount;

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

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

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

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

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

1 synchronized:Vector在多线程环境下提供了同步机制。
2 add 方法并没有进行null判断,允许null元素。

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

 protected transient int modCount = 0;

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 = 0;
            int lastRet = -1;
            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 + 1);

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

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

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

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

elementData[elementCount++] = e;

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


2 删除元素

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

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

elementData[--elementCount] = null; 

因为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 = 0;
        int lastRet = -1;
        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 == -1)
                throw new IllegalStateException();
            checkForComodification();

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

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

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

expectedModCount = modCount;

next 和 hasNext 方法简单易懂。

3 Vector迭代遍历,按照加入元素的顺序输出所有元素。
4 Vector加入元素时并没有进行排序。


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

所以代码输出结果为:
5
4
3
2
1


Vector#总结

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

Stack

类声明如下

public class Stack<E> extends Vector<E>

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 + 1);
    elementData[elementCount++] = obj;
}

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

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

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

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 - 1);
        return obj;
}

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

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

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

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

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

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

Stack<Integer> stack = new Stack<Integer>();
        stack.push(1);
        stack.push(2);
        stack.push(3);
        stack.push(4);
        stack.push(5);
        System.out.println(stack.search(5));
        System.out.println(stack.search(1));

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 2
public ArrayList(int initialCapacity) {
        super();
        if (initialCapacity < 0)
            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 成员属性

1 private transient Object[] elementData;

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

2 private int size;

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

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

public boolean add(E e) {
        ensureCapacity(size + 1); 
        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 - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index + 1, 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 = 0;

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,为下面添加元素做准备
这里写图片描述


接下来看一下 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);

创建一个新的 元素节点。
这里写图片描述

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

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

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

这里写图片描述

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 < 0 || index >= size)
        throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+size);
    Entry<E> e = header;
    if (index < (size >> 1)) {
        for (int i = 0; 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<0 || 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 != 0;
        }

        public E previous() {
            checkForComodification();
            try {
                int i = cursor - 1;
                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 - 1;
        }

        public void set(E e) {
            if (lastRet == -1)
                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 = -1;
                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. 元素可以重复加入

这里写图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值