集合(二):Collection,List,ArrayList,LinkedList,Vector

包括:

一. Colleciton 与List

二. ArrayList

三. Vector

四. LinkedList


一. Collection 与 List
         Collection 是 Java 集合的一个根接口,JDK 没有它的实现类。Collection 内部有 add(),remove() 等方法供子类实现。List接口是Collection 接口的一个子类,在Collection 基础上扩充了方法。插入的值运行为空,也允许重复。同时可以对每个元素插入的位置进行精确的控制,它的主要实现类有 ArrayList,Vector,LinkedList。

二. ArrayList
       ArrayList 实现了 List 接口,意味着可以插入空值,也可以插入重复的值,非同步,它是基于数组的一个实现。
成员变量如下:

private static final int DEFAULT_CAPACITY = 10;                        //默认初始值
transient Object[] elementData;                                        //存放数据的数组
private int size;                                                      //ArrayList包含的元素个数(不是数组大小)
private static final Object[] EMPTY_ELEMENTDATA = {};                  //构造函数所使用,可参考以下的构造函数
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};  //默认构造函数所使用,可参考以下的构造函数

       上述用一个 transient 修饰,这样当我们持久化对象实例的时候,transient修饰的值不会在序列化列表中。

Ps:static 修饰的值也不会在序列化列表中。


构造函数(一). 如下:
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
        像平常使用的如下代码就会使用预先创建好的空数组。 那么,当我们 add() 的时候,是如何创建数组,如何使用默认值的呢?
List list = new ArrayList();

        当我们使用 add(E e) 方法的时候,先执行如下函数:
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

        ensurecapacityInternal(size + 1) 就会确保目前的数组不会是空数组。实现如下:

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}

        首次调用 add(E e) 的时候,size 为 0,所以 size + 1 = 1 < DEFAULT_CAPACITY = 10,此时取最小容量为10。最后会调用 grow 方法进行对数组的创建:
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);
}

       这就是一个首次创建的过程

       可以看到,每次进行 add(E e)方法的时候,都会检测数组容量,当快要溢出的时候,就会进行扩容操作。所以如果我们明确所插入的元素的多少,最好指定一个初始容量值,避免过多的扩容而浪费时间。

       Ps:平常我们都说 ArrayList 不适合增删频繁的操作,这句话不是很严谨,根据 ArrayList 的add(E e) 函数的 elementData[size ++] = e可以知道,如果不进行扩容操作的话,增加方法还是有着很高的效率的。


构造函数(二). 如下:
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}
可以看到,我们可以指定初始容量。其实为了避免频繁的扩容,最好指定初始容量。

方法介绍:
1. 在上面已经介绍了 add() 方法,现在再介绍 remove() 方法。
public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

        该remove() 方法 使用 for 循环查找,再 equals 方法比较,找到接着删除,删除方法如下:

private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work
}

        可以看到,删除的方法会比增添的方法麻烦一点,因为会把删除位置的元素的后面元素全部往前移动一位。除此之外,特定位置的插入和删除操作也都会引起数组数据的移动,这样是非常耗时低效的。

总结:

  • 基本信息:可以加入空值,重复值,非同步,基于数组实现。
  • 适用:特定位置的增删操作不多,以及删除操作不多。
  • 注意事项:适用的时候最好初始化容量。

三. Vector
       Vector可以看做是 ArrayList 的线程安全版本。由于实现了List接口,那么也是可以加入重复的值,也可以加入空值,它也是基于数组的实现。
成员变量:
protected Object[] elementData;      //存放数据
protected int elementCount;          //数组中元素的个数
protected int capacityIncrement;     //如果该值小于等于0.那么Vector容量双倍增长

构造函数:
public Vector() {
    this(10);
}

public Vector(int initialCapacity) {
    this(initialCapacity, 0);
}

public Vector(int initialCapacity, int capacityIncrement) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    this.elementData = new Object[initialCapacity];
    this.capacityIncrement = capacityIncrement;
}

可以看到,默认的话,Vector 的容量为10,capacityIncrement 为0。

普通方法:

1.add(E e)方法

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

        modCount 暂时不管, 对于 ensureCapacityHelper ,如下代码:
private void ensureCapacityHelper(int minCapacity) {
    // 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 + ((capacityIncrement > 0) ?
                                     capacityIncrement : oldCapacity);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

       从代码可以看出,当容量满了之后,新的容量大小 = 原始容量大小 + capacityIncrement / 原始容量 大小。当 capacityIncrement >=0 的时候,那么新的容量就是原始容量的两倍,否则,新的容量 = 原始容量 + capacityIncrement 的值。所以,最好我们还是要指定capacityIncrement,否则每次扩容都会双倍增长。另外,从上面的 add(E e)方法中的 elementData[elementCount++] = e;可以知道,Vector 的插入数据的方式还是挺高效的。

2.remove()方法

public boolean remove(Object o) {
    return removeElement(o);
}

public synchronized boolean removeElement(Object obj) {
    modCount++;
    int i = indexOf(obj);
    if (i >= 0) {
        removeElementAt(i);
        return true;
    }
    return false;
}

public synchronized int indexOf(Object o, int index) {
    if (o == null) {
        for (int i = index ; i < elementCount ; i++)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = index ; i < elementCount ; i++)
            if (o.equals(elementData[i]))
                return i;
    }
    return -1;
}

public synchronized void removeElementAt(int index) {
    modCount++;
    if (index >= elementCount) {
        throw new ArrayIndexOutOfBoundsException(index + " >= " +
                                                 elementCount);
    }
    else if (index < 0) {
        throw new ArrayIndexOutOfBoundsException(index);
    }
    int j = elementCount - index - 1;
    if (j > 0) {
        System.arraycopy(elementData, index + 1, elementData, index, j);
    }
    elementCount--;
    elementData[elementCount] = null; /* to let gc do its work */
}

那么对于remove()方法,也会普通的for循环找出位置,再删除,删除的时候后续元素往前移动一位。当然,这也是比较低效率的。和ArrayList 一样,特定位置的插入和删除也都会引起数组的数据移动,这是非常耗时低效的。

总结:

  • 基本信息:可以加入空值,重复值,同步,基于数组实现。
  • 适用:特定位置的增删操作不多,以及删除操作不多。
  • 注意事项:适用的时候最好初始化容量。

四. LinkedList
       LinkedList 实现了 List 接口,所以LinkedList 也可以放入重复的值,也可以放入空值。LinkedList不支持同步。LinkedList 不同于ArrayList 和Vector,它是使用链表的数据结构,不再是数组。
成员变量:
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;
    }
}

构造函数:
public LinkedList() {
}

成员方法:
1. add(E e) 方法:
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++;
}

        可以看到,首先创建一个 newNode 节点,再创建的时候,完成部分的节点连接,如下代码:
Node(Node<E> prev, E element, Node<E> next) {
    this.item = element;
    this.next = next;
    this.prev = prev;
}

       最后,在LinkLst(E e) 中 完成节点的连接。


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

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

对于remove()方法,先for 循环遍历,然后再像普通链表操作一样移除节点。

那么对于链表的实现方式来说,增删操作会强于ArrayList,Vector等,因为删除操作,特定位置的增删操作,ArrayList,Vector还需要把该位置往后的位置都向前移动或者需要扩容,浪费了时间,基于链表的实现方法没有这种问题。


总结:
  • 基本信息:可以加入空值,重复值,不同步,基于链表的实现。
  • 适用:增删操作多的情况中。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值