也快要秋招了,博客也没有任何的代码,只有几个遇到的问题记录,所以就写些吧,顺便复习下,如果有哪块写的有问题,欢迎大家批评指正。
public class ArrayListextends AbstractList E> c) { //将容器转化为数组 elementData = c.toArray(); //容器是否不是空的,给size(也就是当前的arraylist中对象的个数)中赋值。 if ((size = elementData.length) != 0) { //判断字节码对象是否相等,也就是类型是否一致,字节码对象只会在第一次加载这种类时加载字节码,且只加载一次 if (elementData.getClass() != Object[].class) //将内置的数组赋值为该容器转化的数组 elementData = Arrays.copyOf(elementData, size, Object[].class); } else { //将内置的数组赋值为空数组 this.elementData = EMPTY_ELEMENTDATA; } } //我们平时常用的add方法 public boolean add(E e) { //确保容量足够,因为每次添加一个对象,所以给的参数是size+1 //这个方法调用的是最关键的地方,在这之后会调用一个方法将容器的容量扩容 ensureCapacityInternal(size + 1); // Increments modCount!! //上面已经确保了容量足够,且没有异常 //所以直接给当前数组的不是null的那个最末尾的下表的下一个赋值 //注意:size是真正的当前存储的对象的个数 //而elementData这个数组是很有可能有一部分位置是空的,也就是null elementData[size++] = e; return true; } //确定容量方法,其内调用了一个确定最小容量和一个确定详细容量两个方法 private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } //这个方法内就是判断下elementData是否是空的,是空的就直接判断这个size+1是否比默认的初始容量10大 private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //比较minCapacity是否比10大 return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; //总之就是返回一个最小的容量数值 } //该方法确定当前的容量是否足够,并决定是否增加容量 private void ensureExplicitCapacity(int minCapacity) { //modCount用来记录修改次数,在迭代器内判断是否出现并发修改异常,这个先不管 modCount++; //上面也说过了,elementData很可能是有一部分是null //所以还需要判断一下,elementData是否足够大 //一般添加一个元素,elementData.length和size是一样大的 //但是不排除我们会使用addAll(Collection c)方法 if (minCapacity - elementData.length > 0) //容量不够,增加容量,将calculateCapacity方法确定的最小容量传入 grow(minCapacity); } //增长容量方法 private void grow(int minCapacity) { int oldCapacity = elementData.length; //(oldCapacity >> 1)是右移操作,相当于除2 //但是这个是最快的,直接将命令给cpu的,我们平时除2也可以这样,能稍微快点,上过组成原理的同学应该知道 //这里就是得到一个elementData.length的1.5倍的值,准备和minCapacity比较出一个合理的值 int newCapacity = oldCapacity + (oldCapacity >> 1); //如果这个1.5倍的值大于最小容量,那么我们直接取值最小容量就可以 //没必要用1.5倍的值,数组是固定大小,会浪费内存 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //MAX_ARRAY_SIZE的定义为private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 //MAX_ARRAY_SIZE已经很大,接近int的极限,如果比这个值还要大 if (newCapacity - MAX_ARRAY_SIZE > 0) //得出一个容量值,这个值会非常大 newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: //这句英文说这个这最小容量通常接近于我们elementData的实际存储对象个数 //大概他觉得我们最长使用的是add(E e)这样的添加很少的元素对象,不会一次添加大量元素对象 //System.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength)); //这个Arrays.copyOf之后会调用上面这一句代码 //original是elementData,newLength是newCapacity,copy是完成扩容后的数组 //最后会return copy,也就是完成了这一次扩容 elementData = Arrays.copyOf(elementData, newCapacity); } //比较出一个合适的容量值的方法 private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError ("Required array size too large"); //这里Integer.MAX_VALUE这个值可能会报错 //因为jvm会有一个默认的内存大小,这个值为21亿的MAX_VALUE可能会超过这个内存 //和数组下标越界异常类似,但是这回是内存不够,可以调整jdk默认内存解决 return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; } //知道了add(E e)方法后,其他方法也差不多了 // public E remove(int index) { //检查是否超出边界范围,只有一句判断是否判处数组越界异常 //private void rangeCheck(int index) { // if (index >= size) // throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); //} rangeCheck(index); //同add方法一样,modCount用来记录修改次数 modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) //其实ArrayList的改变大小就是靠System.arraycopy这个本地方法完成的 System.arraycopy(elementData, index+1, elementData, index, numMoved); //清除数组对那个删除对象的引用,垃圾回收器才会回收那个没有引用标记的对象 elementData[--size] = null; // clear to let GC do its work return oldValue; } }implements List , RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8683452581122892189L; //默认的一个初始化数组大小 private static final int DEFAULT_CAPACITY = 10; //这两个都是空数组,主要是用来初始化容器或者判断某些给的形参数组是否为空 private static final Object[] EMPTY_ELEMENTDATA = {}; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //实际存放对象的数组 transient Object[] elementData; //当前的数组中存放的对象的个数 private int size; //我们平时最常用的无参构造方法 public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } //比较少用的有参构造方法,直接将给的形参容器复制到我们的arraylist中 public ArrayList(Collectionextends
上面提到了两个可能会有问题的地方,一个是Integer.MAX_VALUE这个值会使得jvm内存不够分配报异常,一个是modCount记录修改次数,也可能报异常
先说第一个地方
try { int[] a=new int[Integer.MAX_VALUE-2]; System.out.println(a.length); } catch (java.lang.OutOfMemoryError e) { e.printStackTrace(); } try { int[] b=new int[Integer.MAX_VALUE]; System.out.println(b.length); } catch (java.lang.OutOfMemoryError e) { e.printStackTrace(); }
这个代码执行后会报错误,Java heap space是内存快到极限时抛出,Requested array size exceeds VM limit是已经在内存中找不到该内存位置了,解决方法是调整jvm默认分配内存大小
第二个地方modCount,先看这么一段代码
ArrayList list=new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
Iterator iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
list.remove(0);
}
运行后,会 抛出java.util.ConcurrentModificationException,这样的一个异常,为什么会抛出这样的一个异常呢?并没有多个线程同时修改集合啊
先看下Iterator
继续往下,看到一个内部类
这个类中的next方法,可以看到调用了checkForComodification方法,下面的if (i >= elementData.length) throw new ConcurrentModificationException();是为了防止多线程修改
调用的checkForComodification方法
到这里知道了,每次ArrayList去增删改元素对象的时候,改变了modCount,而其内部类对象的expectedModCount只是修改前的一个modCount的值,
所以调用next方法时,如果使用ArrayList的增删改,那么就会抛出这么一个异常,解决方法是使用迭代器的remove方法
可以看到,这个remove方法中,expectedModCount和modCount每次都会同步下,所以不会报异常了,但是它只有remove方法,如果想要使用其它方法可以使用list.listIterator()获取一个更加强大的迭代器
其余的get set lastIndexOf等方法的代码行数大都比较少,有兴趣的同学可以自己看下,我也是java新手,写博客更多是为了做个记录,难免有些地方有错误,如果有哪块写的不好,或者有问题,欢迎大家批评指正。