ArrayList解析
ArrayList
属性
// 默认长度
private static final int DEFAULT_CAPACITY = 10;
// 底层是以数组格式存储
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
transient Object[] elementData; // non-private to simplify nested class access
// 实际长度
private int size;
构造方法
ArrayList中提供了三个构造方法:
1、无参构造
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
此处注意其注释,说默认容量大小是10的空数组,其实这里只是给了一个空数组。在第一次添加元素的时候才将容量扩大为10的,如下:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
注意calculateCapacity方法:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity); // DEFAULT_CAPACITY==10
}
return minCapacity;
}
2、自定义长度的构造方法:
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
}
}
以上两个构造函数可以看出,如果在创建结合实例时就知道容量大小,就直接给出需要的大小,性能更高。
3、参数为Collection的构造函数:
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
这个构造函数稍微复杂一点,逻辑大致是先将集合转化为数组,然后通过反射的机制进行数组复制 。
常用方法
以下我们通过我们惯用的“增删改查”的逻辑来看ArrayList的常用方法。
增
1、boolean add(E e)
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
我们上面提到了,这个方法是像集合中添加元素的方法,返回值是boolean类型。
注意:这个方法添加的元素都是集合的最后一个。
以下来分析底层实现:
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
重点关注ensureExplicitCapacity方法中的grow方法:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
这个方法主要就是集合的扩容的底层方法。
注意,重点关注:
// 将集合扩充到原来的1.5倍。如果原来的容量是奇数n,则现扩充为n+(n-1)/2的长度。(这个也是与Vector的区别之一)。
int newCapacity = oldCapacity + (oldCapacity >> 1);
如果扩充1.5倍后不够用,则扩充为传入的长度;
如果传入的长度大于2^31-1-8,则调用hugeCapacity方法:
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
2、void add(int index, E element)
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
这个方法基本与上个方法类似(底层调用的是同一个方法),不同的是,它是将元素添加到对应的位置上,同时向右移动当前位于该位置的元素以及所有后续元素(将其索引加1)。
3、boolean addAll(Collection<? extends E> c)
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;
}
这个方法也不用多说,基本上看命名和参数就知道有什么用途,底层也类似。
4、boolean addAll(int index, Collection<? extends E> c)
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
删
我们将几个删除方法放在一起看看:
1、E remove(int index)
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);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
2、boolean remove
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
以上两个方法我们可以看出,集合的删除,基本上就是数组的(复制)操作,核心方法是System.arraycopy。我们看看该方法:
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
该方法是一个native方法,也即该方法与平台有关,非java语言实现。此处不做过多涉及。
3、boolean removeAll(Collection<?> c)
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
// Preserve behavioral compatibility with AbstractCollection,
// even if c.contains() throws.
if (r != size) {
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
if (w != size) {
// clear to let GC do its work
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
size = w;
modified = true;
}
}
return modified;
}
4、void clear()
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
一目了然,清空数组。
改
1、E set(int index, E element)
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
注意:这个方法是将原有位置的元素进行替换,长度不变。
查
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
其余方法
void sort(Comparator<? super E> c)
public void sort(Comparator<? super E> c) {
final int expectedModCount = modCount;
Arrays.sort((E[]) elementData, 0, size, c);
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
modCount++;
}
集合排序。
本质
通过以上的一些分析,我们基本上可以得出一个结论,也就是ArrayList的底层是数组,对ArrayList的操作,基本都是对数组的操作,归咎到数组的复制,数组元素的移动等。我们对于这种底层的认识很重要。
拓展
关于ArrayList和Vector区别如下:
- ArrayList在内存不够时默认是扩展50% + 1个,Vector是默认扩展1倍。
- Vector提供indexOf(obj, start)接口,ArrayList没有。
- Vector属于线程安全级别的,但是大多数情况下不使用Vector,因为线程安全需要更大的系统开销。
阅读源码的一些感悟
1、在《高效能程序员的修炼》一书中看到这样一句话,任何文档都比不上看源码,所有的本源都可以在源码中找到;
2、人类的智慧真的强大,而且在不断进步。就比如几个修改:
1)初始化
2)容量扩展计算改为了位移运算
3、读源码一定要深入进去,触类旁通,多多思考,多多对比。
ArrayList解析的更多相关文章
- 【Java集合系列一】ArrayList解析
一.基础简介 1.ArrayList继承关系 2.底层用数组来存储数据,数据会在ArrayList创建的时候一并初始化.如果创建ArrayList的时候,没有设置容量,则会delay到第一次add数据 ...
- 【源码解析】- ArrayList源码解析,绝对详细
ArrayList源码解析 简介 ArrayList是Java集合框架中非常常用的一种数据结构.继承自AbstractList,实现了List接口.底层基于数组来实现动态容量大小的控制,允许null值 ...
- Springboot 上传excel并解析文件内容
最近在做一个物业的系统,需要通过excel上传业主的信息,解析并入库. 参考:https://www.cnblogs.com/jyyjava/p/8074322.html 话不多说,直接上核心代码 i ...
- spring5 源码深度解析----- Spring事务 是怎么通过AOP实现的?(100%理解Spring事务)
此篇文章需要有SpringAOP基础,知道AOP底层原理可以更好的理解Spring的事务处理. 自定义标签 对于Spring中事务功能的代码分析,我们首先从配置文件开始人手,在配置文件中有这样一个配置 ...
- 通过swagger json一键解析为html页面、导出word和excel的解析算法分享
写在前面: 完全通过Spring Boot工程 Java代码,将swagger json 一键解析为html页面.导出word和execel的解析算法,不需要任何网上那些类似于“SwaggerMark ...
- 编写Java程序,使用 dom4j 解析上一节王者荣耀“英雄”对应的Xml文件数据内容,打印输出,具体格式
查看本章节 查看作业目录 需求说明: 使用 dom4j 解析上一节王者荣耀"英雄"对应的Xml文件数据内容,打印输出,具体格式如图所示 实现思路: 创建ParseHeroXML用于 ...
- List集合就这么简单【源码剖析】
前言 声明,本文用得是jdk1.8 前一篇已经讲了Collection的总览:Collection总览,介绍了一些基础知识. 现在这篇主要讲List集合的三个子类: ArrayList 底层数据结构是 ...
- 简单读!tomcat源码(一)启动与监听
tomcat 作为知名的web容器,很棒! 本文简单了从其应用命令开始拆解,让我们对他有清晰的了解,揭开神秘的面纱!(冗长的代码流水线,给你一目了然) 话分两头: 1. tomcat是如何启动的? 2 ...
- 【Java集合系列】目录
2017-07-29 13:49:40 一.Collection的全局继承关系 二.系列文章 [Java集合系列一]ArrayList解析 备注: 1.ArrayList本质上就是一个数组,所有对外提 ...
随机推荐
- Codeforces Round #392 (div.2) E:Broken Tree
orz一开始想不画图做这个题(然后脑袋就炸了,思维能力有待提高) 我的做法是动态规划+贪心+构造 首先把题目给的树变成一个可行的情况,同时weight最小 这个可以通过动态规划解决 dp[x]表示以x ...
- RDMA
什么是RDMA? 来源 https://blog.csdn.net/u011459120/article/details/78469098 1. 概述 RDMA是Remote Direct Memor ...
- 洛谷P3806 【模板】点分治1 【点分治】
题目背景 感谢hzwer的点分治互测. 题目描述 给定一棵有n个点的树 询问树上距离为k的点对是否存在. 输入输出格式 输入格式: n,m 接下来n-1条边a,b,c描述a到b有一条长度为c的路径 接 ...
- BEGIN TRAN;
USE master Create Database TestDb on Primary ( name='TestDb_data', filename='G:\TempData\Db\TestDb_d ...
- BZOJ day2_plus
大半夜的刷b站,好爽啊... 突破十九题 1008105110591088117911911192143218761951196821402242243824562463276128184720
- java一个接口可以继承另外一个接口吗
一个接口可以继承多个接口. interface C extends A, B {}是可以的. 一个类可以实现多个接口: class D implements A,B,C{} 但是一个类只能继承一个类, ...
- bzoj 4879 失控的数位板 4881 线段游戏 贪心,瞎搞
[Lydsy1705月赛]失控的数位板 Time Limit: 30 Sec Memory Limit: 256 MBSubmit: 148 Solved: 33[Submit][Status][ ...
- Python爬虫学习笔记之爬今日头条的街拍图片
代码: import requests import os from hashlib import md5 from urllib.parse import urlencode from multip ...
- RPC-Thrift(三)
TProtocol TProtocol定义了消息怎么进行序列化和反序列化的. TProtocol的类结构图如下: TBinaryProtocol:二进制编码格式: TCompactProtocol:高 ...
- 【UOJ131/NOI2015D2T2-品酒大会】sam求后缀树
题目链接:http://uoj.ac/problem/131 题意:给出一个字符串,第i个字符对应的值为a[i], 对于i∈[0,n),求最长公共前缀大于等于i的字串对个数,并求这些字符串对开头对应值 ...