zoukankan      html  css  js  c++  java
  • Java 集合学习--ArrayList

    一、ArrayList 定义

    ArrayList 是一个用数组实现的集合,支持随机访问,元素有序且可以重复。

    ①、实现 List 接口

    List接口继承Collection接口,是List类的顶层接口,定义了大量方法,子类可进行个性化实现

    ②、实现RandomAccess接口

    RandomAccess 接口是一个标记接口,类似我们熟悉的Serializable接口,表明支持随机访问,在工具类Collections中有发挥其作用。

    ③、实现 Cloneable 接口

    能否调用Object.clone() 方法的关键,如果未实现,调用clone则抛出CloneNoSupportException异常。

    ④、实现 Serializable 接口

    这个接口没什么好说的,能都进行序列化的关键。

    二、字段属性

    ArrayList类的主要属性如下:

    //Arraylist默认初始大小
    private static final int DEFAULT_CAPACITY = 10;
    //空的数组实例
    private static final Object[] EMPTY_ELEMENTDATA = {};
    //这也是一个空的数组实例,与上面空数组的区别在于,当第一个元素添加的时候,需要膨胀多少
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    //存储 ArrayList集合的元素的数组
    transient Object[] elementData;
    //ArrayList集合元素个数
    private int size;

    三、构造函数

    1.无参构造函数,构造一个空的数组。

     

    2.有参构造函数,参数指定数组的初始大小。构造给定的初始大小的数组。

     3.有参构造函数,参数是一个集合,最终是将给定的集合复制到数组中。

    四、主要方法

    1.添加元素

    public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
    }

    在添加元素之前,需要调用ensureCapacityInternal()方法来确定数组的大小,保证数组有足够的大小来容量新增的元素。如果数组已经满了,则进行数组增大,主要是借助Arrays.copyOf()方法实现的。

     1 private void ensureExplicitCapacity(int minCapacity) {
     2         modCount++;
     3 
     4         // overflow-conscious code
     5         if (minCapacity - elementData.length > 0)
     6             grow(minCapacity);
     7 }
     8 
     9  private void grow(int minCapacity) {
    10         // overflow-conscious code
    11         int oldCapacity = elementData.length;
    12         int newCapacity = oldCapacity + (oldCapacity >> 1);
    13         if (newCapacity - minCapacity < 0)
    14             newCapacity = minCapacity;
    15         if (newCapacity - MAX_ARRAY_SIZE > 0)
    16             newCapacity = hugeCapacity(minCapacity);
    17         // minCapacity is usually close to size, so this is a win:
    18         elementData = Arrays.copyOf(elementData, newCapacity);
    19 }

    ①、当通过 ArrayList() 构造一个空集合,初始长度是为0的,第 1 次添加元素,会创建一个长度为10的数组,并将该元素赋值到数组的第一个位置。

    ②、第 2 次添加元素,集合不为空,而且由于集合的长度size+1是小于数组的长度10,所以直接添加元素到数组的第二个位置,不用扩容。

    ③、第 11 次添加元素,此时 size+1 = 11,而数组长度是10,这时候创建一个长度为10+10*0.5 = 15 的数组(扩容1.5倍),然后将原数组元素引用拷贝到新数组。并将第 11 次添加的元素赋值到新数组下标为10的位置。

    数组的最大长度为 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    2.删除元素

     ①、通过数组索引删除元素

    public E remove(int index) {
            rangeCheck(index);
    
            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;
    }

    首先判断索引的值是否大于等于size,超过集合的大小则抛出IndexOutOfBoundsException异常,再通过System.arraycopy()方法对数组进行复制操作,最终形成的数组是删除指定位置后的数组,达到了移动数组中元素位置的效果。

    ②、直接删除指定元素

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

    删除指定元素的逻辑非常简单,即首先遍历数组,找到指定元素在数组中的位置后,最终的逻辑和通过索引删除元素一致。

    3.查找元素

     public E get(int index) {
            rangeCheck(index);
    
            return elementData(index);
    }

    查找元素是根据索引查找,即通过索引获取数组中的值。

    4.集合遍历

    ①、普通 for 循环遍历,通过循环,在借助get方法即可遍历ArrayList集合。

    ②、通过terator遍历

    首先获取迭代器,其返回的是ArrayList类中自定义的内部类Itr。

    public Iterator<E> iterator() {
            return new Itr();
    }
     1 private class Itr implements Iterator<E> {
     2         int cursor;       // index of next element to return
     3         int lastRet = -1; // index of last element returned; -1 if no such
     4         int expectedModCount = modCount;
     5 
     6         Itr() {}
     7 
     8         public boolean hasNext() {
     9             return cursor != size;
    10         }
    11 
    12         @SuppressWarnings("unchecked")
    13         public E next() {
    14             checkForComodification();
    15             int i = cursor;
    16             if (i >= size)
    17                 throw new NoSuchElementException();
    18             Object[] elementData = ArrayList.this.elementData;
    19             if (i >= elementData.length)
    20                 throw new ConcurrentModificationException();
    21             cursor = i + 1;
    22             return (E) elementData[lastRet = i];
    23         }
    24 
    25         public void remove() {
    26             if (lastRet < 0)
    27                 throw new IllegalStateException();
    28             checkForComodification();
    29 
    30             try {
    31                 ArrayList.this.remove(lastRet);
    32                 cursor = lastRet;
    33                 lastRet = -1;
    34                 expectedModCount = modCount;
    35             } catch (IndexOutOfBoundsException ex) {
    36                 throw new ConcurrentModificationException();
    37             }
    38         }
    39 
    40         @Override
    41         @SuppressWarnings("unchecked")
    42         public void forEachRemaining(Consumer<? super E> consumer) {
    43             Objects.requireNonNull(consumer);
    44             final int size = ArrayList.this.size;
    45             int i = cursor;
    46             if (i >= size) {
    47                 return;
    48             }
    49             final Object[] elementData = ArrayList.this.elementData;
    50             if (i >= elementData.length) {
    51                 throw new ConcurrentModificationException();
    52             }
    53             while (i != size && modCount == expectedModCount) {
    54                 consumer.accept((E) elementData[i++]);
    55             }
    56             // update once at end of iteration to reduce heap write traffic
    57             cursor = i;
    58             lastRet = i - 1;
    59             checkForComodification();
    60         }
    61 
    62         final void checkForComodification() {
    63             if (modCount != expectedModCount)
    64                 throw new ConcurrentModificationException();
    65         }
    66     }
    View Code

    ③、forEach遍历ArrayList本质即通过迭代器遍历。

    5.toArray,ArrayList集合转换成数组

    public Object[] toArray() {
            return Arrays.copyOf(elementData, size);
    }

    6.trimToSize()

    public void trimToSize() {
            modCount++;
            if (size < elementData.length) {
                elementData = (size == 0)
                  ? EMPTY_ELEMENTDATA
                  : Arrays.copyOf(elementData, size);
            }
    }

    这个方法主要是释放内存,将ArrayList集合中数组的大小调整为size的大小。

    7.subList方法

    这个方法往往引起我们犯错,subList返回的并不是一个ArrayList。经常我们容易使用subList后,再使用add方法,然后报异常。具体原因源码显示的很清楚。

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

    其他方法的学习省略,感兴趣的可以参考源码。

    五、总结

    • ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存。
    • ArrayList不是线程安全的,只能用在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的CopyOnWriteArrayList类。
    • ArrayList的实现中大量地调用了Arrays.copyof()和System.arraycopy()方法
    • ArrayList基于数组实现,可以通过下标索引直接查找到指定位置的元素,因此查找效率高,但每次插入或删除元素,就要大量地移动元素,插入删除元素的效率低。
    • ArrayList中允许元素为null,且可以重复。
  • 相关阅读:
    MIT自然语言处理第三讲:概率语言模型(第四部分)
    MIT自然语言处理第二讲:单词计数(第三部分)
    MIT自然语言处理第五讲:最大熵和对数线性模型(第二部分)
    MIT自然语言处理第二讲:单词计数(第四部分)
    MIT自然语言处理第三讲:概率语言模型(第一部分)
    MIT自然语言处理第二讲:单词计数(第二部分)
    MIT自然语言处理第五讲:最大熵和对数线性模型(第一部分)
    MIT自然语言处理第四讲:标注(第二部分)
    MIT自然语言处理第二讲:单词计数(第一部分)
    MIT自然语言处理第一讲:简介和概述(第二部分)
  • 原文地址:https://www.cnblogs.com/liupiao/p/9326663.html
Copyright © 2011-2022 走看看