写点什么

Java 中高级核心知识全面解析 - 容器(ArrayList)

用户头像
极客good
关注
发布于: 刚刚

/**


  • 用指定的元素替换此列表中指定位置的元素。*/public E set(int index, E element) {//对 index 进行界限检查 rangeCheck(index);


E oldValue = elementData(index);elementData[index] = element;//返回原来在这个位置的元素 return oldValue;}


/**


  • 将指定的元素追加到此列表的末尾。*/public boolean add(E e) {ensureCapacityInternal(size + 1); // Increments modCount!!//这里看到 ArrayList 添加元素的实质就相当于为数组赋值 elementData[size++] = e;return true;}


/**


  • 在此列表中的指定位置插入指定的元素。*先调用 rangeCheckForAdd 对 index 进行界限检查;然后调用 ensureCapacityInternal 方 法


【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


保证 capacity 足够大;*再将从 index 开始之后的所有成员后移一个位置;将 element 插入 index 位置;最后 size 加 1。*/public void add(int index, E element) {rangeCheckForAdd(index);


ensureCapacityInternal(size + 1); // Increments modCount!!//arraycopy()这个实现数组之间复制的方法一定要看一下,下面就用到了 arraycopy()方法实 现数组自己复制自己 System.arraycopy(elementData, index, elementData, index + 1,size - index);elementData[index] = element;size++;}


/**


  • 删除该列表中指定位置的元素。 将任何后续元素移动到左侧(从其索引中减去一个元素)。*/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;}


/**


  • 从列表中删除指定元素的第一个出现(如果存在)。 如果列表不包含该元素,则它不会更改。*返回 true,如果此列表包含指定的元素*/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 remove method that skips bounds checking and does not

  • return the value removed.*/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 void clear() {modCount++;


// 把数组中所有的元素的值设为 nullfor (int i = 0; i < size; i++)elementData[i] = null;


size = 0;}


/**


  • 按指定集合的 Iterator 返回的顺序将指定集合中的所有元素追加到此列表的末尾。*/public boolean addAll(Collection<? extends E> c) {Object[] a = c.toArray();int numNew = a.length;ensureCapacityInternal(size + numNew); // Increments modCountSystem.arraycopy(a, 0, elementData, size, numNew);size += numNew;return numNew != 0;}


/**


  • 将指定集合中的所有元素插入到此列表中,从指定的位置开始。*/public boolean addAll(int index, Collection<? extends E> c) {rangeCheckForAdd(index);


Object[] a = c.toArray();int numNew = a.length;ensureCapacityInternal(size + numNew); // Increments modCount


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


/**


  • 从此列表中删除所有索引为 fromIndex (含)和 toIndex 之间的元素。*将任何后续元素移动到左侧(减少其索引)。*/protected void removeRange(int fromIndex, int toIndex) {modCount++;int numMoved = size - toIndex;System.arraycopy(elementData, toIndex, elementData, fromIndex,numMoved);// clear to let GC do its workint newSize = size - (toIndex-fromIndex);for (int i = newSize; i < size; i++) {elementData[i] = null;}size = newSize;}


/**


  • 检查给定的索引是否在范围内。/private void rangeCheck(int index) {if (index >= size)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}/*

  • add 和 addAll 使用的 rangeCheck 的一个版本*/private void rangeCheckForAdd(int index) {if (index > size || index < 0)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));}


/**


  • 返回 IndexOutOfBoundsException 细节信息*/private String outOfBoundsMsg(int index) {return "Index: "+index+", Size: "+size;}


/**


  • 从此列表中删除指定集合中包含的所有元素。*/public boolean removeAll(Collection<?> c) {Objects.requireNonNull(c);//如果此列表被修改则返回 truereturn batchRemove(c, false);}


/**


  • 仅保留此列表中包含在指定集合中的元素。*换句话说,从此列表中删除其中不包含在指定集合中的所有元素。*/public boolean retainAll(Collection<?> c) {Objects.requireNonNull(c);return batchRemove(c, true);}


/**


  • 从列表中的指定位置开始,返回列表中的元素(按正确顺序)的列表迭代器。*指定的索引表示初始调用将返回的第一个元素为 next 。 初始调用 previous 将返回指定索引减 1 的 元素。*返回的列表迭代器是 fail-fast 。*/public ListIterator<E> listIterator(int index) {if (index < 0 || index > size)throw new IndexOutOfBoundsException("Index: "+index);return new ListItr(index);}


/***返回列表中的列表迭代器(按适当的顺序)。*返回的列表迭代器是 fail-fast 。*/public ListIterator<E> listIterator() {return new ListItr(0);}


/***以正确的顺序返回该列表中的元素的迭代器。*返回的迭代器是 fail-fast 。*/public Iterator<E> iterator() {return new Itr();}

三、 ArrayList 源码分析

1. System.arraycopy()和 Arrays.copyOf()方法

通过上面源码我们发现这两个实现数组复制的方法被广泛使用而且很多地方都特别巧妙。比如下面add(int index, E element)方法就很巧妙的用到了arraycopy()方法让数组自己复制自己实现让index开始之后的所有成员后移一个位置:


/**


  • 在此列表中的指定位置插入指定的元素。

  • *先调用 rangeCheckForAdd 对 index 进行界限检查;然后调用 ensureCapacityInternal 方法保证 capacity 足够大;*再将从 index 开始之后的所有成员后移一个位置;将 element 插入 index 位置;最后 size 加 1。*/public void add(int index, E element) {rangeCheckForAdd(index);


ensureCapacityInternal(size + 1); // Increments modCount!!//arraycopy()方法实现数组自己复制自己//elementData:源数组;index:源数组中的起始位置;elementData:目标数组;index + 1:目标数组中的起始位置; size - index:要复制的数组元素的数量;System.arraycopy(elementData, index, elementData, index + 1, size - index);elementData[index] = element;size++;}


又如 toArray()方法中用到了 copyOf()方法


/***以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。*返回的数组将是“安全的”,因为该列表不保留对它的引用。 (换句话说,这个方法必须分配一个新的 数组)。*因此,调用者可以自由地修改返回的数组。 此方法充当基于阵列和基于集合的 API 之间的桥梁。*/public Object[] toArray() {//elementData:要复制的数组;size:要复制的长度 return Arrays.copyOf(elementData, size);}

2.两者联系与区别

联系:看两者源代码可以发现 copyOf() 内部调用了 System.arraycopy() 方法区别:A) arraycopy()需要目标数组,将原数组拷贝到你自己定义的数组里,而且可以选择拷贝的起点和长度以及放入新数组中的位置 B)copyOf()是系统自动在内部新建一个数组,并返回该数组。

3.ArrayList 核心扩容技术

//下面是 ArrayList 的扩容机制//ArrayList 的扩容机制提高了性能,如果每次只扩充一个,//那么频繁的插入会导致频繁的拷贝,降低性能,而 ArrayList 的扩容机制避免了这种情况。/**


  • 如有必要,增加此 ArrayList 实例的容量,以确保它至少能容纳元素的数量

  • @param minCapacity 所需的最小容量*/public void ensureCapacity(int minCapacity) {int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)// any size if not default element table? 0// larger than default for default empty table. It's already// supposed to be at default size.: DEFAULT_CAPACITY;


if (minCapacity > minExpand) {ensureExplicitCapacity(minCapacity);}}//得到最小扩容量 private void ensureCapacityInternal(int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {// 获取默认的容量和传入参数的较大值 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);}ensureExplicitCapacity(minCapacity);}//判断是否需要扩容,上面两个方法都要调用 private void ensureExplicitCapacity(int minCapacity) {modCount++;// 如果说 minCapacity 也就是所需的最小容量大于保存 ArrayList 数据的数组的长度的话,就需要调用 grow(minCapacity)方法扩容。//这个 minCapacity 到底为多少呢?举个例子在添加元素(add)方法中这个 minCapacity 的大小 就为现在数组的长度加 1if (minCapacity - elementData.length > 0)//调用 grow 方法进行扩容,调用此方法代表已经开始扩容了 grow(minCapacity);}/**


  • ArrayList 扩容的核心方法。*/private void grow(int minCapacity) {//elementData 为保存 ArrayList 数据的数组//elementData.length 求数组长度 elementData.size 是求数组中的元素个数// oldCapacity 为旧容量,newCapacity 为新容量 int oldCapacity = elementData.length;//将 oldCapacity 右移一位,其效果相当于 oldCapacity /2,//我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的 1.5 倍,int newCapacity = oldCapacity + (oldCapacity >> 1);//然后检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作 数组的新容量,if (newCapacity - minCapacity < 0)newCapacity = minCapacity;//再检查新容量是否超出了 ArrayList 所定义的最大容量,//若超出了,则调用 hugeCapacity()来比较 minCapacity 和 MAX_ARRAY_SIZE,//如果 minCapacity 大于 MAX_ARRAY_SIZE,则新容量则为 Interger.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE。if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// minCapacity is usually close to size, so this is a win:elementData = Arrays.copyOf(elementData, newCapacity);}


扩容机制代码已经做了详细的解释。另外值得注意的是大家很容易忽略的一个运算符:移位运算符简介:移位运算符就是在二进制的基础上对数字进行平移。按照平移的方向和填充数字的规则分为三种:<<(左移)>>(带符号右移)>>>(无符号右移)


作用:对于大数据的 2 进制运算,位移运算符比那些普通运算符的运算要快很多,因为程序仅仅移动一下而已,不去计算,这样提高了效率,节省了资源


比如这里:int newCapacity = oldCapacity + (oldCapacity >> 1);右移位相当于除 2,右移 n 位相当于除以 2 的 n 次方。这里oldCapacity明显右移了 1 位所以相当于oldCapacity /2


另外需要注意的是:


A. java 中的 length 属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了length这个属性.B. java 中的 length()方法是针对字符串String说的,如果想看这个字符串的长度则用到length()这个方法.C. java 中的 size()方法是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看!

4.内部类

(1)private class Itr implements Iterator<E> (2)private class ListItr extends Itr implements ListIterator<E> (3)private class SubList extends AbstractList<E> implements RandomAccess (4)static final class ArrayListSpliterator<E> implements Spliterator<E>


ArrayList 有四个内部类,其中的 Itr 是实现了 Iterator 接口,同时重写了里面的hasNext()next()remove() 等方法;其中的ListItr 继承 Itr,实现了ListIterator接口,同时重写了hasPrevious()nextIndex()previousIndex()previous()set(E e)add(E e) 等方法,所以这也可以看出了 Iterator 和 ListIterator 的区别: ListIteratorIterator的基础上增加了添加对象修改对象逆向遍历等方法,这些是 Iterator 不能实现的。

四、ArrayList 经典 Demo

package list;import java.util.ArrayList;import java.util.Iterator;


public class ArrayListDemo {


public static void main(String[] srgs){ArrayList<Integer> arrayList = new ArrayList<Integer>();


System.out.printf("Before add:arrayList.size() =%d\n",arrayList.size());


arrayList.add(1);arrayList.add(3);arrayList.add(5);arrayList.add(7);arrayList.add(9);System.out.printf("After add:arrayList.size() =%d\n",arrayList.size());


System.out.println("Printing elements of arrayList");// 三种遍历方式打印元素// 第一种:通过迭代器遍历 System.out.print("通过迭代器遍历:");Iterator<Integer> it = arrayList.iterator();while(it.hasNext()){System.out.print(it.next() + " ");}System.out.println();


// 第二种:通过索引值遍历 System.out.print("通过索引值遍历:");for(int i = 0; i < arrayList.size(); i++){System.out.print(arrayList.get(i) + " ");}System.out.println();


// 第三种:for 循环遍历 System.out.print("for 循环遍历:");for(Integer number : arrayList){System.out.print(number + " ");}


// toArray 用法// 第一种方式(最常用)Integer[] integer = arrayList.toArray(new Integer[0]);


// 第二种方式(容易理解)Integer[] integer1 = new Integer[arrayList.size()];arrayList.toArray(integer1);


// 抛出异常,java 不支持向下转型//Integer[] integer2 = new Integer[arrayList.size()];//integer2 = arrayList.toArray();System.out.println();


// 在指定位置添加元素 arrayList.add(2,2);// 删除指定位置上的元素 arrayList.remove(2);// 删除指定元素 arrayList.remove((Object)3);// 判断 arrayList 是否包含 5System.out.println("ArrayList contains 5 is: " +arrayList.contains(5));


// 清空 ArrayList arrayList.clear();

用户头像

极客good

关注

还未添加个人签名 2021.03.18 加入

还未添加个人简介

评论

发布
暂无评论
Java中高级核心知识全面解析-容器(ArrayList)