说明

此文章分两部分,1.ArrayList用法。2.源码分析。先用法后分析是为了以后忘了查阅起来方便~~

ArrayList 基本用法

1.创建ArrayList对象

//创建默认容量的数组列表(默认为10)
ArrayList<E> a = new ArrayList<E>(); //创建容量为initialCapacity的数组列表
ArrayList<E> a = new ArrayList<E>(int initialCapacity); //用一个集合来初始化数值列表
ArrayList<E> a = new ArrayList<E>(Collection<? extends E> c);

Mark:

CapacitySize 时两个不同的概念:
Size: ArrayList包含的元素个数。
capacity : ArrayList的容量(默认为10)。它是一个大于或等于size的值。当它的值小于size是就会对ArrayList进行扩容实现可变长数组。简单来说,capacity就是为了实现可变长数组而设计的。 (具体分析请看下面的源码分析)

2.ArrayList方法

向ArrayList添加元素

/***********a是上面创建的ArrayList对象***************/

//添加一个元素
a.add(E e); //在特定的位置添加元素,index后面的元素全部向后移一位
a.add(int index, E e); //在ArrayList 后面 添加集合
a.addAll(Collection<? extends E> c); //从特定的位置添加集合
a.addAll(int index, Collection<? extends E> c);

获取ArrayList的元素

//获取特定索引的元素
a.get(int index);

Mark:

遍历ArrayList: 一般使用for-each
for(E e : a)
   dosomething;

用for-each的主要优势:
相对传统的for循环,for-each更简洁,不容易出错,而且没有性能的损失

修改(更新)ArrayList的元素

//用e替换换index位置的元素
a.set(int index, E e);

Mark:

注意: 区分set与add的用法
add 是添加一个新的元素,要开辟新的空间来保存元素
set 是修改特定位置的元素,index位置必须要已经存在元素,否则会越界

删除ArrayList的元素

//删除索引为index的元素,后面的元素全都向前移一位
a.remove(int index); /*删除在列表中和obj相等的第一个元素(首次出现),
* 就做动作,保持列表原来的状态
* 比如:a [1, 3, 7, 5, 7],执行a.remove((Integer)7)后
* 结果为:[1, 3, 5, 7]
*/
a.remove(Object obj); //删除与集合c中元素相等的元素
a.removeAll(Collection<?> c); //保留与集合c中元素相等的元素,然后把其他元素删除(与removeAll相反)
a.retainAll(Collection<?> c); //删除ArrayList的所有元素,没有removeAll();方法
a.clear();

ArrayList的其他一些比较常用的方法

//获取ArrayList的元素个数
a.size(); //判断ArrayList是否包含某个元素
a.contains(Object o) //判断ArrayList是否是空列表([]),是就返回true
a.isEmpty(); //获取ArrayList的子列表,(字串包含索引为fromIndex的元素,不包含toIndex元素)
a.subList(int fromIndex, int toIndex); //返回ArrayList的基本数组形式
a.toArray(); /**ArrayList克隆,调用方法后,会返回a的一份复制。它与普通的复制(=)不同
*赋值后,两个引用指向同一个对象,会相互影响
*从ArrayList源码可知,是开辟新的空间来新建一个对象,再把数据复制到新建的对象。
*所以,clone后会形成两个不同的对象,因此不会相互影响。
*/
a.clone();

Mark:

a.remove(Object obj): 如果a是Integer的泛型列表在支持remove对象时要强制转换为Ingeger或者Object类型(参考上面例子),如果直接执行a.remove(7),就会调用 a.remove(int index)方法。


ArrayList源码分析

首先,看看ArrayList定义的属性

private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
transient Object[] elementData;
private int size;

DEFAULT_CAPACITY就是上面提到的默认容量大小,从属性定义可知,容量的默认大小为10。

EMPTY_ELEMENTDATA一个空的数组对象,主要用来与elementData做比较,elementData是否为空

elementData最核心的一个属性。它是ArrayList的庐山真面目,ArrayList之所以是可变长的,主要是ArrayList内部有elementData这个东东在搞鬼~~。 ArrayList可变长原理:当size大于capacity时,ArrayList就会调用扩容函数新建一个容量更大的elementData数组来代替原来的数组来实现扩容。
总的来说,ArrayList的可变长功能主要时围绕着capacity与elementData来实现的

实现可变长数组的主要方法:

public void ensureCapacityInternal(int minCapacity)
private void ensureExplicitCapacity(int minCapacity)
private void grow(int minCapacity) //核心方法

