综合技术

Collection集合框架

微信扫一扫,分享到朋友圈

Collection集合框架
0

概述

Collection接口是集合类的根接口,Java中没有提供这个接口的直接的实现类。但是却让其被继承产生了两个接口,就是Set和List。Set中不能包含重复的元素。List是一个有序的集合,可以包含重复的元素,提供了按索引访问的方式。

1.Set(无序、不可重复)

Set集合类似于一个罐子,"丢进"Set集合里的多个对象之间没有明显的顺序。Set继承自Collection接口,不能包含有重复元素。

Set判断两个对象相同不是使用"=="运算符,而是根据equals方法。也就是说,我们在加入一个新元素的时候,如果这个新元素对象和Set中已有对象进行equals比较都返回false,则Set就会接受这个新元素对象,否则拒绝。

如果希望遍历Set集合中的元素只能调用其iterator方法,通过返回的Iterator对象来完成。

1.1 HashSet

HashSet是底层通过HashMap实现,为快速查找设计的Set。

存入HashSet的对象必须定hashCode(),HashSet使用HASH算法来存储集合中的元素,因此具有良好的存取和查找性能。当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据该HashCode值决定该对象在HashSet中的存储位置。

值得注意的是,HashSet集合判断两个元素相等的标准是两个对象通过equals()方法比较相等,并且两个对象的hashCode()方法的返回值相等

1.2 TreeSet

TreeSet是依靠TreeMap来实现的。 

TreeSet集合底层数据结构是红黑树(自平衡二叉查找树)。

TreeSet的本质是一个”有序的,并且没有重复元素”的集合,而且支持自定义排序。

2.List(有序、可重复)

List集合代表一个元素有序、可重复的集合,集合中每个元素都有其对应的顺序索引。List集合允许加入重复元素,因为它可以通过索引来访问指定位置的集合元素。List集合默认按元素的添加顺序设置元素的索引

2.1 LinkedList

概括的说,LinkedList 是线程不安全的,允许元素为null的双向链表。 

因其底层数据结构是链表,所以可想而知,它的增删只需要移动指针即可,故时间效率较高。不需要批量扩容,也不需要预留空间,所以空间效率比ArrayList高。

缺点就是需要随机访问元素时,时间效率很低,虽然底层在根据下标查询Node的时候,会根据index判断目标Node在前半段还是后半段,然后决定是顺序还是逆序查询,以提升时间效率。不过随着n的增大,总体时间效率依然很低。

构造方法

//集合元素数量

transient int size = 0;

//链表头节点

transient Node<E> first;

//链表尾节点

transient Node<E> last;

//啥都不干

public LinkedList() {

}

public LinkedList(Collection<? extends E> c) {

this();
 addAll(c);

}

对比JDK1.6构造方法

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

private transient int size = 0;

public LinkedList() {

header.next = header.previous = header;

}

public LinkedList(Collection<? extends E> c) {

this();
addAll(c);

}

可以看出在1.7之前LinkedList是双向循环链表,在这之后,因为不再使用header节点,所以默认构造方法什么也不做,first和last会被默认初始化为null。

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

增加

1 addAll

//addAll ,在尾部批量增加

public boolean addAll(Collection<? extends E> c) {

return addAll(size, c);//以size为插入下标,插入集合c中所有元素

}

//以index为插入下标,插入集合c中所有元素

