List集合源码阅读

List简介

List接口是Collection接口的子接口
1)List集合类中元素有序,存取顺序一致,且可以重复。
2)List集合中元素都对应一个整数型的序号记录其在容器中的位置,可以按照序号存取集合中的元素。

1-1. List集合常用方法

ListCollection集合的子接口,在Collection原有的方法上,还多了一些对索引操作集合元素的方法。

    1) void add(int index, Object ele)			在index位置插入元素
    2) boolean addAll(int index, Collection eles)	从index位置添加集合元素
    
    3) Object get(int index)				获取index位置的元素
    
    4) int indexOf(Object obj)				返回obj在集合中首次出现的位置
    
	5) int lastIndexOf(Object obj)			返回obj在当前集合中最后出现的位置
    
	6.1) Object remove(int index)				移除指定位置的元素
    6.2) Object remove(Object obj)				移除指定元素
    
	7) Object set(int index, Object ele)	设置指定index位置的元素ele,如果该位置不存在,报错数组下标越界。
    
	8) List subList(int fromIndex, int toIndex)		返回[fromIndex, toIndex)范围的子集合

1-2. 方法演示

public class ArrayListTest {
    public static void main(String[] args) {
        List list = new LinkedList();

//        1) void add(int index, Object ele)			在index位置插入元素
        list.add(1);

//        2) boolean addAll(int index, Collection eles)	从index位置添加集合元素
        ArrayList arrayList = new ArrayList();
        arrayList.add(2);
        arrayList.add(3);
        list.addAll(0, arrayList);
        System.out.println(list + "\n");

//        3) Object get(int index)				获取index位置的元素
        System.out.println(list.get(0));

//        4) int indexOf(Object obj)				返回obj在集合中首次出现的位置
        System.out.println(list.indexOf(1));

//        5) int lastIndexOf(Object obj)			返回obj在当前集合中最后出现的位置
        System.out.println(list.lastIndexOf(1));

//        6) Object remove(int index)				移除指定位置/指定的元素
        list.remove(1);
        list.remove(new Integer(1));
        System.out.println(list);

//        7) Object set(int index, Object ele)	设置指定index位置的元素ele
        list.set(0, 6);
        //list.set(1, 7);       报错
        System.out.println(list);

//        8) List subList(int fromIndex, int toIndex)	返回[fromIndex, toIndex)范围的子集合
        for (int i = 0; i < 5; i++){
            list.add(i);
        }
        System.out.println(list);

        System.out.println(list.subList(1, 5));
    }
}

ArrayList集合

2-1. 介绍

  1. ArrayList可以加入null,并且可以是多个。
  2. ArrayList底层是基于数组实现存储的。
  3. ArrayList使用方法基本等同于Vector,但是Vector是线程安全的,ArrayList是线程不安全的。

2-2. CRUD源码

底层基本元素

  • ArrayList底层还是使用了数组来存储元素,对象名为elementData
  • ArrayList有一个默认容量为10,并且有共享的空对象DEFAULTCAPACITY_EMPTY_ELEMENTDATA用于集合的创建。
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, 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 = {};

    //用于默认大小的空实例的共享空数组实例。
    //将它与EMPTY_ELEMENTDATA区别开来,以了解添加第一个元素时要膨胀多少。
    transient Object[] elementData; // 非私有简化嵌套类访问

    //集合元素个数,并非是集合容量
    private int size;

new对象

  • 如果没有指定集合大小,创建对象时
/**
* 无参构造器会将公共的无元素的集合赋值给elementData
*/
	public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
  • 如果传入了参数指定集合大小,会直接new对象数组。(HashSet不会,要区分)
/**
* 首先判断传入参数是否大于0,如果大于0,直接创建对应大小的数组
* 如果等于0,会赋默认含0个元素的共享数组对象给集合
* 如果小于0则抛出异常
*/
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

增加元素

总结就是:

  1. 如果添加元素时,集合元素为空(刚new无参),那么先去给集合容量赋值为10,然后再进行添加操作;
  2. 如果添加元素时集合元素不为空(new有参、已经扩容过),那么要去判断当前集合大小+1当前集合容量大小
    1. 当前大小+1 > 当前集合容量 ----> 扩容1.5倍,实际为位移操作 new = old + (old >>1),添加元素。
    2. 当前大小+1 < 当前集合容量 ----> 添加元素。
/**
*	第一条语句是检查集合大小是否充裕,里面包含扩容机制(重点)
*	第二三行是赋值返回
*/
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  //直译为:确保内部容量。将当前集合大小加1,查看是否需要扩容
        elementData[size++] = e;
        return true;
    }

/**
“确保内存容量”方法,将当前集合大小+1作为参数,查看“如果”添加新的元素,集合需不需要扩容?
	
	1. calculateCapacity方法先查看当前集合大小,如果为0,则默认赋10,如果不为0,获得当前集合元素个数+1大小

	2. ensureExplicitCapacity方法正式比较当前集合大小+1 和 当前集合容量,当前集合容量不够,那么调用grow扩容,否则不操作。

*/
    private void ensureCapacityInternal(int minCapacity) {
        //1. 判断是否需要扩容
        ensureExplicitCapacity(
            //2. 查看当前容量大小,如果是0(刚new),那么就赋值为10,否则返回当前容量
            calculateCapacity(elementData, minCapacity)	//↓↓↓↓↓↓↓
        );
    }


        2.---> calculateCapacity(Object[] elementData, int minCapacity) {
                    //如果是空集合,那么就赋值集合大小为10
                    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                        return Math.max(DEFAULT_CAPACITY, minCapacity);
                    }
                    //如果此时集合已经不为空,那么返回当前容量大小
                    return minCapacity;
                }





	//3. 比较元素+1大小、集合大小,判断是否需要扩容
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // 如果当前所需的集合容量(当前元素个数+1) > 集合大小,扩容,否则不做操作
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

