1. ArrayList的基本实现原理

ArrayLiST其内部用一个普通数组来存储数据,当此数组不够容纳新添加的元素的时候,则创建一个更大长度的新数组,并将原来数组中的元素复制到新数组中。

2.ArrayList主要的全局变量/常量

除了其他一般的全局变量之外,还有一个继承于父类的 modCount属性,它用来记录集合结构被修改的次数,主要应用在迭代过程中确认没有删除或添加元素的操作,防止出现重复遍历或遍历遗漏错误。
    // 用来储存集合元素的数组
transient Object[] elementData;
//不通过构造方法指定容量时的默认容量(向ArrayList第一次添加元素时,数组elementData的长度)
private static final int DEFAULT_CAPACITY = 10;
/*
* 空数组,如果使用new ArrayList(0)构造方法而不向里面添加元素,
* 所有这类型的ArrayList对象内部的elementData都会引用这一个静态常量。
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
// 集合包含的元素个数
private int size; /*修改记数器,ArrayList中不含此属性,它来自于父类AbstractList
* 当动态数组的结构(添加或删除元素)发生改变时,modCount加1
*/
protected transient int modCount = 0; /*
* 空数组,如果使用new ArrayList()构造方法而不向里面添加元素,
* 所有这类型的ArrayList对象内部的elementData都会引用这一个静态常量
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

3.ArrayList构造方法

1)指定初始容量的构造方法

    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);
}
}

2)无参构造方法,不指定初始容量,使用默认的初始容量DEFAULT_CAPACITY

   public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

3)带集合参数的参构造方法,初始化后将含有集合参数对应的所有元素

    public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();//将集合转为数组
if ((size = elementData.length) != 0) { //构造参数集合c含有元素时
/*
* 构造参数集合c内部数组不是Object[],则将其转为Object[]。
* 子类型向父类型转换,是安全的转换操作
*/
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//构造参数集合c不含元素时
this.elementData = EMPTY_ELEMENTDATA;
}
}

4.ArrayList主要的公共方法

1)public boolean add(E e) 在尾部添加元素

先用ensureCapacityInternal(int)方法判断,当前elementData是否能够容纳新元素,若是不能将扩展数组长度,

然后再进行将新元素添加到elementData数组的下标为size的位置。添加元素后,集合元素个数多了一个,最后将size加1。

      public boolean add(E e) {
//先检查容量
ensureCapacityInternal(size + 1);
//内部数组elementData的赋值
elementData[size++] = e;
return true;
}

来看ensureCapacityInternal(int)方法的实现

       private void ensureCapacityInternal(int minCapacity) {
// 如果是空数组,则找minCapacity 、DEFAULT_CAPACITY中较大的那个数作为最小容量
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//确保显式容量
ensureExplicitCapacity(minCapacity);
}

再看ensureExplicitCapacity(minCapacity)  方法的实现。

        private void ensureExplicitCapacity(int minCapacity) {
modCount++; // 调用当前方法表明,一定会添加一个元素,那么让modCount加1 /*
* 当数组长度确实不满足当前所需的最小容量,必须进行扩容处理。
* 直到这步才进行真正的数组扩容。扩容处理比较耗费资源,
* 最好一开始指定大致的容量个数,而不至于反复扩容。
*/
if (minCapacity - elementData.length > 0)//
grow(minCapacity);
}

再探真正执行数组扩容处理的方法 grow(int)

      private void grow(int minCapacity) {
int oldCapacity = elementData.length;
/*
* 新容量(即数组elements的长度),是原容量的1.5倍。
* "oldCapacity >> 1"表示是oldCapacity右移一位,结果是oldCapacity的1/2
*/
int newCapacity = oldCapacity + (oldCapacity >> 1);
/*
* 将容量扩大至原容量的1.5倍还不够时
* 就将minCapacity作为新容量
*/
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
/*
* 若数组长度扩展超出int最大的可表示范围(MAX_ARRAY_SIZE= Integer.MAX_VALUE - 8)
* hugeCapacity(int)方法的主要代码是
* "(minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE"
* 即取两者最接近minCapacity的那个值,以保证数组长度尽可能小
*/
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//将原数组的元素复制到更大长度的新数组上,并让elementData引用这个新数组。
elementData = Arrays.copyOf(elementData, newCapacity);
}

2)public void add(int index, E element) 在指定下标处添加元素

        public void add(int index, E element) {
rangeCheckForAdd(index);//检查下标是否越界 ensureCapacityInternal(size + 1); //同样地检查容量是否够
//将从index下标后(包括index)的所有元素整体向后移动一个位置
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//将原来index位置上的元素引用替换为新添加的元素引用
elementData[index] = element;
size++;
}

