Java集合详解(一)——ArrayList、LinkedList与Vector

Java集合

数组是一种数据结构,用来存储同一类型值,数组不是面向对象的,集合弥补了数组的缺点,更加灵活使用。

  • 数组能存放基本数据类型和对象,而集合类存放的都是对象的引用(对象在内存中的存储地址,一个哈希值),而非对象本身。
  • 一个数组只能存放一种类型的数据,但是一个集合可以存放的类型不止一种,如List list 时可以存放多种数据类型,但是不建议这样使用。
  • 数组一旦创建长度是固定的,集合类容量可以动态改变。
  • 数组无法判断实际存储了多少元素,length只告诉了数组的容量,而集合的size()方法可以确切得到元素的个数。
  • 数组顺序存储元素,而集合不一定。
  • 集合以类的形式存在,具有封装、继承和多态等类的特性,通过简单的方法可以实现各种复杂的操作,大大提高软件开发效率。
  • Java集合框架

    Java集合类库将接口与实现类分离。
    Collection接口和Map接口是集合框架的两个根接口

    Collection接口

    Collection接口扩展了Iterable接口,因此对于标准类库中的任何集合都可以使用"for each"循环
    Collection的子接口:

    • Set接口,具有实现类HashSetLinkedHashSet
      • Set的子接口SortedSet接口,具有实现类TreeSet
    • List接口,具有实现类LinkedListVectorArrayList

    List有序、可重复
    ArrayList由于底层是数组,所以各个元素在内存中存储位置是连续的,查询快,插入与删除慢。
    LinkedList由于底层是链表,各结点通过指针连接起来,但是各个结点在内存中的位置不一定连续,插入与删除快,查询慢。

    **Set无序、唯一。

    ArrayList

    ArrayList底层是Object类型动态数组实现的,与Java数组相比,其容量能够动态增长,本质上是定义新的更大的数组,将旧的数组内容拷贝到新数组来实现扩容。

    //默认初始化容量为10
    private static final int DEFAULT_CAPACITY = 10;
    public void ensureCapacity(int minCapacity) {
            if (minCapacity > elementData.length
                && !(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
                     && minCapacity <= DEFAULT_CAPACITY)) {
                modCount++;
                grow(minCapacity);
            }
        }
    

    ArrayList中的size()方法是获取的集合的当前元素数量,并不是集合的容量(底层动态数组的长度)。源码中科院发现私有属性elementData用于存放数组元素,通过反射机制获取得到elementData的值并通过elementData.length就可以得到集合的容量。

     public static Integer getCapacity(ArrayList list) {
            Integer length = null;
            Class c = list.getClass();
            Field f;
            try {
                f = c.getDeclaredField("elementData");
                f.setAccessible(true);
                Object[] o = (Object[]) f.get(list);
                length = o.length;
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            return  length;
         }
    

    新创建一个集合对象,不添加元素是,其容量和大小都是0,添加一个元素之后,其容量为10,大小为1。

    ArrayList list = new ArrayList<>();
            System.out.println("容量:" + getCapacity(list));
            System.out.println("大小:" + list.size());
            list.add(8);
            System.out.println("容量:" + getCapacity(list));
            System.out.println("大小:" + list.size());
    

    当往该集合对象中添加的元素为11个,即超过了默认容量大小时,会进行扩容,新的容量为原来容量的1.5倍。例如容量依次为10,15,22....。

    ArrayList继承了AbstractList类,实现了ListRandomAccessCloneablejava.io.Serializable接口。
    ArrayList实现了RandomAccess接口,RandomAccess是一个标志接口,表明实现这个接口的List结合是支持快速随机访问的,在ArrayList中,可以通过元素的序号快速获取元素对象,即快速随机访问。
    ArrayList实现了Cloneable接口,即覆盖了函数clone(),能被克隆。
    ArrayList实现了java.io.Serializable接口,意味着ArrayList支持序列化,能通过序列化去传输。
    ArrayList线程不安全,效率高。

    toArray()方法用法

        List list = new ArrayList<>();
        list.add("good");
        list.add("thing");
        String[] str = (String[]) list.toArray();
        System.out.println(str.toString());
    

    以上使用会报错 java.base/[Ljava.lang.Object; cannot be cast to java.base/[Ljava.lang.String;

        List list = new ArrayList<>();
        list.add("good");
        list.add("thing");
        String[] str = new String[list.size()];
        list.toArray(str);
    

    以上使用可以正确将集合对象转成一个新的数组。

    LinkedList

    LinkedList底层是一个双向链表,实现了ListDequeCloneablejava.io.Serializable接口。
    LinkedList底层的链表结构使它支持高校的插入和删除操作,另外实现了Deque接口,使得LinkedList类具有队列的特性
    LinkedList线程不安全,如果使其对象线程安全,可调用静态类Collections类中的synchronizedList方法:

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

    可利用LinkedList实现栈(stack)、队列(queue)、双向队列(doubie-ended queue)。常用方法有addFirst()、addLast()、getFirst()、getLast()、removeFirst()、removeLast()等。

    源码分析

    LinkedList类中有一个内部私有类Node:

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

    用已有的集合创建链表的构造方法:

    public LinkedList(Collection c) {
            this();
            addAll(c);
        }
    

    具体使用方法:

        List list = new ArrayList<>();
        list.add("good");
        list.add("thing");
        LinkedList list2 = new LinkedList(list);
        System.out.println(list2.getFirst() + list2.getLast());
    

    将已有集合插入到链表尾部:

    public boolean addAll(Collection c) {
            return addAll(size, c);
        }
    

    将已有集合插入到链表指定位置:

    1. 检查index范围是否在size之内;
    2. toArray()方法把集合的数据存入对象数组中;
    3. 得到插入位置的前驱和后继结点;
    4. 遍历数据,插入到指定位置。
    public boolean addAll(int index, Collection c) {
            checkPositionIndex(index); //检查index范围是否在size之内
    
            Object[] a = c.toArray();  //把集合的数据存入对象数组中
            int numNew = a.length;
            if (numNew == 0)
                return false;
    
            Node pred, succ; //插入位置的前驱和后继结点
            if (index == size) { //如果插入位置为尾部,前驱结点为last,后继结点为null
                succ = null;
                pred = last;
            } else {  //否则调用node()方法得到后继结点,再得到前驱结点
                succ = node(index);
                pred = succ.prev;
            }
            //遍历数据将其插入链表中
            for (Object o : a) {
                @SuppressWarnings("unchecked") E e = (E) o;
                Node newNode = new Node<>(pred, e, null); //创建新结点
                if (pred == null) //插入位置在链表头部
                    first = newNode;
                else
                    pred.next = newNode;
                pred = newNode;
            }
            //插入位置在链表尾部
            if (succ == null) {
                last = pred;
            } else {
                pred.next = succ;
                succ.prev = pred;
            }
    
            size += numNew;
            modCount++;
            return true;
        }
    

    具体使用方法如下:

        List list = new ArrayList<>();
        list.add("good");
        list.add("thing");
        List list2 = new LinkedList<>();
        list2.add("Southeast");
        list2.add("University");
        list2.addAll(1, list);
        for (String s : list2)
            System.out.println(s);
    

    输出list2为["Southeast","good","thing","University"]。

    获取头结点(index = 0)数据的方法有getFirst()element()peek()peekFirst()
    前两种方法在链表为空时抛出NoSuchElementException异常,后两种方法返回null。

    获取尾结点(index = -1)数据方法有getLast()peekLast()
    前者在链表为空是抛出NoSuchElementException异常,后者返回null。

    根据对象获取索引的方法有:

    public int indexOf(Object o) {...} //从头遍历
    
    public int lastIndexOf(Object o) {...} //从尾遍历
    

    删除结点:
    删除头结点的方法有E remove()E removeFirst()E pop()
    删除尾结点的方法有E removeLast()E pollLast(),前者在链表为空时抛出NoSuchElementException异常,后者返回null。
    删除指定元素的方法boolean remove(Object o),一次只能匹配并删除一个。
    删除指定位置元素的方法E remove(int index)

    Vector

    Vector与ArrayList类似,底层也都是数组实现,但是相关方法都加了同步检查,因此线程安全,效率低。

    你可能感兴趣的:(Java集合详解(一)——ArrayList、LinkedList与Vector)