【Java基础】分析List接口常用实现类

Java集合架构图

点击放大查看
在这里插入图片描述List , Set, Map都是接口,List , Set继承至Collection接口(Collection继承至Iterable),Map为独立接口

List接口简介

  1. List接口继承了Collention接口(继承Iterable)
  2. 可以允许重复的对象,可以插入多个null元素,输出的顺序就是插入的顺序
  3. 只能保存对象类型的数据,基本类型的数据会自动装箱成包装类
  4. 实现类有ArrayListLinkedListVactorStackArrayList 最为流行,它提供了使用索引的随意访问,而 LinkedList 则对于经常需要从 List 中添加或删除元素的场合更为合适。

List接口直接或间接实现类简介

ArrayList
  • 底层是动态数组结构,数据查询方便、数据增删改不方便,线程不安全,本质上就是通过创建新的更大的数组,将旧数组内容拷贝到新数组,来实现扩容

  • 当调用无参构造方法来创建ArrayList时,默认容量为10, 当添加的数据容量超过数组容量大小时,会产生一个新数组,新数组容量大小为原数组容量的1.5倍,接着把原数组中的数据复制到新数组中。

    常用方法
    get(int index) 不需要遍历数组,速度快;
    iterator() 方法中调用了get(int index),所以速度也快
    set(int index, E e) 不需要遍历数组,速度快
    add(Object o) 方法需要考虑扩容与数组复制问题,速度慢
    remove(Object o) 需要遍历数组,并复制数组元素,速度慢
    remove(int index) 不需要遍历数组,需要复制数组元素,但不常用
    contain(E) 需要遍历数组
    clear(): 清空表

LinkedList
  • LinkedList底层数据结构是环形双向链表,查询慢,增删快,线程不安全

  • (LinkedList实现了接口Deque,可以被当做队列或者栈使用)

  • 它内部封装的是双向链表的数据结构,每个节点是一个Node对象,Node对象中封装的是你要添加的元素,还有一个指向上一个Node对象的引用和一个指向下一个Node对象的引用。
    每个节点都应该有3部分内容:

    class  Node {
    	Node  previous;   //前一个节点    
    	Object  element;    //本节点保存的数据  
    	Node  next;       //后一个节点    
    }
    

    常用方法
    get(int index) 需要遍历链表,速度慢;
    iterator() 方法中调用了get(int index),所以速度也慢
    set(int index, E e) 方法中调用了get(int index),所以速度也慢
    add方法不需要考虑扩容与数组复制问题,只需创建新对象,再将新对象的前后节点的指针指向重新分配一下就好,速度快
    remove(Object o) 需要遍历链表,但不需要复制元素,只需将所要删除的对象的前后节点的指针指向重新分配一下以及将所要删除的对象的三个属性置空即可,速度快
    remove(int index) 需要遍历链表,但不需要复制元素,只需将所要删除的对象的前后节点的指针指向重新分配一下以及将所要删除的对象的三个属性置空即可,但不常用
    contain(E) 需要遍历链表

ArrayList与LinkedList性能对比

模拟10w条数据指定插入第一位,然后查询全部循环删除第一位

public class TestList {
    @Test
    public void mian() {
        testArrayList();
        testLinkedList();
    }

    /**
     * 测试ArrayList新增/查询/删除100000次
     */
    @Test
    public void testArrayList() {
        List<String> list = new ArrayList<>();
        int maxTestCount = 100000;

        //--------测试添加--------
        long start = System.currentTimeMillis();
        for (int i = 0; i < maxTestCount; i++) {
            list.add(0, String.valueOf(i));
        }
        long end = System.currentTimeMillis();
        System.out.println("ArrayList add cost time :" + (end - start));

        //--------测试查询--------
        start = System.currentTimeMillis();
        for (int i = 0; i < maxTestCount; i++) {
            list.get(i);
        }
        end = System.currentTimeMillis();
        System.out.println("ArrayList get cost time :" + (end - start));

        //--------测试删除--------
        start = System.currentTimeMillis();
        for (int i = maxTestCount; i > 0; i--) {
            list.remove(0);
        }
        end = System.currentTimeMillis();
        System.out.println("ArrayList remove cost time :" + (end - start));
    }

    /**
     * 测试LinkedList新增/查询/删除100000次
     */
    @Test
    public void testLinkedList() {
        List<String> list = new LinkedList<>();
        int maxTestCount = 100000;

        //--------测试添加--------
        long start = System.currentTimeMillis();
        for (int i = 0; i < maxTestCount; i++) {
            list.add(0, String.valueOf(i));
        }
        long end = System.currentTimeMillis();
        System.out.println("LinkedList add cost time :" + (end - start));

        //--------测试查询--------
        start = System.currentTimeMillis();
        for (int i = 0; i < maxTestCount; i++) {
            list.get(i);
        }
        end = System.currentTimeMillis();
        System.out.println("LinkedList get cost time :" + (end - start));

        //--------测试查询--------
        start = System.currentTimeMillis();
        for (int i = maxTestCount; i > 0; i--) {
            list.remove(0);
        }
        end = System.currentTimeMillis();
        System.out.println("LinkedList remove cost time :" + (end - start));
    }
}

执行结果:
在这里插入图片描述
可以

  • ArrayList插入、删除效率明显低于LinkedList
  • ArrayList查询效率远远高于LinkedList

