java基础-数据容器之集合List

上一节讨论数组时,我们知道数据容器是用来存放数据的,那么既然数组就可以集中存储一批业务相关的数据,为什么jdk还要提供其它“集合”类?

我一直认为世界上任何一种技术或事物的产生一定是因为它解决了以前技术或事物没有或不能解决的问题,至少是对“前任”的改进。那么集合到底解决了什么问题?或改进了什么问题?


先了解一下数组:

特点

  • A:内存-预先开辟了一块连续的内存
  • B:查询速度-因为内存连续,下标固定,所以查询速度极快,复杂度衡定
  • C:插入、删除速度-因为内存连续,必须移动数据,才能空出位置或移处位置,以方便插入值或删除值
  • D:无法扩展-因为内存预先连续分配,所以扩展时无法保证内存尾部刚好有足够的空闲内存可用,因此无法扩展,只能全部重新分配内存。
  • E:数据游标-数据放入数组,并没有一个值记录当前数据存放到第几个位置,需要使用方自己维护。否则需要通过遍历找到数据尾部第一个空位置。
  • F:数据可空-不能保证存储的数据为可空或非空
  • G:数据重复-不能保证存储的数据是否重复与否
  • H:性能-不能保证不同操作对应性能为最优情况
  • I:安全-不能保证并发或并行操作数据安全


数组有自己的优点,也有自己的短板,新的集合相关类,就是为了解决上面所有问题中的部分,不同的集合类解决不同的问题。


数据容器种类及主要实现类



 集合关注点:

  • A:是否允许空
  • B:是否允许重复数据
  • C:是否有序:(有序不是指是否排充,而是指取数据的顺序与存数据的顺序是否一样:如数据a[2]=1,那么a[2]取仍然是1,这就是有序。但a[2]=1,a[3]=0, a[4]=5并不影响数组是有序的)
  • D:是否线程安全 


Collection-集合根接口

Collection接口是所有集合类(Set/list/queue)的根接口。

它的主要功能有:

  • 增:add/addAll
  • 删:remove/removeAll/removeIf/clear
  • 改:
  • 查:iterator(遍历)
  • 是否存在:contains/containsAll
  • 长度:size/isEmpty
Collection根接口,只规定了核心的容器操作功能,并未做具体限制,是否为空、是否允许重复、是否有序、是否安全由子接口及其子类规定并实现。


今天只讨论List

List接口:

我们在前面提到集合的关注点主要是:是否为空、是否重复、是否有序、是否安全,那么集合的不同子接口的主要差别也基本围绕这向个方面。

List是一个:可空、可重复、有序的数据容器。是否安全由子实现类决定。

List实现了Collection接口的所有方法,同时提供了自己的特色方法:

  • 增:set/add(int index,E e)
  • 删:remove(int index)-通过下标删除数据
  • 查:get/
  • 下标查询:index/indexOf/
  • 排序:sort

List有很多不同的实现类,分别通过不同的底层实现以关注不同的方面,主要有ArrayList、LinkedList、Vector、Stack。


ArrayList:

  ArrayList是一个实现了List接口的实现类,可空、可重复、有序的数据容器、线程不安全、查询性能极高(o(1))、删除和插入性能较差、可自动扩展。

 

  通过名称就可以看出ArrayList是一个底层通过数组来实现的List,因此它拥有数组的所有特点。


  • 底层存储
打开源码,会发现ArrayList通过一个 transient Object[] elementData; 数组来存储数据元素。
 /**
     * Default initial capacity.
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * Shared empty array instance used for empty instances.
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access


  • 初始数组大小
创建数组必须指定大小,但ArrayList是一个可以自动扩展的数据集合,创建时通常并不需要指定大小(可以指定),那么底层的数组必须设置一个默认大小,根据源码可知,这个数组默认是一个长度为0的数组。
    /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access
  

   /** 此处:构造方法如果未指定大小,则使用
DEFAULTCAPACITY_EMPTY_ELEMENTDATA这个长度为0的空数组
* Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
 
  
 
  
 
  
 
  
 
 
这样的好处是:创建的List并不知道什么时间使用,以及是否使用,因此不需要预先分配一段内存,防止浪费减少开支。
  • 自动扩容
根据上面的分析可知,如果创建List时未指定初始长度,那么存储元素的数组是一个空数组,它并不能存储任何数据,但实际插入数据时,必须有一个还有空闲位置的数组才能成功存储。

根据源码分析可知,ArrayList是通过插入时检查数组的长度来实现是否自动扩容。
/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!! 检查是否需要扩容,如果需要则扩容
        elementData[size++] = e;
        return true;
    }

    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);//扩容
    }

 /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1); //容量为原来基础上增加50%
        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);
    }

从源码中可以看出:当插入元素时,会检查当前是否还有空位置,如果没有,则使用默认长度(第一次增加时,默认为10)或在原来长度基础上增加50%(
int newCapacity = oldCapacity + (oldCapacity >> 1)

扩容方式:主要通过Arrays工具类的copyOf实现,具体可参考对应的源码(创建一个新数组,并把原数组数据一一复制过去)
  • 插入
插入操作分为:集合尾部插入和集合中间插入。尾部插入只需要找到尾部第一个空位即可。中间插入则涉及到数据的移动。

    /**
     * Inserts the specified element at the specified position in this
     * list. Shifts the element currently at that position (if any) and
     * any subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);//数据移动
        elementData[index] = element;//插入
        size++;
    }

主要移动方式通过System.arraycopty来实现的,它是通过本地方法实现,大概过程如下:



  • 删除
remove: 删除原理同上,移动过程与插入方向相反:插入是向后移动-留空。删除操作为向前移动-覆盖。

注意事项:如果是遍历删除,不要使用for循环,因为循环过程会导致游标向前移动,删除后数据又会向后移动,导致游标位置错乱。
                  最好使用iterator遍历,它会正常维护游标位置。
  • 查询
查询主要通过两种方式:一种是通过数组的下标查询,另一种通过Iterator模式遍历。
  • 序列化
细心的朋友会注意到list中的数组前使用了transient关键字,它的意思是对象在序列化时不要序列化当前关键字修饰的属性。
 transient Object[] elementData;

可是,如果不序列化elementData,那么反序列化后,数组中的元素不是全没了吗?

  /**
     * Save the state of the <tt>ArrayList</tt> instance to a stream (that
     * is, serialize it).
     *
     * @serialData The length of the array backing the <tt>ArrayList</tt>
     *             instance is emitted (int), followed by all of its elements
     *             (each an <tt>Object</tt>) in the proper order.
     */
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

    /**
     * Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
     * deserialize it).
     */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;

        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in capacity
        s.readInt(); // ignored

        if (size > 0) {
            // be like clone(), allocate array based upon size not capacity
            ensureCapacityInternal(size);

            Object[] a = elementData;
            // Read in all elements in the proper order.
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();
            }
        }
    }

根据源码可知,ArrayList自己实现了序列化和反序列化方法,因为数组是提前分配长度的,而实际数组中的值可能并未装满数组,那么序列化空值毫无意义,因此/arrayList只序列化有值的部分(中间可能有空值)。


  • LinkedList
未完待续 。。。




猜你喜欢

转载自blog.csdn.net/yangspgao/article/details/78441424