【Java集合】ArrayList源码理解笔记

参考文章


1.底层数据结构是用Object elementData[]数组存储,且用transient关键字修饰,防止序列化。为什么transient关键字修饰


2.ArrayList类主要是继承AbstractList类并实现了List接口,实现Cloneable和Serializable接口使得ArrayList具有克隆和序列化的功能。

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

3.拥有以下属性:

//序列化
private static final long serialVersionUID = 8683452581122892189L;

//初始化后调用add()后的初始容量
private static final int DEFAULT_CAPACITY = 10;

//共享的空数组实例
private static final Object[] EMPTY_ELEMENTDATA = {};
//共享的空数组实例
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

transient Object[] elementData; // non-private to simplify nested class access

//此时ArrayList的实际长度
private int size;

4.两个空数组,当new ArrayList(0)时或者new ArrayList(Collection<? extends E> c) 集合c长度为0时,则会返回第一个共享的空数组实例EMPTY_ELEMENTDATA引用;当new ArrayList()初始化实例时,会返回第二个空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA 实例。第一个与第二个的区别在于,后者当第一次调用add()添加元素时,数组的容量会初始化为DEFAULT_CAPACITY也就是10,而前者,第一次调用add()则不会初始化容量至10。


5.默认初始容量值DEFAULT_CAPACITY虽然为10,但在List list = new ArrayList();初始化时,此时的容量是为0。


6.记录list当前存储元素多少的size是私有变量,用size()方法获得,在进行add操作时+1,在进行remove操作时-1;而list容量空间大小可以利用反射机制获取:

Class<ArrayList> arrayListClass = ArrayList.class;
//获取 elementData 字段
Field field = arrayListClass.getDeclaredField("elementData");
//开始访问权限
field.setAccessible(true);
//把示例传入get,获取实例字段elementData的值
Object[] objects = (Object[])field.get(list);
//输出数组length属性的值
System.out.println(objects.length);

7.MAX_ARRAY_SIZE表示ArrayList最大的容量,为Integer.MAX_VALUE - 8 = 231-1-8,这里的8个字节留着可能是存内存存储对象头信息和对象头信息,但实际上数组容量最大还是可以扩充到Integer.MAX_VALUE = 231-1(hugeCapacity)。


8.当进行添加元素时,若容量不够则会扩充至原先的1.5倍(grow方法,当用ensureCapacity扩充容量时,若扩充后小于原来1.5倍时,会变为扩充为1.5倍)。

private void grow(int minCapacity) {
	// overflow-conscious code
	int oldCapacity = elementData.length;
	int newCapacity = oldCapacity + (oldCapacity >> 1);
	if (newCapacity - minCapacity < 0) 
    	newCapacity = minCapacity;
	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);
}

9.modCount记录集合被修改的次数,作用是在使用迭代器Iterator对集合进行遍历时,用modCount来判断集合内数据没有发生变化,否则会抛出异常。


10.由于底层数据类型为Object,所以可以存储null元素。


11.trimToSize()方法用来将当前容量缩小至存储元素多少size的长度,释放多的内存空间。


12.当需要不断的add添加很多元素时,且会多次触发自动扩充容量时,利用ensureCapacity(int minCapacity)可以将容量一口气提升至足够的容量,只触发一次扩充容量grow方法,减少数组容量自动扩充1.5倍的操作,可以提升性能。


13.indexOf(Object o)获取第一次出现Object o的数组下标位置,lastIndexOf(Object o)获取最后一次出现,其中o可以为null


14.clone()是浅克隆,只是创建了新对象以及新的底层数组对象,但是其他非基本数据类型的属性都是引用原先对象所引用的内存地址,底层数组里的元素也是引用同一内存地址。(浅复制与深复制,只有序列化与反序列化才能完全深复制


15.public Object[] toArray()以及public T[] toArray(T[] a) 都是浅复制底层数组返回一个数组,区别在于:前者返回的是Object类型数组,容量为size大小,相当于将底层数组Object的引用放到一个新的Object数组对象中,容量空间恰好为size大小;后者返回的是指定类型的数组,若原先ArrayList中存在非指定类型或其子类的引用,则会抛出异常。


16.set(int index, E element)会返回被替换位置的原来的元素,其中index不可以>=size,即原来的位置必须有元素已存在。


17.add(int index, E element)方法会将对应位置以及其后的所有元素往后移动,index>=0&index<=size,若index==size,size是刚好add(E element)时候的位置,不用移动。


18.remove(Object o)删除的是第一次出现的o元素,可以是null,成功返回true,失败返回false,方法中调用fastRemove(int index)删除元素,System.arraycopy(elementData, index+1, elementData, index, numMoved);使后面元素前移一位;remove(int index)比fastRemove的代码多了index合法检查和返回值,返回被删除的元素值。


19.clear()将数组所有的indx>=0&&index<size位置的元素置为null,将size=0。


20.addAll(Collection<? extends E> c)成功加入元素返回true,加入元素数量为0则返回false;addAll(int index, Collection<? extends E> c)先判断index合法性,index>=0&&index<=size先将index位置上及其后的元素System.arraycopy(elementData, index, elementData, index + numNew, numMoved)复制到后面去,空出加入集合的元素数个位置,然后再System.arraycopy(a, 0, elementData, index, numNew)将集合元素复制到空出来的位置上,成功加入元素返回true,加入元素数量为0则返回false。


21.removeAll(Collection<?> c)和retainAll(Collection<?> c)(取交集)都先对c集合进行合法检查,是否为null或空,然后调用batchRemove(Collection<?> c, boolean complement),该方法从0开始循环ArrayList的数组,主要操作如下:

int r = 0, w = 0;
for (; r < size; r++)
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];

22.writeObject(java.io.ObjectOutputStream s)和readObject(java.io.ObjectInputStream s)可进行序列化和反序列化。


23.迭代器分两种Iterator和ListIterator,Iterator可以进行hasNext、next和remove操作,ListIterator可以双向遍历,添加元素等操作。【参考文章】在使用迭代器时,除了使用迭代器里的方法外,不允许有修改集合的操作,基本上迭代器里的方法都会先判断集合状态是否改变,状态值expectedModCount初始值就是modCount.,改变modCount就会抛出异常。(modCount的快速失败机制)

final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

24.List subList(int fromIndex, int toIndex)返回截取的fromIndex, toIndex-1位置之间元素的list。还有.clear()删除的用法,例如:

ArrayList<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c", "d", "e", "f", "g", "h"));
list.subList(3, 6).clear();
list.stream().forEach(p->{
        System.out.println(p);
    });
  结果:
  a
  b
  c
  g
  h

25.因为可以用上面的方法进行ArrayList的范围remove元素,所以为了不冗余,将ArrayList下面的removeRange方法是私有的。【参考文章

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 work
    int newSize = size - (toIndex-fromIndex);
    for (int i = newSize; i < size; i++) {
        elementData[i] = null;
    }
    size = newSize;
}

26.sort(Comparator<? super E> c),自定义排序。【参考文章


27.继承父类containsAll(Collection<?> c)方法,比较c是否是该ArrayList集合的子集,若是返回true。


以下方法有涉及到Java8新特性函数表达式。。lambda表达式等等,以后再深入学习


spliterator()并行迭代器,可用于多线程。【参考文章
forEach(Consumer<? super E> action),循环遍历。【参考文章

list.forEach(x -> System.out.print(x));
//x -> System.out.print(x)就是lambda表达式

removeIf(Predicate<? super E> filter),过滤删除。【参考文章
replaceAll(UnaryOperator operator),替换。【参考文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值