扩容机制

    private void grow(int minCapacity) {
        // 获取当前集合容量
        int oldCapacity = elementData.length;
    	// 新集合容量 = old集合容量 * 1.5,做位移操作
        int newCapacity = oldCapacity + (oldCapacity >> 1);
    	// 如果新容量 - 需求容量 < 0(比如添加了大集合),那么直接让新容量 = 所需集合大小
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
    	// 理解为如果成立,则赋值一个“超级大”的容量
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 将老集合内容,拷贝到新集合中,并规定新集合容量
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

删除元素

public boolean remove(Object o) {
    //判断元素是否为null
    if (o == null) {
        //循环遍历查看元素null是否在集合中,如果在,那么调用fastRemove方法移除
    	//并且将后面位置的元素全部向前拷贝一格,做到空间连续
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        //如果移除元素不为null,那么遍历元素并移除元素
    	//并且将后面位置的元素全部向前拷贝一格,做到空间连续
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
	//没有找到移除的元素返回false
    return false;
}

Vector集合

3-1. 介绍

  1. Vector底层也是一个对象数组,protected Object[] elementData;
  2. Vector是线程同步的,即方法是线程安全的synchronized,所以操作起来比ArrayList要慢。
  3. 开发时只有要求线程安全才会用到,否则用ArrayList。

3-2. new对象

Vector创建无参对象的过程和ArrayList略有不同,主要体现在直接赋值为10初始化内存。
而ArrayList则是赋值一个空的数组,然后在添加元素时才判扩容为10.

//无参构造器
	public Vector() {
        this(10);
    }


//单个有参构造器,调用两个参数的构造器
    public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }


//最终调用,两个参数构造器
    public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        //如果传入参数>0,就创建新对象
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }

3-3. Vector扩容机制

Vector方法源码和ArrayList几乎一样,区别是线程安全的,只有添加元素扩容时有较大区别。
Vector调用添加方法在进行扩容时,直接扩展为原来的两倍,并非使用位移运算。
newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);

    public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

    private void ensureCapacityHelper(int minCapacity) {
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

/*************** 可以看到前面调用和ArrayList一模一样 **************/
/******************* 只有grow方法实现有区别↓ ******************/
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

3-4. Vector和ArrayList区别

底层结构版本线程安全(同步)扩容倍数
ArrayList可变数组jdk1.2不安全,效率高1.5倍
Vector可变数组jdk1.0安全,效率不高2倍

LinkedList集合

4-1. 介绍

  • LinkedList实现了双向链表和双端队列特点。
  • 可以添加任意元素,包括null。
  • 线程不安全,没有实现同步。

4-2. new源码

没啥好看,啥操作都没做,不像ArrayList和LinkedList

//无参构造器
	public LinkedList() {
    }


//有参构造器
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

4-3. 添加源码(链表不存在扩容)

每个LinkedList都是添加节点的方式扩展链表,没有初始大小,下面是节点的定义

//很简单的一个Node节点
	private static class Node<E> {
        E item;		//保存信息
        Node<E> next;	//保存下一个节点的引用
        Node<E> prev;	//保存上一个绩点的引用

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

具体的添加元素操作

//最初进入的添加方法,调用了linkLast方法将新加入的元素放在链表最后一个位置
	public boolean add(E e) {
        linkLast(e);
        return true;
    }


//具体的操作
//注意last、first均为标识指针,只是表面“头”和“尾”,方便遍历时从前往后/从后往前。
    void linkLast(E e) {
         //transient Node<E> last;一个未初始化的引用为null
        final Node<E> l = last;
         //初始化节点,"上一个元素"引用和"下一个元素"引用此时都为null
        final Node<E> newNode = new Node<>(l, e, null);
         //last是尾结点,直接将尾结点指向新元素
        last = newNode;
         //第一次添加元素时 l=last(最初)=null,所以判断成立,此时直接将头指针指向新节点
        if (l == null)
            first = newNode;
        else
            l.next = newNode;	//如果链表不为空,那么尾部元素指向新添加元素
        size++;	//链表大小
        modCount++;	//操作次数
    }

4-4. 按位置删除元素

//按位置删除元素入口
	public E remove(int index) {
        checkElementIndex(index);		//操作①
        return unlink( node(index) );		//操作②
    }

	//操作①:做检查操作,查看传入参是否越界
    private void checkElementIndex(int index) {
        if (!isElementIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }



	//操作②、参数方法:主要做查询优化。
	//如果要查询的位置接近头结点,那么从前往后查询,否则从尾结点查询。
	Node<E> node(int index) {
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

	//操作②、返回方法:this.first.next=this.next,下一个元素节点的first=this.first
	//最后返回删除掉的节点元素。
    E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;

        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }
        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }

        x.item = null;
        size--;
        modCount++;
        return element;
    }

4-5. LinkedList和ArrayList区别

底层结构扩容方式增删效率改查效率
ArrayList可变数组1.5倍较低,数组扩容较高,根据下标
LinkedList双向链表没有容量一说较高,链表追加较低,沿链搜索

一般增删用LinkedList,改查用ArrayList。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值