Java集合之List接口

时间:2021-06-13 17:59:24

List接口、ArrayList类和LinkedList类

1.List

List接口继承自Collection接口,其中常用的较为重要的方法如下:

public interface List<AnyType> extends Collection<AnyType>{
    int size();
    boolean add(E e);
    boolean remove(Object o);
    E get(int index);
    E set(int index, E element);
}

get和set使得用户可以访问或改变通过由位置索引index给定的表中指定位置上的项。索引0位于表的前端,索引size()-1代表表中的最后一项。
List有两种流行的实现方式,ArrayList和LinkedList。

ArrayList、LinkedList

ArrayList类提供了List的一种可增长数组的实现.使用ArrayList的优点在于,对get和set的调用花费常数时间。其缺点是新项的插入和现有项的删除代价昂贵,除非变动是在ArrayList的末端进行。
LinkedList则提供了List的双向链表实现。使用LinkedList的优点在于,新项的插入和现有项的删除均开销很小,这意味着在表的前端进行添加和删除都是常数时间的操作,由此LinkedList更提供了方法addFirst和removeFirst、addLast和removeLast等方法可以有效的添加、删除和访问表两端的项。使用LInkedList的缺点是它不容易作索引,因此对get的调用是昂贵的,除非调用非常接近表的端点。
首先,我们在表的末端添加一项来构造list:

 public static void makeList1(List<Integer> lst, int N) {
        lst.clear();
        for (int i = 0; i < N; i++) {
            lst.add(i);
        }
    }

不管ArrayList还是LinkedListlist作为参数进行传递,makeList1的运行时间都是o(N),因此对add的每次调用都是在表的末端进行从而花费常数时间。
如果我们从表的前端构造一个List:

public static void makeList2(List<Integer> lst, int N) {
        lst.clear();
        for (int i = 0; i < N; i++) {
            lst.add(0, i);
        }
    }

那么,对于LinkedList它的运行时间是O(N),但是对于ArrayList其运行时间则是O(N2),因为在ArrayList中,在前端进行添加是一个O(N)操作。
下一个例子是极端List中的数的和:

public static int makeList3(List<Integer> lst, int N) {
        int total = 0;
        for (int i = 0; i < N; i++) {
            total += lst.get(i);
        }
        return total;
    }

这里ArrayList的运行时间是O(N),但是对于LinkedList来说,其运行时间则是O(N2),因为在LinkedList中,对get的调用为O(N)操作。
如果使用一个增强的for循环,那么它对任意List的运行时间都是O(N),因为迭代器将有效地从一项到下一项推进。

将list表中所有偶数值删除:
因为ArrayList删除效率地下,所以在涉及需要进行删除操作时,我们应该构建LinkedList表。但是在LinkedList中,由于对get调用的效率不高,因此也会花费较多时间,所以remove的效率同样低下,代码如下:

public static void removeEvenVer1(List<Integer> lst){
        int i = 0;
        while (i<lst.size()){
            if(lst.get(i)%2==0){
                lst.remove(i);
            }else {
                i++;
            }
        }
    }

如果我们不使用get()方法去获取数据,而是使用迭代器,会不会变得高效一点呢

    public static void removeEvenVer2(List<Integer> lst){
        for (Integer x:lst){
            if(x%2==0){
                lst.remove(x);
            }
        }
    }

但是我们运行这个程序,会产生一个异常,因为当一项被删除时,由增强的for循环所使用的基础迭代器是非法的。

迭代器使用:
在迭代器找到一个偶数值项之后,我们可以使用该迭代器来删除这个它刚看到的值。对于一个LinkedList,对该迭代器的remove方法的调用只花费常数时间,因为该迭代器位于被删除的节点。因此,对于LinkedList,整个程序花费线性时间,而不是二次时间。对于ArrayList,因为数组的删除伴随着数组项的移动,所以花费的还是二次时间。

public static void removeEvensVer3(List<Integer> lst){
        Iterator<Integer> itr = lst.iterator();
        while (itr.hasNext()){
            if(itr.next()%2 == 0){
                itr.remove();
            }
        }
    }

ArrayList类的实现

package collections.list;

