深入学习Java之ArrayList
来源:互联网 发布:单片机中断程序编写 编辑:程序博客网 时间:2024/06/18 13:59
深入学习Java之ArrayList
前言
对于Java的容器类,虽然之前一直有在使用,不过使用归使用,很多的原理本质上还是不懂,比如说什么时候进行扩容等,乘着最近有空,每天抽出点时间来重新学习下容器类,主要包括了(Collection、Map),其中Collection主要包括List、Set、Queue等,本小节主要来学习List中的ArrayList
ArrayList的继承结构
首先从宏观上来了解ArrayList的结构,然后再从源码的角度来学习。
ArrayList的继承结构如下所示
从上图中可以看到,ArrayList继承自AbstractList,并且实现了RandomAccess、Clonable、Serializable接口,而其中的AbstractList又实现了AbstractCollection接口,并且继承了List接口,AbstractCollection实现了Collection接口,List继承了Collection接口,Collection接口又继承了Iterable接口。
接下来我们根据上图,从上到下来逐个类、接口来学习
首先是Iterable接口
从上图中可以看到,Iterable接口中主要提供了iterator方法,用于返回一个Iterator对象,主要用于遍历容器(foreach)
接下来是Collection接口
从上图中可以看到,Collection接口基本上定义了操作容器的基本方法,包括了获取容器大小的size方法,判断容器是否为空的isEmpty方法,判断容器是否包含某个元素的contains方法,将容器的内容装换为数组的toArray方法,添加元素的add方法,移除元素的remove方法等,这些方法都是操作容器的最基本的方法,在对应的实现类中,不同的容器实现类将根据自身的特性,对不同的方法进行相应的实现。
接下来是AbstractCollection接口,AbstractCollection是一个抽象类,主要实现了Collection接口中的部分通用方法,具体如下代码所示
public boolean contains(Object o) { // 获得迭代器对象 Iterator<E> it = iterator(); // 判断对象是否是NULL,如果是NUll,则查看容器中是否 // 有对象也是NULL if (o==null) { while (it.hasNext()) if (it.next()==null) return true; } else { // 如果不是NULL,则查看容器中是否包含该方法 while (it.hasNext()) if (o.equals(it.next())) return true; } return false; }
// 原理同上 public boolean remove(Object o) { Iterator<E> it = iterator(); if (o==null) { while (it.hasNext()) { if (it.next()==null) { it.remove(); return true; } } } else { while (it.hasNext()) { if (o.equals(it.next())) { it.remove(); return true; } } } return false; }
// 将容器转化为数组 public Object[] toArray() { // 默认大小为容器中元素的个数 Object[] r = new Object[size()]; Iterator<E> it = iterator(); for (int i = 0; i < r.length; i++) { // 当发现容器中的元素个数没有那么多时,将数组进行缩减 if (! it.hasNext()) // fewer elements than expected return Arrays.copyOf(r, i); r[i] = it.next(); } // 当发现容器中个数多余size时,进行扩容,并将剩余元素添加到数组中 return it.hasNext() ? finishToArray(r, it) : r; } // 将容器中剩余的元素加入到数组中 private static <T> T[] finishToArray(T[] r, Iterator<?> it) { int i = r.length; while (it.hasNext()) { int cap = r.length; if (i == cap) { // 当发现数组容量不足时,进行扩容,扩容后大小为原来的大小+原来大小的1/2 + 1 int newCap = cap + (cap >> 1) + 1; // 对扩容后的大小进行判断,防止容量过大 // 其中的 MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; // 也就是如果扩容后的大小大于Integer.MAX_VALUE - 8,则进行 // 容量判断,防止其整数溢出 if (newCap - MAX_ARRAY_SIZE > 0) newCap = hugeCapacity(cap + 1); r = Arrays.copyOf(r, newCap); } r[i++] = (T)it.next(); } // 如果数组容量由空余,则进行缩减至数组容量大小 return (i == r.length) ? r : Arrays.copyOf(r, i); } // 限制数组容量 private static int hugeCapacity(int minCapacity) { // 如果新容量太大,造成整数溢出,则抛出异常 if (minCapacity < 0) // overflow throw new OutOfMemoryError ("Required array size too large"); // 如果大于允许的最大容量,则将其限制在Integer.MAX_VALUE // 否则则将其限制在MAX_ARRAY_SIZE return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
当然,AbstractCollection中还有其他一些方法,不过,由于在具体的实现类中,可能对应的实现不同,所以这里就不进行其源码的研究,等到具体的实现类中再进行研究
接下来是List接口
由于List接口继承自Collection接口,所以Collection中有的方法List也自然有,不过,除此之外,List还增加了几个新的方法,主要用于针对List的操作
从上图中可以看到,主要是获取指定位置的get方法,将某个位置的元素设置为新的值的set方法,获取某个元素在List中的索引的indexOf、LastIndexOf方法,以及List增加的用于获取新的迭代器的ListIterator方法
接下来是AbstractList,正如AbstractCollection之于Collection,AbstractList是List的实现,主要实现了几个常用的方法,这里同样不进行展开
从ArrayList的结构图中可以看到,ArrayList除了AbstractList和实现了List接口之外,还实现了RandomAccess、Cloneable、Serializable接口,其中RandomAccess、Cloneable和Serializable接口均是标记接口,RandomAccess用于标记ArrayList支持随机访问,Cloneable接口用于标记ArrayList支持克隆,Serializable接口用于标记ArrayList支持序列化
ArrayList的源码剖析
上面从宏观上了解了ArrayList的继承结构,总体把握了ArrayList的方法之后,接下来我们来详细地学习ArrayList的源码
ArrayList的组成
transient Object[] elementData;
可以看到,ArrayList的底层实现其实就是一个Object数组,这也是为什么ArrayList称之为ArrayList的原因了
接下来来学习ArrayList的构造方法
// 带初始容量的初始化方法 public ArrayList(int initialCapacity) { // 如果初始容量大于0,则使用该容量进行创建数组 if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; // 如果容量是0,则使用默认的的空数组对象,其中的EMPTY_ELEMENTDATA定义如下 // private static final Object[] EMPTY_ELEMENTDATA = {}; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; // 如果容量小于0,则抛出异常 } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } // 无参构造方法,也是默认的构造方法 public ArrayList() { // 默认也是初始化为空数组 // private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } // 带一个容器对象的构造方法,主要用于将容器中的所有对象来初始化ArrayList对象 public ArrayList(Collection<? extends E> c) { // 将Collection转化为数组对象 elementData = c.toArray(); // 如果传进来的Collection中有元素,则将元素添加到数组中 if ((size = elementData.length) != 0) { // 如果转化后的数组不是Object类型,则初始化为实际类型 if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); // 否则将其初始化为空容量 } else { this.elementData = EMPTY_ELEMENTDATA; } }
获取某个元素
public E get(int index) { // 对index的范围进行检查 rangeCheck(index); return elementData(index); } // 对index的范围进行检查,由于对数组进行访问时,负数索引会抛出异常 // 所以这里不需要进行额外检查,但是由于此时数组的容量并不代表所有的 // 位置都是有效元素,所以需要进行检查 private void rangeCheck(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
将指定位置的元素替换为指定元素
public E set(int index, E element) { rangeCheck(index); // 替换旧值并且返回旧值 E oldValue = elementData(index); elementData[index] = element; return oldValue; }
添加某个元素
// 默认添加在最后面 public boolean add(E e) { // 进行容量检测 ensureCapacityInternal(size + 1); elementData[size++] = e; return true; } // 容量检测 private void ensureCapacityInternal(int minCapacity) { // 如果是此时的数组的默认的空数组,则将其容量初始化为 // max(默认容量(10), 此时所需要的最小容量) if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } // 判断此时容量是否足够 ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { // 记录对ArrayList结构进行操作 modCount++; // 如果所需要的容量大于数组此时的长度,则增长数组 if (minCapacity - elementData.length > 0) grow(minCapacity); } // 动态增长,注意这里比较耗费资源 private void grow(int minCapacity) { int oldCapacity = elementData.length; // 大小为旧容量大小+ 1/2 旧容量大小 int newCapacity = oldCapacity + (oldCapacity >> 1); // 如果新容量大小小于所需要的大小,则将大小设置为实际所需要的大小 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(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
在指定位置插入元素
public void add(int index, E element) { rangeCheckForAdd(index); // 同上,进行容量检测并在需要时进行动态增长 ensureCapacityInternal(size + 1); // 将要插入的位置的元素后面所有元素后移 // 这里可以看出,ArrayList在中间插入元素是比较消耗资源的 System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
添加一个容器中的所有元素
// 基本原理同上,这里不进行过多的解析 public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); // Increments modCount System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; }
移除指定位置元素
public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; // 如果要移出的元素不是最后一个元素,则进行紧凑 // 从这里也可以看到,ArrayList在中间移除元素是比较消耗资源的 if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work return oldValue; }
在指定位置添加所有的元素
// 基本原理同上,不做过多解析 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; if (numMoved > 0) System.arraycopy(elementData, index, elementData, index + numNew, numMoved); System.arraycopy(a, 0, elementData, index, numNew); size += numNew; return numNew != 0; }
移除指定值的元素
// 移除第一个匹配的值 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; } // 基本原理同上,移除后进行紧凑 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++; // 这里需要注意,由于数组中的元素是通过引用关系跟 // 具体的对象关联的,所有如果此时只是简单的移动索引 // 那么这些元素所占用的内存依旧不会被GC,因为依旧保持 // 跟数组的管理,GC回收是不会对这种对象进行回收的 // 这里写得非常棒,非常值得学习 for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }
查找某个元素的索引以及查找从后往前找某个元素的索引基本同AbstractCollection,这里就不进行展开了
克隆数组
public Object clone() { try { 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); } }
手动触发容量检测
public void ensureCapacity(int minCapacity) { // 如果是默认的空数组,则容量为0,否则容量为10 int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) ? 0 : DEFAULT_CAPACITY; // 对最小容量与需要的容量进行判断,并根据需要进行容量扩展 if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } }
手动将ArrayList中数组的大小进行确定,限制为实际的大小
public void trimToSize() { modCount++; if (size < elementData.length) { elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size); } }
对与ArrayList中的Iterator以及ListIterator的源码,目前还没看懂,所以暂时无法进行研究,等以后研究懂了再进行补充
总结
本小节主要从宏观上学习了ArrayList的结构,以及从ArrayList的源码中研究了ArrayList的底层实现,以及ArrayList的扩容,紧凑原理(数组的拷贝,移动),从本质上明白了为何ArrayList在中间进行插入,删除会带来严重性能的原因(数组元素的移动),加深了对ArrayList的认识。
- 深入学习Java之ArrayList
- java深入学习之ArrayList工作原理
- 深入学习java之ArrayList 实现原理(一)
- 深入了解JAVA之Arraylist
- Java学习之ArrayList
- Java学习之ArrayList
- Java学习之ArrayList
- 深入学习集合之ArrayList实现原理
- Java集合深入学习总结-ArrayList
- 深入学习java集合:ArrayList<E>实现
- Java深入源码之ArrayList总结
- java学习 集合之arrayList
- java 学习之四 ArrayList
- Java集合学习之arrayList
- 深入Collection之ArrayList
- 深入探索Java-ArrayList
- 【深入学习java集合框架】ArrayList源码学习
- 深入Java集合学习系列:ArrayList的实现原理
- 设计的理念
- HDOJ-1002 A + B Problem II
- 四大排序分析与总结
- Android的pull解析XML文件
- 小心使用typedef带来的陷阱
- 深入学习Java之ArrayList
- 数据库日期格式转换函数
- Oracle中 (+)与left join 的用法区别
- Android解析XML(PULL)与Android解析Json
- 了解JAVA
- FFmpeg的初学习常见问题
- html+CSS 进阶
- 剑指Offer:数组中出现次数超过一半的数字
- 阿里巴巴2017实习生笔试(一)