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源码解析的主要内容,如果未能解决你的问题,请参考以下文章
设计模式 行为型模式 -- 迭代器模式 JDK源码解析:ArrayList