这个和数据结构有关系了,

  • Arraylist 顺序表,用数组的方式实现。查询哪个元素只给出其下标即可,所以才说ArrayList随机访问多的场景比较合适。但是如果删除某个元素比如第 i 个元素,则要将 i 之后的元素都向前移一位以保证顺序表的正确,增加也是一样,要移动多个元素。要多次删除增加的话是很低效的。
  • LinkedList是双向链表。只能从头节点开始逐步查询,没有直接根据下标查询的方法,需要遍历。但是,如果要增加后删除一个元素的话,只需要改变其前后元素的指向即可,不需要像ArrayList那样整体移动,所以才说多用于增删多的场合
Vector(线程安全的ArrayList)
  • Vector底层是动态数组结构,线程不安全,和ArrayList类似,但属于强同步类(所有的方法都是synchronized修饰),增删查都效率低

  • 扩容机制与ArrayList不同, 当使用无参构造方法创建Vector时,默认长度为10,当添加的数据容量超过数组的容量大小时,会产生一个新数组,新数组容量大小为原数组容量大小的2倍

  • 建议如果你的程序本身是线程安全的(没有在多个线程之间共享同一个集合/对象),那么使用ArrayList是更好的选择。

  • Vector除了继承Collection定义的所有方法,也定义了自己的一些方法

方法 描述
void add(int index, Object element) 从index索引的位置添加element元素,后面的元素都往后移一位。
boolean add(Object o) 将指定元素添加到此向量的末尾
boolean addAll(Collection c) 将指定 Collection 中的所有元素添加到此向量的末尾,按照指定 collection 的迭代器所返回的顺序添加这些元素。
boolean addAll(int index, Collection c) 从index索引位置开始添加c集合里所有的元素,后面的元素都往后移c.size()位
void addElement(Object obj) 将指定的组件添加到此向量的末尾,将其大小增加 1。无论该元素是什么类型的,都会把他的toString()的返回值添加进去
int capacity() 返回此向量的当前容量,不是元素个数。
void copyInto(Object[] anArray) 把集合中的元素复制到anArray数组中去
Object elementAt(int index) 返回索引位置的元素
Enumeration elements() 返回集合的枚举
void ensureCapacity(int minCapacity) 增加集合的容量,如果增大的容量小于10,那么无效,也就是增大容量要是10倍数
Object firstElement() 返回此向量的第一个元素(位于索引 0) 处的项)。
void insertElementAt(Object obj, int index) 将指定对象插入到向量的 指定index 处
Object lastElement() 返回此向量的最后一个元素
void removeAllElements() 删除集合的所有元素,并且设置容量为0,和clear()方法一样,clear底层也是用removeAllElements()方法的
void setSize(int newSize) 设置集合的容量大小为newSize,如果newSize大于集合元素个数,那么会在后面添加null,如果newSize小于集合元素个数,那么直保留newSize个元素
void trimToSize() 整理集合的容量大小,如果集合元素个数等于容量大小,那么没有变化,如果集合个数小于容量大小,那么容量会设置为元素个数大小
Stack(继承于Vector)
  • Stack(实现堆栈)继承于Vector,底层是动态数组结构,线程安全.

  • Stack继承自Vector,实现一个后进先出/先进后出的堆栈

  • 堆栈只定义了默认构造函数,用来创建一个空栈(Stack刚创建后是空栈)

  • Stack除了继承Vector定义的所有方法,也定义了自己的一些方法。

方法 描述
boolean empty() 测试堆栈是否为空
Object peek( ) 查看堆栈顶部的元素,但不从堆栈中移除它。
Object pop( ) 移除堆栈顶部的元素,并返回此对象
Object push(Object element) 把元素压入堆栈顶部。
int search(Object element) 返回元素在堆栈中的位置,以 1 为基数
  • 效率低下,可采用双端队列DequeLinkedList来实现,Deque用的较多

示例代码

import java.util.*;
public class StackDemo {
    /**
     * 把元素压入栈顶,并打印
     *
     * @param stack 栈对象
     * @param num  要压栈的元素
     */
    static void showPush(Stack<Integer> stack, int num) {
        stack.push(new Integer(num));
        System.out.println("push(" + num + ")");
        System.out.println("stack: " + stack);
    }
    /**
     * 移除栈顶元素,并打印
     *
     * @param stack 栈对象
     */
    static void showPop(Stack<Integer> stack) {
        System.out.print("pop -> ");
        Integer a = (Integer) stack.pop();
        System.out.println(a);
        System.out.println("stack: " + stack);
    }

    public static void main(String args[]) {
        Stack<Integer> stack = new Stack<Integer>();
        System.out.println("stack: " + stack);
        // 压栈
        showPush(stack, 1);
        showPush(stack, 2);
        showPush(stack, 3);
        // 出栈
        showPop(stack);
        showPop(stack);
        showPop(stack);
        try {
            showPop(stack);
        } catch (EmptyStackException e) {
            System.out.println("empty stack");
        }
    }
}

执行结果:
在这里插入图片描述

总结

  1. 局部变量不存在线程安全问题时,在查询(get)、遍历(iterator)、修改(set)使用的比较多的情况下,用ArrayList
  2. 局部变量不存在线程安全问题时,在增加(add)、删除(remove)使用比较多的情况下,用LinkedList
  3. 在需要线程安全而且对效率要求比较低的情况下,使用Vector,当然,实现ArrayList线程安全的方法也有很多

ArrayList是线程不安全的,轻量级的。如何使ArrayList线程安全?

  1. 继承Arraylist,然后重写或按需求编写自己的方法,这些方法要写成synchronized,在这些synchronized的方法中调用ArrayList的方法。
  2. List list = Collections.synchronizedList(new ArrayList());
  1. 在需要使用栈结构的情况下,使用DequeStack废弃就行了
发布了62 篇原创文章 · 获赞 109 · 访问量 5296

猜你喜欢

转载自blog.csdn.net/qq877728715/article/details/103025443
今日推荐