Java容器源码攻坚战--第二战:ArrayList

mac2022-06-30  110

基于java10.1

零、前言

如果想读读源码测试功力,或读读源码修养身心,或读读源码满足自虐倾向,我建议第一个类是:ArrayList 第一、常用----所以用法比较熟悉,看完源码你也会更明白如何去用 第二、相对简单----1595行代码,刨去注释的一大堆也没有太多,还是hold的住的

总得来说ArrayList源码最主要的是对System.arraycopy的理解,很多操作都是基于此
void arraycopy( Object src, //源数组 int srcPos,//源数组中的起始位置 Object dest,//目标数组 int destPos,//目的地数据中的起始位置 int length);//要复制的数组元素的数量

一.继承与实现:

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable image

二、成员变量

//serialVersionUID来验证版本一致性 private static final long serialVersionUID = 8683452581122892189L; //默认列表的容量 private static final int DEFAULT_CAPACITY = 10; //一个空的Object数组--空数组(本文中别名:空1) private static final Object[] EMPTY_ELEMENTDATA = {}; //另一个空的Object数组--默认容量空数组(本文中别名:空2) private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //列表中盛放的数组 transient Object[] elementData; //非私有来简化内部类访问 //内部元素个数 private int size; //最大数组尺寸,这里是Integer最大值-8 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

二.构造方法

1.构造方法1:无参构造

使用无参构造ArrayList,在未添加元素前,内部数组的长度是为0。在添加第一个元素时,才会扩容到10

//无参构造:elementData=空2 public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
2.构造方法2:一参构造:指定初始容量
//入参initialCapacity:初始容量 public ArrayList(int initialCapacity) { if (initialCapacity > 0) {//入参>0有效 //创建长度为入参的Object数组 this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else {//入参<0异常 throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity); } }
3.构造方法3:用已有Collection对象,创建ArrayList
// 入参c:已有的容器对象 public ArrayList(Collection<? extends E> c) { elementData = c.toArray();//传入的集合转化为数组并赋值给elementData //获取数组长度赋值值给size,当size不为0 if ((size = elementData.length) != 0) { if (elementData.getClass() != Object[].class) //Arrays.copyOf见:A--01 elementData = Arrays.copyOf(elementData, size, Object[].class); } else {//入参 = 0 时:elementData=空1 this.elementData = EMPTY_ELEMENTDATA; } }
A--01:java.util.Arrays#copyOf(U[], int, java.lang.Class<? extends T[]>)

将一个数组拷贝指定长度,变为另一种类型的数组

@param <U> 初始数组类型 @param <T> 返回数组类型 @param original 被拷贝数组 @param newLength 拷贝的长度 @param newType 返回的数组类型 @return public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) { T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; }

三.常用方法的源码分析

1.添加: 代号m1
m1 : java.util.ArrayList#add(E)

modCount:父类AbstractList中的protected变量,每次添加和移除都会+1,记录所有的修改次数 作用:在Iterator使用时校验期望修改的次数与真实修改次数是否相同

