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 }
③、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的主要内容,如果未能解决你的问题,请参考以下文章