Java集合:arraylist及java.util.ConcurrentModificationException

一 list:

上一篇整理了 Java 集合框架的的主要底层接口collection,本文继续整理它的子接口list。其它子接口见下图


官方文档: https://docs.oracle.com/javase/tutorial/collections/interfaces/list.html

一个 List 是一个元素有序的、可以重复、可以为 null 的集合。

Positional access — manipulates elements based on their numerical position in the list. This includes methods such as get, set, add, addAll, and remove.
Search — searches for a specified object in the list and returns its numerical position. Search methods include indexOf and lastIndexOf.
Iteration — extends Iterator semantics to take advantage of the list's sequential nature. The listIterator methods provide this behavior.
Range-view — The sublist method performs arbitrary range operations on the list.
The Java platform contains two general-purpose List implementations. ArrayList, which is usually the better-performing implementation, and LinkedList which offers better performance under certain circumstances.
位置相关:List 和 数组一样,都是从 0 开始,我们可以根据元素在 list 中的位置进行操作,比如说 get, set, add, addAll, remove;
搜索:从 list 中查找某个对象的位置,比如 indexOf, lastIndexOf;
迭代:使用 Iterator 的拓展版迭代器 ListIterator 进行迭代操作;
范围性操作:使用 subList 方法对 list 进行任意范围的操作。

The Java platform contains two general-purpose List implementations. ArrayList, which is usually the better-performing implementation, and LinkedList which offers better performance under certain circumstances.

jdk两种list的实现方式,arraylist基于数组,linklist基于链表。

二 arraylist 实现接口

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
ArrayList 是一个用数组实现的集合,支持随机访问,元素有序且可以重复。

2.1 实现RandomAccess

public interface RandomAccess {
}

      这是一个标记接口,一般此标记接口用于 List 实现,以表明它们支持快速(通常是恒定时间)的随机访问。该接口的主要目的是允许通用算法改变其行为,以便在应用于随机或顺序访问列表时提供良好的性能。

     如果集合类是RandomAccess的实现,则尽量用for(int i = 0; i < size; i++) 来遍历而不要用Iterator迭代器来遍历,在效率上要差一些。反过来,如果List是linkedList,则最好用迭代器来进行迭代。看个demo:

public class TestRadomAccess {
	// 初始化列表
	public static void initList(List list, int n) {
		for (int i = 0; i < n; i++) {
			list.add(i);
		}
	}
	// 使用循环进行对列表的迭代
	public static void traverseWithLoop(List list) {
		long starttime = 0;
		long endtime = 0;
		starttime = System.currentTimeMillis();

		for (int i = 0; i < list.size(); i++) {
			list.get(i);
		}

		endtime = System.currentTimeMillis();
		System.out.println("使用loop迭代:" + (endtime - starttime) + "ms时间");
	}
	// 使用迭代器对列表进行迭代
	public static void traverseWithIterator(List list) {
		long starttime = 0;
		long endtime = 0;
		starttime = System.currentTimeMillis();
		for (Iterator itr = list.iterator(); itr.hasNext();) {
			itr.next();
		}
		endtime = System.currentTimeMillis();
		System.out.println("使用Iterator迭代:" + (endtime - starttime) + "ms时间");
	}

	public static void traverse(List list) {

		long starttime = 0;
		long endtime = 0;
		if (list instanceof RandomAccess) {
			System.out.println("该list实现了RandomAccess接口");
			starttime = System.currentTimeMillis();

			for (int i = 0; i < list.size(); i++) {
				list.get(i);
			}

			endtime = System.currentTimeMillis();
			System.out.println("迭代:" + (endtime - starttime) + "ms时间");
		} else {
			System.out.println("该list未实现RandomAccess接口");
			starttime = System.currentTimeMillis();
			for (Iterator itr = list.iterator(); itr.hasNext();) {
				itr.next();
			}
			endtime = System.currentTimeMillis();
			System.out.println("迭代:" + (endtime - starttime) + "ms时间");
		}
	}

