浅谈ArrayList
浅谈ArrayList
废话不多说(事实是不会说),让我们直接进入正题
首先讲一讲最基本的ArrayList的初始化,也就是我们常说的构造函数,ArrayList给我们提供了三种构造方式,我们逐个来查看
Arraylist();
无参的构造方法,这种方式的初始化,ArrayList内部会为我们声明一个长度为0的集合,但在我们调用add方法加入一个元素时,它内部会经历add->ensureCapacityInternal->calculateCapacity->ensureExplicitCapacity->grow->调用Arrays.copyOf方法返回新生成的集合(内部最终调用System.arraycopy完成元素的转移)->grow->ensureExplicitCapacity->ensureExplicitCapacity->add方法,该执行流程最终生成一个容量为十(默认值)的集合,并将元素加入到集合中。以下列出部分关键源码
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果为无参的构造
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//该处的DEFAULT_CAPCITY=10,minCapcity=1
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
//集合结构发生改变的记录值,没发生一次结构改变(扩容、元素增加、元素删除),该值加一
modCount++;
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
...
elementData = Arrays.copyOf(elementData, newCapacity);
}
也许你会问,既然有初始默认容量值,为什么不在调用构造函数的时候就初始化一个默认长度的集合呢?
个人意见:我认为在存在一种情况,就是仅声明一个ArrayList的集合对象,但是后面并未使用,在这种情况下,如果在声明是就初始化一个长度为10的集合,会造成空间的浪费,而且在真正使用集合时(即调用add方法),才初始化集合,有一种懒加载的思想在其中,避免了耗资源操作的集中,但也并非所有的构造方法中都不会初始化集合容量,在ArrayList(int capacity)构造方法中,只要输入的初始容量为正整数,那么就会在构造函数中就定义出指定大小的集合对象
ArrayList(int capacity);
该方式的初始化在执行构造函数的时候传入了一个初始容量值,不过要求这个初始容量值必须为正整数,而且如果为0的话等同于无参的构造方法,假定初始化容量为5,那么在调用add方法时,经历的方法依次为add->ensureCapacityInternal->calculateCapacity->ensureExplicitCapacity->add,下面我们来看看部分关键源码:
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
//按指定容量初始化集合,此时elementData.length==initialCapacity
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//传入值等于0,初始化一个容量为0的初始集合
this.elementData = EMPTY_ELEMENTDATA;
} else {
//传入值为不符合要求,报错
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
private static final Object[] EMPTY_ELEMENTDATA = {};
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//不满足条件,不会进入该分支
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//直接返回最小容量,即集合当前元素值+1(给新增元素的空间)
return minCapacity;
}
ArrayList(Collection<? extends E> c);
该方式的初始化需要传入Collection的子类对象,并根据该对象来初始化ArrayList
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
//如果不是Object[]类型的集合,转化为Object[]类型
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
到这里对ArrayList的三个构造函数做了简单的介绍
在继续进行ArrayList中方法的介绍之前,需要注意ArrayList不是线程安全的,所以如果涉及并发操作,建议在初始化的使用调用Collections.synchronizedList(list)来将线程不安全的集合对象转化为线程安全的对象,内部的实现原理:使用代理模式在原来的方法执行之前嵌套了一个同步机制,这里摘取其中的size()方法,源码如下:
public int size() {
synchronized (mutex) { //同步代码块
return c.size(); //c为被代理对象
}
}
因为给所有的方法加上锁会降低代码的执行效率,而且有些方法是不需要加锁的,如果不想对所有的方法都加锁,可以在需要加锁的特定方法调用之前手动的做同步处理
下面进入ArrayList中的部分方法介绍,首先介绍从List父接口中继承过来的方法,其中使用到的[]代表对应参数可以存在也可以不存在,不过存在与不存在构成了不同的重载方法:
add([int index,] E element);
该方法用于向集合中添加元素,存在单参方法和双参两个重载方法,单参直接往集合末尾添加新元素,双参方法则可以手动指定插入到集合的哪个索引位置。每当集合元素的个数(size)超过集合容量(elementData.length)时,会触发grow方法(扩容方法),每次在老容量的基础上增加一半的容量,然后通过Arrays.copyOf方法进行新集合的创建和拷贝原集合内容
//在grow方法中关于新集合容量定义的关键代码
int newCapacity = oldCapacity + (oldCapacity >> 1);
addAll([int index,] Collection<? extends E> c);
该方法用于将另一个集合中的所有元素加入到当前集合中,该方法也存在单参和双参两个重载方法,单参方法把所有新元素添加到集合末尾,双参方法则在指定索引位置开始插入所有元素,扩容方法的触发和过程同add
set(int index, E element);
该方法用于替换集合中指定索引位置的值,内部会先进行索引范围检查,返回值为被替换下的老元素
isEmpty();
该方法用于判断集合中是否存在元素,返回值是一个布尔类型,源码的返回值为size == 0
get(int index);
该方法用于获取集合中指定位置的值并返回
remove(int index);
该方法用于删除集合中特定索引位置的值,返回值为被删除的老元素
remove(Object o);
该方法用于删除集合中第一个为特定元素的值,可以传入null,代表删除集合中第一个为null的元素,返回值为布尔类型
clear();
该方法用于清空集合中的所有元素,内部对elementData集合对象的所有元素置空,并记录modCount(集合结构记录变量)值,最后将size置0
removeAll(Collection<?> c);
该方法用于删除集合中存在于传入集合中的所有指定元素,要注意的是,该方法为全集合查找删除,不同于remove方法只删除第一次出现的位置
retainAll(Collection<?> c);
该方法用于跟removeAll的作用刚好互补,用于保留传入集合中包含的所有指定元素,即删除指定集合之外的所有元素
size();
该方法返回集合中元素的个数(size),但该值并不代表集合当前的容量(elementData.length)
indexOf(Object o);
该方法用于查询集合中特定元素出现的第一个索引位置,如果未找到则返回-1
lastIndexOf(Object o);
该方法用于查询集合中特定元素出现的最后一个下标位置,如果未找到则返回-1
contains(Object o);
该方法用于判断集合中是否存在指定的元素,返回值为布尔类型,源码的返回值为indexOf(o) >= 0
sort(Comparator<? super E> c);
该方法用于对集合元素按传入的指定排序规则进行排序,以下使用匿名内部类并结合Lambda表达式来作为示例构造一个二级排序规则,可根据需要增加更多层级的排序规则:
list.sort((o1,o2) -> {
int result = -(o1.getClick() - o2.getClick());// 数值类型的比较写法,点击量降序
if (result == 0) {// 如果点击量相同,进入二级排序
result = o1.getDate().compareTo(o2.getDate());// 字符串类型的比较写法,时间升序
}
return result;// 返回比较结果
});
subList(int fromIndex,int toIndex);
该方法用于返回一个可操作性的子集合,不过要注意的是,子集合只不过是添加了偏移量的父集合,所以两个集合的对象是一致的,对于子集合中的操作,源码中都是在添加上对应的偏移量之后直接对父集合做对应修改,所以,对子集合的操作实际上就是对父集合的操作
iterator();
该方法用于返回当前集合的一个普通迭代器,提供了hasNext,next,remove和forEachRemaining方法,其中hasNext用于判断是否存在下一个迭代对象,next用于将cursor指向下一个待操作元素,并返回当前元素(由内部的lastRet索引值进行指定),remove用于删除当前元素(该方法不可连续调用多次,因为内部的lastRet索引在一次操作后会被置为-1,可以会在下一次next时重新指向当前元素),forEachRemaining用于对集合未迭代对象执行传入的钩子方法
listIterator([int index]);
该方法用于返回一个加强版的迭代器对象,由于内部继承了iterator的类,所以可以提供iterator的所有功能,除此之外,还提供了自己独有的功能和特性,首先在初始化时就可以指定迭代初始索引,其次还提供了hasPrevious,nextIndex,previousIndex,previous,set和add方法,其中的previous方法可以实现集合的逆序遍历,set可以替换集合的当前迭代对象,add可以在当前迭代位置添加新的集合元素
splitIterator();
该方法用于切割集合元素,可用于并发编程时多线程操作集合,需要先进行切割,方法内部默认对半分,再对切割完成的集合进行并行处理,示例代码如下:
ist list = new ArrayList();
list.add(1);list.add(2);list.add(3);list.add(4);list.add(5);
list.add(6);list.add(7);list.add(8);list.add(9);list.add(0);
//对集合进行切割
Spliterator<Integer> a = list.spliterator();//将集合转化为可拆分集合
Spliterator<Integer> b = a.trySplit();//将可拆分的a集合对半分
Spliterator<Integer> c = a.trySplit();//继续将可拆分的a集合对半分
Spliterator<Integer> d = b.trySplit();//将对半分得到的b集合对半分
//对切割好的集合进行操作
a.forEachRemaining(x -> System.out.print(x + " "));
System.out.println();
b.forEachRemaining(x -> System.out.print(x + " "));
System.out.println();
c.forEachRemaining(x -> System.out.print(x + " "));
System.out.println();
d.forEachRemaining(x -> System.out.print(x + " "));
打印结果为:
8 9 0
3 4 5
6 7
1 2
toArray(T[] a);
该方法用于将集合转化为数组,可以使用参数来指定生成数组的数据类型,返回值为指定类型的数组,以下为代码示例:
String[] strs=list.toArray(new String[0]);// list.toArray(new Object[0]);等同于list.toArray();
下面的方法是ArrayList中特有的方法,如果在你的代码中调用不到,请检查下声明该ArrayList对象的时候对象声明部分是否为ArrayList喔~
clone();
该方法继承自Cloneable接口父类,用于创建并返回一个浅克隆的集合对象
ensureCapacity(int minCapacity);
该方法用于检查集合容量是否已经达到瓶颈,继而判断是否需要进行扩容操作
trimToSize();
该方法用于将集合容量调整至集合的元素总数,可以对不需要继续添加新元素的集合使用该操作用于释放部分资源,使用了该方法的集合,在添加新元素时会进行扩容操作,扩容方法依旧是扩容原容量的一半
forEach(Consumer<? super E> action);
用于对集合中的所有元素执行对应的操作,该处使用匿名内部类结合Lambda表达式的方式进行简单演示:
list.forEach(x -> System.out.println(x.getAge()+1));// 如果只是简单的打印,还可以使用list.forEach(System.out::println);
removeIf(Predicate<? super E> filter);
该方法用于移除集合中符合条件的所有元素,该处使用匿名内部类结合Lambda表达式的方式进行简单演示:
list.removeIf(x -> x.getAge() < 18);
如果对你有帮助,点个赞,或者打个赏吧,嘿嘿
整理不易,请尊重博主的劳动成果
浅谈ArrayList的更多相关文章
- 浅谈 ArrayList 及其扩容机制
浅谈ArrayList ArrayList类又称动态数组,同时实现了Collection和List接口,其内部数据结构由数组实现,因此可对容器内元素实现快速随机访问.但因为ArrayList中插入或删 ...
- 浅谈 Java集合
Java 集合 集合是对象的容器,定义了多个对象进行操作的常用方法,可实现数组的功能. Java集合类库所处位置:java.util.*. 与现代的数据结构类库的常见做法一样,Java集合类库也将接口 ...
- 浅谈Android编码规范及命名规范
前言: 目前工作负责两个医疗APP项目的开发,同时使用LeanCloud进行云端配合开发,完全单挑. 现大框架已经完成,正在进行细节模块上的开发 抽空总结一下Android项目的开发规范:1.编码规范 ...
- 浅谈JAVA集合框架
浅谈JAVA集合框架 Java提供了数种持有对象的方式,包括语言内置的Array,还有就是utilities中提供的容器类(container classes),又称群集类(collection cl ...
- 浅谈TabLayout(ViewPager+Tab联动)
google发布了的Android Support Design库中提供了TabLayout 通过TabLayout+ViewPager实现导航栏效果,点击Tab ,ViewPager跟随变化,滑动V ...
- 安卓开发_浅谈ListView(SimpleAdapter数组适配器)
安卓开发_浅谈ListView(ArrayAdapter数组适配器) 学习使用ListView组件和SimapleAdapter适配器实现一个带图标的ListView列表 总共3部分 一.MainAc ...
- 浅谈Java的集合框架
浅谈Java的集合框架 一. 初识集合 重所周知,Java有四大集合框架群,Set.List.Queue和Map.四种集合的关注点不同,Set 关注事物的唯一性,List 关注事物的索引列表,Q ...
- 浅谈java类集框架和数据结构(2)
继续上一篇浅谈java类集框架和数据结构(1)的内容 上一篇博文简介了java类集框架几大常见集合框架,这一篇博文主要分析一些接口特性以及性能优化. 一:List接口 List是最常见的数据结构了,主 ...
- 浅谈 Java 主流开源类库解析 XML
在大型项目编码推进中,涉及到 XML 解析问题时,大多数程序员都不太会选用底层的解析方式直接编码. 主要存在编码复杂性.难扩展.难复用....,但如果你是 super 程序员或是一个人的项目,也不妨一 ...
随机推荐
- Delphi10.3的DBGrid中memo类型显示内容而不是(WIDEMEMO)
1]连接好数据库,并显示: 2]增加所有字段: 3]添加事件: // FDQuery1UserName: TWideMemoField; procedure TForm1.FDQuery1Use ...
- [剑指Offer]41.和为S的两个数字 VS 和为S的连续正数序列
[剑指Offer]41 和为S的两个数字 VS 和为S的连续正数序列 Leetcode T1 Two Sum Given an array of integers, return indices of ...
- Scapy编写ICMP扫描脚本
使用Scapy模块编写ICMP扫描脚本: from scapy.all import * import optparse import threading import os def scan(ipt ...
- 1、Spark Core所处位置和主要职责
Spark组件是基于分布式资源引擎层(Yarn等)和分布式存储层(HDFS等)之上的一个组件,Spark本质上是一个计算引擎,负责计算的,根据不同计算场景划分出了SQL.Streaming.MLib. ...
- Building Applications with Force.com and VisualForce (DEV401) (三):Application Essential:Building Your Data Model
Dev 401-003:Application Essential:Building Your Data Model Object Relationships1.Link two objects- P ...
- OpenCV-Python 傅里叶变换 | 三十
目标 在本节中,我们将学习 使用OpenCV查找图像的傅立叶变换 利用Numpy中可用的FFT函数 傅立叶变换的某些应用程序 我们将看到以下函数:cv.dft(),cv.idft()等 理论 傅立叶变 ...
- PyTorch专栏(八):微调基于torchvision 0.3的目标检测模型
专栏目录: 第一章:PyTorch之简介与下载 PyTorch简介 PyTorch环境搭建 第二章:PyTorch之60分钟入门 PyTorch入门 PyTorch自动微分 PyTorch神经网络 P ...
- ArrayList 扩容 和 Vector
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[siz ...
- ubuntu 如何在命令行打开当前目录
nautilus /var 打开var文件夹
- vue 听说你很会传值?
前置 大小 vue 项目都离不开组件通讯, 在这里总结一下vue组件通讯方式并列出, 都是简单的例子. 适合像我这样的小白.如有错误,欢迎指正. 温馨提示: 下文没有列出 vuex, vuex 也是重 ...