美文网首页
集合之ArrayList

集合之ArrayList

作者: 风月寒 | 来源:发表于2021-07-23 14:13 被阅读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);
            }
        }
    
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    

    从上面可知,当initialCapacity大于0的时候,直接初始化一个大小为initialCapacity的Object数组。initialCapacity为0的时候,则将空的Object数组赋值给elementData。

    在这里需要区别的是,数组的长度和list的长度,在面试的时候,问过一个这样的问题,就是在有参初始化list的时候,数组的长度有没有变化?list的长度有没有变化?

    ArrayList<Integer> list = new ArrayList(10);
    System.out.println(list.size());
    list.set(2,1)
    

    第一行返回的是0,第二行则抛出IndexOutOfBoundsException异常。所以在初始化ArrayList的时候,数组的长度会改变,但是list的长度返回的是size。只有当add()或者remove()的时候size才会变化。

    add()
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    public void add(int index, E element) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                             size - index);
        elementData[index] = element;
        size++;
    }
    

    在add方法中,会先调用ensureCapacityInternal()。

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

    会与DEFAULT_CAPACITY先进行比较,取最大值,然后紧接着调用ensureExplicitCapacity更新modCount的值,并判断是否需要扩容。

    private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
    
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
    

    在ensureExplicitCapacity()主要是判断是否进行扩容处理,当传入的是数据大于数据的长度的时候,则需要扩容。

    private void grow(int minCapacity) {
            // overflow-conscious code
            int oldCapacity = elementData.length;
            //更新为旧容量的1.5倍
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            //检查新容量的大小是否小于最小需要容量,如果小于那旧将最小容量最为数组的新容量
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            ////如果新容量大于MAX_ARRAY_SIZE,使用hugeCapacity比较二者
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            // 将原数组中的元素拷贝
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    

    下面来看看hugeCapacity();

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        //对minCapacity和MAX_ARRAY_SIZE进行比较
        //若minCapacity大,将Integer.MAX_VALUE作为新数组的大小
        //若MAX_ARRAY_SIZE大,将MAX_ARRAY_SIZE作为新数组的大小
        //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
        return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
    }
    
    

    这里需要注意MAX_ARRAY_SIZE,有些虚拟机会在数组中保存 header words 头部字。当虚拟机大于 MAX_ARRAY_SIZE (Integer.MAX -8 )就容易OOM。

    所有有这个判断的目的是尽量减小OOM。

    进行一系列处理之后,然后将添加的值放入到对应的位置,并size++;

    add(int index, E element)与add(E element)最大的区别是add(int index, E element)需要进行数组的拷贝。调用的是arraycopy()

    System.arraycopy(elementData, index, elementData, index + 1,
                             size - index);
    

    ArrayList所谓的查询快增删慢就是体现在这里。

    remove()
    public E remove(int index) {
            if (index >= size)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    
            modCount++;
            E oldValue = (E) elementData[index];
    
            int numMoved = size - index - 1;
            //如果这个值大于0 说明后续有元素需要左移(size=index+1)
            //如果是0说明被移除的对象就是最后一位元素(不需要移动别的元素)
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            elementData[--size] = null; // clear to let GC do its work
    
            return oldValue;
        }
    

    先进行size检测,并对modCount进行修改,然后也是进行数组的拷贝处理。

    另外还有一个remove(Object o)方法,区别就是先根据值找到对应的index,然后和remove(int index)是一摸一样的。

    但是在我们平常工作中,遍历删除元素对于新手可能会出现一些问题。下面我们进行细讲。

    ArrayList遍历时删除元素总结有下面几种方式。

    1、 普通for循环正序删除(结果:会漏掉元素判断)

    2、 普通for循环倒序删除(结果:正确删除)

    3、 for-each循环删除(结果:抛出异常)

    4、 Iterator遍历,使用ArrayList.remove()删除元素(结果:抛出异常)

    5、 Iterator遍历,使用Iterator的remove删除元素(结果:正确删除)

    普通for循环正序删除
    原ArrayList是[4, 5, 6, 7, 7, 5]
    
    for (int i = 0; i < arrayList.size(); i++) {
        if (arrayList.get(i) == 7) {
            arrayList.remove(i);
            //解决方案: 加一行代码i = i - 1; 删除元素后,下标减1
        }
        System.out.println("当前arrayList是"+arrayList.toString());
    }
    
    返回的结果为[4,5,6,7,5]
    
    

    可以看到与我们预期的结果不一致,因为在最删除的时候,我们知道,当前数字后面的数的位置都会往前移,第二个7的位置会移到第一个7的位置。为当下一轮循环的时候,i++了,对应位置上是5了,所以会出想上面的情况。

    解决办法:在删除之后,i--操作。

    for-each循环删除
    for (Integer value : arrayList) {
            if (value.equals(7)) {
                arrayList.remove(value);
            }
        System.out.println("当前arrayList是"+arrayList.toString());
        }
        
        
    结果 Exception in thread "main" java.util.ConcurrentModificationException
        at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
        at java.util.ArrayList$Itr.next(ArrayList.java:851)
        at com.test.ArrayListTest1.removeWayThree(ArrayListTest1.java:50)
        at com.test.ArrayListTest1.main(ArrayListTest1.java:24)
    
    

    会抛出ConcurrentModificationException异常,主要在于for-each的底层实现是使用ArrayList.iterator的hasNext()方法和next()方法实现的,

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

    调用next()的时候,会进行判断,modCount如果不等于expectedModCount,则会报上面的那个异常。expectedModCount是一开始酒精性赋值,相当于是一个固定值,不会改变。而我们进行remove操作的时候,modCount会增加,所以导致两个值不一致,所以报异常了。

    Iterator遍历,使用ArrayList.remove()删除元素这种方式包异常也是跟上面一摸一样的原因。

    Iterator遍历,使用Iterator的remove删除元素
    Iterator<Integer> iterator = arrayList.iterator();
    while (iterator.hasNext()) {
        Integer value = iterator.next();
        if (value.equals(3)) {//3是需要删除的元素
            iterator.remove();
        }
    }
    
    
    

    当调用iterator.remove()时,调用的是Itr的remove

    public void remove() {
                if (lastRet < 0)
                    throw new IllegalStateException();
                if (modCount != expectedModCount)
                    throw new ConcurrentModificationException();
    
                try {
                    ArrayList.this.remove(lastRet);
                    cursor = lastRet;
                    lastRet = -1;
                    expectedModCount = modCount;
                    limit--;
                } catch (IndexOutOfBoundsException ex) {
                    throw new ConcurrentModificationException();
                }
            }
    

    从上面可以看到,对expectedModCount进行的重新赋值,所以就不行报异常。

    Arrays.asList
    List<Integer> statusList = Arrays.asList(1, 2);
    statusList.add(3);
    System.out.println(statusList.contains(3));
    

    预期的结果,应该是输出true,但是实际却是抛出了java.lang.UnsupportedOperationException异常:

    因为asList 返回的ArrayList却是Arrays类的内部类,它也继承了AbstractList类,重写了很多方法,比如我们上面使用的contains方法,但是却没有重写add方法,所以我们在调用add方法时才会抛出java.lang.UnsupportedOperationException异常。

    subList

    1、修改原集合元素的值,会影响子集合

    2、修改原集合的结构(指的是添加和删除元素操作),会引起ConcurrentModificationException异常

    3、修改子集合元素的值,会影响原集合

    4、修改子集合的结构,会影响原集合

    相关文章

      网友评论

          本文标题:集合之ArrayList

          本文链接:https://www.haomeiwen.com/subject/gwtpmltx.html