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功能.好了,你现在就有阅读源码的技术基础 ...
随机推荐
- P&R 2
Floorplan: 要做好floorplan需要掌握哪些知识跟技能? 通常,遇到floorplan问题,大致的debug步骤跟方法有哪些? 如何衡量floorplan的QA? Floorplan是后 ...
- win api 音频可视化
暂时记录,改天有时间再完善...其实写好好久了,但以前的代码丢了,重新写一遍.. 原理和 python 的一样,获取输入设备,然后把数据读取到 buffer 中,在绘制出来. 这里要注意两点: 1. ...
- django 模版查找路径
路径的配置,模版上下文的配置等.模版路径可以在两个地方配置. 1.'DIRS':这是一个列表,在这个列表中可以存放所有的模版路径,以后在视图中使用render或者render_to_string渲染模 ...
- 原生JS实现旋转木马轮播图特效
大概是这个样子: 首先来简单布局一下(emm...随便弄一下吧,反正主要是用js来整的) <!DOCTYPE html> <html lang="en"> ...
- linux开启端口命令
1. 开放端口命令: /sbin/iptables -I INPUT -p tcp --dport 8080 -j ACCEPT 2.保存:/etc/rc.d/init.d/iptables save ...
- 树莓派安装raspbian并配置开发环境
1.烧录系统 首先准备好我们要烧录的raspbian系统,可以在树莓派官网中下载https://www.raspberrypi.org/downloads/ 这里我们选择 2018-11-13-ras ...
- 分布式事务 --- BASE 理论
部分图片总结出自参考资料 问题 : Base 理论为什么会被提出,动机是什么 Base 和 ACID 的区别与联系 概述 上一篇我们知道CAP 理论,也知道由于现实中网络等原因,分区容错性这一元素大多 ...
- 虚拟机中安装centos7后无法上网,使用桥接网络+ssh
首先是桥接网络解决无法上网的问题: 1保证你Vmware里面的虚拟机是关机状态2右键点击电脑屏幕右下角小电脑图标,选择打开网络与共享中心,然后点击弹出来的窗口左上角的“更改适配器设置”.这里指的是你W ...
- C++中宏的定义与用法(现已被内联函数所代替)
在noip中,宏还是被经常采用,所以这里讲一下,C++中宏的定义与用法 第一种用法——配合条件编译:#define DEBUG 定义一个叫DEBUG的标识符.它应该与#ifdef或#ifndef配合使 ...
- 常用的php函数
最严格身份证号码验证,支持15位和19世纪出生的人的身份证号码 # 计算身份证校验码,根据国家标准GB 11643-1999 function idcard_verify_number($idcard ...