public boolean addAll(int index, Collection<? extends E> c) {

checkPositionIndex(index);//检查越界 [0,size] 闭区间

Object[] a = c.toArray();//拿到目标集合数组
int numNew = a.length;//新增元素的数量
if (numNew == 0)//如果新增元素数量为0,则不增加,并返回false
    return false;

Node<E> pred, succ;  //index节点的前置节点,后置节点
if (index == size) { //在链表尾部追加数据
    succ = null;  //size节点(队尾)的后置节点一定是null
    pred = last;//前置节点是队尾
} else {
    succ = node(index);//取出index节点,作为后置节点
    pred = succ.prev; //前置节点是,index节点的前一个节点
}

for (Object o : a) {//遍历要添加的节点。
    @SuppressWarnings("unchecked") E e = (E) o;
    Node<E> newNode = new Node<>(pred, e, null);//以前置节点 和 元素值e,构建new一个新节点,
    if (pred == null) //如果前置节点是空,说明是头结点
        first = newNode;
    else//否则 前置节点的后置节点设置为新节点
        pred.next = newNode;
    pred = newNode;//步进,当前的节点为前置节点了,为下次添加节点做准备
}

if (succ == null) {//循环结束后,判断,如果后置节点是null。 说明此时是在队尾append的。
    last = pred; //则设置尾节点
} else {
    pred.next = succ; // 否则是在队中插入的节点 ,更新前置节点 后置节点
    succ.prev = pred; //更新后置节点的前置节点
}

size += numNew;  // 修改数量size
modCount++;  //修改modCount
return true;

}

//根据index 查询出Node,

Node<E> node(int index) {

// assert isElementIndex(index);

//通过下标获取某个node 的时候,(增、查 ),会根据index处于前半段还是后半段 进行一个折半,以提升查询效率

if (index < (size >> 1)) {
    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;
}

}

private void checkPositionIndex(int index) {

if (!isPositionIndex(index))
    throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

}

private boolean isPositionIndex(int index) {

return index >= 0 && index <= size;  //插入时的检查,下标可以是size [0,size]

}

小结:

为什么添加新节点只是让前一个节点的next指向新节点?

链表批量增加,是靠for循环遍历原数组,依次执行插入节点操作。

通过下标获取某个node 的时候,并不是只能从头到尾查询,因为同时存储了头节点和尾节点,会根据index处于前半段还是后半段进行一个折半查找,以提升查询效率

2 插入单个节点add

//在尾部插入一个节点: add

public boolean add(E e) {

linkLast(e);
return true;

}

//在指定下标,index处,插入一个节点
public void add(int index, E element) {
    checkPositionIndex(index);//检查下标是否越界[0,size]
    if (index == size)//在尾节点后插入
        linkLast(element);
    else//在中间插入
        linkBefore(element, node(index));
}

//生成新节点 并插入到 链表尾部, 更新 last/first 节点。

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++;//修改size
modCount++;//修改modCount

}

//在succ节点前,插入一个新节点e
void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    //保存后置节点的前置节点
    final Node<E> pred = succ.prev;
    //以前置和后置节点和元素值e 构建一个新节点
    final Node<E> newNode = new Node<>(pred, e, succ);
    //新节点new是原节点succ的前置节点
    succ.prev = newNode;
    if (pred == null)//如果之前的前置节点是空,说明succ是原头结点。所以新节点是现在的头结点
        first = newNode;
    else//否则构建前置节点的后置节点为new
        pred.next = newNode;
    size++;//修改数量
    modCount++;//修改modCount
}
private void linkFirst(E e) {
    final Node<E> f = first;
    final Node<E> newNode = new Node<>(null, e, f);
    first = newNode;
    if (f == null)
        last = newNode;
    else
        f.prev = newNode;
    size++;
    modCount++;
}

删除

//删:remove目标节点
public E remove(int index) {
    checkElementIndex(index);//检查是否越界 下标[0,size)
    return unlink(node(index));//从链表上删除某节点
}

//删: 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;
}


//从链表上删除x节点
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++;  //修改modCount
return element; //返回取出的元素值

}

private void checkElementIndex(int index) {
    if (!isElementIndex(index))
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
//下标[0,size)
private boolean isElementIndex(int index) {
    return index >= 0 && index < size;
}
public E removeFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return unlinkFirst(f);
}

