容器--ArrayList
一、前言
作为List的重要实现之一,ArrayList已经成了我们编写程序时不可或缺的重要容器之一,面试的时候也经常会被问到,所以,深入理解其实现机制,无论是对于我们正确使用这个类来说,还是准备面试,都是非常有好处的。
二、实现原理
ArrayList不再是一个抽象类,而是可以直接使用,所以我们要弄清楚这个类是如何实现一个List容器的,具体来说,我们需要关注以下几点:
1)数据如何存储?
这个比较明显,ArrayList内部定义了一个数组字段:private transient Object[] elementData; 注意虽然ArrayList是支持泛型的,但数组的类型还是Object, 这个是因为无法用泛型来new 一个数组,比如T data[] = new T[size],这是肯定不行的,所以只能使用Object. 另外注意这个字段是transient的,也就意味着在序列化时会忽略,需要特殊处理。
那么,elementData数组中存储了ArrayList中的每一个元素,由于数组天生具备按下标随机访问,所以这使得ArrayList的get,set等方法变得非常方便。
2)存储空间如何扩容和回收?
如果ArrayList只能起到和数组一样的作用,那也就没有必要再定义这样的集合了,直接用数组就完了。我们使用ArrayList,至少有一部分原因是因为它是可以动态扩容的,而且使用者不用关心其是如何扩展的,而数组想要扩容只能程序员自己搞了,而且还很麻烦。那么ArrayList是如何扩容的呢?其容量和数组元素个数之前有什么区别呢?
ArrayList定义了一个字段,int size, 这个表示容器中元素的个数。而elementData的长度则表示其容量大小,通常情况下size < 数组的长度。当有元素到列表中时,系统会先检查当前容量的大小以判断是否需要扩容,以add(index, element)为例,相关实现如下:
public boolean add(E e) { //事实上并不是每一次add操作都要扩容,但每一次,modCount都需要加1
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
} private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length; //原大小
//新的大小等于原大小在原来的基础上增加1/2,比如原长度是10, 则新长度是15
int newCapacity = oldCapacity + (oldCapacity >> 1); //如果新的容量 < 目标容量(比如目标是16), 则取目标
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity; //太大,则根据目标容易来取值,最大不能超过整型的最大值
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win: //分配一个长度为newCapacity的数组,并将elementData中的元素复制过去
//当然,多出的空间,数组中元素的值默认就是null了
elementData = Arrays.copyOf(elementData, newCapacity);
}
我们看关键的grow方法,先是将空间增加1/2,然后如果还是未到预期空间,则newCapacity等于参数所指定的容器值。最后通过数组复制的方式将这个数组中的元素复制到新数组,以达到扩容的目的。
对于每次添加一个元素来说,这种机制并不会每次都需要扩容,但如果是addAll的方式添加一个集合,则是有可能的。
对于删除来说,elementData的空间并不会缩小,但是多出的部分会被置为null, 以避免不必要的内存泄露。
我们可以在初始化时就指定一个elementData的大小,若不指定,则为长度为0的数组,第一次添加时,默认扩展到10.
3)如何实现迭代器?这个比较简单,就是通过对数组的遍历来实现。
4)如何实现indexOf? 基本思路还是对数组中的元素进行遍历,对每个元素调用equals来比较,返回第一个匹配的元素,或者返回-1. 但ArrayList 是允许null元素存在的,所以遍历要分两种情况,当目标对象为null时,其实判断方式就是 == null的形式。
5)数组元素如何调整位置?
记得笔试和面试经常会被问到ArrayList和LinkedList的优点和不足,通常我们会说ArrayList的不足在于添加和删除元素时会涉及到数组元素位置的调整,这个涉及到数组元素的移动,效率会比较慢。但看了源码之后,发现这个描述是错误的,因为再增加和删除元素后,其实现并非是在原数组的基础上改变元素的位置,而是直接使用到数组复制的方式。研究ArrayList的源码,你会发现很多地方都用到了System的arraycopy方法,下面对该方法做一个具体的定义。
本方法的定义如下:
/**
* Copies an array from the specified source array, beginning at the
* specified position, to the specified position of the destination array.
* A subsequence of array components are copied from the source
* array referenced by <code>src</code> to the destination array
* referenced by <code>dest</code>. The number of components copied is
* equal to the <code>length</code> argument. The components at
* positions <code>srcPos</code> through
* <code>srcPos+length-1</code> in the source array are copied into
* positions <code>destPos</code> through
* <code>destPos+length-1</code>, respectively, of the destination
* array.
* <p>
* If the <code>src</code> and <code>dest</code> arguments refer to the
* same array object, then the copying is performed as if the
* components at positions <code>srcPos</code> through
* <code>srcPos+length-1</code> were first copied to a temporary
* array with <code>length</code> components and then the contents of
* the temporary array were copied into positions
* <code>destPos</code> through <code>destPos+length-1</code> of the
* destination array.
* <p>
* If <code>dest</code> is <code>null</code>, then a
* <code>NullPointerException</code> is thrown.
* <p>
* If <code>src</code> is <code>null</code>, then a
* <code>NullPointerException</code> is thrown and the destination
* array is not modified.
* <p>
* Otherwise, if any of the following is true, an
* <code>ArrayStoreException</code> is thrown and the destination is
* not modified:
* <ul>
* <li>The <code>src</code> argument refers to an object that is not an
* array.
* <li>The <code>dest</code> argument refers to an object that is not an
* array.
* <li>The <code>src</code> argument and <code>dest</code> argument refer
* to arrays whose component types are different primitive types.
* <li>The <code>src</code> argument refers to an array with a primitive
* component type and the <code>dest</code> argument refers to an array
* with a reference component type.
* <li>The <code>src</code> argument refers to an array with a reference
* component type and the <code>dest</code> argument refers to an array
* with a primitive component type.
* </ul>
* <p>
* Otherwise, if any of the following is true, an
* <code>IndexOutOfBoundsException</code> is
* thrown and the destination is not modified:
* <ul>
* <li>The <code>srcPos</code> argument is negative.
* <li>The <code>destPos</code> argument is negative.
* <li>The <code>length</code> argument is negative.
* <li><code>srcPos+length</code> is greater than
* <code>src.length</code>, the length of the source array.
* <li><code>destPos+length</code> is greater than
* <code>dest.length</code>, the length of the destination array.
* </ul>
* <p>
* Otherwise, if any actual component of the source array from
* position <code>srcPos</code> through
* <code>srcPos+length-1</code> cannot be converted to the component
* type of the destination array by assignment conversion, an
* <code>ArrayStoreException</code> is thrown. In this case, let
* <b><i>k</i></b> be the smallest nonnegative integer less than
* length such that <code>src[srcPos+</code><i>k</i><code>]</code>
* cannot be converted to the component type of the destination
* array; when the exception is thrown, source array components from
* positions <code>srcPos</code> through
* <code>srcPos+</code><i>k</i><code>-1</code>
* will already have been copied to destination array positions
* <code>destPos</code> through
* <code>destPos+</code><i>k</I><code>-1</code> and no other
* positions of the destination array will have been modified.
* (Because of the restrictions already itemized, this
* paragraph effectively applies only to the situation where both
* arrays have component types that are reference types.)
*
* @param src the source array.
* @param srcPos starting position in the source array.
* @param dest the destination array.
* @param destPos starting position in the destination data.
* @param length the number of array elements to be copied.
* @exception IndexOutOfBoundsException if copying would cause
* access of data outside array bounds.
* @exception ArrayStoreException if an element in the <code>src</code>
* array could not be stored into the <code>dest</code> array
* because of a type mismatch.
* @exception NullPointerException if either <code>src</code> or
* <code>dest</code> is <code>null</code>.
*/
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
这是System类里定义的一个本地化方法,用于进行数组间的元素复制,具体的来说,将src数组里从strPos位置起的元素复制length个,放到dest数组中的descPos位置。这个方法由于是本地实现,直接进行内存copy,在数组的数据量比较大的情况下性能仍然比较好。
这个方法要求两个数组中存储的元素类型是一致的,至少是可转换的,不能一个是引用类型,另一个是基本类型。另外,strPos + length和descPos + length都不能超过自身数组的空间,否则会有越界异常。
特别的,src和dest可以是同一个数组,这种情况下,src的元素会先复制到一个临时数组里,然后再从临时数组中复制到dest中。
应该说,有了这个方法之后,元素的插入和删除并不会带来太大的性能开销。
三、总结
ArrayList的底层是基于数组的,arraycopy方法在其实现上发挥了很大的作用,理解了这个方法,整个结构也就不难理解了。所以其它的方法就不再一一介绍了,接下来我们会继续学习List的另一种常用实现:LinkedList
容器--ArrayList的更多相关文章
- 数组容器(ArrayList)设计与Java实现,看完这个你不懂ArrayList,你找我!!!
数组容器(ArrayList)设计与Java实现 本篇文章主要跟大家介绍我们最常使用的一种容器ArrayList.Vector的原理,并且自己使用Java实现自己的数组容器MyArrayList,让自 ...
- List容器——ArrayList及常用API
List: ① List容器是有序的collection(也称为序列).此接口的用户可以对List容器中每个元素的插入位置进行精确地控制.用户可以根据元素的整数索引(在列表中的位置)访问元素,并搜 ...
- List容器-ArrayList
特点: 有序重复,包括null,通过整数索引访问 实现类ArrayList和LinkedList ArrayList--动态数组 不线程同步 单线程合适 List<String> ...
- 练习:自己写一个容器ArrayList集合 一一数组综合练习2
package cn.bjsxt.collection; /** * 自己实现一个ArrayList */ import java.util.ArrayList; import java.util.L ...
- 练习:自己写一个容器ArrayList集合 一一数组综合练习
package cn.bjsxt.myCollection; import java.util.Arrays; /** * 天下文章一大抄,看你会抄不会抄. * 模拟Stringbuilder 写一个 ...
- 容器ArrayList原理(学习)
一.概述 动态数组,容量能动态增长,元素可以为null,用数组存储,非线程同步(vector线程同步) 每个 ArrayList 实例都有一个容量,该容量是指用来存储列表元素的数组的大小,自动增长(默 ...
- Java基础学习总结--对象容器
目录: ArrayList 顺序泛型容器 HashSet 集合容器 HashMap<Key,Value>容器 要用Java实现记事本的功能.首先列出记事本所需功能: 可以添加记录(字符串) ...
- 【Java学习笔记】<集合框架>定义功能去除ArrayList中的重复元素
import java.util.ArrayList; import java.util.Iterator; import cn.itcast.p1.bean.Person; public class ...
- [转] Java中的容器
在书写程序的时候,我们常常需要对大量的对象引用进行管理.为了实现有效的归类管理,我们常常将同类的引用放置在同一数据容器中. 由于数据容器中存放了我们随时可能需要使用到的对象引用,所以一般的数据容器要都 ...
随机推荐
- 开启MSDTC
DOS方式以运行中输入cmd 然后输入下面命令: 停止MSDTC: net stop msdtc 开启MSDTC: net start msdtc 卸载MSDTC: msdtc -uninstall ...
- 深入浅出 nginx lua 为什么高性能
最近很多人问我nginx lua的优势是什么?为什么? 一.同步和异步.阻塞和非阻塞 如果要说清楚这个问题首先要了解:同步和异步.阻塞和非阻塞的关系 同步:php.java的正常代码都是同步执行的 异 ...
- Python strange questions list
sys.setrecursionlimit(1<<64) Line 3: OverflowError: Python int too large to convert to C long ...
- git pull 指定版本
git init git remote add origin git@bitbucket.org:huashiyiqike/lstm-hf.git git pull origin master
- win8磁盘占用100%的12种解决办法
解决方法1:硬盘4K对齐,能减少磁盘占用100%情况.(大部分用户能解决) 解决方法2:检查硬盘AHCI驱动是否安装 解决方法3:通过 HD Tune Pro 5 专业的硬盘测试工具 检测硬盘健康度, ...
- Eclipse项目名出现红叉
一.背景与原因 项目第一次加进来的时候,我用的是D:\Java\jdk1.7.0_17,后来由于配置将tomcat切换到jboss,说是JBOSS某个版本只支持jdk6,我就将“环境变量JAVA_HO ...
- SNF开发平台WinForm之十一-程序打包-SNF快速开发平台3.3-Spring.Net.Framework
原来我们用的是微软自带的打包工具去打包,但感觉好像也是第三方做的打包并且很是麻烦,还有时不成功报错.那综合考虑就找一个简单实用的打包工具吧,就找到了NSIS这个.具体打包步骤如下: 1.安装NSIS ...
- Teambition可用性测试记
引言:最开始知道Teambition是几个月前,当时是想找一个团队协作工具.Teambition是候选之一,它的界面设计给我留下了印象.后来得知其背后年轻的创始团队还是让我有些小惊讶的.这次通过朋友介 ...
- Popup 显示阴影
WPF Popup: How to put a border around the popup? 通过设置 Border 的 margin 来为阴影留出位置,并设置 Popup: AllowsTran ...
- 利用__index和__newindex实现默认值表、监控表、只读表
__index和__newindex实际上相当于是在读写表的时候分别加了一道过滤的逻辑,让读写表的操作可以被监控或说回调,利用这个特性可以实现一些带有特殊功能的表. 带有默认值的表: setdefau ...