3) public E remove(int index)  移除指定位置的元素

        public E remove(int index) {
rangeCheck(index);//检查下标是否越界
modCount++; //// 移除一个元素,modCount加一;
E oldValue = elementData(index); //保存原来index位置上的元素 int numMoved = size - index - 1; //计算需要移动的元素个数
if (numMoved > 0)
//将从index+1下标后(包括index+1)的所有元素整体向前移动一位
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
/*
* 对于最后一个元素而言它后而已经没有元素了,也就是说没有元素能将最后一个元素覆盖,
* 所以要将最后一个元素赋为null,让垃圾回收器去回收它,否则可能造成内存泄漏。
*/
elementData[--size] = null; return oldValue;
}

4) public E 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 {
/*
* 从头至尾进行遍历,对象o与循环集合时的当前元素相等
* 则可移除当前索引位置的元素,将退出循环,返回true。
* 若遍历了所有元素都没有一个元素与o相等,则集合中没有这个元素,
* 那么移除指定元素失败,返回false
*/
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
/*
* 与remove(int)类似,fastRemove(int)只是少去了边界检查、老值保存操作,
* 能更快地移除元素
*/
fastRemove(index);
return true;
}
}
return false;
}

 5)public E set(int index, E element)  替换指定下标对应的元素

public E set(int index, E element) {
rangeCheck(index); //下标边界检查
E oldValue = elementData(index); //保存index下标处的原值
elementData[index] = element;//将新元素的赋值给index下标元素
return oldValue;
}

6)public  E get(int index)  获取指定下标对应的元素

    public E get(int index) {
rangeCheck(index);
return elementData(index);
}

这里只罗列了6种广泛使用的API,其他的API大多基于这几个方法,或是思路大致相同,我就不一 一列举了。

5.自定义的简单动态数组

package list;