private E unlinkFirst(Node<E> f) {
    // assert f == first && f != null;
    final E element = f.item;
    final Node<E> next = f.next;
    f.item = null;
    f.next = null; // help GC
    first = next;
    if (next == null)
        last = null;
    else
        next.prev = null;
    size--;
    modCount++;
    return element;
}
public E removeLast() {
    final Node<E> l = last;
    if (l == null)
        throw new NoSuchElementException();
    return unlinkLast(l);
}

private E unlinkLast(Node<E> l) {
    // assert l == last && l != null;
    final E element = l.item;
    final Node<E> prev = l.prev;
    l.item = null;
    l.prev = null; // help GC
    last = prev;
    if (prev == null)
        first = null;
    else
        prev.next = null;
    size--;
    modCount++;
    return element;
}

修改

public E set(int index, E element) {

checkElementIndex(index); //检查越界[0,size)
Node<E> x = node(index);//取出对应的Node
E oldVal = x.item;//保存旧值 供返回
x.item = element;//用新值覆盖旧值
return oldVal;//返回旧值

}

改也是先根据index找到Node,然后替换值,改不修改modCount

查找

//根据index查询节点

public E get(int index) {

checkElementIndex(index);//判断是否越界 [0,size)
return node(index).item; //调用node()方法 取出 Node节点,

}

//根据节点对象,查询下标

public int indexOf(Object o) {
    int index = 0;
    if (o == null) {//如果目标对象是null
    //遍历链表
        for (Node<E> x = first; x != null; x = x.next) {
            if (x.item == null)
                return index;
            index++;
        }
    } else {////遍历链表
        for (Node<E> x = first; x != null; x = x.next) {
            if (o.equals(x.item))
                return index;
            index++;
        }
    }
    return -1;
}

从尾至头遍历链表,找到目标元素值为o的节点

public int lastIndexOf(Object o) {
    int index = size;
    if (o == null) {
        for (Node<E> x = last; x != null; x = x.prev) {
            index--;
            if (x.item == null)
                return index;
        }
    } else {
        for (Node<E> x = last; x != null; x = x.prev) {
            index--;
            if (o.equals(x.item))
                return index;
        }
    }
    return -1;
}

toArray()

public Object[] toArray() {
    //new 一个新数组 然后遍历链表,将每个元素存在数组里,返回
    Object[] result = new Object[size];
    int i = 0;
    for (Node<E> x = first; x != null; x = x.next)
        result[i++] = x.item;
    return result;
}

队列操作

//返回头结点,但不删除

public E peek() {

final Node<E> f = first;
   return (f == null) ? null : f.item;

}

//返回头结点,但不删除

public E element() {

return getFirst();

}

//返回头结点并移除

public E poll() {

final Node<E> f = first;
   return (f == null) ? null : unlinkFirst(f);

}

//删除头结点并返回

public E remove() {

return removeFirst();

}

//添加指定元素在集合末尾

public boolean offer(E e) {

return add(e);

}

双端队列操作

//在集合头部插入元素

public boolean offerFirst(E e) {

addFirst(e);
   return true;

}

//在集合尾部插入元素

public boolean offerLast(E e) {

addLast(e);
   return true;

}

//得到集合第一个元素

public E peekFirst() {

final Node<E> f = first;
   return (f == null) ? null : f.item;

}

//得到集合最后一个元素但不删除

public E peekLast() {

final Node<E> l = last;
   return (l == null) ? null : l.item;

}

//得到并移除第一个元素

public E pollFirst() {

final Node<E> f = first;
   return (f == null) ? null : unlinkFirst(f);

}

//得到并移除最后一个元素

public E pollLast() {

final Node<E> l = last;
   return (l == null) ? null : unlinkLast(l);

}

//在集合头部插入元素

public void push(E e) {

addFirst(e);

}

//得到并删除第一个元素 ,如果为空抛出异常

public E pop() {

return removeFirst();

}

迭代器操作

public Iterator<E> iterator() {

return listIterator();
}

public ListIterator<E> listIterator() {

return listIterator(0);
}

