ArrayList源码分析

来源:互联网 发布:淘宝手绘兼职 编辑:程序博客网 时间:2024/06/09 21:31

事先说明,这是原创文章,不是从网上扒的,从晚上6点半写到了8点才搞定,饭都还没吃

如果你是新手,请一步一步的看,你一定能看懂的

说明:为了去掉原来的英文注释,我把ArrayList改为了ArrayListsrc

import java.util.AbstractList;import java.util.Arrays;import java.util.Collection;import java.util.ConcurrentModificationException;import java.util.List;import java.util.RandomAccess;/** *  * @author yj * @date 2015-12-5 下午6:27:51 * @description: ArrayList的源码分析 * @param <E> *  * @Declaration 关于容量(Capacity)和大小(Size)的理解 * 容量:ArrayList可以容纳的元素有多少个,比如初始化大小为10 * 大小:是说当前ArrayList中有多少个元素.想想平时怎么用size()的就很容易明白了 *///实现的接口介绍:RandomAccess(用于随机访问),Cloneable(用于克隆),Serializable(用于对象的序列化)public class ArrayListSrc<E> extends AbstractList<E> implements List<E>,RandomAccess, Cloneable, java.io.Serializable {private static final long serialVersionUID = 8683452581122892189L;// 底层使用一个Object数组实现的private transient Object[] elementData;// elementData中有多少个元素private int size;// 以指定的容量大小来初始化数组public ArrayListSrc(int initialCapacity) {super();// 如果初始化容量<0,抛出非法参数异常if (initialCapacity < 0)throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);//初始化数组this.elementData = new Object[initialCapacity];}/* * 这是默认的构造函数,调用了ArrayListSrc(int initialCapacity) * 也就是说默认的数组容量是10 */public ArrayListSrc() {this(10);}/* * 将一个集合转换为数组并赋给elementData * <? extends E>是泛型的范围限定,E是ArrayList类的泛型,表示E及E的子类 */public ArrayListSrc(Collection<? extends E> c) {elementData = c.toArray();size = elementData.length;//c.toArray()方法可能不能正确的转换,这里加了判断if (elementData.getClass() != Object[].class)//copyOf:将elementData[0-size]复制到Object[]中elementData = Arrays.copyOf(elementData, size, Object[].class);}/* * 先看看老外起的方法名:trimToSize,削减到大小(size) * 也就是说将当前的容量(Capacity)减小到size * 比如现在ArrayList的容量是10,但是里面只有5个元素,就把容量减小到5 */public void trimToSize() {//modCount是父类AbstractList中的一个变量,用于记录被修改了多少次modCount++;int oldCapacity = elementData.length;if (size < oldCapacity) {//这里就是实现削减的方式:已size为数组长度赋值一份原来的elementDataelementData = Arrays.copyOf(elementData, size);}}/* * 简单的说就是在必要的时候给数组扩容,记住在我们new一个数组是,长度就已经确定了, * 扩容只能再创建一个长度更大数组,并把原来的复制过去 */public void ensureCapacity(int minCapacity) {//修改次数+1modCount++;//获取底层数组的长度int oldCapacity = elementData.length;//如果参数>数组长度,说明需要扩容了if (minCapacity > oldCapacity) {//保存原来的数组Object oldData[] = elementData;//新的数组长度是原来的1.5倍int newCapacity = (oldCapacity * 3) / 2 + 1;//如果扩容以后还是不够,那么直接把minCapacity作为新数组长度if (newCapacity < minCapacity){newCapacity = minCapacity;}//copyOf内部创建一个新数组,并把elementData复制进去,最后的返回值赋给elementData//要想看的更清楚可以选中copyOf按F3进去看源码elementData = Arrays.copyOf(elementData, newCapacity);}}//获取ArrayList当前的元素个数public int size() {return size;}//判断是否为空public boolean isEmpty() {return size == 0;}//判断是否包含一个元素public boolean contains(Object o) {//indexOf:如果能索引到该元素,返回值是>=0的,如果索引不到返回-1return indexOf(o) >= 0;}//获取元素的索引public int indexOf(Object o) {//判断o是否是一个空引用,如果调用该方法是为:indexOf(null)if (o == null) {//源码的for和if都没有{},看得我好难受,果断加上for (int i = 0; i < size; i++){//如果某个元素为null,返回索引if (elementData[i] == null){return i;}}} else {//暴力搜索,一个一个比较,相等就返回索引for (int i = 0; i < size; i++)if (o.equals(elementData[i]))return i;}//找不到就返回-1return -1;}//搜索元素o最后出现的位置public int lastIndexOf(Object o) {//o==null同indexOfif (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;}//找不到就返回-1return -1;}/* * 覆写Object类的clone方法,可以看出是一个深克隆 * 友情复习:如果一个类中的成员遍历都是基本数据类型,那么就是浅克隆 * 而ArrayList中有Object[] elementData,这是引用类型遍历,所以是深克隆 */public Object clone() {try {ArrayListSrc<E> v = (ArrayListSrc<E>) super.clone();//这一步只是浅克隆v.elementData = Arrays.copyOf(elementData, size);//elementData也要克隆才是深克隆v.modCount = 0;//克隆后初始话记录修改次数的变量modCount为0return v;} catch (CloneNotSupportedException e) {// this shouldn't happen, since we are Cloneablethrow new InternalError();}}//把ArrayList转化为数组public Object[] toArray() {//直接拷贝了一个elementData[size]数组return Arrays.copyOf(elementData, size);}//参数传入一个数组,输出和参数同类型的数组(没用过)public <T> T[] toArray(T[] a) {if (a.length < size){//把elementData以size大小赋值到T[]数组中并返回return (T[]) Arrays.copyOf(elementData, size, a.getClass());}//所以说一般传入一个长度和size大小相同的数组System.arraycopy(elementData, 0, a, 0, size);//如果传入的数组长度>size就设置a[size]为空if (a.length > size)a[size] = null;return a;}//获取index角标处的元素public E get(int index) {//检查角标是否越界RangeCheck(index);return (E) elementData[index];}//用指定的元素替代指定位置上的元素public E set(int index, E element) {//检查角标是否越界RangeCheck(index);//获取原来的元素E oldValue = (E) elementData[index];//设置新元素elementData[index] = element;//以前位于该指定位置上的元素(虽然我也很好奇有返回值)return oldValue;}//添加元素public boolean add(E e) {//用来保证当前的容量大小可以保存该元素,也就是添加这个元素超过了容量就要扩容了ensureCapacity(size + 1);//添加到最后的位置elementData[size++] = e;//添加成功就返回truereturn true;}//在指定的位置添加元素public void add(int index, E element) {//如果index大于size或者<0就抛出异常if (index > size || index < 0)throw new IndexOutOfBoundsException("Index: " + index + ", Size: "+ size);ensureCapacity(size + 1); // Increments modCount!!增加了修改次数/* * 说下这个方法吧,arraycopy:把elementData<1>数组的index位置  复制到   elementData<2>的index+1位置 * 复制size-index个元素,相当于index后面的元素整体后移一位,想想让自己如何完成这个功能,就很好想通了 */System.arraycopy(elementData, index, elementData, index + 1, size- index);//index后面的元素都后移一位了,那么把element放在这就行了elementData[index] = element;//最后size++size++;}//删除指定位置的元素public E remove(int index) {//这俩不说了RangeCheck(index);modCount++;//获取index处的元素E oldValue = (E) elementData[index];//计算要移动的数目,也就是说把index处的元素删除了,后面都得往前移一位int numMoved = size - index - 1;if (numMoved > 0){//后面的元素都往前移一位System.arraycopy(elementData, index + 1, elementData, index,numMoved);}/* * 这里很重要,设置最后一个元素为null,可以让gc来回收,避免了内存泄露 * 友情复习(我是好人):内存泄露就是说一个引用不指向任何一个对象,但是它还占据着内存空间 * 如果把它设置为null,gc就可以回收了 */elementData[--size] = null; // Let gc do its work//返回被删除的元素return oldValue;}//删除指定的元素(如果一步一步看的这里不用说了,就说说fastRemove吧)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;}//首先这是一个private方法,属于类内部,用于删除index角标处的元素private void fastRemove(int index) {modCount++;//和remove(int index)一样int numMoved = size - index - 1;if (numMoved > 0)System.arraycopy(elementData, index + 1, elementData, index,numMoved);elementData[--size] = null; // Let gc do its work}//清空集合public void clear() {modCount++;//把所有元素都设置为空引用for (int i = 0; i < size; i++)// Let gc do its work 设置为null,交给gc回收elementData[i] = null;//size设置为0size = 0;}//把一个已有的集合添加到ArrayList中public boolean addAll(Collection<? extends E> c) {//先转成数组Object[] a = c.toArray();//获取数组长度int numNew = a.length;//保证ArrayList的容量ensureCapacity(size + numNew); // Increments modCount//把转换后的数组赋值到elementData数组中,长度为numNewSystem.arraycopy(a, 0, elementData, size, numNew);//size也会增加size += numNew;//新手看不懂返回值的话就不看return,只看numNew != 0,这个逻辑表达式要么为true,要么为falsereturn numNew != 0;}//这个方法是对上面方法的一个小增强:在指定的位置添加一个集合public boolean addAll(int index, Collection<? extends E> c) {//判断索引if (index > size || index < 0)throw new IndexOutOfBoundsException("Index: " + index + ", Size: "+ size);Object[] a = c.toArray();int numNew = a.length;ensureCapacity(size + numNew); // Increments modCountint numMoved = size - index;if (numMoved > 0){//不用之处只是复制到elementData数组的位置为index + numNewSystem.arraycopy(elementData, index, elementData, index + numNew,numMoved);}//这是添加一个空集合的情况System.arraycopy(a, 0, elementData, index, numNew);size += numNew;return numNew != 0;}//删除角标在[fromIndex,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);while (size != newSize){// Let gc do its workelementData[--size] = null;}}//很简单的一个角标判断private void RangeCheck(int index) {if (index >= size)throw new IndexOutOfBoundsException("Index: " + index + ", Size: "+ size);}/* * writeObject和readObject都是用于序列化的方法,我主要将ArrayList的线性表部分,只对序列化做一个简单介绍给新手 * 序列化: * 我们代码中的对象都会在程序运行结束后销毁,那么能不能存到硬盘上, * 或者直接通过网络传输呢?答案是可以,只要实现了序列化接口(Serializable)即可 * 这是一个标志接口(Cloneable也是),意思是这个接口中什么都没有,相当于给类打个标签,实现该接口的就可以序列化 * writeObject:将所有元素写入到一个输出流中 * readObject:从一个输入流中读取元素 *  * @question 留个疑问给新手,为什么ArrayList已经实现了序列化接口还有写这两个方法 * 提示:transient Object[] elementData,transient是干嘛的? */private void writeObject(java.io.ObjectOutputStream s)throws java.io.IOException {// Write out element count, and any hidden stuffint expectedModCount = modCount;s.defaultWriteObject();// Write out array lengths.writeInt(elementData.length);// Write out all elements in the proper order.for (int i = 0; i < size; i++)s.writeObject(elementData[i]);if (modCount != expectedModCount) {throw new ConcurrentModificationException();}}private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {// Read in size, and any hidden stuffs.defaultReadObject();// Read in array length and allocate arrayint arrayLength = s.readInt();Object[] a = elementData = new Object[arrayLength];// Read in all elements in the proper order.for (int i = 0; i < size; i++)a[i] = s.readObject();}}

0 0
原创粉丝点击