java面试基础 -- ArrayList 和 LinkedList有什么区别, ArrayList和Vector呢?

简介: java面试基础 -- ArrayList 和 LinkedList有什么区别, ArrayList和Vector呢?


基本介绍

还记得我们的java集合框架吗, 我们来复习一下, 如图:

        可以看出来 ArrayList和LinkedList 都是具体类, 他们都是接口List的实现类.

但是他们底层的逻辑是不同的, 相信学过这个的应该大概有个映像吧, 如下图:

       可以看出来, ArrayList是向内存申请一块连续的数组空间进行存储, 在数组的存储形式的基础上进行链表的增删改查, 而LinkedList则是每次添加元素的时候就向系统申请一块内存, 不用就直接释放, 他们虽然在内存上不是连续的, 但是在逻辑上他们是连在一起的.

有什么不同??

  • 底层实现不同, ArrayList是基于动态数组的数据结构, 而LInkedLIst是基于双向链表的数据结构
  • 随机访问的性能不同, Arraylist的随机访问性能是由于LinkedList的, 因为ArrayList可以根据下标来直接访问, 类似于数组, 时间复杂度为O(1), 但是LinkedList的随机访问时间复杂度为O(n), 因为他需要遍历整个链表才能找到指定的元素
  • 插入和删除不同, 对于插入和删除, LInkedList要明显由于ArrayList, 因为LInkedList的掺入和删除操作时间复杂度为O(1), 例如插入, 我们可以直接在链表的头部进行插入或者是通过一个元素来记录最后一个结点的位置, 然后直接在最后一个结点进行尾插, 删除是相同的操作, 因此时间复杂度为O(1), 但是对于ArrayList就不同了, ArrayList的插入和删除需要移动插入位置的元素的后面的所有元素, 最坏的情况需要移动ArrayList的所有元素, 因此时间复杂度为O(n)

但是需要注意的是, 其实他们插入的平均时间复杂度是一样的, 接下来我们来探讨一下, 他们两个的平均时间复杂度:

  • 对于ArrayList的插入的平均时间复杂度, 它想要插入一个数字, 虽然是直接指定插入的地方, 也就是他的一个随机插入的性质, 但是它还需要对后面的元素进行一个挪动, 运气不好的话, 就需要挪动整个数组, 时间复杂度最坏的情况下是O(n), 最坏的情况下是O(1), 平均下来就是O(n/2), 我们去掉系数, 也就是O(n)
  • 对于LinkedList来说, 他进行插入, 需要遍历链表,从头到尾遍历, 好的情况就是在头的时候就便利到了, 时间复杂度为O(1), 坏的情况下需要遍历完整个链表, 时间复杂度最坏的情况就是O(n),平均下来就是O(n/2) ,去掉系数就是O(n)

   所以对于我们真是运用链表的场景, 没有说他们两个哪个时间复杂度, 或者是性能更好, 只能是在不同的情况下适应不同的场景.

小结:

       ArrayList 和 LinkedList 都是 List 接口的实现类,但它们的底层实现(结构)不同、随机访问的性能和添加/删除的效率不同。如果是随机访问比较多的业务场景可以选择使用 ArrayList,如果添加和删除比较多的业务场景可以选择使用 LinkedList。

       ArrayList适用于需要快速随机访问元素的场景,因为它的底层是基于数组实现的,可以通过下标直接访问元素。但是,当需要频繁插入或删除元素时,由于需要移动元素,效率较低。

       LinkedList适用于需要频繁插入或删除元素的场景,因为它的底层是基于链表实现的,插入或删除元素只需要改变指针指向,效率较高。但是,当需要随机访问元素时,由于需要遍历链表,效率较低。

       因此,根据具体的场景需求,选择合适的集合类可以提高程序的效率。

ArrayList的扩容机制

我们首先创建一个ArrayList如图:

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
 