public ListIterator<E> listIterator(int index) {

checkPositionIndex(index);
    return new ListItr(index);
}

从上面可以看到三者的关系是iterator()——>listIterator(0)——>listIterator(int index)。最终都会调用listIterator(int index)方法,其中参数表示迭代器开始的位置。ListIterator是一个可以指定任意位置开始迭代,并且有两个遍历方法。下面直接看ListItr的实现:

private class ListItr implements ListIterator<E> {
    private Node<E> lastReturned;
    private Node<E> next;
    private int nextIndex;
    private int expectedModCount = modCount;//保存当前modCount,确保fail-fast机制

    ListItr(int index) {
        // assert isPositionIndex(index);
        next = (index == size) ? null : node(index);//得到当前索引指向的next节点
        nextIndex = index;
    }

    public boolean hasNext() {
        return nextIndex < size;
    }

    //获取下一个节点
    public E next() {
        checkForComodification();
        if (!hasNext())
            throw new NoSuchElementException();

        lastReturned = next;
        next = next.next;
        nextIndex++;
        return lastReturned.item;
    }

    public boolean hasPrevious() {
        return nextIndex > 0;
    }

    //获取前一个节点,将next节点向前移
    public E previous() {
        checkForComodification();
        if (!hasPrevious())
            throw new NoSuchElementException();

        lastReturned = next = (next == null) ? last : next.prev;
        nextIndex--;
        return lastReturned.item;
    }

    public int nextIndex() {
        return nextIndex;
    }

    public int previousIndex() {
        return nextIndex - 1;
    }

    public void remove() {
        checkForComodification();
        if (lastReturned == null)
            throw new IllegalStateException();

        Node<E> lastNext = lastReturned.next;
        unlink(lastReturned);
        if (next == lastReturned)
            next = lastNext;
        else
            nextIndex--;
        lastReturned = null;
        expectedModCount++;
    }

    public void set(E e) {
        if (lastReturned == null)
            throw new IllegalStateException();
        checkForComodification();
        lastReturned.item = e;
    }

    public void add(E e) {
        checkForComodification();
        lastReturned = null;
        if (next == null)
            linkLast(e);
        else
            linkBefore(e, next);
        nextIndex++;
        expectedModCount++;
    }
}

在ListIterator的构造器中,得到了当前位置的节点,就是变量next。next()方法返回当前节点的值并将next指向其后继节点,previous()方法返回当前节点的前一个节点的值并将next节点指向其前驱节点。由于Node是一个双端节点,所以这儿用了一个节点就可以实现从前向后迭代和从后向前迭代。另外在ListIterator初始时,exceptedModCount保存了当前的modCount,如果在迭代期间,有操作改变了链表的底层结构,那么再操作迭代器的方法时将会抛出ConcurrentModificationException。

其他

public boolean contains(Object o) {
    return indexOf(o) != -1;
}

//返回一个浅拷贝LinkedList对象

public Object clone() {

LinkedList<E> clone = superClone();

   // Put clone into "virgin" state
   clone.first = clone.last = null;
   clone.size = 0;
   clone.modCount = 0;

   // Initialize clone with our elements
   for (Node<E> x = first; x != null; x = x.next)
       clone.add(x.item);

   return clone;

}

private LinkedList<E> superClone() {

try {
        return (LinkedList<E>) super.clone();
    } catch (CloneNotSupportedException e) {
        throw new InternalError(e);
    }
}
public void clear() {
    for (Node<E> x = first; x != null; ) {
        Node<E> next = x.next;
        x.item = null;
        x.next = null;
        x.prev = null;
        x = next;
    }
    first = last = null;
    size = 0;
    modCount++;
}

2.2 ArrayList

ArrayList 是一个动态数组,它是线程不安全的,允许元素为null。

因其底层数据结构是数组,所以可想而知,它是占据一块连续的内存空间(容量就是数组的length),所以它也有数组的缺点,空间效率不高。由于数组的内存连续,可以根据下标以O(1)的时间读写(改查)元素,因此时间效率很高。