	public static void main(String[] args) {
		ArrayList arraylist = new ArrayList();
		LinkedList linkedlist = new LinkedList();
		initList(arraylist, 50000);
		initList(linkedlist, 50000);

		traverseWithIterator(arraylist);
		traverseWithLoop(arraylist);
		traverseWithIterator(linkedlist);
		traverseWithLoop(linkedlist);

		traverse(arraylist);
		traverse(linkedlist);
	}
}
输出:使用Iterator迭代:5ms时间
使用loop迭代:3ms时间
使用Iterator迭代:2ms时间
使用loop迭代:1169ms时间
该list实现了RandomAccess接口
迭代:1ms时间
该list未实现RandomAccess接口
迭代:3ms时间

2.2 实现了Cloneable接口

也是标记性接口,前面我们讲解深拷贝和浅拷贝的原理时,我们介绍了浅拷贝可以通过调用 Object.clone() 方法来实现,但是调用该方法的对象必须要实现 Cloneable 接口,否则会抛出 CloneNoSupportException异常。

2.3 实现 Serializable 接口

2.4 实现了List 接口

  List 类集合的上层接口。

三 ArrayList属性和构造函数

        //集合的默认大小
        private static final int DEFAULT_CAPACITY = 10;
        //空的数组实例
        private static final Object[] EMPTY_ELEMENTDATA = {};
        //这也是一个空的数组实例,和EMPTY_ELEMENTDATA空数组相比是用于了解添加元素时数组膨胀多少
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
        //存储 ArrayList集合的元素,集合的长度即这个数组的长度
        //1、当 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 时将会清空 ArrayList
        //2、当添加第一个元素时,elementData 长度会扩展为 DEFAULT_CAPACITY=10
        transient Object[] elementData;
        //表示集合的长度
        private int size;

构造函数

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

注释说的初始值是ten,其实是空。此无参构造函数将创建一个 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 声明的数组,注意此时初始容量是0。

 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);
        }
    }
初始化集合大小创建 ArrayList 集合。当大于0时,给定多少那就创建多大的数组;当等于0时,创建一个空数组;当小于0时,抛出异常。
public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
这是将已有的集合复制到 ArrayList 集合中去

四 方法

添加add

通过前面的字段属性和构造函数,我们知道 ArrayList 集合是由数组构成的,那么向 ArrayList 中添加元素,也就是向数组赋值。我们知道一个数组的声明是能确定大小的,而使用 ArrayList 时,好像是能添加任意多个元素,这就涉及到数组的扩容。

   public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
   add实现里面首先调用 ensureCapacityInternal 方法来确定集合的大小,如果集合满了,则要进行扩容操作。

      

 private void ensureCapacityInternal(int minCapacity) {//minCapacity= 当前集合size+1
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);//取默认值10与minCapacity之间最大的值
        }

        ensureExplicitCapacity(minCapacity);
    }
     private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    在 ensureExplicitCapacity 方法中,首先对修改次数modCount加一,这里的modCount给ArrayList的迭代器使用的,在并发操作被修改时,提供快速失败行为(保证modCount在迭代期间不变,否则抛出ConcurrentModificationException异常),接着判断minCapacity是否大于当前ArrayList内部数组长度,大于的话调用grow方法对内部数组elementData扩容,grow方法代码如下:  

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;//得到原始数组的长度
        int newCapacity = oldCapacity + (oldCapacity >> 1);//新数组长度是原数组的1.5倍
        if (newCapacity - minCapacity < 0)//当新数组长度仍然比minCapacity小,则为保证最小长度,新数组等于minCapacity
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)//当得到的新数组长度比 MAX_ARRAY_SIZE 大时,调用 hugeCapacity 处理大数组
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

初始化默认长度是10

    /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;

上面代码oldCapacity >> 1 就是右移,相当于除以2.所以扩容后有可能溢出。

所以下面的就是做判断,结果就是将新容量更新为旧容量的1.5倍,
然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量,
再检查新容量是否超出了ArrayList所定义的最大容量,若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE,如果minCapacity大于最大容量,则新容量则为ArrayList定义的最大容量,否则,新容量大小则为 minCapacity。

最后是数组的拷贝,底层依赖于数组的 System.arraycopy。因为这里扩容很耗性能。所以最好是一开始就估算好list的大小。

