Java源码-集合-ArrayList
基于JDK1.8.0_191
介绍
在Java中,对于数据的保存和使用有多种方式,主要的目的是以更少的资源消耗解决更多的问题,数组就是其中的一种,它的特点是所有的数据都保存在内存的一段连续空间中,使它能更容易的进行数据的修改和查找。
而ArrayList就是基于数组的特性,进行一系列封装而得到的一个数据的工具类,它有以下特点:
- 大小可变(数组的大小并不可变,ArrayList实际上是申请一个新的更大的数组,然后把原来数组的数据拷贝到新数组当中,以此实现扩容)
- 修改和查找的时间复杂度都为o(1)
- 允许所有元素,包括null
- ArrayList是线程不安全的(如果需要使用线程安全的,可以这么写
List list = Collections.synchronizedList(new ArrayList(...));
构造函数
ArrayList有三个构造函数
- 第一个,无参构造函数
public ArrayList();
翻译一下就是Object[] elementData = {},创建一个空数组
- 第二个,设定初始大小
public ArrayList(int initialCapacity);
创建一个initialCapacity大小的数组,建议使用并根据实际当中的情况设置初始大小,因为扩容ArrayList是一个很耗资源的事
- 第三个,创建包含指定元素的数组
public ArrayList(Collection<? extends E> c);
创建一个和传入的参数一模一样的数组。参数是实现了Collection接口的类,会通过集合的toArray方法转换为数组
部分源码解析
点击查看详细内容
//新增,放到数组末尾
public boolean add(E e) {
//判断是否需要扩容
ensureCapacityInternal(size + 1);
//插到末尾
elementData[size++] = e;
return true;
}
//在指定位置插入
public void add(int index, E element) {
//判断index大小是否合法
rangeCheckForAdd(index);
//判断是否扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
//要插入的位置之后的数据整体后移一位。耗费资源极大,所以不建议频繁插入
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//插入数据
elementData[index] = element;
//调整大小
size++;
}
//插入集合
public boolean addAll(Collection<? extends E> c) {
//先将集合转换为数组
Object[] a = c.toArray();
int numNew = a.length;
//判断新的大小是否要扩容
ensureCapacityInternal(size + numNew); // Increments modCount
//在数组末尾插入新的数据
System.arraycopy(a, 0, elementData, size, numNew);
//重新调整大小
size += numNew;
return numNew != 0;
}
//清空ArrayList
public void clear() {
modCount++;
//将数组的每一位设为空
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
//扩容判断
public void ensureCapacity(int minCapacity) {
int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
// 如果不是空数组,可以设置成任意大小的正数值
? 0
//如果是空数组,最小就是10,参数比10还小,是不会扩容的
: DEFAULT_CAPACITY;
if (minCapacity > minExpand) {
ensureExplicitCapacity(minCapacity);
}
}
//jdk 8新增的通过lambda表达式遍历ArrayList
public void forEach(Consumer<? super E> action);
//使用案例
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.forEach(x -> System.out.println(x));
//最后一次出现指定对象的位置,注意区分NULL
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
//移除位置上的对象
public E remove(int index) {
rangeCheck(index);
modCount++;
//需要移除的对象
E oldValue = elementData(index);
//需要移动的数据的长度
int numMoved = size - index - 1;
if (numMoved > 0)
//把指定位置之后的所有数据前移一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//把最后一位设为NULL
elementData[--size] = null;
return oldValue;
}
//删除满足过滤条件的对象,为1.8新出的方法
public boolean removeIf(Predicate<? super E> filter);
//使用案例
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
//删除比1大的
list.removeIf(x -> x > 1);
System.out.println(list.toString());
//输出结果:[1]
//对数组的每个元素,执行某个操作
public void replaceAll(UnaryOperator<E> operator);
//使用案例
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
//删除比1大的
list.replaceAll(x -> ++x);
System.out.println(list.toString());
//输出结果[2, 3, 4]
//程序对数组的每个元素都进行了+1的操作
ArrayList的查询
通过阅读源码我们发现,在源码当中需要使用到遍历整个ArrayList的时候,都是使用的传统for循环,那么可以试一试各个遍历ArrayList的方式有什么不同
1.传统for循环
List<String> list = new ArrayList<>();
for(int i = 0; i < 10000000; i++){
list.add(i + "");
}
long time1 = System.currentTimeMillis();
for(int i = 0; i < list.size(); i++){}
long time2 = System.currentTimeMillis();
System.out.println(time2 - time1);
输出的结果为5ms
2.简便for循环
把循环方式改为
for(String str : list){}
输出的结果为40ms
3.Iterator
把循环方式改为
Iterator<String> iter = list.iterator();
while(iter.hasNext()){
iter.next();
}
输出的结果为7ms
4.foreach
list.forEach(x -> {});
使用jdk8新出的foreach
输出的结果为77ms
这四种方式差距的还是有些明显的,那么来分析下原因
首先第二种的简便for循环,其实内部使用的还是Iterator方式,我们把第二种代码进行编译,然后查看.class文件可以发现,for(String str : list){}编译后就变成了
String var5;
for(Iterator var4 = list.iterator(); var4.hasNext(); var5 = (String)var4.next()) {}
然后我们看第四种,第四种方式的源码为:
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
我们可以看出来,其实它内部嵌套的是第二种循环方式,所以耗时更久
总结:使用第一种最好,虽然代码长了一点,但是效率会提高很多
Java源码-集合-ArrayList的更多相关文章
- 浅析Java源码之ArrayList
面试题经常会问到LinkedList与ArrayList的区别,与其背网上的废话,不如直接撸源码! 文章源码来源于JRE1.8,java.util.ArrayList 既然是浅析,就主要针对该数据结构 ...
- Java源码阅读ArrayList
1简介 public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAc ...
- Java源码之ArrayList分析
一.ArrayList简介 ArrayList底层的数据结构是数组,数组元素类型为Object类型,即可以存放所有类型数据. 与Java中的数组相比,它的容量能动态增长.当创建一个数组的时候,就必须确 ...
- Java源码之ArrayList
本文源码均来自Java 8 总体介绍 Collection接口是集合类的根接口,Java中没有提供这个接口的直接的实现类.Set和List两个类继承于它.Set中不能包含重复的元素,也没有顺序来存放. ...
- Java源码-集合-LinkedList
基于JDK1.8.0_191 介绍 LinkedList是以节点来保存数据的,不像数组在创建的时候需要申请一段连续的空间,LinkedList里的数据是可以存放在不同的空间当中,然后以内存地址作为 ...
- jdk源码->集合->ArrayList
类的属性 public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomA ...
- Java源码解析——集合框架(一)——ArrayList
ArrayList源码分析 ArrayList就是动态数组,是Array的复杂版本,它提供了动态的增加和减少元素.灵活的设置数组的大小. 一.类声明 public class ArrayList< ...
- Java集合源码剖析——ArrayList源码剖析
ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线 ...
- Java 源码学习线路————_先JDK工具包集合_再core包,也就是String、StringBuffer等_Java IO类库
http://www.iteye.com/topic/1113732 原则网址 Java源码初接触 如果你进行过一年左右的开发,喜欢用eclipse的debug功能.好了,你现在就有阅读源码的技术基础 ...
随机推荐
- 解决:执行python脚本,提示错误:/usr/bin/python^M: 解释器错误: 没有那个文件或目录。
执行python脚本,提示错误: /usr/bin/python^M: 解释器错误: 没有那个文件或目录. 产生错误原因: \r字符被显示为^M,这时候只需要删除这个字符就可以了. Linux环境下: ...
- vscode管理员身份运行
管理员身份运行,如vscode 如何设置呢? vscode图标右键 以管理员身份运行程序打钩就行了 运行“在终端打开”的时候,要以管理员身份运行 刚下载完vscode并运行并不是管理员身份会报错 解 ...
- Map-HashMap 与 IF 判断内存占用对比
HashMap与IF判断内存占用对比,事实证明,Map对象在以下情况确实比IF判断占用内存低. HashMap占用内存:13000 package com.taiping.bky; import ja ...
- 排序算法大荟萃——希尔(Shell)排序算法
1.基本思想:先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组.所有距离为d1的倍数的记录放在同一个组中.先再各族中进行直接插入排序,然后取第二个增量d2<d1重复上述的分组 ...
- Apache Shiro——初识
Shrio是什么? Shrio是一个用Java开发的安全框架,用来保证系统或系统数据安全的.他可以用在大多数程序上,比如移动应用程序.Web程序或者大型的企业应用程序等. Shrio能干什么? 能用来 ...
- php源码加密--screw plus
screw plus是一个开源的php扩展,作用是对php文件进行加密,网络上提供php加密的服务很多,但大多都只是混淆级别的加密,被人拿到加密文件问只要有足够耐心就能破解,与之不同的是,screw ...
- idea 快捷使用(三)中断Debug的使用
想要在Debug的时候,中断请求,不要再走剩余的流程了? 不需要关闭服务重新启动程序,可以通过Force Return,即强制返回来避免后续的流程. 点击Force Return,弹出Return V ...
- 在HTML中实现两个div并排显示
在HTML中让两个div并排显示,通常情况下有三种实现方式,包括: (1)设置为行内样式,display:inline-block (2)设置float浮动 (3)设置position定位属性为abs ...
- 缓存验证Last-Modified和Etag的使用
缓存工作示意图: 在http协议里面,数据的验证方式,主要有两个验证头:Last-Modified 和 Etag. Last-Modified 配合Last-Modified-Since或者If-Un ...
- 爬虫(十三):PIL模块
1. PIL模块 在爬虫(十二):图形验证码的识别.滑动验证码的识别(B站滑动验证码)中我留下了一个悬念,为什么安装的是pillow模块,而不是PIL模块.这是因为PIL是python2的产物,它并没 ...