当集合中的元素超出这个容量,便会进行扩容操作。扩容操作也是ArrayList 的一个性能消耗比较大的地方,所以若我们可以提前预知数据的规模,应该通过public ArrayList(int initialCapacity) {}构造方法,指定集合的大小,去构建ArrayList实例,以减少扩容次数,提高效率。或者在需要扩容的时候,手动调用public void ensureCapacity(int minCapacity) {}方法扩容。

构造方法

//默认构造函数里的空数组

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//存储集合元素的底层实现:真正存放元素的数组
transient Object[] elementData; 
//当前元素数量
private int size;

//默认构造方法
public ArrayList() {
    //默认构造方法只是简单的将 空数组赋值给了elementData
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

//空数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//带初始容量的构造方法
public ArrayList(int initialCapacity) {
    //如果初始容量大于0,则新建一个长度为initialCapacity的Object数组.
    //注意这里并没有修改size(对比第三个构造函数)
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {//如果容量为0,直接将EMPTY_ELEMENTDATA赋值给elementData
        this.elementData = EMPTY_ELEMENTDATA;
    } else {//容量小于0,直接抛出异常
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

//利用别的集合类来构建ArrayList的构造函数
public ArrayList(Collection<? extends E> c) {
    //直接利用Collection.toArray()方法得到一个对象数组,并赋值给elementData 
    elementData = c.toArray();
    //因为size代表的是集合元素数量,所以通过别的集合来构造ArrayList时,要给size赋值
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)//这里是当c.toArray出错,没有返回Object[]时,利用Arrays.copyOf 来复制集合c中的元素到elementData数组中
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        //如果集合c元素数量为0,则将空数组EMPTY_ELEMENTDATA赋值给elementData 
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

public Object[] toArray() {

return Arrays.copyOf(elementData, size);
}

常用API

1 增加

每次 add之前,都会判断add后的容量,是否需要扩容。

public boolean add(E e) {

ensureCapacityInternal(size + 1);
elementData[size++] = e;//在数组末尾追加一个元素,并修改size
return true;

}

private static final int DEFAULT_CAPACITY = 10;//默认扩容容量 10

private void ensureCapacityInternal(int minCapacity) {

//利用 == 可以判断数组是否是用默认构造函数初始化的
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);

}

private void ensureExplicitCapacity(int minCapacity) {

modCount++;

if (minCapacity - elementData.length > 0)
    grow(minCapacity);

}

//需要扩容的话,默认扩容一半

private void grow(int minCapacity) {

int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);//默认扩容一半
if (newCapacity - minCapacity < 0)//如果还不够,那么就用 能容纳的最小的数量。(add后的容量)
    newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0) //若newCapacity 大于最大存储容量,则进行大容量分配
    newCapacity = hugeCapacity(minCapacity);

elementData = Arrays.copyOf(elementData, newCapacity);//拷贝,扩容,构建一个新数组,

}

//大容量分配,最大分配 Integer.MAX_VALUE

private static int hugeCapacity(int minCapacity) {

if (minCapacity < 0) 
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;

}

public boolean addAll(Collection<? extends E> c) {

Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); //确认是否需要扩容
System.arraycopy(a, 0, elementData, size, numNew);// 复制数组完成复制
size += numNew;
return numNew != 0;

}

public boolean addAll(int index, Collection<? extends E> c) {

rangeCheckForAdd(index);//越界判断

Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew);//确认是否需要扩容

int numMoved = size - index;
if (numMoved > 0)
    System.arraycopy(elementData, index, elementData, index + numNew,
                     numMoved);//移动(复制)数组

System.arraycopy(a, 0, elementData, index, numNew);//复制数组完成批量赋值
size += numNew;
return numNew != 0;

}

add、addAll

先判断是否越界,是否需要扩容,如果扩容, 就复制数组,然后设置对应下标元素值。

