ArrayList源码解析

Posted hansonyao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ArrayList源码解析相关的知识,希望对你有一定的参考价值。

一、ArrayList认识

  ArrayList是可以动态增长和缩减的索引序列,它是基于数组实现的List类。如图技术图片

 

 

 二、源码解析

  内部存储元素是数组,默认数据大小是10,下面介绍常用方法。

2.1、构造方法

  • ArrayList():构造一个初始容量为10的空列表
  • ArrayList( int initialCapcity ):构造一个具有初始容量值得空列表
  • ArrayList(Collection<?extend E> c):构造一个包含指定元素的列表
//第一种
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    //    第二种
    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(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

 2.2、增删改查操作

对于增删改查的基本操作,在这里只给出一些比较重要的源代码,实现起来比较简单的就不给出了。

(1)增加元素

  • add(E e):添加一个元素到列表的末尾。
  • add( int index, E element ) :在指定的位置添加元素
  • addAll( Collection<? extends E> c ):添加一个集合到元素的末尾.以上返回类型是boolean
 1     //第一种 尾插
 2     public boolean add(E e) {
 3         //确保数组边界可以存放新元素 size为当前已存放元素个数
 4         ensureCapacityInternal(size + 1);
 5         elementData[size++] = e;
 6         return true;
 7     }
 8     //第二种 插入
 9     public void add(int index, E element) {
10         rangeCheckForAdd(index);
11         ensureCapacityInternal(size + 1); 
12         System.arraycopy(elementData, index, elementData, index + 1,
13                 size - index);
14         elementData[index] = element;
15         size++;
16     }
17     //第三种 批量增加
18     public boolean addAll(Collection<? extends E> c) {
19         Object[] a = c.toArray();
20         int numNew = a.length;
21         ensureCapacityInternal(size + numNew);  // Increments modCount
22         System.arraycopy(a, 0, elementData, size, numNew);
23         size += numNew;
24         return numNew != 0;
25     }

(2)删除操作

  • remove(Object o):删除列表中第一个出现O的元素
  • remove( int index):删除列表中指定位置的元素
  • removeAll(Collection<?> c):删除列表中包含C的所有元素
  • removeIf(Predictcate<? super E> filter):删除列表中给定谓词的所有元素
  • removeRange( int from,int to ):删除从from到to的所有元素
  • clear():清除所有的元素。返回类型为void
 1  //第一种
 2     public E remove(int index) {
 3         //1、检查越界
 4         rangeCheck(index);
 5         modCount++;
 6         //2、获取被删除元素
 7         E oldValue = elementData(index);
 8         int numMoved = size - index - 1;
 9         if (numMoved > 0)
10             //3、index之后的元素迁移一位
11             System.arraycopy(elementData, index + 1, elementData, index,
12                     numMoved);
13         elementData[--size] = null; // clear to let GC do its work
14         return oldValue;
15     }
16     //第二种
17     public boolean remove(Object o) {
18         if (o == null) {
19             for (int index = 0; index < size; index++)
20                 if (elementData[index] == null) {
21                     fastRemove(index);
22                     return true;
23                 }
24         } else {
25             for (int index = 0; index < size; index++)
26                 if (o.equals(elementData[index])) {
27                     fastRemove(index);
28                     return true;
29                 }
30         }
31         return false;
32     }

(3)更改操作

  • retainAll( Collection<?> c ):仅仅保留列表中和C相同的元素,相当于&运算
  • set(int index,E element):用element替换index位置上的元素。
  • size():返回此列表的元素数
  • sort(Comparator<? super E> c):按照指定的排序规则排序
  • subList( int from , int to ):返回从from到to之间的列表
  • toArray():将列表转化为数组
  • trimToSize( ):修改当前实例的容量是列表的当前大小。
 1     public E set(int index, E element) {
 2         //1、检查越界
 3         rangeCheck(index);
 4         //2、获取被替换元素
 5         E oldValue = elementData(index);
 6         //3、替换
 7         elementData[index] = element;
 8         //4、返回被替换元素
 9         return oldValue;
10     }

(4)查操作

  • contains(Object o):如果包含元素o,则返回为true
  • get(int index):返回指定索引的元素
  • indexOf( Object o ):返回此列表中指定元素的第一次出现的索引,如果列表不包含此元素,返回-1
  • lastindexOf( Object o ):返回此列表中指定元素的最后一次出现的索引,如果列表不包含此元素,返回-1
  • isEmpty():如果列表为空,返回true.
  • iterator():返回列表中元素的迭代器
  • listIterator():返回列表的列表迭代器(按适当的顺序)
  • listIterator(int index):从适当的位置返回列表的列表迭代器(按照正确的顺序)
 1     public boolean contains(Object o) {
 2         return indexOf(o) >= 0;
 3     }
 4     public int indexOf(Object o) {
 5         if (o == null) {
 6             for (int i = 0; i < size; i++)
 7                 if (elementData[i] == null)
 8                     return i;
 9         } else {
10             for (int i = 0; i < size; i++)
11                 if (o.equals(elementData[i]))
12                     return i;
13         }
14         return -1;
15     }

三、总结

1、ArrayList 每次容器增长时,都是以1.5倍增长 

2、每次容器变化时需要将原数组copy到新数组中,使用的方法是系统native 方法  System.arraycopy(src,srcPos,dest,destPos,length)

3、尽量声明时,预估好容器大小,以免多次容器大小的变化

4、线程不安全,数组位置使用的是size标记

一个 ArrayList ,在添加一个元素的时候,它可能会有两步来完成: 
1. 在 Items[Size] 的位置存放此元素; 
2. 增大 Size 的值。 
在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1; 
而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。
但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0
(注意,添加一个元素是要两个步骤,而线程A仅仅完成了步骤1),
所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。  那好,现在我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置
0,而 Size 却等于 2。这就是“线程不安全”了。 

5、舍弃效率使之变成线程安全的方法:使用synchronized关键字、Collections.synchronizedList();

 

以上是关于ArrayList源码解析的主要内容,如果未能解决你的问题,请参考以下文章

CopyOnWriteArrayList源码解析

设计模式 行为型模式 -- 迭代器模式 JDK源码解析:ArrayList

ArrayList 源码解析

Java 集合深入理解 :ArrayList源码解析,及动态扩容机制

源码解读:ArrayList源码解析(JDK8)

ArrayList源码解析