public class Test  {
    public static void main(String[] args) {
        List<Integer> arrayList = new ArrayList<>(); // 第0步:创建基于链表的List
        arrayList.add(1);  // 1 添加元素
        arrayList.add(2);  // 2 添加元素
        arrayList.add(3);  // 3 添加元素
        arrayList.add(4);  // 4 添加元素
        arrayList.add(5);  // 5 添加元素
        arrayList.add(6);  // 6 添加元素
        arrayList.add(7);  // 7 添加元素
        arrayList.add(8);  // 8 添加元素
        arrayList.add(9);  // 9 添加元素
        System.out.println("hello");  // 打印
 
    }
}

第0步, 初始化:

ArrayList的构造方法如下:

    /**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }
 
    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
 
    /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
 

里面有三个重载的构造方法, 简单来说就是:

  1. 无参构造: Obeject数组elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
  2. 给定初始容量: ①当 传入的初始容量initialCapacity > 0为真时,创建一个大小为initialCapacity的空数组,并将引用赋给elementData;
    ②当 传入的初始容量initialCapacity = 0为真时,将空数组EMPTY_ELEMENTDATA赋给elementData;
    ③当 传入的初始容量initialCapacity < 0为真时,直接抛出IllegalArgumentException异常。

此处我们传入的initialCapacity为空, 也就是无参构造方法, 如下:

transient Object[] elementData;
 
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
 
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

        在构造方法中,它将DEFAULTCAPACITY_EMPTY_ELEMENTDATA赋值给elementData,这个DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个空的Object数组,而elementData就是ArrayList实际存储数据的容器

第1步, 添加元素1:

触发:

   public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

ensureCapacityInternal为确认初始化容量:

进入ensureCapacityInternal:

    private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

随后进入calculateCapacity计算最低所需容量:

    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

       此处的minCapacity为 size + 1, 翻译过来也就是最低所需的容量, 研究发现, 如果是空数组(刚开始使用无参构造方法的时候)就返回DEFAULT_CAPACITY, 值为10.

       当elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的时候, 直接返回minCapacity.

       随后进入ensureExplicitCapacity(int minCapacity)方法:

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
 
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

modCount++,这是用来记录数组被修改次数的变量,我们先不管它. minCapacity为计算出来的最小所需容量, elementData.length为当前容量,如果最小所需容量大于当前容量, 就需要扩容, 然后进入grow方法:

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        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);
    }

       老的容量为当前容量,新的容量为老的容量的1.5倍,如果新的容量–最小所需容量<О那么新的容量就等于最小所需容量,如果新的容量-当前数组最大的容量限制>0,那么就进入hugeCapacity方法,然后使用copyOf方法进行数据迁移.将老的数据迁移到新的容量的数组中

总结一下:

       我们使用无参构造方法的时候, 就会创建一个底层为空的数组的链表, 此时size 为0, 然后向里面添加元素的时候, 此时的minCapacity为 size + 1 = 1, 在calculateCapacity方法中返回了DEFAULT_CAPACITY(10), 然后进入ensureExplicitCapacity(10), 此时的minCapacity = 10 > 当前容量 = 0, 所以进行初始扩容(elementData = Arrays.copyOf(elementData, newCapacity)), 将容量扩充到10, 也就是elementData/length == 10, 随后的插入, 只要没有超过10, 那就可以直接插入, 如果容量满了, 那么就进行1.5倍扩容.

ArrayLIst的基本使用

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
 
public class Test  {
    public static void main(String[] args) {
        ArrayList<Integer> arrayList = new ArrayList<>(); // 第0步:创建基于链表的List
        // add(int x) 直接向元素末尾位置添加x
       arrayList.add(1);
 
       // get(int index) 方法, 返回下标为index的元素
       int a = arrayList.get(0);
        System.out.println(a);
 
        // add(int index, int element); 向指定位置插入元素
        arrayList.add(1,3);
 
        // size(); 获取当前元素的个数
        int size = arrayList.size();
 
        // remove(int index); 删除下标为index 的元素
        arrayList.remove(0);
 
        // 判断arrList是否为空, 如果为空就返回true, 否则返回false;
        arrayList.isEmpty();
 
        // set(int index, int element); 将index 下标的元素设置为 element
        arrayList.set(0,5);
 
    }
}

LInkedList与之类似

ArrayList和Vector

       ArrayList和Vector都实现了List接口. 代码演示如图:

import java.util.ArrayList;
import java.util.Vector;
 
public class Main {
    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        Vector<String> vector = new Vector<>();
 
        // 添加元素
        arrayList.add("Java");
        arrayList.add("Python");
        arrayList.add("C++");
 
        vector.add("Java");
        vector.add("Python");
        vector.add("C++");
 
        // 获取元素
        System.out.println(arrayList.get(0));
        System.out.println(vector.get(0));
 
        // 删除元素
        arrayList.remove(0);
        vector.remove(0);
 
        // 获取元素个数
        System.out.println(arrayList.size());
        System.out.println(vector.size());
    }
}

但它们有以下区别:

  1. 线程安全性:Vector 是线程安全的,而 ArrayList 不是。所以在多线程环境下,应该使用 Vector
  2. 性能:由于 Vector 是线程安全的,所以它的性能通常比 ArrayList 差。在单线程环境下,ArrayList 比 Vector 快
  3. 初始容量增长方式:当容量不足时,ArrayList 默认会增加 50% 的容量,而 Vector 会将容量翻倍。这意味着在添加元素时,ArrayList 需要更频繁地进行扩容操作,而 Vector 则更适合于存储大量数据。 Vector的grow方法

       综上所述,如果不需要考虑线程安全问题,并且需要高效的存取操作,则 ArrayList 是更好的选择;如果需要考虑线程安全以及更好的数据存储能力,则应该选择 Vector。

目录
相关文章
|
5天前
|
存储 缓存 网络协议
计算机网络常见面试题(二):浏览器中输入URL返回页面过程、HTTP协议特点,GET、POST的区别,Cookie与Session
计算机网络常见面试题(二):浏览器中输入URL返回页面过程、HTTP协议特点、状态码、报文格式,GET、POST的区别,DNS的解析过程、数字证书、Cookie与Session,对称加密和非对称加密
|
30天前
|
机器学习/深度学习 算法 Java
通过 Java Vector API 利用 SIMD 的强大功能
通过 Java Vector API 利用 SIMD 的强大功能
36 10
|
1月前
|
编译器
经典面试题:变量的声明和定义有什么区别
在编程领域,变量的“声明”与“定义”是经典面试题之一。声明告诉编译器一个变量的存在,但不分配内存,通常包含变量类型和名称;而定义则为变量分配内存空间,一个变量必须至少被定义一次。简而言之,声明是告知变量形式,定义则是实际创建变量并准备使用。
|
30天前
|
算法 Java 数据中心
探讨面试常见问题雪花算法、时钟回拨问题,java中优雅的实现方式
【10月更文挑战第2天】在大数据量系统中,分布式ID生成是一个关键问题。为了保证在分布式环境下生成的ID唯一、有序且高效,业界提出了多种解决方案,其中雪花算法(Snowflake Algorithm)是一种广泛应用的分布式ID生成算法。本文将详细介绍雪花算法的原理、实现及其处理时钟回拨问题的方法,并提供Java代码示例。
59 2
|
1月前
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
90 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
26天前
|
前端开发 小程序 JavaScript
面试官:px、em、rem、vw、rpx 之间有什么区别?
面试官:px、em、rem、vw、rpx 之间有什么区别?
29 0
|
1月前
|
JSON 安全 前端开发
第二次面试总结 - 宏汉科技 - Java后端开发
本文是作者对宏汉科技Java后端开发岗位的第二次面试总结,面试结果不理想,主要原因是Java基础知识掌握不牢固,文章详细列出了面试中被问到的技术问题及答案,包括字符串相关函数、抽象类与接口的区别、Java创建线程池的方式、回调函数、函数式接口、反射以及Java中的集合等。
27 0
|
存储 安全 Java
Java中Vector和ArrayList的区别
Java中Vector和ArrayList的区别
99 0
|
存储 安全 Java
Java中Vector和ArrayList的区别
首先看这两类都实现List接口,而List接口一共有三个实现类,分别是ArrayList、Vector和LinkedList。List用于存放多个元素,能够维护元素的次序,并且允许元素的重复。3个具体实现类的相关区别如下: ArrayList是最常用的List实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。
857 0