值得注意的是:

如果需要扩容的话,默认扩容一半。如果扩容一半不够,就用目标的size作为扩容后的容量。在扩容成功后,会修改modCount。

扩容操作会导致数组复制,批量删除会导致找出两个集合的交集,以及数组复制操作,因此,增、删都相对低效。 而 改、查都是很高效的操作。

Vector的内部也是数组做的,区别在于Vector在API上都加了synchronized所以它是线程安全的,以及Vector扩容时,是翻倍size,而ArrayList是扩容50%。

2 删除

public E remove(int index) {

rangeCheck(index);//判断是否越界
modCount++;//修改modeCount 因为结构改变了
E oldValue = elementData(index);//读出要删除的值
int numMoved = size - index - 1;
if (numMoved > 0)
    System.arraycopy(elementData, index+1, elementData, index,
                     numMoved);//用复制 覆盖数组数据
elementData[--size] = null;//置空原尾部数据 不再强引用
return oldValue;

}

//根据下标从数组取值 并强转
E elementData(int index) {
    return (E) elementData[index];
}

//删除该元素在数组中第一次出现的位置上的数据。 如果有该元素返回true,如果false。

public boolean remove(Object o) {

if (o == null) {
    for (int index = 0; index < size; index++)
        if (elementData[index] == null) {
            fastRemove(index);//根据index删除元素
            return true;
        }
} else {
    for (int index = 0; index < size; index++)
        if (o.equals(elementData[index])) {
            fastRemove(index);
            return true;
        }
}
return false;

}

//不会越界 不用判断 ,也不需要取出该元素。

private void fastRemove(int index) {

modCount++;//修改modCount
int numMoved = size - index - 1;//计算要移动的元素数量
if (numMoved > 0)
    System.arraycopy(elementData, index+1, elementData, index,
                     numMoved);//以复制覆盖元素 完成删除
elementData[--size] = null;//置空 不再强引用

}

//批量删除

public boolean removeAll(Collection<?> c) {

Objects.requireNonNull(c);//判空
return batchRemove(c, false);

}

//批量移动

private boolean batchRemove(Collection<?> c, boolean complement) {

final Object[] elementData = this.elementData;
int r = 0, w = 0;//w 代表批量删除后 数组还剩多少元素
boolean modified = false;
try {
  
    for (; r < size; r++)
        if (c.contains(elementData[r]) == complement) // 如果c里不包含当前下标元素, 
            elementData[w++] = elementData[r];//则保留
} finally {

    if (r != size) { //出现异常会导致 r !=size , 则将出现异常处后面的数据全部复制覆盖到数组里。
        System.arraycopy(elementData, r,
                         elementData, w,
                         size - r);
        w += size - r;//修改 w数量
    }
    if (w != size) {//置空数组后面的元素
       
        for (int i = w; i < size; i++)
            elementData[i] = null;
        modCount += size - w;//修改modCount
        size = w;// 修改size
        modified = true;
    }
}
return modified;

}

可以看出,当用来作为删除元素的集合里的元素多余被删除集合时,也没事,只会删除它们共同拥有的元素。

小结:

删除操作一定会修改modCount,且可能涉及到数组的复制,相对低效。

3 修改

不会修改modCount,相对增删是高效的操作。

public E set(int index, E element) {

rangeCheck(index);//越界检查
E oldValue = elementData(index); //取出元素 
elementData[index] = element;//覆盖元素
return oldValue;//返回元素

}

4 查询

不会修改modCount,相对增删是高效的操作。

public E get(int index) {

rangeCheck(index);//越界检查
return elementData(index); //下标取数据

}

E elementData(int index) {

return (E) elementData[index];

}

5 清空 clear

会修改modCount。

public void clear() {

modCount++;//修改modCount
for (int i = 0; i < size; i++)  //将所有元素置null
    elementData[i] = null;

size = 0; //修改size

}

