JDK源码阅读 - 03 集合 之 List

集合是一个容器,它内部管理了一些对象。

在业务场景中,我们对集合的操作不外乎添加、拿取、修改、移除之类的操作。对集合源码的阅读主要围绕这四个方面。

Collection接口是部分集合类的根接口。相关继承和实现Collection接口的类如下图:

Iterable:

迭代接口,包含的方法有:

  • 对集合生成一个迭代器对象
Iterator<T> iterator();
  • 对集合里的元素进行foreach循环,1.8版本
default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}
  • 对集合生成一个可拆分的迭代器,1.8版本,为并行设计的
default Spliterator<T> spliterator() {
    return Spliterators.spliteratorUnknownSize(iterator(), 0);
}

Collection:

是上图里各类集合的根接口。

  • 它继承Iterable接口,重定义了方法 iterator() 和 方法 spliterator();
  • 在接口里定义了方法 equals() 和 hashCode(),用来让实现接口的具体集合类重写这俩方法,不同的具体类不同的写法。
  • 1.8版本增加了两个流方法 stream()和 parallelStream()。
  • 常见集合使用的相关方法有:
    • 集合里增加数据:add、 addAll;
    • 集合里移除数据:remove、removeAll、removeIf;
    • 集合里元素个数:size;
    • 集合里是否有数据或指定数据:isEmpty、contains、containsAll;
    • 集合转数组:toArray;
    • 集合清空数据:clear
    • 集合求交集:retainAll

AbstractCollection:

实现Collection接口,主要做了以下几件事:

  • 将接口方法转成抽象方法,包含的方法有:iterator、size、
  • 对部分方法进行了模板式的逻辑实现,包含有:
    • isEmpty,判断size==0;
    • contains、containsAll,迭代集合判断
    • toArray,转成Object类型数据和具体类型的数组
    • remove、removeAll、retainAll,从集合里移除指定的对象
    • clear,清空集合

List:

继承Collection接口,并进行了接口方法的扩展,包含有:

  • 添加数据时可以给指定索引位置,add、addAll、set;
  • 移除指定索引位置的元素,remove
  • 查看指定索引位置的元素,get
  • 互获取元素的索引位置,indexOf、lastIndexOf;
  • 从集合中截取一段生成新集合,subList
  • 其他方法有,replaceAll、listIterator等

AbstarctList:

继承AbstractCollection类,实现List接口,主要对List里的扩展的新方法进行了部分实现。同时也实现了equals方法和hashCode方法。

包含内部类 Itr,实现了Iterator接口,并实现了接口方法,该内部类是 iterator() 方法的模板实现。

包含内部类 ListItr,继承 Itr类,并实现 ListIterator接口,该内部类是 listIterator()方法的模板实现。

包含内部类 SubList,继承AbstractList,用于 subList 方法;

包含内部类 RandomAccessSubList,继承SubList,实现 RandomAccess 接口,用于 subList 方法;

ArrayList:

继承AbstractList,实现List接口。底层是个数组:

transient Object[] elementData;

无参构造方法或者有参且参数为0的构造方法 会创建一个空数组,length=0;

有参且参数大于0的构造方法,会创建一个有长度的数组,length >0;

添加数据数组容量检查流程

  1. 计算如果新增了元素后容量值,size + 待新增元素个数 = minCapacity;
  2. 如果当前数组是空数组,取minCapacity 与默认容量10 两者之间最大的;不是空数组就取minCapacity;作为本次确定好的容量值;
  3. 本次新容量值大于数组长度时,就要对数组就行扩展
  4. 一般旧数组长度 * 1.5 作为新数组长度,记作 newCapacity;如果newCapacity小于本次的容量值,那么就把本次的容量值作为 newCapacity;
  5. 此时如果 newCapacity 大于2147483639( Integer.MAX_VALUE - 8),再次扩展容量大小,最大为2147483647( Integer.MAX_VALUE )
  6. 将原数组里的元素全部copy到新数组里,并在尾部添加新元素
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);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

向指定索引位置一次添加单个或多个元素,需要让数组里该位置之后的所有元素进行移动。

add和set的区别是,对同一个索引位置,add是新增元素,索引位置后的元素都要移动;set是用新元素代替旧元素,数组长度无变化;

get是通过索引直接拿去当前位置元素,直接定位到位置,速度快;其他查看元素的方法如 indexOf、contains、lastIndexOf等 需要遍历数组,找到第一个匹配的并返回。

移除指定索引位置、索引范围下的元素,是将索引范围终止位置之后的所有元素copy到索引范围起始位置,并将新数组长度作为数组的起始位置,与原数组长度作为终点位置,两者直接的元素全部设置为null。

移除指定对象,需要循环数组,找到相等的第一个元素的索引位置,并移除此索引位置。

clear()方法,循环把元素设置为null。

clone()方法,将当前数组里的元素copy一份作为新的ArrayList里的数组,此新数组的length 等于 旧数组的 size。

Vector:

继承AbstractList,实现List接口。底层也是数组。默认容量也是10。Vector与ArrayList的区别在于:

  1. 相关实现方法被synchronized关键字修饰,是个阻塞方法。也可称Vector是个线程安全类
  2. 在底层数组扩容时,vector默认是 原数组长度 * 2作为新容量;非默认时在构造方法里的参数可自定义增量值,增量值大于0就用 原数组长度 + 增量值作为新容量;

Stack:

继承Vector。没有重写Vector里的方法,定义了属于栈的方法,

  • peek,线程安全方法,获取数组尾部元素。
  • pop,线程安全方法,获取数组尾部元素,同时从数组里移除该元素。
  • push,线程安全方法,压栈操作,数组里加元素。
  • search,线程安全方法,从栈里找最近的一个元素。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值