容器--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中的容器
在书写程序的时候,我们常常需要对大量的对象引用进行管理.为了实现有效的归类管理,我们常常将同类的引用放置在同一数据容器中. 由于数据容器中存放了我们随时可能需要使用到的对象引用,所以一般的数据容器要都 ...
随机推荐
- 浅谈html5网页内嵌视频
更好的阅读体验:浅谈html5网页内嵌视频 如今在这个特殊的时代下:flash将死未死,微软和IE的历史问题,html5标准未定,苹果和谷歌的闭源和开源之争,移动互联网的大势所趋,浏览器各自为战... ...
- Gradle 笔记
网上有一篇文章说的很明白,图文来教你在eclipse下用gradle 来打包Androidhttp://blog.csdn.net/x605940745/article/details/4124268 ...
- Winform 数据库连接app.config文件配置 数据库连接字符串
1.添加配置文件 新建一个winform应用程序,类似webfrom下有个web.config,winform下也有个App.config;不过 App.config不是自动生成的需要手动添加,鼠标右 ...
- android 开发 - 对图片进行虚化(毛玻璃效果,模糊)
概述 IPAD,IPHONE上首页背景的模糊效果是不是很好看,那么在 Android中如何实现呢.我通过一种方式实现了这样的效果. 开源库名称:anroid-image-blur 一个android ...
- android私有文件夹的访问
首先内部存储路径为/data/data/youPackageName/,下面讲解的各路径都是基于你自己的应用的内部存储路径下. 所有内部存储中保存的文件在用户卸载应用的时候会被删除. 一. files ...
- TIB自动化测试快讯 - Appium手机自动化测试学习资料精选
TIB自动化测试快讯 - Appium手机自动化测试学习资料精选 Appium+Android+Javahttp://automationqa.com/forum.php?mod=viewthre ...
- [LeetCode] Number of Islands II
Problem Description: A 2d grid map of m rows and n columns is initially filled with water. We may pe ...
- 彻底删除mysql方法
首先,先在服务(开始——>控制面板——>管理工具——>服务)里停掉MySQL的服务.打开控制面板-添加删除程序,找到MySQL,卸载.或者用360安全卫士来卸载也行.也可以用mysq ...
- Office2016下载地址
Office 2016 专业增强版32 位: 文件名:SW_DVD5_Office_Professional_Plus_2016_W32_ChnSimp_MLF_X20-41351.ISO SHA1: ...
- Android动画效果translate、scale、alpha、rotate详解
动画类型 Android的animation由四种类型组成 XML中 alpha 渐变透明度动画效果 scale 渐变尺寸伸缩动画效果 translate 画面转换位置移动动画效果 rotate 画面 ...