import java.util.Iterator;
import java.util.function.Consumer;

/** * Created with IntelliJ IDEA. * * @Author crazyang * @Desciption: * @Date 2018-6-8 17:04 */
public class MyArrayList<AnyType> implements Iterator<AnyType> {

    private static final int DEFAULT_CAPACITY = 10;

    private int theSize;
    private AnyType[] theItems;

    public MyArrayList(){
        doClear();
    }
    public void clear(){
        doClear();
    }
    public void doClear(){
        theSize = 0;
        ensureCapacity(DEFAULT_CAPACITY);
    }
    public int size(){
        return  theSize;
    }
    public boolean isEmpty(){
        return size() == 0;
    }
    public void trimToSize(){
        ensureCapacity(size());
    }

    public AnyType get(int index){
        if(index<0||index>=size()){
            throw new ArrayIndexOutOfBoundsException();
        }
        return theItems[index];
    }
    public AnyType set(int index,AnyType newVal){
        if(index<0||index>=size()){
            throw new ArrayIndexOutOfBoundsException();
        }
        AnyType old = theItems[index];
        theItems[index] = newVal;
        return old;
    }

    public void ensureCapacity(int newCapacity){
        if(newCapacity<theSize){
            return;
        }
        AnyType [] old = theItems;
        theItems=(AnyType[])new Object[newCapacity];
        for(int i =0;i<size();i++){
            theItems[i] = old[i];
        }
    }
    public boolean add(AnyType x){
        add(size(),x);
        return true;
    }
    public void add(int index,AnyType x){
        if(theItems.length == size()){
            ensureCapacity(size()*2+1);
        }
        for(int i = theSize;i>index;i--){
            theItems[i] = theItems[i-1];
        }
        theItems[index] = x;
        theSize++;
    }

    public AnyType remove(int index){
        AnyType removeItem = theItems[index];
        for(int i = index;i<size()-1;i++){
            theItems[i] = theItems[i+1];
        }
        theSize--;
        return removeItem;
    }
    private int current=0;


    @Override
    public boolean hasNext() {
        return current<size();
    }

    @Override
    public AnyType next() {
        if(!hasNext()){
          throw new java.util.NoSuchElementException();  
        }
        return theItems[current++];
    }

    @Override
    public void remove() {
        MyArrayList.this.remove(--current);
    }

}

LinkedList类的实现

LinkedList将作为双向链表来实现,而且我们还需要保持该表两端的引用。
设计:
1、MyLinkedList类本身,它包含到两端的链、表的大小以及一些方法。
2、Node类,它可能是一个私有的嵌套类。一个节点包含数据以及到前一个节点的链和到下一个节点的链,还有一些适当的构造方法。
3、LinkedListIterator类,该类抽象了位置的概念,是一个私有类,并实现接口Iterator。它提供了next、hasNext和remove的实现。

package collections.list;

import java.util.Iterator;

/** * Created with IntelliJ IDEA. * * @Author crazyang * @Desciption: * @Date 2018-6-8 17:29 */
public class MyLinkedList<AnyType> implements Iterable<AnyType> {
    private int theSize;
    private int modCount = 0;
    private Node<AnyType> beginMarker;
    private Node<AnyType> endMarker;

    private static class Node<AnyType> {
        public AnyType data;
        public Node<AnyType> prev;
        public Node<AnyType> next;

        public Node(AnyType d, Node<AnyType> p, Node<AnyType> n) {
            data = d;
            prev = p;
            next = n;
        }
    }

    public int size() {
        return theSize;
    }

    public boolean add(AnyType x) {
        add(size(), x);
        return true;
    }

    public void add(int index, AnyType x) {
        addBefore(getNode(index, 0, size()), x);
    }

    public void addBefore(Node<AnyType> p, AnyType x) {
        Node newNode = new Node<>(x, p.prev, p);
        newNode.prev.next = newNode;
        p.prev = newNode;
        theSize++;
        modCount++;
    }

    public Node<AnyType> getNode(int index) {
        return null;
    }

    public Node<AnyType> getNode(int index, int lower, int upper) {
        return null;
    }


    @Override
    public Iterator<AnyType> iterator() {
        return null;
    }
}