集合之List数组

ArrayList

它可以以O(1)时间复杂度对元素进行随机访问。因为数组申请的内存是一段连续的内存地址,由于所申请的内存地址是连续的,我们只需要知道第一个内存地址和数组空间,就可以推断出查找下标的内存地址,即所存放的元素。

  • 优缺点:数组查询快,根据地址和索引直接获取元素。
  • 数组增删慢,每次都需要创建新数组,且移动元素位置。

新旧数组之间元素的复制函数:

	 * @param      src      the source array.
     * @param      srcPos   starting position in the source array.
     * @param      dest     the destination array.
     * @param      destPos  starting position in the destination data.
     * @param      length   the number of array elements to be copied.
public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);

添加元素

只有在扩容的时候才会通过复制元素创建新的数组。

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
		//判断数组中的元素类型是否发生变化
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        //从原数组下标为0开始复制,复制长度为Math.min(original.length, newLength)
        //原数组元素从新数组下标为0的位置开始填充,填充的长度为Math.min(original.length, newLength)
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

删除元素remove

元素的删除是从下标为0的元素开始,每次删除后都会对原数组通过复制方式将其余元素向前移动。
正常删除方式使用:

	//创建迭代器对象,局部变量初始化
	Iterator<String> iterator = list.iterator();
	while (iterator.hasNext()){
            iterator.next();//返回删除的元素
            iterator.remove();
          )
private class Itr implements Iterator<E> {
        int cursor;       // 作用就是用来判断数组中是否还存在元素
        int lastRet = -1; // 下一个元素的下标
        int expectedModCount = modCount;//创建对象时初始化变量

        public boolean hasNext() {
            return cursor != size;
        }
       
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                //迭代器中remove方法最终调用集合中remove方法【说明集合中的remove方法不是提供给外部调用的】,通过数组复制方式进行删除
                ArrayList.this.remove(lastRet);
                //游标复位,保证下次删除从下标为0的位置开始
                cursor = lastRet;//该方法中的cursor ==lastRet==0
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

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

ArrayList.this.remove(lastRet):

public E remove(int index) {
        rangeCheck(index);
		//即使删除modCount++
        modCount++;
        E oldValue = elementData(index);

        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

        return oldValue;
    }
//删除元素时,原始数组与目标数组都是同一个数组,所以只是元素的移动。
//index:被删除元素的下标位置
//numMoved:被移动元素的个数
//index+1:被移动元素的起始位置
System.arraycopy(elementData, index+1, elementData, index,numMoved);

增强For循环之For…Each

jdk1.5之后出现的,其内部原理是一个迭代器Iterator;实现Iterator接口的类才能使用迭代器和增强for;简化数组和collection集合的遍历。

如下方式:该种方式删除会产生异常 java.util.ConcurrentModificationException。

for(Integer i : list){
    System.out.println(list.size());
    list.remove(i);
}

编译之后:

Iterator i$ = list.iterator();
while(i$.hasNext()) {
   Integer i = (Integer)i$.next();
   System.out.println(list.size());
   list.remove(i);
}

next()方法里面会校验modCount 与 expectedModCount值,但是此时remove方法调用的不是迭代器中的remove方法,导致expectedModCount值不会发生改变,第二次遍历由于next()方法校验会抛出ConcurrentModificationException异常。

remove方法一共有3个:

  1. public E remove(int index);
  2. public boolean remove(Object o);

迭代器中的public void remove(),每次值发生变化会重置expectedModCount值;

扩容

初始化容量为10的集合容器;
每次扩容量为当前容量的一半;
扩容的条件为:当前数组中的元素大于数组长度

public boolean add(E e) {
//首次扩容size = 0
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //size是数组索引:先赋值后运算
    elementData[size++] = e;
    return true;
}

private void ensureCapacityInternal(int minCapacity) {
   if (elementData == EMPTY_ELEMENTDATA) {//首次成立
   	//DEFAULT_CAPACITY = 10
       minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
   }
   ensureExplicitCapacity(minCapacity);
}

private void ensureExplicitCapacity(int minCapacity) {
   modCount++;
   // 扩容的条件:minCapacity = size + 1;size在上次添加元素时会自增1
   if (minCapacity - elementData.length > 0)
       grow(minCapacity);
}
//首次 minCapacity 当前数组中的元素个数
 private void grow(int minCapacity) {
    // overflow-conscious code
    //当前数组容器的容量
    int oldCapacity = elementData.length;
    //每次容量扩容数量是oldCapacity + (oldCapacity >> 1);
    //10 --> 15 -->22--->...
    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:
    //首次初始化容量为10的数组
    elementData = Arrays.copyOf(elementData, newCapacity);
}

LinkedList

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

线程安全问题

class ArrayListInThread implements Runnable {

    //线程不安全
    private List threadList = new ArrayList();
    //线程安全
    //private List threadList = Collections.synchronizedList(new ArrayList());

    @Override
    public void run() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    //把当前线程名称加入list中
        threadList.add(Thread.currentThread().getName());
    }

    public static void main(String[] args) throws InterruptedException {
        ArrayListInThread listThread = new ArrayListInThread();


        for (int i = 0; i < 100; i++) {
            Thread thread = new Thread(listThread, String.valueOf(i));
            thread.start();
        }

    //等待子线程执行完
        Thread.sleep(2000);

        System.out.println(listThread.threadList.size());
    //输出list中的值
        for (int i = 0; i < listThread.threadList.size(); i++) {
            if (listThread.threadList.get(i) == null) {
                System.out.println();
            }
            System.out.println(listThread.threadList.get(i) + " ");
        }
    }
}

结果中,有的值没有出现(结果一中3没有出现),有的出现了null值,这是由于赋值时出现了覆盖。赋值语句为:elementData[size++] = e,这条语句可拆分为两条:

elementData[size] = e;
size ++

假设A线程执行完第一条语句时,CPU暂停执行A线程转而去执行B线程,此时ArrayList的size并没有加一,这时在ArrayList中B线程就会覆盖掉A线程赋的值,而此时,A线程和B线程先后执行size++,便会出现值为null的情况;
ArrayIndexOutOfBoundsException异常:
则是A线程在执行ensureCapacity(size+1)后没有继续执行,此时恰好minCapacity等于oldCapacity,B线程再去执行,同样由于minCapacity等于oldCapacity,ArrayList并没有增加长度,B线程可以继续执行赋值(elementData[size] = e)并size ++也执行了,此时,CPU又去执行A线程的赋值操作,由于size值加了1,size值大于了ArrayList的最大长度,
因此便出现了ArrayIndexOutOfBoundsException异常。

既然ArrayList是线程不安全的,但如果需要在多线程中使用,可以采用list list =Collections.synchronizedList(new ArrayList)来创建一个ArrayList对象。

数组与链表的优缺点

  • ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
  • 对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
  • 对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据 。
  • LinkedList是以元素链表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。
    **备注1:**如果要在数组中增加一个元素,需要移动大量元素,那么这个元素后的所有元素的内存地址都要往后(前)移动(数组的内存地址是连续的),对最后一个元素插入(或删除)时才比较快,然后将要增加的元素放在其中。同样的道理,如果想删除一个元素,同样需要移动大量元素去填掉被移动的元素。
    **备注2:**增加和删除一个元素对于链表数据结构只要修改元素中的指针(包括指针指向,节点值)就可以了。如果应用需要经常插入和删除元素你就需要用链表数据结构了。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值