删除remove

    public E remove(int index) {
        rangeCheck(index);//检查给定索引的范围,超了异常

        modCount++;//增加修改次数
        E oldValue = elementData(index);//得到索引处的要删除元素

        int numMoved = size - index - 1;
        if (numMoved > 0)
//转换一下为size - 1>index 即索引不是最后一个元素
       System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null; // clear to let GC do its work 数组最后一个清空 return oldValue; }remove(int index) 方法表示删除索引index处的元素,首先通过 rangeCheck(index) 方法判断给定索引的范围,超过集合大小则抛出异常;接着通过 System.arraycopy 方法对数组进行自身拷贝

3 修改

 通过调用 set(int index, E element) 方法在指定索引 index 处的元素替换为 element。并返回原数组的元素。

public E set(int index, E element) {
        rangeCheck(index);//判断索引合法性

        E oldValue = elementData(index);//获得原数组指定索引的元素
        elementData[index] = element;//将指定所引处的元素替换为 element
        return oldValue;//返回原数组索引元素
    }

4 遍历

for循环就不介绍了,看下迭代器,有两种listIterator,Iterator

 public ListIterator<E> listIterator() {
        return new ListItr(0);
    }

    /**
     * Returns an iterator over the elements in this list in proper sequence.
     *
     * <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
     *
     * @return an iterator over the elements in this list in proper sequence
     */
    public Iterator<E> iterator() {
        return new Itr();
    }
private class Itr implements Iterator<E> {
        int cursor;       //游标, 下一个要返回的元素的索引
        int lastRet = -1; // 返回最后一个元素的索引; 如果没有这样的话返回-1.
        int expectedModCount = modCount;

        //通过 cursor != size 判断是否还有下一个元素
        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)
                throw new ConcurrentModificationException();
            cursor = i + 1;//游标向后移动一位
            return (E) elementData[lastRet = i];//返回索引为i处的元素,并将 lastRet赋值为i
        }

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

            try {
                ArrayList.this.remove(lastRet);//调用ArrayList的remove方法删除元素
                cursor = lastRet;//游标指向删除元素的位置,本来是lastRet+1的,这里删除一个元素,然后游标就不变了
                lastRet = -1;//lastRet恢复默认值-1
                expectedModCount = modCount;//expectedModCount值和modCount同步,因为进行add和remove操作,modCount会加1
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {//便于进行forEach循环
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        //前面在新增元素add() 和 删除元素 remove() 时,我们可以看到 modCount++。修改set() 是没有的
        //也就是说不能在迭代器进行元素迭代时进行增加和删除操作,否则抛出异常
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
private class ListItr extends Itr implements ListIterator<E> {
        ListItr(int index) {
            super();
            cursor = index;
        }

        public boolean hasPrevious() {
            return cursor != 0;
        }

        public int nextIndex() {
            return cursor;
        }

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

        @SuppressWarnings("unchecked")
        public E previous() {
            checkForComodification();
            int i = cursor - 1;
            if (i < 0)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i;
            return (E) elementData[lastRet = i];
        }

        public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.set(lastRet, e);
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        public void add(E e) {
            checkForComodification();

            try {
                int i = cursor;
                ArrayList.this.add(i, e);
                cursor = i + 1;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
    }

相比于 Iterator 迭代器,这里的 ListIterator 多出了能向前迭代,以及能够新增元素。

注意一点:在进行 next() 方法调用的时候,会进行 checkForComodification() 调用,该方法表示迭代器进行元素迭代时,如果同时进行增加和删除操作,会抛出 ConcurrentModificationException 异常。比如下面的例子

public class Manager {
	
	
	static ArrayList<Integer> list = new ArrayList<Integer>();
    static{
    	 for (int i = 0; i < 2000; i++) {
    		 list.add(Integer.valueOf(i));
         }
    }
	
	public void modify(){
		for (int i = 3000; i < 5000; i++) {
			list.add(i);
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
   }
}
public class ListTest {

	public static void main(String[] args) {    
		Manager maneger = new Manager();

        // 复现
//        Iterator<Integer> iterator = arrayList.iterator();
//        while (iterator.hasNext()) {
//            Integer integer = iterator.next();
//            if (integer.intValue() == 5) {
//            	iterator.remove();
//            }
//        }
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
            	System.out.println("thread1 start");
                ListIterator<Integer> iterator = Manager.list.listIterator();
                while (iterator.hasNext()) {
                    System.out.println("thread1 " + iterator.next().intValue());
                   
                }
            }
        });
        thread1.start();
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
            	System.out.println("thread2 start");
            	try {
					Thread.sleep(1);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
            	maneger.modify();
            }
        });    
        thread2.start();
	}
}
如果不看thread2.只读到thread1,我第一眼认为没问题的,不就是循环遍历个arraylist嘛。
但是线上偶发的会出现ConcurrentModificationException ,定位代码就是这里,所以模拟下读取的时候有修改的case。
输出结果:
thread1 53
thread2 start
thread1 54
thread1 55
thread1 56
thread1 57
thread1 58
thread1 59
thread1 60
Exception in thread "Thread-0" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
	at java.util.ArrayList$Itr.next(ArrayList.java:851)
	at com.daojia.collect.ListTest$1.run(ListTest.java:26)
	at java.lang.Thread.run(Thread.java:745)
