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功能.好了,你现在就有阅读源码的技术基础 ...
随机推荐
- 解决vscode出现两个光标的问题
转载自本人独立博客:https://liushiming.cn/2020/01/20/vscode-two-cursors-in-vim-mode/ 问题概述 今天用vscode的vim模式编辑htm ...
- RS422接口与RS485接口
RS422具体接线参考网站 RS485接口 RS485设备为半双工设备,RS485收发器信号相关引脚包括控制引脚.485A.485B,其中控制引脚的高低电平决定当前处于接收模式还是发送模式. RS48 ...
- php 加解密函数
PHP 加密解密函数: /** * 系统加密方法 * @param string $data 要加密的字符串 * @param string $key 加密密钥 * @param int $expir ...
- java 多线程并发问题
问题:50个线程,先查询数据库的一个记录 t,然后对这个记录+1,最后更新到数据库 (更新的时候,不允许使用 update test_concurrent set sum =sum -1 where ...
- Go语言基础之rand(随机数)包
在Golang中,有两个包提供了rand,分别为 "math/rand" 和 "crypto/rand", 对应两种应用场景. "math/rand ...
- 【PAT甲级】1075 PAT Judge (25 分)
题意: 输入三个正整数N,K,M(N<=10000,K<=5,M<=100000),接着输入一行K个正整数表示该题满分,接着输入M行数据,每行包括学生的ID(五位整数1~N),题号和 ...
- Scrapy模拟登陆
1. 为什么需要模拟登陆? #获取cookie,能够爬取登陆后的页面 2. 回顾: requests是如何模拟登陆的? #1.直接携带cookies请求页面 #2.找接口发送post请求存储cooki ...
- 洛谷 P2239 螺旋矩阵(模拟 && 数学)
嗯... 题目链接:https://www.luogu.org/problem/P2239 这道题首先不能暴力建图,没有简单方法,只有进行进行找规律. AC代码: #include<cstdio ...
- 棍子Sticks(poj_1011)[经典搜索]
[题意描述] George用相同的长度棍子,将他们随机切成最多64个单位的长度,现在,他想回到原来的状态,但他忘了他原来的多少根,以及他们原本是多长.请帮助他和设计一个程序,计算最小的可能的原始长度. ...
- springweb 详解。
spring web架构图 从图中可以看出, 如果要对输出的内容进行重构,不需要视图的话,在handlerMethodReturnValueHandler里进行操作,可以重构这个对象,以达到自定义输出 ...