import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException; public class DynamicArray<E> implements Iterable<E> {
private Object[] elements;// 储存元素的数组
private int size = 0;// 当前动态数组中所含元素个数
private static final int DEFAULT_CAPACITY = 16;// 默认的初始容量
/*
* 空数组,如果只创建DynamicArray动态数组而不向里面添加元素,
* 所有这类型的DynamicArray对象内部都会引用这一个静态常量。
* 这时运用了flyweight享元模式,共享一个对象,节省内存空间
*/
private static final Object[] EMPTY_ELEMENTS = {}; public boolean isEmpty() {
return size == 0 ;
} public DynamicArray() {
elements = new Object[DEFAULT_CAPACITY];
} public DynamicArray(int capacity) {
if (capacity > 0) {
elements = new Object[capacity];
} else if (capacity == 0) {
elements = EMPTY_ELEMENTS;
} else {
throw new IllegalArgumentException("DynamicArray的初始容量不能小于0");
}
} //修改记数器,当动态数组的结构(添加或删除元素)发生改变时,modCount加1
private int modCount = 0; public int size() {
return size;
} public boolean add(E e) { modCount++;// 添加一个元素,modCount加1
ensureCapacity(size + 1);// 添加元素前先检查数组elements的容量
elements[size] = e; // 索引和长度相差1,所以不是elements[size+1]=e
size++;
return true;
} public E set(int index, E e) {
checkIndex(index);
@SuppressWarnings("unchecked")
E oldValue = (E) elements[index];
elements[index] = e;
return oldValue;
} public boolean insert(int index, E e) {
checkIndex(index); ensureCapacity(size + 1);
int movedNum = size - index;
System.arraycopy(elements, index, elements, index + 1, movedNum);
modCount++;// 添加一个元素,modCount加1
elements[index] = e;
size++;
return true;
} // 确保最小容量
private void ensureCapacity(int minCapacity) {
// 如果是空数组,则找minCapacity 、DEFAULT_CAPACITY中较大的那个数作为最小容量
if (elements == EMPTY_ELEMENTS) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
// 数组elements的长度不够,必须扩展原数组
if (elements.length < minCapacity) {
expand(minCapacity); } } // 容量扩展
private void expand(int minCapacity) { int oldCapacity = elements.length;
// 确保minCapacity大于内部数组elements的长度
if (oldCapacity >= minCapacity) {
return;
}
/*
* 期望的容量(即数组elements的长度),是原容量的1.5倍,
* "oldCapacity >> 1"是右移一位, 值是oldCapacity的1/2
*/
int desireCapacity = oldCapacity + oldCapacity >> 1;
/*
* 扩展后实际的容量 若将新容量扩大至原容量的1.5倍还不够时,就将minCapacity作为新容量
*/
int newCapacity = desireCapacity > minCapacity ? desireCapacity : minCapacity; elements = Arrays.copyOf(elements, newCapacity);
} public E remove(int index) {
checkIndex(index);// 检查下标 modCount++;// 移除一个元素,modCount加一;
@SuppressWarnings("unchecked")
E removedElement = (E) elements[index]; int movedNum = size - 1 - index;// 要移动的元素个数
if (movedNum > 0)
System.arraycopy(elements, index + 1, elements, index, movedNum); //删除元素成功后,最后一个元素赋空,垃圾回收,集合元素个数减1.
elements[--size] = null;
return removedElement;
} @SuppressWarnings("unchecked")
public E get(int index) {
checkIndex(index);// 检查下标
return (E) elements[index];
} public boolean remove(E e) {
int index = -1;
//如果有一个元素和准备移除的对象相等,则将此元素的索引赋为index,并退出循环
for (int i = 0; i < size; i++) {
@SuppressWarnings("unchecked")
E curElement = (E) elements[i];
if ((curElement == e) || (curElement != null && curElement.equals(e))) {
index = i;
break;
}
}
//index为-1,表明没有要移除的这个元素
if (index == -1) {
return false;
} else {
remove(index);
return true;
}
} private void checkIndex(int index) {
if (index < 0 || index >= size) {
throw new IndexOutOfBoundsException(indexOutOfBoundsTip(index));
} } private String indexOutOfBoundsTip(int index) {
return "Index [" + index + "] , Size [" + size + "]";
} @Override
public Iterator<E> iterator() { return new SimpleIterator();
} /**
* 迭代器内部类
* @author Administrator
*
*/
private class SimpleIterator implements Iterator<E> {
private int desireModCount = modCount;// 预期的的修改次数
private int nextIndex = 0;// 预期情况下的下个元素的索引
private int lastIndex = -1;// 实际情况下的下个元素索引 @Override
public boolean hasNext() {
if (nextIndex < size) {
return true;
}
return false;
} @SuppressWarnings("unchecked")
@Override
public E next() {
if (nextIndex >= size) {
throw new NoSuchElementException("迭代到尾了,没有其它元素了");
}
checkModCount();
lastIndex = nextIndex;// 预期的索引和实际的索引一致
nextIndex++;// 预期的下个索引自加1
return (E) elements[lastIndex];
} @Override
public void remove() {
/*
* 在迭代过程中连续多次移除元素,将触发这个异常
* eg:下而的代码将抛出异常
* iterator.remove();
* iterator.remove();
*/
if (lastIndex == -1) {
throw new IllegalStateException("在迭代过程中不能多次移除同一个元素");
}
checkModCount();
DynamicArray.this.remove(lastIndex); // 调用外围类的元素移除方法
/*
* DynamicArray.remove(index )方法会改变成员变量的modCount的值
* 重新赋值给SimpleIterator的成员变量desireModCount,
* 使desireModCount和 modCount再次相等
*/
desireModCount = modCount;
/*
* 当前元素被移除后,后一个元素将顶替当前元素的位置
* 所以下次遍历的索引下标还是当前迭代位置
*/
nextIndex = lastIndex;
/*
* 每次移除元素都将lastIndex的值重新赋为-1.
* 可用来检测是否在迭代器中连续执行remove()的非法操作
*/
lastIndex = -1;
} /*
* 校验在迭代过程中,是否使用了非迭代的方式改变了当前动态数组的结构(添加或删除)
*/
private void checkModCount() {
if (desireModCount != modCount)
throw new IllegalStateException("迭代过程中DynamicArray对象的结构被改变了");
} } public static void main(String[] args) {
DynamicArray<String> dyArray = new DynamicArray<String>();
dyArray.add("34");
dyArray.add("45");
dyArray.add("啥子");
dyArray.add("哈");
dyArray.add("he");
Iterator<String> itor = dyArray.iterator();
int i = 0;
while (itor.hasNext()) {
if (i == 2) {
itor.remove();
}
i++;
System.out.println(itor.next()); }
System.out.println(itor.next()); } }

参考:《Java编程的逻辑》马俊昌

