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

Java集合

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

  • 数组能存放基本数据类型和对象,而集合类存放的都是对象的引用(对象在内存中的存储地址,一个哈希值),而非对象本身。
  • 一个数组只能存放一种类型的数据,但是一个集合可以存放的类型不止一种,如List<Object> 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<Integer> 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<String> 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<String> 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> {
        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 LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

具体使用方法:

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

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

public boolean addAll(Collection<? extends E> c) {
        return addAll(size, c);
    }

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

  1. 检查index范围是否在size之内;
  2. toArray()方法把集合的数据存入对象数组中;
  3. 得到插入位置的前驱和后继结点;
  4. 遍历数据,插入到指定位置。
public boolean addAll(int index, Collection<? extends E> c) {
        checkPositionIndex(index); //检查index范围是否在size之内

        Object[] a = c.toArray();  //把集合的数据存入对象数组中
        int numNew = a.length;
        if (numNew == 0)
            return false;

        Node<E> 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<E> 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<String> list = new ArrayList<>();
    list.add("good");
    list.add("thing");
    List<String> 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类似,底层也都是数组实现,但是相关方法都加了同步检查,因此线程安全,效率低。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容