Java 集合 - ArrayList
源码分析
属性
// 默认的初始化容量 private static final int DEFAULT_CAPACITY = 10; // 用于无参构造中初始化一个空数组 private static final Object[] EMPTY_ELEMENTDATA = {}; // ArrayList 是动态数组实现的,我们操作ArrayList 实际就是操作的这个对象 private transient Object[] elementData; // 数组长度 private int size;
构造方法
// 无参构造,初始化一个没有元素的数组 public ArrayList() { super(); this.elementData = EMPTY_ELEMENTDATA; } // 自定义初始化容量 public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[initialCapacity]; } // 创建一个包含 collection 的数组 public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); size = elementData.length; if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); }
增
public boolean add(E e):在数组末尾增加一个元素
public boolean add(E e) { ensureCapacityInternal(size + 1); elementData[size++] = e; return true; }
扩充容量,设置尾部元素,数组长度自增。
返回值为 true。
public void add(int index, E element):在 index 位置插入元素
public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacityInternal(size + 1); System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; }
1). 越界检测和容量扩充
2). index 位置之后的元素全部往后挪动一个单位
3). 为 index 位置填入元素,数组长度 +1
public 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; }
简单不解释。只要数组改变就返回 true。
public boolean addAll(int index, Collection<? extends E> c):在 index 位置插入一组元素
public boolean addAll(int index, Collection<? extends E> c) { rangeCheckForAdd(index); Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); 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; }
类似 add(int index, E element)。只要数组有改变就返回 true。
删
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; }
1). 进行越界检查,修改 modCount
2). 将移除位置之后的元素全部往前挪动一个位置
3). 将最后一个位置的元素置为空同时将数组长度减一
4). 返回的对象是删除的元素
boolean remove(Object o):删除此元素
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; }
1). 若此元素为空,顺序遍历数组,找到第一个也为空的元素并且删除,返回 true
2). 若此元素不为空,顺序遍历数组,找到相等的元素并且删除,返回 true
3). 若是找不到就会返回 false
4). 删除元素使用的方法是 faseRemove 如下:基本和 remove(int index) 一样的
private void fastRemove(int index):和 remove(int index) 一样
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 }
public boolean removeAll(Collection<?> c):删除所有和 c 中元素相同的元素
public boolean removeAll(Collection<?> c) { return batchRemove(c, false); }
调用 batchRemove 方法。遍历判断 c 是否包含元素。若包含,则不理会。若不包含就会依次存入该数组中。
返回值:只要集合有改变,就会返回 true。
下面的 retainAll 方法参考 removeAll。基本相同。
public boolean retainAll(Collection<?> c):删除所有和 c 中元素不同的元素
public boolean retainAll(Collection<?> c) { return batchRemove(c, true); }
private boolean batchRemove(Collection<?> c, boolean complement):设计的挺巧妙的,一个方法多用。
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 { if (r != size) { System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } if (w != size) { for (int i = w; i < size; i++) elementData[i] = null; modCount += size - w; size = w; modified = true; } } return modified; }
如果 w 不等于 size,说明此数组没有填充完。所以从 w 位置开始后面的都置为空。修改数组大小。
protected void removeRange(int fromIndex, int toIndex):删除两个位置之间的元素
protected void removeRange(int fromIndex, int toIndex) { modCount++; int numMoved = size - toIndex; System.arraycopy(elementData, toIndex, elementData, fromIndex, numMoved); int newSize = size - (toIndex-fromIndex); for (int i = newSize; i < size; i++) { elementData[i] = null; } size = newSize; }
1). 修改 modCount,toIndex 之后的所有元素都向前挪动 numMoved 个单位
2). 最后面的 numMoved 个单位置为空,修改数组长度
public void clear():清空数组
public void clear() { modCount++; for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }
所有位置的元素都置为空,数组长度改为 0。
改
public E set(int index, E element):替换掉 index 位置的元素
public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; }
越界检测,获得 index 的旧元素并且设置上新元素。
返回旧元素。
查
public E get(int index)
public E get(int index) { rangeCheck(index); return elementData(index); }
越界检测,返回 index 位置的元素。
调整数组容量
// 扩充容量时会出现 size 很小而 length 很大的情况。出现空间浪费。使用这个方法将返回新的数组。size 和 length 相等。节省空间 public void trimToSize() { modCount++; if (size < elementData.length) { elementData = Arrays.copyOf(elementData, size); } } public void ensureCapacity(int minCapacity) { int minExpand = (elementData != EMPTY_ELEMENTDATA)? 0 : DEFAULT_CAPACITY; if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } } private void ensureCapacityInternal(int minCapacity) { if (elementData == EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; if (minCapacity - elementData.length > 0) // 判断如果容量不足的时候再去扩充 grow(minCapacity); } // 新容量将至少为旧容量的约 150% // 扩容后的容量和自定义容量相比去较大值,再和最大容量相比去较小值。 private void grow(int minCapacity) { int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
其他方法
public Object clone():克隆 ArrayList
public Object clone() { try { ArrayList<E> v = (ArrayList<E>) super.clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { throw new InternalError(); } }
并没有克隆集合内的元素。
public boolean contains(Object o):判断集合是否包含元素 o
public boolean contains(Object o) { return indexOf(o) >= 0; }
public int indexOf(Object o) { if (o == null) { for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; } return -1; } 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; }
利用 indexof 定位元素。若能定位到就说明包含,否则就是不包含。
其他:
// 数组长度 public int size() { return size; } // 判断是否为空 public boolean isEmpty() { return size == 0; } // 集合转化为数组 public Object[] toArray() { return Arrays.copyOf(elementData, size); } public <T> T[] toArray(T[] a) { if (a.length < size) return (T[]) Arrays.copyOf(elementData, size, a.getClass()); System.arraycopy(elementData, 0, a, 0, size); if (a.length > size) a[size] = null; return a; }
总结
1). 基于数组实现,是一个动态数组,增加元素的时候会自行扩容。
ArrayList 扩容后容量为旧容量的约 1.5 倍。而 Vector 扩容要看增长因子是否大于0,若大于0就增加增长因子这么大容量,否则扩容为旧容量的两倍。
private void grow(int minCapacity) { int oldCapacity = elementData.length; int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); // Vector 扩容 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); }
2). 增加(插入)元素的时候,总是需要先将插入位置之后的元素往后挪动一个元素,再进行插入操作。同理删除需要往前挪。和 LinkedList 相比效率低。
3). 修改元素和删除元素可以直接利用下标进行操作。比 LinkedList 效率高。
4). 允许元素为 NULL。
5). 非同步非线程安全。而 Vector 是同步的线程安全的。线程同步必定会对性能造成影响,所以 ArrayList 要比 Vector 性能好一些。
6). fase-fail 机制:迭代器在面临并发修改时候,能够马上反馈失败,避免将来不确定的时间发生不确定行为的风险。
private class Itr implements Iterator<E> { int cursor; int lastRet = -1; int expectedModCount = modCount; // 赋值 public boolean hasNext() { return cursor != size; } public E next() { checkForComodification(); // 迭代过程中进行判断 int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; } public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); // 迭代过程中进行判断 try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
迭代器在初始化过程中会把 modCount 赋值给 exceptedModCount。在迭代器迭代的过程中如果发现这两个值不一样,就说明另外有线程对集合进行了修改,马上抛出异常。
Java 集合 - ArrayList的更多相关文章
- Java 集合 ArrayList和LinkedList的几种循环遍历方式及性能对比分析 [ 转载 ]
Java 集合 ArrayList和LinkedList的几种循环遍历方式及性能对比分析 @author Trinea 原文链接:http://www.trinea.cn/android/arrayl ...
- Java基础系列 - JAVA集合ArrayList,Vector,HashMap,HashTable等使用
package com.test4; import java.util.*; /** * JAVA集合ArrayList,Vector,HashMap,HashTable等使用 */ public c ...
- Java集合---ArrayList的实现原理
目录: 一. ArrayList概述 二. ArrayList的实现 1) 私有属性 2) 构造方法 3) 元素存储 4) 元素读取 5) 元素删除 6) 调整数组容量 ...
- Java集合 -- ArrayList集合及应用
JAVA集合 对象数组 集合类之ArrayList 学生管理系统 斗地主案例 NO.one 对象数组 1.1 对象数组描述 A:基本类型的数组:存储的元素为基本类型 int[] arr={1,2,3, ...
- Java集合--ArrayList出现同步问题的原因
1 fail-fast简介 fail-fast 机制是java集合(Collection)中的一种错误机制.当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件.例如:当某一个线 ...
- java集合-- arraylist小员工项目
import java.io.*; import java.util.ArrayList; public class Emexe { public static void main(String[] ...
- Java集合ArrayList的应用
/** * * @author Administrator * 功能:Java集合类ArrayList的使用 */ package com.test; import java.io.BufferedR ...
- java集合-ArrayList
一.ArrayList 概述 ArrayList 是实现 List 接口的动态数组,所谓动态就是它的大小是可变的.实现了所有可选列表操作,并允许包括 null 在内的所有元素.除了实现 List 接口 ...
- Java集合ArrayList源码解读
最近在回顾数据结构,想到JDK这样好的代码资源不利用有点可惜,这是第一篇,花了心思.篇幅有点长,希望想看的朋友认真看下去,提出宝贵的意见. :) 内部原理 ArrayList 的3个字段 priva ...
- Java集合-ArrayList源码解析-JDK1.8
◆ ArrayList简介 ◆ ArrayList 是一个数组队列,相当于 动态数组.与Java中的数组相比,它的容量能动态增长.它继承于AbstractList,实现了List, RandomAcc ...
随机推荐
- mac下docker使用笔记
安装docker https://docs.docker.com/mac/ 启动docker环境launchpad -> Docker Quickstart Terminal ## ...
- mysqli_multi_query($link, $sql_w);
$sql_w = 'INSERT INTO w1 (wint) VALUES (55);'; $sql_w .= 'INSERT INTO w1 (wint) VALUES (505);'; var_ ...
- NPOI操作excel之读取excel数据
NPOI 是 POI 项目的 .NET 版本.POI是一个开源的Java读写Excel.WORD等微软OLE2组件文档的项目. 一.下载引用 去NPOI官网http://npoi.codeplex. ...
- 安装docker-compose
下载到合适的位置 curl -L https://github.com/docker/compose/releases/download/1.8.0/docker-compose-`uname -s` ...
- [Android Tips] 16. Update Android SDK from command-line
$ cd $ANROID_HOME $ tools/android update sdk -u -s 参数 -s --no-https : Uses HTTP instead of HTTPS (th ...
- 【Selenium】3.介绍Selenium IDE
本文供学习交流之用,没有商业用途,没有盈利. 完全是我自己为督促自己学习而翻译的.翻译的不好,见谅.来源于:http://www.guru99.com/introduction-selenuim-id ...
- Ajax请求中的async:false/true的作用
async: false,(默认是true);false为同步,Ajax请求将整个浏览器锁死,只有tet.php执行结束后,才可以执行其它操作. 当async: true 时,ajax请求是异步的.但 ...
- 获取访问者ip的方法
package com.mi.util; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.S ...
- SQLServer 使用smb存放数据文件
安装smb: 服务器管理器->角色->文件服务 1.配置smb共享时,更改NTFS权限,需要将SQLServer启动域帐户加入,读.写.完全控制等权限 2.实例启动用户需要使用域帐户 3. ...
- 《zw版·Halcon-delphi系列原创教程》 只有2行代码的超市收款单ocr脚本
<zw版·Halcon-delphi系列原创教程> 只有2行代码的超市收款单ocr脚本只有2行代码的超市收款单ocr脚本 发了这么多教程,有网友问,为什么没有ocr的. 的确,在 ...