JAVA 集合

 

1.框架图

 

图源:(108条消息) java集合框架_程序员老石的博客-CSDN博客_java集合框架

 

 

 

 2.Collection API

public boolean add(E e);添加元素
public void clear();清空
public boolean remove(E e);指定的元素删除掉
public int size();返回集合中元素的个数
public Object[] toArray();把集合中的元素存到数组里面。

3 ArrayList

3.1 特性

ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacity操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。

  • 它继承于 AbstractList,实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口。
  • ArrayList 插入删除元素的时间复杂度为O(n),求表长以及增加元素,取第 i 元素的时间复杂度为O(1)
  • ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
  • ArrayList 实现了RandomAccess 接口, RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持快速随机访问的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。
  • ArrayList 实现了Cloneable 接口,即覆盖了函数 clone(),能被克隆。
  • ArrayList 实现java.io.Serializable 接口,这意味着ArrayList支持序列化,能通过序列化去传输。
  • 和 Vector 不同,ArrayList 中的操作不是线程安全的!所以,建议在单线程中才使用 ArrayList,而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList。

3.2 ArrayList 扩容机制

 

private static final int DEFAULT_CAPACITY = 10;//数组默认初始容量

private static final Object[] EMPTY_ELEMENTDATA = {};//定义一个空的数组实例以供其他需要用到空数组的地方调用

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//定义一个空数组,跟前面的区别就是这个空数组是用来判断ArrayList第一添加数据的时候要扩容多少。默认的构造器情况下返回这个空数组

transient Object[] elementData;//数据存的地方它的容量就是这个数组的长度,同时只要是使用默认构造器(DEFAULTCAPACITY_EMPTY_ELEMENTDATA )第一次添加数据的时候容量扩容为DEFAULT_CAPACITY = 10

private int size;//当前数组的长度

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//允许分配的最大数组容量

public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}//创建了一个空的elementData数组


//添加函数
public boolean add(E e) {
        ensureCapacityInternal(size + 1);  //确定是否要扩容
        elementData[size++] = e;//赋值,size++
        return true;
        }

/**
 * 确定minCapacity
 */
private void ensureCapacityInternal(int minCapacity){
    ensureExplicitCapacity(calculateCapacity(elementData,minCapacity));
}
/**
/计算minCapacity
 */
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    //若elementData为空,那么第一次扩容为10
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //否则返回size+1
        return minCapacity;
}
/**
 *
 */

private void ensureExplicitCapacity(int minCapacity) {
        modCount++;//操作次数+1

        // 如果要当前elementData的长度小于要求的minCapacity则扩容
        if (minCapacity - elementData.length > 0)
        grow(minCapacity);//扩容函数
}
private void grow(int minCapacity) {
       //将原本的长度记录下来
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);//原长度的1.5倍
        if (newCapacity - minCapacity < 0)//判断扩充后的容量是否满足要求,如果不满足要求,直接将容量扩大到指定大小
{
newCapacity = minCapacity;

if (newCapacity - MAX_ARRAY_SIZE > 0)//如果1.5倍超出数组最大长度即INT_MAX-8
        newCapacity = hugeCapacity(minCapacity);//抛出异常
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);//扩容使用的函数
        }

  

当创建ArrayList对象的时候,如果使用的是无参构造器,则初始elementData容量为0,第一次添加,则扩容elementData为10,如需再次扩容, 则扩容elementData1.5倍
如果使用的是指定大小的构造器,则初始化elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍  // 例:8->12->18

 

3.3 ArrayList 和 Vector

  • Vector与ArrayList一样,也是通过数组实现的,Vector类的所有方法都是同步的。它也是线程安全的,而Arraylist是线程异步(ASynchronized)的,是不安全的

如果不考虑到线程的安全因素,一般用Arraylist效率比较高。

  • 使用ArrayList时,如果不指定大小,会生成一个空的数组
  • 使用Vector时,如果不指定大小,会默认生成一个10个元素大小的数组
  • Vector 实现类中有一个变量 capacityIncrement 用来表示每次容量自增时应该增加多少,如果不指定,默认为0
  • 在扩容时,会判断,如果指定了capacityIncrement,会先把数组容量扩大到oldCapacity + capacityIncrement,如果没有指定capacityIncrement,会先把数组容量扩大到2倍的oldCapacity, 然后再进行判断扩充后的容量是否满足要求,如果不满足要求,直接将容量扩大到指定大小

3.4 LinkedList

 LinkedList是一个实现了List接口和Deque接口的双端链表。 LinkedList底层的链表结构使它支持高效的插入和删除操作,另外它实现了Deque接口,使得LinkedList类也具有队列的特性; LinkedList不是线程安全的,如果想使LinkedList变成线程安全的,可以调用静态类Collections类中的synchronizedList方法:

List list=Collections.synchronizedList(new LinkedList(...));

看LinkedList类中的一个内部私有类Node就很好理解了:

private static class Node<E> {
        Node<E> prev;//前驱节点
        E item;//节点值
        Node<E> next;//后继节点
 
        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

 

 

 

linkedList.remove(); // 这里默认删除的是第一个结点
          1. 执行 removeFirst
            public E remove() {
                return removeFirst();
            }
         2. 执行
            public E removeFirst() {
                final Node<E> f = first;
                if (f == null)
                    throw new NoSuchElementException();
                return unlinkFirst(f);
            }
          3. 执行 unlinkFirst, 将 f 指向的双向链表的第一个结点拿掉
            private E unlinkFirst(Node<E> f) {
                // assert f == first && f != null;
                final E element = f.item;
                final Node<E> next = f.next;
                f.item = null;
                f.next = null; // help GC
                first = next;
                if (next == null)//
                    last = null;
                else
                    next.prev = null;
                size--;
                modCount++;
                return element;
            }
         */

 

/*
*get(index)
*/
public E get(int index) {
        checkElementIndex(index);//确定是否符合范围
        return node(index).item;
    }

 private void checkElementIndex(int index) {
        if (!isElementIndex(index))//错误则抛出异常
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
     private boolean isElementIndex(int index) {
        return index >= 0 && index < size;//判断
    }
    //取元素,遍历取
   Node<E> node(int index) {
        // assert isElementIndex(index);
     
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {   //超过1/2则从后往前取
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

3.5 ArrayList与LinkedList的区别

  • 是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
  • 底层数据结构: Arraylist 底层使用的是Object数组;LinkedList 底层使用的是双向链表数据结构;
  • 插入和删除是否受元素位置的影响: ① ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e) 方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element) )时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)而数组为近似 O(n)。
  • 是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index) 方法)。
  • 内存空间占用: ArrayList的空间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。

3.6 ArrayList 与Array

1.Array可以包含基本类型和对象类型,ArrayList只能包含对象类型;(所以ArrayList有个自动装箱的过程)
2.Array(数组)的大小是固定的,ArrayList(列表)的大小是动态变化的;
3.ArrayList提供了更多的方法和特性:addAll()、removeAll()、iterator等;
4.对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。

 

posted @ 2022-06-16 19:15  blakee  阅读(27)  评论(0编辑  收藏  举报