6 包含 contain

//普通的for循环寻找值,只不过会根据目标对象是否为null分别循环查找。

public boolean contains(Object o) {

return indexOf(o) >= 0;

}

//普通的for循环寻找值,只不过会根据目标对象是否为null分别循环查找。

public int indexOf(Object o) {

if (o == null) {
    for (int i = 0; i < size; i++)
        if (elementData[i]==null)
            return i;
} else {
    for (int i = 0; i < size; i++)
        if (o.equals(elementData[i]))
            return i;
}
return -1;

}

7 迭代器 Iterator.

public Iterator<E> iterator() {

return new Itr();

}

private class Itr implements Iterator<E> {

int cursor;       // 要返回的下一个元素的索引,默认是0
int lastRet = -1; //上一次返回的元素 (删除的标志位)
int expectedModCount = modCount; //用于判断集合是否修改过结构的标志

public boolean hasNext() {
    return cursor != size;//游标是否移动至尾部
}

@SuppressWarnings("unchecked")
public E next() {
    checkForComodification();
    int i = cursor;
    if (i >= size)//判断是否越界
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)//再次判断是否越界,在 我们这里的操作时,有异步线程修改了List
        throw new ConcurrentModificationException();
    cursor = i + 1;//游标+1
    return (E) elementData[lastRet = i];//返回元素 ,并设置上一次返回的元素的下标
}

public void remove() {//remove 掉 上一次next的元素
    if (lastRet < 0)//先判断是否next过
        throw new IllegalStateException();
    checkForComodification();//判断是否修改过

    try {
        ArrayList.this.remove(lastRet);//删除元素 remove方法内会修改 modCount 所以后面要更新Iterator里的这个标志值
        cursor = lastRet; //要删除的游标
        lastRet = -1; //不能重复删除 所以修改删除的标志位
        expectedModCount = modCount;//更新 判断集合是否修改的标志,
    } catch (IndexOutOfBoundsException ex) {
        throw new ConcurrentModificationException();
    }
}

//判断是否修改过了List的结构,如果有修改,抛出异常

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

}

总结

ArrayList和Vector

vector是线程同步的,所以它也是线程安全的,而arraylist是线程异步的,是不安全的。如果不考虑到线程的安全因素,一般用arraylist效率比较高。

如果集合中的元素的数目大于目前集合数组的长度时,vector增长率为目前数组长度的100%,而arraylist增长率为目前数组长度的50%。如果在集合中使用数据量比较大的数据,用vector有一定的优势。

如果查找一个指定位置的数据,vector和arraylist使用的时间是相同的,如果频繁的访问数据,这个时候使用vector和arraylist都可以。而如果移动一个指定位置会导致后面的元素都发生移动,这个时候就应该考虑到使用linklist,因为它移动一个指定位置的数据时其它元素不移动。

ArrayList 和Vector是采用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,都允许直接序号索引元素,但是插入数据要涉及到数组元素移动等内存操作,所以索引数据快,插入数据慢,Vector由于使用了synchronized方法(线程安全)所以性能上比ArrayList要差,LinkedList使用双向链表实现存储,按序号索引数据需要进行向前或向后遍历,但是插入数据时只需要记录本项的前后项即可,所以插入数度较快。

ArrayList和LinkedList

ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。

对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。

对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据。 这一点要看实际情况的。若只对单条数据插入或删除,ArrayList的速度反而优于LinkedList。但若是批量随机的插入删除数据,LinkedList的速度大大优于ArrayList. 因为ArrayList每插入一条数据,要移动插入点及之后的所有数据。

阅读原文...


微信扫一扫,分享到朋友圈

Collection集合框架
0

SegmentFault博客

Unchecked casts in Java

上一篇

Enjoy higher sound quality with new low prices on these Bose speakers

下一篇

评论已经被关闭。

插入图片

热门分类

往期推荐

Collection集合框架

长按储存图像,分享给朋友