Java 集合学习--ArrayList

Posted 一曲天下,一曲江湖

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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,且可以重复。

以上是关于Java 集合学习--ArrayList的主要内容,如果未能解决你的问题,请参考以下文章

Java 集合学习笔记:ArrayList

Java小白集合源码的学习系列:ArrayList

Java小白集合源码的学习系列:ArrayList

Java集合源码学习笔记ArrayList分析

Java 集合学习--ArrayList

java语言基础--集合学习,ArrayList和Linkedlist