迎难而上ArrayList,源码分析走一波
先看再点赞,给自己一点思考的时间,思考过后请毫不犹豫微信搜索【沉默王二】,关注这个长发飘飘却靠才华苟且的程序员。
本文 GitHub github.com/itwanger 已收录,里面还有技术大佬整理的面试题,以及二哥的系列文章。
关于 Java 基础、Java 面向对象编程、Java 字符串、Java 数组等方面的知识点已经可以告一段落了,小伙伴们可以在「沉默王二」公众号后台回复「小白」获取第二版手册。觉得不错的话,请随手转发给身边的小伙伴,赠人玫瑰,手有余香哈。
那么接下来,我开始肝 Java 集合方面的文章了,小伙伴们请默默为我鼓个掌,我能听得到,真的,别吝啬你的掌声,响起来。第一篇,必须得从 ArrayList 开始,毕竟 ArrayList 可以称得上是集合方面最常用的类了,估计没有之一。
ArrayList 实现了 List 接口,是基于数组实现的。小伙伴们都知道,数组的大小是固定的,创建的时候指定了大小,就不能再调整了,如果数组满了,就不能再添加任何元素了。ArrayList 是数组很好的替代方案,它提供了比数组更丰富的预定义方法(增删改查),并且大小是可以根据元素的多少进行自动调整的,非常灵活。
准备在 ArrayList 的第四个位置(下标为 3)上添加一个元素 55。
此时 ArrayList 中第五个位置以后的元素将会向后移动。
准备把 23 从 ArrayList 中移除。
此时下标为 7、8、9 的元素往前挪。
01、如何创建一个 ArrayList
ArrayList<String> alist = new ArrayList<String>();
可以通过上面的语句来创建一个字符串类型的 ArrayList(通过尖括号来限定 ArrayList 中元素的类型,如果尝试添加其他类型的元素,将会产生编译错误),更简化的写法如下:
List<String> alist = new ArrayList<>();
由于 ArrayList 实现了 List 接口,所以 alist 变量的类型可以是 List 类型;new 关键字声明后的尖括号中可以不再指定元素的类型,因为编译器可以通过前面尖括号中的类型进行智能推断。
如果非常确定 ArrayList 中元素的个数,在创建的时候还可以指定初始大小。
List<String> alist = new ArrayList<>(20);
这样做的好处是,可以有效地避免在添加新的元素时进行不必要的扩容。但通常情况下,我们很难确定 ArrayList 中元素的个数,因此一般不指定初始大小。
02、向 ArrayList 中添加一个元素
可以通过 add()
方法向 ArrayList 中添加一个元素,如果不指定下标的话,就默认添加在末尾。
alist.add("沉默王二");
感兴趣的小伙伴可以研究一下 add()
方法的源码,它在添加元素的时候会执行 grow()
方法进行扩容,这个是面试官特别喜欢考察的一个重点。
下面是 add(E e)
方法的源码:
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
调用了私有的 add(E e, Object[] elementData, int s)
方法:
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
然后调用了非常关键的 grow(int minCapacity)
方法:
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length;
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity, /* minimum growth */
oldCapacity >> 1 /* preferred growth */);
return elementData = Arrays.copyOf(elementData, newCapacity);
} else {
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
}
}
如果创建 ArrayList 的时候没有指定初始大小,那么 ArrayList 的初始大小就是 DEFAULT_CAPACITY:
private static final int DEFAULT_CAPACITY = 10;
可以容纳 10 个元素。
还可以通过 add(int index, E element)
方法把元素添加到指定的位置:
alist.add(0, "沉默王三");
add(int index, E element)
方法的源码如下:
public void add(int index, E element) {
rangeCheckForAdd(index);
modCount++;
final int s;
Object[] elementData;
if ((s = size) == (elementData = this.elementData).length)
elementData = grow();
System.arraycopy(elementData, index,
elementData, index + 1,
s - index);
elementData[index] = element;
size = s + 1;
}
该方法会调用到一个非常重要的本地方法 System.arraycopy()
,它会对数组进行复制(要插入位置上的元素往后复制,参照文章一开头提到的两张图片)。
03、更新 ArrayList 中的元素
可以使用 set()
方法来更改 ArrayList 中的元素,需要提供下标和新元素。
alist.set(0, "沉默王四");
原来 0 位置上的元素为“沉默王三”,现在将其更新为“沉默王四”。
来看一下 set()
方法的源码:
public E set(int index, E element) {
Objects.checkIndex(index, size);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
该方法会先对指定的下标进行检查,看是否越界,然后替换新值并返回旧值。
04、删除 ArrayList 中的元素
remove(int index)
方法用于删除指定下标位置上的元素,remove(Object o)
方法用于删除指定值的元素。
alist.remove(1);
alist.remove("沉默王四");
先来看 remove(int index)
方法的源码:
public E remove(int index) {
Objects.checkIndex(index, size);
final Object[] es = elementData;
@SuppressWarnings("unchecked") E oldValue = (E) es[index];
fastRemove(es, index);
return oldValue;
}
该方法返回要删除的元素,真正的删除操作在 fastRemove(es, index)
方法中。
再来看 remove(Object o)
方法的源码:
public boolean remove(Object o) {
final Object[] es = elementData;
final int size = this.size;
int i = 0;
found: {
if (o == null) {
for (; i < size; i++)
if (es[i] == null)
break found;
} else {
for (; i < size; i++)
if (o.equals(es[i]))
break found;
}
return false;
}
fastRemove(es, i);
return true;
}
该方法通过 break label 的方式找到要删除元素(null 的时候使用 == 操作符判断,非 null 的时候使用 equals()
方法,意味着如果有相同元素时,删除第一个)的下标,然后调用 fastRemove()
方法。
既然都调用了 fastRemove()
方法,那就继续来跟踪一下源码:
private void fastRemove(Object[] es, int i) {
modCount++;
final int newSize;
if ((newSize = size - 1) > i)
System.arraycopy(es, i + 1, es, i, newSize - i);
es[size = newSize] = null;
}
当删除的是末尾的元素时,不需要复制数组,直接把末尾的元素赋值为 null 即可;否则的话,就需要调用 System.arraycopy()
对数组进行复制。参照文章一开头提到的第三张、第四张图片。
05、查找 ArrayList 中的元素
如果要正序查找一个元素,可以使用 indexOf()
方法;如果要倒序查找一个元素,可以使用 lastIndexOf()
方法。
alist.indexOf("沉默王二");
alist.lastIndexOf("沉默王二");
来看一下 indexOf()
方法的源码:
public int indexOf(Object o) {
return indexOfRange(o, 0, size);
}
int indexOfRange(Object o, int start, int end) {
Object[] es = elementData;
if (o == null) {
for (int i = start; i < end; i++) {
if (es[i] == null) {
return i;
}
}
} else {
for (int i = start; i < end; i++) {
if (o.equals(es[i])) {
return i;
}
}
}
return -1;
}
如果元素为 null 的时候使用“==”操作符,否则使用 equals()
方法——该方法不是 null 安全的。
lastIndexOf()
方法和 indexOf()
方法类似,不过遍历的时候从最后开始。
contains()
方法可以判断 ArrayList 中是否包含某个元素,其内部调用了 indexOf()
方法:
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
如果 ArrayList 中的元素是经过排序的,就可以使用二分查找法,效率更快。
Collections
类的 sort()
方法可以对 ArrayList 进行排序,该方法会按照字母顺序对 String 类型的列表进行排序。如果是自定义类型的列表,还可以指定 Comparator 进行排序。
List<String> copy = new ArrayList<>(alist);
copy.add("a");
copy.add("c");
copy.add("b");
copy.add("d");
Collections.sort(copy);
System.out.println(copy);
输出结果如下所示:
[a, b, c, d]
排序后就可以使用二分查找法了:
int index = Collections.binarySearch(copy, "b");
06、最后
关于 ArrayList,就先介绍这么多吧,通过源码的角度,我想小伙伴们一定对 ArrayList 有了更深刻的印象。
简单总结一下 ArrayList 的时间复杂度,方便后面学习 LinkedList 时作为一个对比。
1)通过下标(也就是 get(int index)
)访问一个元素的时间复杂度为 O(1),因为是直达的,无论数据增大多少倍,耗时都不变。
2)添加一个元素(也就是 add()
)的时间复杂度为 O(1),因为直接添加到末尾。
3)删除一个元素的时间复杂度为 O(n),因为要遍历列表,数据量增大几倍,耗时也增大几倍。
4)查找一个未排序的列表时间复杂度为 O(n),因为要遍历列表;查找排序过的列表时间复杂度为 O(log n),因为可以使用二分查找法,当数据增大 n 倍时,耗时增大 logn 倍(这里的 log 是以 2 为底的,每找一次排除一半的可能)。
我是沉默王二,一枚有颜值却靠才华苟且的程序员。关注即可提升学习效率,别忘了三连啊,点赞、收藏、留言,我不挑,奥利给。
注:如果文章有任何问题,欢迎毫不留情地指正。
很多读者都同情我说,“二哥,你像母猪似的日更原创累不累?”我只能说写作不易,且行且珍惜啊,关键是我真的喜欢写作。最后,欢迎微信搜索「沉默王二」第一时间阅读,回复「简历」更有阿里大佬的简历模板,本文 GitHub github.com/itwanger 已收录,欢迎 star。
迎难而上ArrayList,源码分析走一波的更多相关文章
- java集合【13】——— Stack源码分析走一波
前言 集合源码分析系列:Java集合源码分析 前面已经把Vector,ArrayList,LinkedList分析完了,本来是想开始Map这一块,但是看了下面这个接口设计框架图:整个接口框架关系如下( ...
- Java入门系列之集合ArrayList源码分析(七)
前言 上一节我们通过排队类实现了类似ArrayList基本功能,当然还有很多欠缺考虑,只是为了我们学习集合而准备来着,本节我们来看看ArrayList源码中对于常用操作方法是如何进行的,请往下看. A ...
- Java ArrayList源码分析(含扩容机制等重点问题分析)
写在最前面 这个项目是从20年末就立好的 flag,经过几年的学习,回过头再去看很多知识点又有新的理解.所以趁着找实习的准备,结合以前的学习储备,创建一个主要针对应届生和初学者的 Java 开源知识项 ...
- Java集合干货——ArrayList源码分析
ArrayList源码分析 前言 在之前的文章中我们提到过ArrayList,ArrayList可以说是每一个学java的人使用最多最熟练的集合了,但是知其然不知其所以然.关于ArrayList的具体 ...
- ArrayList 源码分析
ArrayList 源码分析 1. 结构 首先我们需要对 ArrayList 有一个大致的了解就从结构来看看吧. 1. 继承 该类继承自 AbstractList 这个比较好说 2. 实现 这 ...
- ArrayList源码分析超详细
ArrayList源码分析超详解 想要分析下源码是件好事,但是如何去进行分析呢?以我的例子来说,我进行源码分析的过程如下几步: 找到类:利用 IDEA 找到所需要分析的类(ztrl+N查找ArraLi ...
- Java - ArrayList源码分析
java提高篇(二一)-----ArrayList 一.ArrayList概述 ArrayList是实现List接口的动态数组,所谓动态就是它的大小是可变的.实现了所有可选列表操作,并允许包括 nul ...
- java集合系列之ArrayList源码分析
java集合系列之ArrayList源码分析(基于jdk1.8) ArrayList简介 ArrayList时List接口的一个非常重要的实现子类,它的底层是通过动态数组实现的,因此它具备查询速度快, ...
- ArrayList源码分析超详细(转载)
ArrayList源码分析超详细 ArrayList源码分析超详解 想要分析下源码是件好事,但是如何去进行分析呢?以我的例子来说,我进行源码分析的过程如下几步: 找到类:利用 IDEA 找到所需要 ...
随机推荐
- 开启PG的归档模式
目录 开启PG的归档模式 1.查看pg的数据目录 2.查看pg的归档情况 3.查看归档的模式和位置 4.建立归档目录 5.配置归档参数 6.重启pg 7.查看&&切换归档日志 8.查看 ...
- 利用ssm框架做一个客户管理系统
1. 需求分析 (1)初始化查询条件下拉列表 (2)展示客户列表,并且可以根据查询条件过滤查询结果,并且实现分页处理. (3)修改客户信息: 1)点击客户列表中的“修改”按钮弹出客户信息修改对话框,并 ...
- WeChair项目Alpha冲刺(9/10)
团队项目进行情况 1.昨日进展 Alpha冲刺第九天 昨日进展: 前端:安排页面美化,设计实名认证 后端:更新dao层代码 数据库:修改数据表属性,与后端部署数据库交互 2.今日安排 前端:继续 ...
- SSH免密登录详解
SSH免密登录详解 SSH(Security Shell)安全外壳协议,是较为可靠的,专为远程登录会话和其他网络服务提供安全保证的协议. 对于传统的网络服务程序(例如,FTP,Telnet等)来说 ...
- [ C++ ] set_new_handler解析
当 operator new 中malloc返回值为0(NULL)时,表示分配内存失败(可能是因为内存不足), 此时会通过_callnewh()调用用户通过set_new_handler()设定的ne ...
- yum本地源创建
1 安装yum-utils包,yum-utils可以将需要的包下载在本地,安装后可以使用yumdownloader yum -y install yum-utils* 2 建立目录/yum/yum ...
- python基础知识扩展(一)
python课外笔记 1.print函数 print("helloworld")其实系统默认隐藏了一个参数end,完整的print()语句是 print("hellowo ...
- express高效入门教程(5)
5.ejs模版 5.1.什么是模版引擎? 为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎就会生成一个标准的HTML文档. 在后端开发中,处理数据的代码和展示 ...
- JavaScript中__proto__与prototype的关系(转)
一.所有构造器/函数的__proto__都指向Function.prototype,它是一个空函数(Empty function) 1 2 3 4 5 6 7 8 9 Number.__proto__ ...
- Python之浅谈继承
目录 继承 继承介绍 如何使用继承 新式类:只要继承了object类,就是新式类,再python3中,默认继承object类 经典类:没有继承object的类,就是经典类 利用继承减少代码冗余,菱形问 ...