走进JDK(六)------ArrayList
对于广大java程序员来说,ArrayList的使用是非常广泛的,但是发现很多工作了好几年的程序员不知道底层是啥。。。这我觉得对于以后的发展是非常不利的,因为java中的每种数据结构的设计都是非常完善的,学习了这种思想,在设计自己的容器是非常有帮助的。
一、ArrayList底层结构
ArrayList的底层其实就是一个数组,数组的劣势想必也不用多说,一旦创建,长度无法更改。而ArrayList则可以不停的add或是remove,也可以称之为动态数组。
二、类定义以及成员变量
1、类定义
//1、ArrayList<E>说明此类是支持泛型的 2、继承AbstractList不用多说了,抽象父类中有许多子类可以共用的方法 3、此类实现了RandomAccess接口,说明支持快速随机存取,在实现了该接口的话,那么使用普通的for循环来遍历,性能更高,例如arrayList。而没有实现该接口的话,使用Iterator来迭代,这样性能更高,例如linkedList。
所以这个标记性只是为了让我们知道我们用什么样的方式去获取数据性能更好。 4、Cloneable、Serializable接口则代表可以使用Object.Clone()方法以及可以被序列化 5、这地方还有一个问题,就是ArrayList继承了AbstractList,为啥还要去实现List接口。开发这个的作者已经说了,这是一个错误。。。链接地址:
https://stackoverflow.com/questions/2165204/why-does-linkedhashsete-extend-hashsete-and-implement-sete public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
2、成员变量
//当对象序列化之后,jvm通过这玩意来比对类的版本 private static final long serialVersionUID = 8683452581122892189L; //默认容量大小为10 private static final int DEFAULT_CAPACITY = 10; //空对象数组 private static final Object[] EMPTY_ELEMENTDATA = {}; //默认空对象数组 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //真正存放元素的数组,并且不需要被序列化 transient Object[] elementData; //数组的长度 private int size;
//数组最大容量
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
3、构造函数
//没有给定初始容量时,默认给10个长度 public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } //长度>0则按照给定长度创建;长度为0,则为默认空对象数组;<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); } } //给定collection时,转换成数组并赋值给elementData public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { if (elementData.getClass() != Object[].class) //每个集合的toarray()的实现方法不一样,所以需要判断一下,如果不是Object[].class类型,那么就需要使用 ArrayList中的方法去改造一下。并且Arrays.copyOf是浅拷贝,当数组中存放的为对象,并且对象中的属性值有 改变,会影响到之前的数组 elementData = Arrays.copyOf(elementData, size, Object[].class); } else { this.elementData = EMPTY_ELEMENTDATA; } }
三、主要方法
ArrayList中提供的add()方法有四种:
1、add(E)
//直接在list的最后一位添加此元素 解释几个变量: 1、size:当前底层真正存储元素的个数 2、DEFAULT_CAPACITY:10,默认的容量大小 3、elementData.length:底层数组的长度。比如初始化20长度的list,但是里面只存放了5个元素。length为20,size则是5. public boolean add(E e) { //确定内部容量是否够了,size是数组中数据的个数,因为要添加一个元素,所以size+1,先判断size+1的这个个数数组能否放得下,就在这个方法中去判断是否数组.length是否够用了。 ensureCapacityInternal(size + 1); elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } //如果当前数组为空,则取minCapacity与10之间大的数为数组容量。如果底层数组不为空,则取minCapacity private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //当elementData为空时,并且minCapacity<10时,直接扩容到10 return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; } //如果所需的数组容量已经大于当前底层数组的长度,那么需要扩容。 private void ensureExplicitCapacity(int minCapacity) { modCount++; if (minCapacity - elementData.length > 0) grow(minCapacity); } //真正的扩容模块 private void grow(int minCapacity) { int oldCapacity = elementData.length; //将老容量扩充1.5倍,>>1也就是右移一位,也就是/2,不明白位运算的可以看本人位运算的一篇文章https://www.cnblogs.com/alimayun/p/10693358.html int newCapacity = oldCapacity + (oldCapacity >> 1); //如果扩容1.5倍还是小于minCapacity,那么以minCapacity为准 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //如果大于数组最大容量 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); //最大就是给Integer.MAX_VALUE,否则就是MAX_ARRAY_SIZE=Integer.MAX_VALUE - 8。主要作用就是防止溢出 return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
对上面的整个流程总结一遍吧,用中文来说:
先看下流程图
- 当执行add(E)时,首先将minCapasize=size+1;
- 当底层的elementData为空时,取minCapasize和10之间的最大值;若elementData不为空,则取minCapasize,将最终的结果赋值给minCapasize;
- 判断此时minCapasize是否大于elementData.length,如果大于就开始扩容;
- 获取当前的elementData.length,并扩容1.5倍,扩容完之后与minCapasize比较,取二者的最大值;
- 获取到二者最大值后,判断该值是否大于MAX_ARRAY_SIZE=Integer.MAX_VALUE - 8,如果大于,要么取Integer.MAX_VALUE,要么取MAX_ARRAY_SIZE;
- 通过Arrays.copyOf(elementData, newCapacity)方法,创建一个新长度的数组;
- 将元素E放到size++的索引处,并返回true,结束整个流程
2、add(int index, E element)
public void add(int index, E element) { //判断下index是否符合规矩 rangeCheckForAdd(index); //这个方法前面已经花了很多精力介绍了 ensureCapacityInternal(size + 1); //将elementData在插入位置后的所有元素往后面移一位。 //api: //public static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length) //src:源对象 //srcPos:源对象对象的起始位置 //dest:目标对象 //destPost:目标对象的起始位置 //length:从起始位置往后复制的长度 System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; } //检查index是否合法 private void rangeCheckForAdd(int index) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
3、addAll(Collection)、addAll(index, Collection)
//其实大体上与add()方法差不多 public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); //通过上面确定elementData的长度之后,通过System.arraycopy()将c全部添加到原arrayList的后面,可以理解成追加吧 //关于System.arraycopy(),Arrays.copyOf()底层也是调用的这个方法,这是个native方法,所以也不用去关注它是咋实现的吧 System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; } public boolean addAll(int index, Collection<? extends E> c) { rangeCheckForAdd(index); Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount int numMoved = size - index; //如果index小于size的话,说明ArrayList原元素有一部分需要被覆盖掉 if (numMoved > 0) System.arraycopy(elementData, index, elementData, index + numNew, numMoved); System.arraycopy(a, 0, elementData, index, numNew); size += numNew; return numNew != 0; }
4、get()、set()
public E get(int index) { //检查索引是否超过范围,如果index为负数,则由底层数组报ArrayIndexOutOfBoundsException rangeCheck(index); return elementData(index); } private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } E elementData(int index) { //如果index为负数,则由底层数组报ArrayIndexOutOfBoundsException return (E) elementData[index]; }
public E set(int index, E element) { //跟get()一样 rangeCheck(index); //取出对应index位置的元素 E oldValue = elementData(index); //替换成新元素 elementData[index] = element; //返回老元素 return oldValue; }
5、remove(index)、remove(object)、clear()
public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; //假如当前lsit是这样{0,1,2,3}删除第二个位置的,那么第三个位置的元素需要往左移。当numMoved>0,说明需要移动 if (numMoved > 0) //0到index的元素是不需要动的,index+1之后的需要移动 System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work return oldValue; } public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { //以index为分界线,0-index的元素不用动,后面的往左移动 fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) { fastRemove(index); return true; } } return false; } private void fastRemove(int index) { modCount++; 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 }
public void clear() { modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }
6、size()、empty()、contains()、indexOf()、lastIndexOf()、clone()
public int size() { return size; } public boolean isEmpty() { return size == 0; } //判断是否包含某个对象 public boolean contains(Object o) { return indexOf(o) >= 0; } //这方法很简单,没啥说的 public int indexOf(Object o) { if (o == null) { for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; } return -1; } //跟indexOf的区别就是一个从前面往后匹配,一个从后往前匹配 public int lastIndexOf(Object o) { if (o == null) { for (int i = size-1; i >= 0; i--) if (elementData[i]==null) return i; } else { for (int i = size-1; i >= 0; i--) if (o.equals(elementData[i])) return i; } return -1; } //克隆一个新的ArrayList public Object clone() { try { //若想调用super.clone(),必须要实现cloneable接口 ArrayList<?> v = (ArrayList<?>) super.clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(e); } }
总结:ArrayList是动态数组,优化了数组不能更改长度的缺点,而且继承了数组访问快的优点。缺点就是删除数据时(尤其删除index靠前位置的数据)比较慢。
删除操作时,需要将后面的元素移动空白位置,不慢就怪了。
另外提供了add、get、set等方法来操作数据。另外感兴趣的小伙伴可以自己看下ArrayList的迭代器Itr以及ListItr,这个在AbstractList中有介绍。
posted on 2019-04-14 22:41 阿里-马云的学习笔记 阅读(397) 评论(0) 编辑 收藏 举报