ArrayList的实现及原理
ArrayList
ArrayList是最常见以及每个Java开发者最熟悉的集合类了,顾名思义,ArrayList就是一个以数组形式实现的集合,以一张表格来看一下ArrayList里面有哪些基本的元素:
元素 | 作用 |
private transient Object[] elementData; | ArrayList是基于数组的一个实现,elementData就是底层的数组。 |
private int size; |
ArrayList里面元素的个数,这里要注意一下,size是按照调用add、remove方法的次数进行自增或者自减的, 所以add了一个null进入ArrayList,size也会加1。 |
源代码:
/** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. */ private transient Object[] elementData; /** * The size of the ArrayList (the number of elements it contains). * * @serial */ private int size;
ArrayList类中只定义了两个私有属性,很容易理解,elementData存储ArrayList内的元素,size表示它包含的元素的数量。
有个关键字需要解释:transient。
transient为java关键字,为变量修饰符,如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。当一个对象被序列化的时候,transient型变量的值不包括在序列化的表示中,然而非transient型的变量是被包括进去的。
对于集合,我认为关注的点主要有四点:
1、是否允许空
2、是否允许重复数据
3、是否有序,有序的意思是读取数据的顺序和存放数据的顺序是否一致
4、是否线程安全
四个关注点在ArrayList上的答案
关注点 | 结论 |
ArrayList是否允许空 | 允许 |
ArrayList是否允许重复数据 | 允许 |
ArrayList是否有序 | 有序 |
ArrayList是否线程安全 | 非线程安全 |
构造函数
ArrayList提供了三个构造函数:
ArrayList():默认构造函数,提供初始容量为10的空列表。
ArrayList(int initialCapacity):构造一个具有指定初始容量的空列表。
ArrayList(Collection<? extends E> c):构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。
/*** 构造一个初始容量为 10 的空列表*/ public ArrayList() {this(10); }/*** 构造一个具有指定初始容量的空列表。*/ public ArrayList(int initialCapacity) {super();if (initialCapacity < 0)throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);this.elementData = new Object[initialCapacity]; }/*** 构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。*/ public ArrayList(Collection<? extends E> c) {elementData = c.toArray();size = elementData.length;// c.toArray might (incorrectly) not return Object[] (see 6260652)if (elementData.getClass() != Object[].class)elementData = Arrays.copyOf(elementData, size, Object[].class); }
添加元素
有这么一段代码:
public static void main(String[] args) {List<String> list = new ArrayList<String>();list.add("000");list.add("111"); }
看下底层会做什么,进入add方法的源码来看一下:
1 public boolean add(E e) { 2 ensureCapacity(size + 1); // Increments modCount!! 3 elementData[size++] = e; 4 return true; 5 }
先不去管第2行的ensureCapacity方法,这个方法是扩容用的,底层实际上在调用add方法的时候只是给elementData的某个位置添加了一个数据而已,用一张图表示的话是这样的:
多说一句,我这么画图有一定的误导性。elementData中存储的应该是堆内存中元素的引用,而不是实际的元素,这么画给人一种感觉就是说elementData数组里面存放的就是实际的元素,这是不太严谨的。不过这么画主要是为了方便起见,只要知道这个问题就好了。
扩容
我们看一下,构造ArrayList的时候,默认的底层数组大小是10:
public ArrayList() {this(10); }
那么有一个问题来了,底层数组的大小不够了怎么办?答案就是扩容,这也就是为什么一直说ArrayList的底层是基于动态数组实现的原因,动态数组的意思就是指底层的数组大小并不是固定的,而是根据添加的元素大小进行一个判断,不够的话就动态扩容,扩容的代码就在ensureCapacity里面:
public void ensureCapacity(int minCapacity) {modCount++;int oldCapacity = elementData.length;if (minCapacity > oldCapacity) {Object oldData[] = elementData;int newCapacity = (oldCapacity * 3)/2 + 1;if (newCapacity < minCapacity)newCapacity = minCapacity;// minCapacity is usually close to size, so this is a win:elementData = Arrays.copyOf(elementData, newCapacity);} }
看到扩容的时候把元素组大小先乘以3,再除以2,最后加1。可能有些人要问为什么?我们可以想:
1、如果一次性扩容扩得太大,必然造成内存空间的浪费
2、如果一次性扩容扩得不够,那么下一次扩容的操作必然比较快地会到来,这会降低程序运行效率,要知道扩容还是比价耗费性能的一个操作
所以扩容扩多少,是JDK开发人员在时间、空间上做的一个权衡,提供出来的一个比较合理的数值。最后调用到的是Arrays的copyOf方法,将元素组里面的内容复制到新的数组里面去:
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {T[] copy = ((Object)newType == (Object)Object[].class)? (T[]) new Object[newLength]: (T[]) Array.newInstance(newType.getComponentType(), newLength);System.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength));return copy; }
用一张图来表示就是这样的:
插入元素
看一下ArrayList的插入操作,插入操作调用的也是add方法,比如:
1 public static void main(String[] args)2 {3 List<String> list = new ArrayList<String>();4 list.add("111");5 list.add("222");6 list.add("333");7 list.add("444");8 list.add("555");9 list.add("666"); 10 list.add("777"); 11 list.add("888"); 12 list.add(2, "000"); 13 System.out.println(list); 14 }
有一个地方不要搞错了,第12行的add方法的意思是,往第几个元素后面插入一个元素,像第12行就是往第二个元素后面插入一个000。看一下运行结果也证明了这一点:
[111, 222, 000, 333, 444, 555, 666, 777, 888]
还是看一下插入的时候做了什么:
// 将指定的元素插入此列表中的指定位置。 // 如果当前位置有元素,则向右移动当前位于该位置的元素以及所有后续元素(将其索引加1)。 public void add(int index, E element) { if (index > size || index < 0) throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size); // 如果数组长度不足,将进行扩容。 ensureCapacity(size+1); // Increments modCount!! // 将 elementData中从Index位置开始、长度为size-index的元素, // 拷贝到从下标为index+1位置开始的新的elementData数组中。 // 即将当前位于该位置的元素以及所有后续元素右移一个位置。 System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
看到插入的时候,按照指定位置,把从指定位置开始的所有元素利用System.arraycopy方法做一个整体的复制,向后移动一个位置(当然先要用ensureCapacity方法进行判断,加了一个元素之后数组会不会不够大),然后指定位置的元素设置为需要插入的元素,完成了一次插入的操作。用图表示这个过程是这样的:
在这个方法中最根本的方法就是System.arraycopy()方法,该方法的根本目的就是将index位置空出来以供新数据插入,这里需要进行数组数据的右移,这是非常麻烦和耗时的,所以如果指定的数据集合需要进行大量插入(中间插入)操作,推荐使用LinkedList。
删除元素
接着我们看一下删除的操作。ArrayList支持两种删除方式:
1、按照下标删除
2、按照元素删除,这会删除ArrayList中与指定要删除的元素匹配的第一个元素
对于ArrayList来说,这两种删除的方法差不多,都是调用的下面一段代码:
int numMoved = size - index - 1; if (numMoved > 0)System.arraycopy(elementData, index+1, elementData, index,numMoved); elementData[--size] = null; // Let gc do its work
其实做的事情就是两件:
1、把指定元素后面位置的所有元素,利用System.arraycopy方法整体向前移动一个位置
2、最后一个位置的元素指定为null,这样让gc可以去回收它
比方说有这么一段代码:
public static void main(String[] args) {List<String> list = new ArrayList<String>();list.add("111");list.add("222");list.add("333");list.add("444");list.add("555");list.add("666");list.add("777");list.add("888");list.remove("333"); }
用图表示是这样的:
ArrayList的优缺点
从上面的几个过程总结一下ArrayList的优缺点。ArrayList的优点如下:
1、ArrayList底层以数组实现,是一种随机访问模式,再加上它实现了RandomAccess接口,因此查找也就是get的时候非常快。
2、ArrayList在顺序添加一个元素的时候非常方便,只是往数组里面添加了一个元素而已。
不过ArrayList的缺点也十分明显:
1、删除元素的时候,涉及到一次元素复制,如果要复制的元素很多,那么就会比较耗费性能。
2、插入元素的时候,涉及到一次元素复制,如果要复制的元素很多,那么就会比较耗费性能。
因此,ArrayList比较适合顺序添加、随机访问的场景。
ArrayList和Vector的区别
ArrayList是线程非安全的,这很明显,因为ArrayList中所有的方法都不是同步的,在并发下一定会出现线程安全问题。那么我们想要使用ArrayList并且让它线程安全怎么办?一个方法是用Collections.synchronizedList方法把你的ArrayList变成一个线程安全的List,比如:
List<String> synchronizedList = Collections.synchronizedList(list); synchronizedList.add("aaa"); synchronizedList.add("bbb"); for (int i = 0; i < synchronizedList.size(); i++) {System.out.println(synchronizedList.get(i)); }
另一个方法就是Vector,它是ArrayList的线程安全版本,其实现90%和ArrayList都完全一样,区别在于:
1、Vector是线程安全的,ArrayList是线程非安全的
2、Vector可以指定增长因子,如果该增长因子指定了,那么扩容的时候会每次新的数组大小会在原数组的大小基础上加上增长因子;如果不指定增长因子,那么就给原数组大小*2,源代码是这样的:
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?capacityIncrement : oldCapacity);
总结
ArrayList基于数组实现,可以通过下标索引直接查找到指定位置的元素,因此查找效率高,但每次插入或删除元素,就要大量地移动元素,插入删除元素的效率低。
转载于:https://www.cnblogs.com/Qiansion/p/9796336.html
ArrayList的实现及原理相关推荐
- arraylist 初始化_ArrayList实现原理(JDK1.8)
public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serial ...
- ArrayList的底层实现原理
ArrayList源码分析 1.java.util.ArrayList<E> : List 接口的大小可变数组的实现类 ArrayList 内部基于 数组 存储 各个元素. 所谓大小可变数 ...
- ArrayList 集合底层实现原理解析
文章目录 1. ArrayList 集合底层数据结构 1. ArrayList 集合介绍 2. 数组结构介绍 2. ArrayList 继承关系 2.1 Serializable 标记性接口 2.2 ...
- ArrayList 原理解析
PS: 前阵子面试,被问到ArrayList的原理,我是一脸懵逼.ArrayList是我们在java语言当中,最常用的一个容器类了.我只知道ArrayList是基于数组的,但是让我说明白ArrayLi ...
- java arraylist 序列化_专题二、ArrayList序列化技术细节详解
一.绪论 所谓的JAVA序列化与反序列化,序列化就是将JAVA 对象以一种的形式保持,比如存放到硬盘,或是用于传输.反序列化是序列化的一个逆过程. JAVA规定被序列化的对象必须实现java.io.S ...
- java list 结构_Java中常见数据结构List之ArrayList
这里主要包含ArrayList和LinkedList. 关于Java中的集合内容, 感觉都已经被写烂了, 我这里主要是做个复习, 再从扒下源代码, 尽量用最直白的语言把里面的核心内容记录下来.仅此而已 ...
- java的知识点23——泛型Generics、Collection接口、List特点和常用方法、 ArrayList特点和底层实现
泛型Generics 一般通过"容器"来容纳和管理数据.程序中的"容器"就是用来容纳和管理数据. 数组就是一种容器,可以在其中放置对象或基本类型数据. 数组的优 ...
- C# ArrayList集合
C# ArrayList集合 集合 可以向数组那样存储多个数据,但是长度不固定.往其中存多少个都可以. ArrayList:动态数组(不是一个数组,是具有数组的功能再克服了数组的缺点) ...
- java 实体类arraylist_如何实现Java的ArrayList经典实体类
arraylist是java集合框架中一个经典的实现类.他比起常用的数组而言,明显的优点在于,可以随意的添加和删除元素而不需考虑数组的大小.处于练手的目的,实现一个简单的arraylist,并且把实现 ...
最新文章
- python游戏服务器框架_mqant首页、文档和下载 - Golang/python语言开发的分布式游戏服务器框架 - OSCHINA - 中文开源技术交流社区...
- ELMo:基于上下文的语言模型,5分钟构建语义搜索引擎代码实战
- Python标准库01 正则表达式 (re包)
- 机器学习实战——特征工程之数据预处理
- Spring思维导图(IOC篇)
- mysql--------四种索引类型
- javaScript数据类型(包括基本数据类型和非基本数据类型)
- mysql pdo prepare_PDO::prepare
- python使用sphinx自动生成文档
- linux BufferedImage.createGraphics()卡住不动
- 数据库性能优化之SQL语句优化(下)
- XLN Audio Addictive Trigger for Mac(智能鼓音替换工具)
- openstreetmap地图下载的几种方法
- oracle instant client 12,Oracle Instant Client_12_1 Downloads
- Google Chrome谷歌浏览器繁体字修改为简体字
- 神华集团世界500强酷站欣赏
- matlab绘制单位球的三维网格图,matlab绘制三维网格图
- 《东周列国志》第十三回 鲁桓公夫妇如齐 郑子亹君臣为戮
- python信号处理加汉明窗_SciPyTutorial-非整周期信号加窗
- 二期:Combined Scorecards
热门文章
- (最短路径算法整理)dijkstra、floyd、bellman-ford、spfa算法
- tc溜溜865手机投屏卡_这台手机智商为零,却要挑战小米华为!
- python time智能等待_python中等待怎么表示
- mybatis plus 事务管理器_Mybatis中的事务
- apns 服务器 php,PHP技术来查询APNs反馈服务器
- window.print 固定表头不影响_Excel中的表头,你会处理吗
- linux命令匹配path顺序,PATH 中的路径先后顺序的问题
- android 自定义actionbar前面有一块空白,解决Android V7后自定义Toolbar、ActionBar左侧有空白问题...
- 信令风暴问题根因分析
- sense chrome扩展工具安装问题