参考JDK1.8源码,自己写一个类似于ArrayList的动态数组的更多相关文章

  1. 菜鸟nginx源码剖析数据结构篇(一)动态数组ngx_array_t[转]

    菜鸟nginx源码剖析数据结构篇(一)动态数组ngx_array_t Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.csdn ...

  2. JDK1.8源码阅读系列之一:ArrayList

    本篇随笔主要描述的是我阅读 ArrayList 源码期间的对于 ArrayList 的一些实现上的个人理解,有不对的地方,请指出- 先来看一下 ArrayList 的继承图: 由图可以看出,Array ...

  3. 练习:自己写一个容器ArrayList集合 一一数组综合练习

    package cn.bjsxt.myCollection; import java.util.Arrays; /** * 天下文章一大抄,看你会抄不会抄. * 模拟Stringbuilder 写一个 ...

  4. JDK1.7源码阅读tools包之------ArrayList,LinkedList,HashMap,TreeMap

    1.HashMap 特点:基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了非同步和允许使用 null 之外,HashMap 类与 Has ...

  5. 练习:自己写一个容器ArrayList集合 一一数组综合练习2

    package cn.bjsxt.collection; /** * 自己实现一个ArrayList */ import java.util.ArrayList; import java.util.L ...

  6. 【JUC】JDK1.8源码分析之ArrayBlockingQueue(三)

    一.前言 在完成Map下的并发集合后,现在来分析ArrayBlockingQueue,ArrayBlockingQueue可以用作一个阻塞型队列,支持多任务并发操作,有了之前看源码的积累,再看Arra ...

  7. JDK1.8源码阅读系列之三:Vector

    本篇随笔主要描述的是我阅读 Vector 源码期间的对于 Vector 的一些实现上的个人理解,用于个人备忘,有不对的地方,请指出- 先来看一下 Vector 的继承图: 可以看出,Vector 的直 ...

  8. 【1】【JUC】JDK1.8源码分析之ArrayBlockingQueue,LinkedBlockingQueue

    概要: ArrayBlockingQueue的内部是通过一个可重入锁ReentrantLock和两个Condition条件对象来实现阻塞 注意这两个Condition即ReentrantLock的Co ...

  9. 【1】【JUC】JDK1.8源码分析之ReentrantLock

    概要: ReentrantLock类内部总共存在Sync.NonfairSync.FairSync三个类,NonfairSync与FairSync类继承自Sync类,Sync类继承自AbstractQ ...

随机推荐

  1. jsp页面使用<% 语句%> SQL Server数据库报空指针异常(在控制台可以正常执行)

    一直反感用SQL Server数据库,很影响电脑性能!!数据库作业不得不用 前几天作业一直报空指针异常: 自己检查了所传参数,和数组不为空 数据库查询语句不为空 然后查看SQL服务是否启动  主要是S ...

  2. eclipse 下配置安卓环境

    建议你看博客  http://blog.csdn.net/sinat_21184471/article/details/76131141  其中一些细节问题,我会根据我犯过的错误说明一下的!!!! 它 ...

  3. mysql第五篇 : MySQL 之 视图、触发器、存储过程、函数、事物与数据库锁

    第五篇 : MySQL 之 视图.触发器.存储过程.函数.事物与数据库锁 一.视图 视图是一个虚拟表(非真实存在的),其本质是‘根据SQL语句获取动态的数据集,并为其命名‘ ,用户使用时只需使用“名称 ...

  4. Day 16:输入输出字符流、缓冲输入字符流

    输入输出字节流输出字符时的常见问题 import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStre ...

  5. 前端基础之Html、CSS

    Html.css相关 Html Html结构: 标签 描述 <!DOCTYPE html> 文档声明 <html> 根元素 <head> 头部 <body 主 ...

  6. 对 TD tree 的使用体验

    经过这几天对学长们的作品的应用,感触颇多,忍不住写写随笔. 先谈一下,最初的感受吧,那天下午观看,体验学长学姐们的作品时,感觉他们太厉害了,只比我们多学一年,就已经可以做出手机 app ,和 网页了. ...

  7. 【pwnable.kr】 flag

    pwnable从入门到放弃 第四题 Download : http://pwnable.kr/bin/flag 下载下来的二进制文件,对着它一脸懵逼,题目中说是逆向题,这我哪会啊... 用ida打开看 ...

  8. python的库有多少个?python有多少个模块?

    这里列举了大概500个左右的库: !   Chardet字符编码探测器,可以自动检测文本.网页.xml的编码. colorama主要用来给文本添加各种颜色,并且非常简单易用. Prettytable主 ...

  9. <mvc:default-servlet-handler />说明

    优雅REST风格的资源URL不希望带 .html 或 .do 等后缀.由于早期的Spring MVC不能很好地处理静态资源,所以在web.xml中配置DispatcherServlet的请求映射,往往 ...

  10. Beyond Compare 文件对比工具的使用

    Beyond Compare 文件对比工具的使用 Beyond Compare 工具下载地址: http://www.onlinedown.net/soft/633850.htm 本文下载地址:E:\ ...