ensureCapacityInternal解析

/**
* 此函数大概就叫内部容量确定函数,主要是在每次添加元素这些增大size的
* 动作里调用,用来判断需不需要增加capacity大小实现动态分配
*/
private void ensureCapacityInternal(int minCapacity) {
/**
*这个判断只要是针对使用new ArrayList<E>()构造对象设定,以免频繁的扩容。
*如果if判断为真,那么就是使用new ArrayList<E>()构造对象的.
*此刻就要选一个最大的容量作为期待的最小容量,避免频繁扩容
*详细解析看下面的例子!!
*/
if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
} //调用最终确认方法,需不需要扩容由此方法决定
ensureExplicitCapacity(minCapacity);
}

if (elementData == EMPTY_ELEMENTDATA)作用解析

首先,我们新建一个类(我的为MyList),把ArrayList的源码复制过来方便为所欲为~~,然后把一些报错除去。最后我们修改下代码:

// 把ensureCapacityInternal的if语句注释掉,
/*if (elementData == EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}*/
在**grow**方法里添加一个输出语句
System.out.println("-----invoke grow()------------");

写我们的测试类:

  public class Test {
public static void main(String[] args) { MyList<Integer> i = new MyList<Integer>();
MyList<Integer> i2 = new MyList<Integer>(10); System.out.println("------使用new MyList<Integer>()------ ");
i.add(1);
i.add(1);
i.add(1);
i.add(1); System.out.println("------使用new MyList<Integer>(10)------ ");
i2.add(1);
i2.add(1);
i2.add(1);
i2.add(1);
}

}
输出:

———使用new MyList()———
——-invoke grow()——————
——-invoke grow()——————
——-invoke grow()——————
——-invoke grow()——————
———使用new MyList(10)———

可以看出,如果不是使用if判断,使用new ArrayList()方法每次添加元素都会调用grow,而每次调用grow都会生成一个新的数组。所以为了效率内存考虑要添加if判断。

ensureExplicitCapacity方法

/**
*此代码功能很简单,只是进行一个简单的判断,看是否需要扩容
*如果需要的容量比实际的容量大就进行扩容
*/
private void ensureExplicitCapacity(int minCapacity) {
modCount++; if (minCapacity - elementData.length > 0)
grow(minCapacity);
}

grow(核心方法)

 /**
*扩容的具体动作
*/
private void grow(int minCapacity) { int oldCapacity = elementData.length; //定义扩容后的容量大小为原来容量的3/2
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果扩容后的容量还是比期待的容量小,那么使用minCapacity为扩容后的容量
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity); /*这里实现了动态数组的特性。
*跟踪copyOf源码可以发现,实现方法:新建一个大小为newCapacity数组,
*然后把elementData之前的元素复制到新的数组,
*最后把新数组返回f赋值给elementData以实现动态数组
*/
elementData = Arrays.copyOf(elementData, newCapacity);
}

总结:

根据源码分析,可以知道:

  1. ArrayList本质上是一个数组。只不过是数组上包装了一层华丽的外套而已。
  2. ArrayList动态数组功能主要思想是,每当数组的空间不够时,ArrayList会自动把缓冲区(elementData,我们叫他做缓冲区把~~)的数据复制到一个新的缓冲区里,(大小一般为原来的3/2),并令它覆盖原来的缓冲区成为自己的缓冲区。

常用方法分析

写到这发现文章太长了,所以决定常用方法就写几个算了,不全写了。常用方法的源码都很简单。大多是对elementData这个数组操作返回而已。

add方法

public boolean add(E e) {
//此方法上面已介绍
ensureCapacityInternal(size + 1);
//操作elementData,把e添加到数组里
elementData[size++] = e;
return true;
}

set方法

public E set(int index, E element) {
//检查是否越界,该函数代码很简单,就是简单判断index与size大小。
rangeCheck(index); //以下代码跟add对比下就知道他们的区别
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}

总的来说,ArrayList的源码不算难,主要部分实现动态数组那部分,其他的方法主要还是在操作elementData数组。感觉ArrayList是一个很常用的数据结构,看懂源码对以后使用ArrayList肯定有不少帮助,而且解了ArrayList用起来也踏实:-D。

