ArrayList 的实现原理

一、ArrayList 概述

ArrayList是List接口的可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数 组的大小。 每个ArrayList 实例都有一个容量,该容量是指用来存储列表元素的数组的大小。它总是大于等于列表的大小。随着向ArrayList 中不断添加元素,其容量也自动增长。自动增长会带来数据向新数组的重新拷贝,因此,如果可预知数据量的多少,可在构造 ArrayList 时 指定其容量。

注意,此实现不是同步的。如果多个线程同时访问一个 ArrayList 实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。 相对比的,Vector是线程安全的,其中涉及线程安全的方法皆被同步操作了。即使如此我们也不建议使用Vector,而是使用Collections工具类提供的synchronizedList(List list)将ArrayList转换为线程安全的。

二、ArrayList 的实现(以jdk1.8为例)

对于ArrayList 而言,它实现 List 接口、底层使用数组保存所有元素。其操作基本上是对数组的操作。下面我们来分析 ArrayList 的源代码:

1、底层使用数组实现
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
2、构造器

ArrayList 提供了三种方式的构造器:

  1. 可以构造一个默认初始容量为 10 的空列表(在添加第一个元素的时候默认初始化容量为10,而不是再创建对象的时候就初始化容量为10,,JDK1.7创建对象的时候就会默认初始化创建容量为10 的数组)
  2. 构造一个指定初始容量的空列表
  3. 构造一个包含指定 collection 的元素的列表

这些元素按照 该 collection 的迭代器返回它们的顺序排列的。


    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
    public ArrayList() {
    	// 1、可以构造一个默认初始容量为 10 的空列表
    	// (在添加第一个元素的时候默认初始化容量为10,而不是再创建对象的时候就初始化容量为10)
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
	
	private static final Object[] EMPTY_ELEMENTDATA = {};
	
	public ArrayList(int initialCapacity) {
		// 2、构造一个指定初始容量的空列表
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

    public ArrayList(Collection<? extends E> c) {
    	// 3、构造一个包含指定 collection 的元素的列表
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
	
3、存储

ArrayList 提供了 set(int index, E element)、add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)这些添加元素的方法。

add(E e)方法详解:
	// 将指定的元素添加到此列表的尾部
    public boolean add(E e) {
    	// 确保数组的容量能够装下新添加的元素,如果不够充足则进行扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
	
	private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
	
    private static final int DEFAULT_CAPACITY = 10;
	// elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 判断elementData 是否为空
	// 如果为空则此次添加元素是第一次添加元素,返回DEFAULT_CAPACITY
	// 否则返回minCapacity
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }
	
	// minCapacity - elementData.length > 0 
	// 如果增加一个元素后的容量比现有的容量大,则使用grow进行扩容
	private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
	
	// 扩容方法
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1); //  >> 表示除2,即扩容1.5倍
        if (newCapacity - minCapacity < 0) // 如果扩容后的容量让仍然不够存储,则将minCapacity作为新的数组的容量(addAll()添加集合时或使用到)
            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);  // 将原来的数组中的数据拷贝到新的数组中
    }

从上述代码中可以看出,数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长是其原容量的 1.5 倍。这种操作的代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。当我们可预知要保存的元素的多少时, 要在构造 ArrayList 实例时,就指定其容量,以避免数组扩容的发生。

4、读取
	// 返回此列表中指定位置上的元素。
    public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
    
    E elementData(int index) {
        return (E) elementData[index];
    }
5、删除

ArrayList 提供了根据下标或者指定对象两种方式的删除功能

	// 根据数组下标进行删除
    public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }
	
	// 根据对象进行删除
    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

三、总结:

3.1、ArrayList的源码分析:
3.1.1 jdk 7情况下
  •  ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData
    
  •  list.add(123);//elementData[0] = new Integer(123);
    
  •  ...
    
  •  list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。
    
  •  默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
    
  •   如果是list.addAll(Collection)添加元素,如果容量不够,像扩容1.5倍,
      如果仍然不够,则将原来数组的长度与新添加的集合的长度相加作为新的集合的长度
    
  •  结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
    
3.2.2 jdk 8中ArrayList的变化:
  •  ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没创建长度为10的数组
    
  •  list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
    
  •  ...
    
  •  后续的添加和扩容操作与jdk 7 无异。
    
3.2.3 小结:

jdk7中的ArrayList的对象的创建类似于单例的饿汉式, 而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。

3.2、LinkedList的源码分析:
  •  LinkedList list = new LinkedList(); 内部声明了Node类型的first和last属性,默认值为null
    
  •  list.add(123);//将123封装到Node中,创建了Node对象。
    
  •  其中,Node定义,体现了LinkedList的双向链表的说法
    
  •  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;
          }
      }
    
3.3、Vector的源码分析:

jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。在扩容方面,默认扩容为原来的数组长度的2倍。

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Java中的数组是一种线性数据结构,它由相同类型的元素组成并按照顺序存储在内存中。Java中的数组可以使用new关键字动态地创建,并且长度是固定的,一旦创建后就不能再改变它的大小。 Java中的ArrayList是一个动态数组,它可以自动扩容以适应元素的添加和删除。ArrayList实际上是基于数组实现的,它内部维护了一个Object类型的数组来存储元素。当ArrayList的容量不足时,它会根据一定的策略自动扩容数组的大小,以容纳更多的元素。 ArrayList实现原理主要包括以下几个方面: 1. 初始容量和扩容因子:当创建一个ArrayList时,可以指定一个初始容量和一个扩容因子。初始容量指定了ArrayList内部数组的初始大小,扩容因子指定了数组增长的比例。默认情况下,初始容量为10,扩容因子为1.5。 2. 自动扩容:当ArrayList内部数组的容量不足时,它会自动扩容。具体实现是创建一个新的数组,将原数组中的元素复制到新数组中,并将新元素添加到新数组中。 3. 随机访问:由于ArrayList是基于数组实现的,因此它支持随机访问。可以通过索引来快速访问元素,时间复杂度为O(1)。 4. 插入和删除:在ArrayList中插入和删除元素的时间复杂度取决于操作的位置。如果在末尾插入或删除元素,时间复杂度为O(1),否则需要将后面的元素都向后移动,时间复杂度为O(n)。 总之,ArrayListJava中常用的动态数组,它的实现基于数组,并且支持随机访问、自动扩容等特性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

b u g

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值