List接口实现子类的扩容机制讲解

本文选取 List 接口实现子类中最常用的三个类 ArrayList、Vector 和 LinkedList 进行讲解

1. ArrayList

1.1 扩容机制

  1. ArrayList 中维护了一个Object类型的数组 elementData,该数组用于存放数据

    transient Object[] elementData;
    
  2. 当 ArrayList 创建对象时,如果使用的是无参构造器,则初始 elementData 容量为0。第一次添加元素到数组中,则扩容 elementData为10。如果需要再次扩容,则扩容 elementData 为当前容量的1.5倍

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//无参构造器创建的数组为空
    private int size;//默认为0
    private static final int DEFAULT_CAPACITY = 10;//默认容量大小


	public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

	//数组添加元素方法,其中含有数组扩容机制
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);//检查是否需要扩容
        elementData[size++] = e;
        return true;
    }
	//
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        //如果传入的数组为空
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //返回传入的 minCapacity (此处为size + 1) 和 10 中的最大值
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //不为空返回 size + 1
        return minCapacity;
    }
	//
    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        //如果此时 minCapacity 的大小比数组长度要大,则扩容
        //比如是无参构造器创建的数组,此时 10 - 0 > 0
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

	//扩容方法
    private void grow(int minCapacity) {
        
        int oldCapacity = elementData.length;//数组大小
        
        //新数组大小 = 旧数组大小 + (旧数组大小按二进制形式右移1位)
        //此处如果 oldCapacity = 10,则 (oldCapacity >> 1) = 5,即扩展为原数组1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)//一般不会出现该情况
            newCapacity = hugeCapacity(minCapacity);
        //将数据拷贝至具有 newCapacity 长度的新数组 elementData
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
  1. 如果使用的是指定大小的构造器,则初始 elementData 容量为指定大小,扩容机制同上。
    private static final Object[] EMPTY_ELEMENTDATA = {};
   
	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);
        }
    }

2.Vector

2.1 线程安全

Vector 是线程同步的,即线程安全,Vector 类的操作方法带有 synchronized

在开发中,需要线程同步安全时,考虑使用Vector。

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

2.2 扩容机制

  1. Vector 底层也是一个对象数组;
protected Object[] elementData;
  1. 如果调用无参构造器,默认数组大小是10,填满数组后,则按2倍扩容
  2. 如果使用的是指定大小的构造器,则每次直接按2倍扩容
	//add方法与ArrayList的方法基本一致,也是带有扩容机制
	public synchronized boolean add(E e) {
    	modCount++;
    	ensureCapacityHelper(elementCount + 1);//检查是否需要扩容
    	elementData[elementCount++] = e;
    	return true;
	}
    private void ensureCapacityHelper(int minCapacity) {
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
	//扩容机制
    private void grow(int minCapacity) {
        int oldCapacity = elementData.length;
        //新数组大小 = 2 * 旧数组大小
        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);
    }

2.3 ArrayList 和 Vector比较

底层结构版本线程安全(同步)效率扩容倍数
ArrayListtransient Object[] elementData;jdk 1.2不安全,效率高如果是无参构造器,则初始数组容量为0。第一次添加元素到数组中,则扩容数组为10。如果需要再次扩容,则扩容数组为当前容量的1.5倍;
如果是有参构造器,则初始数组容量为指定大小,扩容机制同上。
Vectorprotected Object[] elementData;jdk 1.0安全,效率不高如果调用无参构造器,默认数组大小是10,填满数组后,则按2倍扩容
如果是有参构造器构造器,则每次直接按2倍扩容

3. LinkedList

由于 LinkedList 底层实现了双向链表和双端队列特点,此处首先需要对双向链表进行基本介绍。

3.1 双向链表

首先我们需要介绍Node结点这一基本概念。Node结点是构成双向链表的基本元素。

//Node 对象表示双向链表的一个结点
class Node{
    public Object item;
    public Node next;//指向后一个结点
    public Node pre;//指向前一个结点

    public Node(Object item) {
        this.item = item;
    }
}

而双向链表则是在Node结点的基础上,将各个结点连接起来的表。下面是一个简单双向链表的实例:

public class LinkedList01 {
    public static void main(String[] args) {
        //简单双向链表
        Node jack = new Node("jack");
        Node tom = new Node("tom");
        Node red = new Node("red");

        //链接结点
        jack.next = tom;
        tom.next = red;
        tom.pre = jack;
        red.pre = tom;

        Node first = jack;//让first引用指向jack,就是链表的头结点
        Node last = red;//让last引用指向red,就是链表的尾结点

        //演示从到头尾遍历
        while (true){
            if (first == null){
                break;
            }
            System.out.println(first);
            first = first.next;
        }

3.2 LinkedList操作机制

  1. LinkedList 底层维护了一个双向链表;
  2. LinkedList 中维护了两个属性 first 和 last ,分别指向首结点和尾结点
  3. 每个结点(Node对象)里面维护了 prev、next、item 三个属性,其中 prev 指向前一个结点, next 指向后一个结点,item 存放元素,最终实现双向链表;
  4. LinkedList 的元素的添加和删除,不是通过数组完成的,相对来说效率较高;
  5. LinkedList 可以添加任意元素(元素可以重复),包括null;
  6. 线程不安全,没有实现同步。

3.3 扩容机制

与前面的 ArrayList 和 Vector 不同,LinkedList 的扩容机制实际上就是双向链表增加结点的操作。

    transient Node<E> first;
	transient Node<E> last;
	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;
        }
    }	

	public boolean add(E e) {
    	linkLast(e);
    	return true;
	}
    void linkLast(E e) {
        final Node<E> l = last;//l 指向尾结点
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;//last 指向新结点
        if (l == null)//尾结点为空,即链表为空时
            first = newNode;//first 指向新结点
        else
            l.next = newNode;//否则,在尾结点后方插入新结点
        size++;
        modCount++;
    }

3.4 ArrayList 和 LinkedList 比较

底层结构增删效率改查效率
ArrayList可变数组较低|
数组扩容
较高
LinkedList双向链表较高
通过链表追加
较低

在实际情况中,如何选择 ArrayList 和 LinkedList :

  1. 如果我们改查的操作多,选择 ArrayList ;
  2. 如果我们增删的操作多,选择 LinkedList ;
  3. 在一个项目中,根据业务灵活选择,也可能一个模块使用的是 ArrayList ,另外一个模块是 LinkedList 。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值