*@param e 添加的元素 public boolean add(E e) { modCount++;//列表在结构上修改的次数 add(e, elementData, size);//此处调用三参的添加方法:见:m1-1 return true;//返回是否添加成功 }
m1-1 : java.util.ArrayList#add(E, java.lang.Object[], int)
*@param e 添加的元素 *@param elementData 数组 *@param s 索引位置 private void add(E e, Object[] elementData, int s) { //要添加的索引为等于数组的长度时进行扩容操作 if (s == elementData.length) elementData = grow();//grow()返回一个扩容后的数组:见:m-1-1 //将传入的元素放到数组的最后一位 elementData[s] = e;(注:由于数组标号从零开始,新数组长度s+1,最后一个元素下标号为s) size = s + 1;//将成员变量size+1 }
m-1-1 : java.util.ArrayList#grow()

扩容操作

private Object[] grow() { return grow(size + 1);//调用了一参的grow()方法 } *@param minCapacity 最小容量 private Object[] grow(int minCapacity) { //newCapacity(minCapacity)扩容核心操作:见m-1-1-1 return elementData = Arrays.copyOf(elementData,newCapacity(minCapacity)); }
m-1-1-1:java.util.ArrayList#newCapacity

扩容核心操作: 最小容量size+1,旧容量:数组长,新容量:1.5倍旧容量 拿教室举例:minCapacity就是班级学生数量+1,oldCapacity就是扩建前的座位数,newCapacity就是扩建后的座位数

*@param minCapacity 最小容量 private int newCapacity(int minCapacity) { //oldCapacity:旧容量 newCapacity:1.5倍旧容量 int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1);//即+50% if (newCapacity - minCapacity <= 0) { //新容量小于等于最小容量 //如果elementData=空2,扩容到DEFAULT_CAPACITY(即10),这就是分空1和空2的目的 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) return Math.max(DEFAULT_CAPACITY, minCapacity); if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return minCapacity; } //新容量大于最小容量时, newCapacity没达到最大尺寸返回newCapacity,否则hugeCapacity(minCapacity) return (newCapacity - MAX_ARRAY_SIZE <= 0) ? newCapacity : hugeCapacity(minCapacity); } //minCapacity大于数组最大尺寸,返回整型的最大值,否则返回数组最大尺寸 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.定位添加:代号:m2
m2 : java.util.ArrayList#add(int, E)
* @param 插入的索引位置 * @param 插入的元素 public void add(int index, E element) { rangeCheckForAdd(index);//m2-1:检查索引合法性 modCount++; final int s;//局部变量,记录方法执行前size值 Object[] elementData//局部变量,记录方法执行前elementData数组 if ((s = size) == (elementData = this.elementData).length) elementData = grow();//扩容1.5倍 System.arraycopy(elementData, index, elementData, index + 1, s - index);//m2-2:核心方法 elementData[index] = element; size = s + 1; }
m2-1 :java.util.ArrayList#rangeCheckForAdd
private void rangeCheckForAdd(int index) { if (index > size || index < 0)//索引在(0,size)之外报异常 throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); }
m2-2 :java.util.ArrayList#rangeCheckForAdd
//将[源数组src]从[指定位置srcPos]选取连续[长度length]个元素复制到[目标数组dest]的[指定位置destPos]。 public static native void arraycopy( Object src, //源数组 int srcPos,//源数组中的起始位置 Object dest,//目标数组 int destPos,//目的地数据中的起始位置 int length);//要复制的数组元素的数量

arraycopy.png
3.添加所有:代号m3
* @param 已有的容器对象 * @return 列表是否已被修改 */ public boolean addAll(Collection<? extends E> c) { Object[] a = c.toArray();//将容器对象转化为数组a modCount++;//修改次数+1 int numNew = a.length;//临时变量numNew记录a的长度 if (numNew == 0)//a的长度为0 return false;//说明列表未被修改 Object[] elementData;//临时变量elementData:数组 final int s;//临时变量s:数组长度 //加入的元素大于数组容积和当前元素个数之差,也就是放入这些元素后会占满 if (numNew > (elementData = this.elementData).length - (s = size)) //扩容 elementData = grow(s + numNew); //将a数组,从0开始,拷贝numNew个元素到elementData数组的s索引处 System.arraycopy(a, 0, elementData, s, numNew); size = s + numNew;//维护size return true;说明列表被修改 }
4.定点添加所有:代号m4
public boolean addAll(int index, Collection<? extends E> c) { rangeCheckForAdd(index);//见:m2-1 Object[] a = c.toArray(); modCount++; int numNew = a.length; if (numNew == 0) return false; Object[] elementData; final int s; if (numNew > (elementData = this.elementData).length - (s = size)) elementData = grow(s + numNew);//至此同上m3 int numMoved = s - index;//添加的位置之后的元素个数 if (numMoved > 0)//说明index在元素个数之内 //将elementData数组,从index开始,拷贝numMoved个元素到elementData数组的index + numNew索引处 //相当于挪一下索引位后的元素 System.arraycopy(elementData, index, elementData, index + numNew, numMoved); //将a数组,从0开始,拷贝numNew个元素到elementData数组的index索引处 System.arraycopy(a, 0, elementData, index, numNew); size = s + numNew; return true; }
5.根据索引删除:代号m5
m5 : java.util.ArrayList#remove(int)
public E remove(int index) { Objects.checkIndex(index, size);//检查索引合法性 final Object[] es = elementData;//将集合赋予临时变量es E oldValue = (E) es[index];//临时变量记录删除值 fastRemove(es, index);//m5-1:删除的核心方法 return oldValue;//返回删除的值 }
m5-1 : java.util.ArrayList#fastRemove
/** * 私有的移除方法:跳过边界检查并且不反会移除的值 */ private void fastRemove(Object[] es, int i) { modCount++;//修改次数+1 final int newSize;//新尺寸:元素总量-1 if ((newSize = size - 1) > i)//索引在元素总量-1之内 //将es数组,从i + 1开始,拷贝size - (i+1)个元素到es数组的i索引处 System.arraycopy(es, i + 1, es, i, newSize - i); es[size = newSize] = null; }
6.移除指定元素
public boolean remove(Object o) { final Object[] es = elementData; final int size = this.size; int i = 0; found: { if (o == null) { for (; i < size; i++) if (es[i] == null) break found; } else { for (; i < size; i++) if (o.equals(es[i])) break found; } return false; } //寻找到第一次出现的指定元素索引位置 fastRemove(es, i);//m5-1:删除的核心方法 return true; }
7.移除所有指定元素:代号m7
public boolean removeAll(Collection<?> c) { return batchRemove(c, false, 0, size);//batchRemove见:m7-1 }
m7-1:java.util.ArrayList#batchRemove
boolean batchRemove(Collection<?> c, boolean complement, final int from, final int end) { Objects.requireNonNull(c); final Object[] es = elementData;//es变量记录当前数组 int r; // Optimize for initial run of survivors for (r = from;; r++) { if (r == end)//没有找到该元素 return false; if (c.contains(es[r]) != complement)//包含但还没有全部扫描 break; } int w = r++; try { for (Object e; r < end; r++) if (c.contains(e = es[r]) == complement) es[w++] = e; } catch (Throwable ex) { System.arraycopy(es, r, es, w, end - r); w += end - r; throw ex; } finally { modCount += end - w; //shiftTailOverGap:见m7-1-1 shiftTailOverGap(es, w, end); } return true; }
7-1-1.范围移除
private void shiftTailOverGap(Object[] es, int lo, int hi) { //将es数组,从hi开始,拷贝size - hi个元素到es数组的lo索引处 System.arraycopy(es, hi, es, lo, size - hi); for (int to = size, i = (size -= hi - lo); i < to; i++) es[i] = null;//将移除的元素置空 }
8.获取元素:代号m8
m8 : java.util.ArrayList#get
/** * 返回列表中指定位置的元素 */ public E get(int index) { //m8-1:检查索引是否合法(1.9开始有) Objects.checkIndex(index, size); return elementData(index);//m8-2 }
m8-1 : java.util.Objects#checkIndex
public static int checkIndex(int index, int length) { return Preconditions.checkIndex(index, length, null);//m8-1-1 }
m8-1-1 : jdk.internal.util.Preconditions#checkIndex
//判断索引值是否在(0,length) public static <X extends RuntimeException> int checkIndex(int index, int length, BiFunction<String, List<Integer>, X> oobef) { if (index < 0 || index >= length) throw outOfBoundsCheckIndex(oobef, index, length);//这就是经常遇到的角标越界 return index; }
m8-2 : java.util.ArrayList#elementData
E elementData(int index) { return (E) elementData[index];//返回数组在索引上的值 }
9.修改元素
public E set(int index, E element) { //检查索引是否越界 Objects.checkIndex(index, size); //记录旧值 E oldValue = elementData(index); //设置新值 elementData[index] = element; //返回旧值 return oldValue; }

四、其他方法

1.获取集合大小:java.util.ArrayList#size
public int size() { return size; }
2.是否非空:java.util.ArrayList#isEmpty
public boolean isEmpty() { return size == 0; }
3.清空:java.util.ArrayList#clear
public void clear() { modCount++;//操作数+1 final Object[] es = elementData;//es存储当前数组 for (int to = size, i = size = 0; i < to; i++) es[i] = null;//全部置空 }
4.元素出现的索引位
indexOf(el) 元素第一次出现的索引位置
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;//从头开始找,直到相等时,返回i } return -1;//找不到返回-1 }
lastIndexOf(el) 元素最后一次出现的索引位置
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;//从后往前找,直到相等时,返回i } return -1;//找不到返回-1 }
5.是否包含某元素
public boolean contains(Object o) { return indexOf(o) >= 0;//找到有索引就ok }
4.转换为数组:java.util.ArrayList#toArray(T[])
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.Iterator上一篇讲的挺详细:见--Java容器源码攻坚战--第一战:Iterator

2.这里主要讲ArrayList特有的私人迭代器:ListIterator

可以认为是Iterator的升级版,继承自Iterator,而且多了一些操作

ListIterator
public interface ListIterator<E> extends Iterator<E> { boolean hasNext();//是否有下一元素 E next();//下一元素 boolean hasPrevious();//是否有前一元素 E previous();//前一元素 int nextIndex();//下一个元素索引 int previousIndex();//前一个元素索引 void remove();//移除 void set(E e);//设置 void add(E e);//添加 }
public ListIterator<E> listIterator() { return new ListItr(0); } public ListIterator<E> listIterator(int index) { rangeCheckForAdd(index); return new ListItr(index); }
ListIterator实现类
private class ListItr extends Itr implements ListIterator<E> { ListItr(int index) {//构造函数传入索引值 super(); cursor = index;//游标处于索引位 } public boolean hasPrevious() { return cursor != 0;//游标不为0说明有前元素 } public int nextIndex() { return cursor; } public int previousIndex() { return cursor - 1; } @SuppressWarnings("unchecked") public E previous() { checkForComodification(); //索引为游标-1 int i = cursor - 1; if (i < 0) throw new NoSuchElementException(); //elementData储存当前数组 Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i;//游标-1 return (E) elementData[lastRet = i];//返回元素 } public void set(E e) { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { //设置最后返回的那个元素索引 ArrayList.this.set(lastRet, e); } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } public void add(E e) { checkForComodification(); try { int i = cursor; //在最后返回的那个元素索引处添加元素 ArrayList.this.add(i, e); cursor = i + 1; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } } }

就酱紫,ArrayList还有个SubList没有接触过,暂时就不看了


后记:捷文规范

1.本文成长记录及勘误表
项目源码日期备注V0.1--无2018-10-2Java容器源码攻坚战--第二战:ArrayListV0.2--无--
2.更多关于我
笔名QQ微信爱好张风捷特烈1981462002zdl1994328语言我的github我的简书我的个人网站
3.声明

1----本文由张风捷特烈原创,转载请注明 2----欢迎广大编程爱好者共同交流 3---个人能力有限,如有不正之处欢迎大家批评指证,必定虚心改正 4----看到这里,我在此感谢你的喜欢与支持

转载于:https://www.cnblogs.com/toly-top/p/9781855.html

最新回复(0)