怎么解决啊。简单粗暴就是加锁。

5. 局部范围操作之sublist

 public List<E> subList(int fromIndex, int toIndex) {
        subListRangeCheck(fromIndex, toIndex, size);
        return new SubList(this, 0, fromIndex, toIndex);
    }

    static void subListRangeCheck(int fromIndex, int toIndex, int size) {
        if (fromIndex < 0)
            throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
        if (toIndex > size)
            throw new IndexOutOfBoundsException("toIndex = " + toIndex);
        if (fromIndex > toIndex)
            throw new IllegalArgumentException("fromIndex(" + fromIndex +
                                               ") > toIndex(" + toIndex + ")");
    }

    private class SubList extends AbstractList<E> implements RandomAccess {
        private final AbstractList<E> parent;
        private final int parentOffset;
        private final int offset;
        int size;

        SubList(AbstractList<E> parent,
                int offset, int fromIndex, int toIndex) {
            this.parent = parent;
            this.parentOffset = fromIndex;
            this.offset = offset + fromIndex;
            this.size = toIndex - fromIndex;
            this.modCount = ArrayList.this.modCount;
        }

这里需要注意的是subList 返回的扔是 List 原来的引用,只不过把开始位置 offset 和 size 改了下。所以对 subList 进行的操作也会影响到原有 List.原文注释有: The returned list is backed by this list, so non-structural     * changes in the returned list are reflected in this list, and vice-versa.     * The returned list supports all of the optional list operations.

举例测试:

package com.daojia.collect;

import java.util.ArrayList;
import java.util.List;

public class ArrayListTest {

	public static void main(String[] args) {	
		
		ArrayList l = new ArrayList();
		
		l.add(1);
                l.add(2);
                l.add(3);
                l.add(4);
		List sub = l.subList(0, 2);
		sub.clear();
		System.out.println(l);
		l.toArray();
	}
}

输出的是:[3,4]

算法:

  • sort — sorts a List using a merge sort algorithm, which provides a fast, stable sort. (A stable sort is one that does not reorder equal elements.)
  • shuffle — randomly permutes the elements in a List.
  • reverse — reverses the order of the elements in a List.
  • rotate — rotates all the elements in a List by a specified distance.
  • swap — swaps the elements at specified positions in a List.
  • replaceAll — replaces all occurrences of one specified value with another.
  • fill — overwrites every element in a List with the specified value.
  • copy — copies the source List into the destination List.
  • binarySearch — searches for an element in an ordered List using the binary search algorithm.
  • indexOfSubList — returns the index of the first sublist of one List that is equal to another.
  • lastIndexOfSubList — returns the index of the last sublist of one List that is equal to another.

vector

Vector和ArrayList很像,都是基于数组实现的集合,它和ArrayList的主要区别在于

  • Vector是线程安全的,而ArrayList不是

  • 由于Vector中的方法基本都是synchronized的,其性能低于ArrayList

  • Vector可以定义数组长度扩容的因子,ArrayList不能

  • vector比较古老了如果没有线程安全的需求,一般推荐使用 ArrayList

 protected int capacityIncrement;
 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);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值