参考JDK1.8源码,自己写一个类似于ArrayList的动态数组
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的动态数组的更多相关文章
- 菜鸟nginx源码剖析数据结构篇(一)动态数组ngx_array_t[转]
菜鸟nginx源码剖析数据结构篇(一)动态数组ngx_array_t Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:Blog.csdn ...
- JDK1.8源码阅读系列之一:ArrayList
本篇随笔主要描述的是我阅读 ArrayList 源码期间的对于 ArrayList 的一些实现上的个人理解,有不对的地方,请指出- 先来看一下 ArrayList 的继承图: 由图可以看出,Array ...
- 练习:自己写一个容器ArrayList集合 一一数组综合练习
package cn.bjsxt.myCollection; import java.util.Arrays; /** * 天下文章一大抄,看你会抄不会抄. * 模拟Stringbuilder 写一个 ...
- JDK1.7源码阅读tools包之------ArrayList,LinkedList,HashMap,TreeMap
1.HashMap 特点:基于哈希表的 Map 接口的实现.此实现提供所有可选的映射操作,并允许使用 null 值和 null 键.(除了非同步和允许使用 null 之外,HashMap 类与 Has ...
- 练习:自己写一个容器ArrayList集合 一一数组综合练习2
package cn.bjsxt.collection; /** * 自己实现一个ArrayList */ import java.util.ArrayList; import java.util.L ...
- 【JUC】JDK1.8源码分析之ArrayBlockingQueue(三)
一.前言 在完成Map下的并发集合后,现在来分析ArrayBlockingQueue,ArrayBlockingQueue可以用作一个阻塞型队列,支持多任务并发操作,有了之前看源码的积累,再看Arra ...
- JDK1.8源码阅读系列之三:Vector
本篇随笔主要描述的是我阅读 Vector 源码期间的对于 Vector 的一些实现上的个人理解,用于个人备忘,有不对的地方,请指出- 先来看一下 Vector 的继承图: 可以看出,Vector 的直 ...
- 【1】【JUC】JDK1.8源码分析之ArrayBlockingQueue,LinkedBlockingQueue
概要: ArrayBlockingQueue的内部是通过一个可重入锁ReentrantLock和两个Condition条件对象来实现阻塞 注意这两个Condition即ReentrantLock的Co ...
- 【1】【JUC】JDK1.8源码分析之ReentrantLock
概要: ReentrantLock类内部总共存在Sync.NonfairSync.FairSync三个类,NonfairSync与FairSync类继承自Sync类,Sync类继承自AbstractQ ...
随机推荐
- BeginInvoke之前检测句柄
只要在BeginInvoke方法的调用语句前再加一句:IntPtr i = this.Handle;就OK了,这比死循环配合this.IsHandleCreated的判断方法更简洁,因为this.Ha ...
- 实验吧-密码学-这里没有key(VBScript脚本加密)
打开网页,查看源代码,发现一段乱码,这就是加密后的密文. #@~^TgAAAA=='[6*liLa6++p'aXvfiLaa6i[[avWi[[a*p[[6*!I'[6cp'aXvXILa6fp[:6 ...
- PHP数组创建和遍历(基础)
数组定义PHP数组可以是混合数组 你的数组里面可以有数字也可以有字符串,二维数组不谈一个数组里还有数组跟C有差别定义方式例如 $dd=array(array(1,2,3),array(1,2,3,4) ...
- Django static配置
STATIC_URL = '/static/' # HTML中使用的静态文件夹前缀 STATICFILES_DIRS = [ os.path.join(BASE_DIR, "static&q ...
- mysql 事务使用教程
一.什么是事务 事务Transaction,是指作为一个基本工作单元执行的一系列SQL语句的操作,要么完全地执行,要么完全地都不执行. 二.事务的特性 原子性 事务是一 ...
- idea自定义快捷鍵
一 生成方法注释 1. File -> Settings... 2. Editor -> Live Templates,点击最右边的+ 3. 依自己情况选择,我这里选择的 Live Te ...
- c++程序—if语句实践
三只小 #include<iostream> using namespace std; #include<string> int main() { //which pig is ...
- vmware搭建linux环境
参考博文:https://www.cnblogs.com/wuzm/p/10877990.html
- 课堂测试_WEB界面链接数据库
课堂测试_WEB界面链接数据库 一,题目: 一. 考试要求: 1登录账号:要求由6到12位字母.数字.下划线组成,只有字母可以开头:(1分) 2登录密码:要求显示“• ”或“*”表示输入位数,密码要求 ...
- 洛谷 P1833 樱花
题目传送门 解题思路: 就是完全背包和多重背包的混合.处理时间的时候注意一下就行了 AC代码: #include<iostream> #include<cstdio> usin ...