java List子类源码分析

XGQ 2020-06-21

ArrayList

ArrayList是集合的一种实现,实现了接口List,List接口继承了Collection接口。Collection是所有集合类的父类。ArrayList使用非常广泛,不论是数据库表查询,excel导入解析,还是网站数据爬取都需要使用到,了解ArrayList原理及使用方法显得非常重要。

特点:当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。

创建一个ArrayList对象

  • 当调用new ArrayList<>()时,将一个空数组{}赋值给了elementData,这个时候集合的长度size为默认长度0;
  • 当调用new ArrayList<>(100)时,根据传入的长度,new一个Object[100]赋值给elementData,当然如果玩儿的话,传了一个0,那么将一个空数组{}赋值给了elementData;
  • 当调用new ArrayList<>(new HashSet())时,根据源码,我们可知,可以传递任何实现了Collection接口的类,将传递的集合调用toArray()方法转为数组内赋值给elementData;

注意:在传入集合的ArrayList的构造方法中,有这样一个判断

if (elementData.getClass() != Object[].class),

给出的注释是:c.toArray might (incorrectly) not return Object[] (see 6260652),即调用toArray方法返回的不一定是Object[]类型,查看ArrayList源码

public Object[] toArray() {    return Arrays.copyOf(elementData, size);}

我们发现返回的确实是Object[],那么为什么还会有这样的判断呢?

如果有一个类CustomList继承了ArrayList,然后重写了toArray()方法呢。。

public class CustomList<E> extends ArrayList {
    @Override
    public Integer [] toArray() {
        return new Integer[]{1,2};
    };
    
    public static void main(String[] args) {
        Object[] elementData = new CustomList<Integer>().toArray();
        System.out.println(elementData.getClass());
        System.out.println(Object[].class);
        System.out.println(elementData.getClass() == Object[].class);
    }
}

执行结果:

class [Ljava.lang.Integer;
class [Ljava.lang.Object;
false

接着说,如果传入的集合类型和我们定义用来保存添加到集合中值的Object[]类型不一致时,ArrayList做了什么处理?读源码看到,调用了Arrays.copyOf(elementData, size, Object[].class);,继续往下走

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {    
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength); 
    System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
    return copy;
}

我们发现定义了一个新的数组,将原数组的数据拷贝到了新的数组中去。

扩容机制

arraylist的默认大小是10,初始化时可以自定义容量大小,扩容算法为当前容量加上当前容量的无符号右移一位(oldCapacity + (oldCapacity >> 1) )也就是原容量的1.5倍

add(E element)

我们通过源码来看一下add("element")到底发生了什么

public boolean add(E e) {
    ensureCapacityInternal(size + 1);
    // Increments modCount!!
    elementData[size++] = e;
    return true;
}

首先通过 ensureCapacityInternal(size + 1) 来保证底层Object[]数组有足够的空间存放添加的数据,然后将添加的数据存放到数组对应的位置上,我们看一下是怎么保证数组有足够的空间?

private void ensureCapacityInternal(int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
	modCount++;
	// overflow-conscious code
	if (minCapacity - elementData.length > 0)
		grow(minCapacity);
}

这里首先确定了Object[]足够存放添加数据的最小容量,然后通过 grow(int minCapacity) 来进行数组扩容

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

扩容规则为“数组当前足够的最小容量 + (数组当前足够的最小容量 / 2)”,即数组当前足够的最小容量 * 1.5,当然有最大值的限制。

Vector

简介:Vector 与 ArrayList 一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一
个线程能够写 Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,
访问它比访问 ArrayList 慢。

原理:构造方法还有增删改查的操作发现,都有一个synchronized关键字

详细细节

Vector继承于AbstractList,实现了List、RandomAccess、Cloneable、 Serializable等接口。

  • Vector 继承了AbstractList,实现了List接口。

  • Vector实现了RandmoAccess接口,即提供了随机访问功能。

  • Vector 实现了Cloneable接口,即实现克隆功能。

  • Vector 实现Serializable接口,表示支持序列化。

Vector实现的这些接口,表示会有这样的能力。Vector是线程安全的。下面我们从源码的角度来分析一下Vector是如何实现这些接口和保持线程安全的特性的。

构造方法

Vector的构造方法一共有四个,因为四个都比较重要,所以在这里就给出四个

  • 创建一个空的Vector,并且指定了Vector的初始容量为10
  • 创建一个空的Vector,并且指定了Vector的初始容量
  • 创建一个空的Vector,并且指定了Vector的初始容量和扩容时的增长系数
  • 根据其他集合来创建一个非空的Vector,调用其他集合的toArray方法

线程安全

从构造方法还有增删改查的操作我们发现了,都有这么一个synchronized关键字,就是这个关键字为Vector容器提供了一个安全机制,保证了线程安全。

Vector与其他容器的区别

ArrayList是线程非安全的,因为ArrayList中所有的方法都不是同步的,在并发下一定会出现线程安全问题。另一个方法就是Vector,它是ArrayList的线程安全版本,其实现90%和ArrayList都完全一样,区别在于:

1、Vector是线程安全的,ArrayList是线程非安全的

2、Vector可以指定增长因子,如果该增长因子指定了,那么扩容的时候会每次新的数组大小会在原数组的大小基础上加上增长因子;如果不指定增长因子,那么就给原数组大小*2

由于性能效率很低,所以Vector与HashTable都开始逐渐被淘汰

LinkedList

简介:LinkedList 是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,他还提供了 List 接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。

对比

  • ArrayList 基于动态数组实现,LinkedList 基于双向链表实现。
  • ArrayList插入删除元素时分两种情况:①把元素添加到末尾所需的时间复杂度是O(1),②在指定位置添加删除元素时就需要移动整个数组,操作代价就比较大了。LinkedList 对于添加删除方法只需要断链然后更改指向,所需的消耗就很小了。
  • ArrayList 支持高效的随机元素访问 而LinkedList 不支持。
  • ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)

参考

https://www.cnblogs.com/LiaHon/p/11089988.html

https://baijiahao.baidu.com/s?id=1638844080997170869&wfr=spider&for=pc

相关推荐