ArrayList用法详解与源码分析的更多相关文章

  1. Android应用AsyncTask处理机制详解及源码分析

    1 背景 Android异步处理机制一直都是Android的一个核心,也是应用工程师面试的一个知识点.前面我们分析了Handler异步机制原理(不了解的可以阅读我的<Android异步消息处理机 ...

  2. Java SPI机制实战详解及源码分析

    背景介绍 提起SPI机制,可能很多人不太熟悉,它是由JDK直接提供的,全称为:Service Provider Interface.而在平时的使用过程中也很少遇到,但如果你阅读一些框架的源码时,会发现 ...

  3. Spring Boot启动命令参数详解及源码分析

    使用过Spring Boot,我们都知道通过java -jar可以快速启动Spring Boot项目.同时,也可以通过在执行jar -jar时传递参数来进行配置.本文带大家系统的了解一下Spring ...

  4. 【转载】Android应用AsyncTask处理机制详解及源码分析

    [工匠若水 http://blog.csdn.net/yanbober 转载烦请注明出处,尊重分享成果] 1 背景 Android异步处理机制一直都是Android的一个核心,也是应用工程师面试的一个 ...

  5. 线程池底层原理详解与源码分析(补充部分---ScheduledThreadPoolExecutor类分析)

    [1]前言 本篇幅是对 线程池底层原理详解与源码分析  的补充,默认你已经看完了上一篇对ThreadPoolExecutor类有了足够的了解. [2]ScheduledThreadPoolExecut ...

  6. SpringMVC异常处理机制详解[附带源码分析]

    目录 前言 重要接口和类介绍 HandlerExceptionResolver接口 AbstractHandlerExceptionResolver抽象类 AbstractHandlerMethodE ...

  7. 2020了你还不会Java8新特性?(五)收集器比较器用法详解及源码剖析

    收集器用法详解与多级分组和分区 为什么在collectors类中定义一个静态内部类? static class CollectorImpl<T, A, R> implements Coll ...

  8. Hadoop RCFile存储格式详解(源码分析、代码示例)

    RCFile   RCFile全称Record Columnar File,列式记录文件,是一种类似于SequenceFile的键值对(Key/Value Pairs)数据文件.   关键词:Reco ...

  9. [转]SpringMVC拦截器详解[附带源码分析]

      目录 前言 重要接口及类介绍 源码分析 拦截器的配置 编写自定义的拦截器 总结 前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:ht ...

随机推荐

  1. SpringMVC关于json、xml自动转换的原理研究[附带源码分析](使用JAXB转换XML)

    前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/spring ...

  2. Rhythmk 一步一步学 JAVA (10): Freemarker 学习 1 - 入门

    FreeMarker 笔记: 1. 注释:   <#-- 注释内容 -#> 2.<#if condition> content1 <#else> content2 ...

  3. views获取数据 -- request包含的方法

    request.GET request.POST request.FILES request.path_info request.xxx.getlist request.method request. ...

  4. vagrant+docker搭建consul集群开发环境

    HashiCorp 公司推出的Consul是一款分布式高可用服务治理与服务配置的工具.关于其配置与使用可以参考这篇文章 consul 简介与配置说明. 一般,我们会在多台主机上安装并启动 consul ...

  5. Linux虚机密码破解

    1 重启机器,在机器读秒时按回车键 2 选择要启动的操作系统按 e 3 选择kernel所在行按 e 4 末尾输入空格  single 5 敲回车 在按 b 系统将进入单用户模式 然后 可以 通过 p ...

  6. 【原】Coursera—Andrew Ng机器学习—Week 11 习题—Photo OCR

    [1]机器学习管道 [2]滑动窗口 Answer:C ((200-20)/4)2 = 2025 [3]人工数据 [4]标记数据 Answer:B (10000-1000)*10 /(8*60*60) ...

  7. tomcat的配置文件server.conf中的元素的理解

    tomcat的配置文件server.conf中的元素的理解 tomcat作为一个servlet服务器本身的配置文件是tomcat_home/conf/server.conf,这个配置文件中有很多元素, ...

  8. etcd raft library

    https://github.com/coreos/etcd/tree/master/raft import "github.com/coreos/etcd/raft" ----- ...

  9. java 蓝桥杯算法提高 出现次数最多的整数

    思路:其实这道题不是太难,但是这个题太坑了,提交了好多次都不是100,后来才知道,一定一定要在输入数组数据之前先判断一下输进去的n的范围,一定一定要注意,否则就是跟我下面的图片一样的效果了,泪奔~ 问 ...

  10. 安装运行okvis odometry

    源码链接https://github.com/ethz-asl/okvis 1. 安装依赖项 sudo apt-get install cmake sudo apt-get install libgo ...