面试官让我聊聊 ArrayList 解决了数组的哪些问题

开发 前端
在面试中经常会问在java中数组长度和ArrayList中的size的区别?通过下面的代码我们其实已经看出来数组获取其长度的时候永远是在声明的时候定义的长度,即使数组中只有一个元素,通过下面的输出有人会问arr[1]输出的是0,这个是因为 int 类型在java中为基本类型所以在初始化数组时会默认用0填充

数组简介

数组对于我们来说并不陌生,在内存中是一块连续的内存空间,通过下标可以随机访问数组元素 如图一所示,而在JDK中提供了功能更加强大的ArrayList其底层是基于数据实现的,为什么JDK已经提供了数据还要使用ArrayList呢?我们通过比较两者之间的差异来聊聊ArrayList的优势

java数组中如何获取数组实际元素

在面试中经常会问在java中数组长度和ArrayList中的size的区别?

通过下面的代码我们其实已经看出来数组获取其长度的时候永远是在声明的时候定义的长度,即使数组中只有一个元素,通过下面的输出有人会问arr[1]输出的是0,这个是因为 int 类型在java中为基本类型所以在初始化数组时会默认用0填充

在ArrayList中的长度为list中实际元素的数量,这一点跟数组有着明显的差别,具体如何实现后面会根据源码分析

数组删除元素和添加元素

在数组中删除一个元素后需要移动元素保证数组的连续性,如果删除的是数组最后一个元素则不需要移动,如果删除了下标为3的元素后使数组连续则需要将3号元素后的元素依次向前移动

删除元素后下标为7的元素不再含有有效元素,如果我们使用的数组删除数据后这些移动的操作需要我们写相关的代码来完成,使用ArrayList删除与数据移动都将自动完成不需要我们手动处理

在数组中添加元素如果插入元素后元素个数超过了数组的长度则会报数组越界的错误,这是因为数组的容量是固定的不能动态的增加

如下图原数组容量为8如果想要成功添加第9个元素则需要将原来数组容量扩大为9然后移动指定位置的元素并将9插入,正因为这样数组不具有动态性因此在使用数据时需要额外考虑很多数组本身的问题导致不能专注于核心代码的编写

ArrayList的出现正式解决了数组存在以下问题

  • 动态扩容
  • 有效数据长度
  • 增加删除元素后数据移动
  • 数据遍历

结合ArrayList一些面试问题来看下具体源代码

  • ArrayList的默认初始化容量
  • 何时扩容
  • 扩容容量大小
  • 遍历删除fast fail原理
  • 迭代器删除为什么不越界

首先看下ArrayList的两个重要的属性

  • elementData 存储数据的数组
  • size 数组中实际元素个数
  1. transient Object[] elementData; // non-private to simplify nested class access 
  2.  /** 
  3.   * The size of the ArrayList (the number of elements it contains). 
  4.   * 
  5.   * @serial 
  6.   */ 
  7.  private int size

ArrayList 的构造函数分为有参和无参两种方式

  • 无参构造函数为ArrayList()其初始化后并未分配内存空间而是在第一次add操作时分配
  • 有参构造函数ArrayList(int initialCapacity)初始化即分配制定大小内存
  • 有参构造函数ArrayList(Collection c) 初始化即分配内存

问题一: 无参初始化默认容量是多少,何时扩容,扩容大小为多少都在下面的注释中可以找到

具体分析请看下面注释

  1. // 默认初始化elementData为空数组 
  2. private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; 
  3. public ArrayList() { 
  4.     this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; 
  5. //接下来进行add操作 
  6. public boolean add(E e) { 
  7.     // 检查当前数据容量是否充足 
  8.     ensureCapacityInternal(size + 1);  // Increments modCount!! 
  9.     // size 自增可以保证有效元素的个数 
  10.     elementData[size++] = e; 
  11.     return true
  12.  } 
  13. //计算容量大小主要用于默认初始化第一次add操作 
  14. private static int calculateCapacity(Object[] elementData, int minCapacity) { 
  15.     //默认初始化时elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA 
  16.     // 此处返回默认容量DEFAULT_CAPACITY,此值大小为10 
  17.     if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { 
  18.         return Math.max(DEFAULT_CAPACITY, minCapacity); 
  19.      } 
  20.     return minCapacity; 
  21. private void ensureCapacityInternal(int minCapacity) { 
  22.      ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); 
  23. private void ensureExplicitCapacity(int minCapacity) { 
  24.      modCount++; 
  25.     //检查是否扩容,如果当前容量大于数组长度进行扩容 
  26.     // overflow-conscious code 
  27.      if (minCapacity - elementData.length > 0) 
  28.         grow(minCapacity); 
  29. //根据原来数据长度进行扩容 
  30. //oldCapacity >> 1 位运算相当于 oldCapacity=oldCapacity/2 
  31. //新容量为 newCapacity = oldCapacity + (oldCapacity >> 1);即为原来的1.5倍 
  32. //将老数组中的数据移动到新数组中 
  33. //此处解释下为何为1.5倍,这是一种折中是对扩容次数和空间大小的折中,如果扩容次数太多会降低效率如果空间太大会浪费空间 
  34. private void grow(int minCapacity) { 
  35.    // overflow-conscious code 
  36.    int oldCapacity = elementData.length; 
  37.    int newCapacity = oldCapacity + (oldCapacity >> 1); 
  38.    if (newCapacity - minCapacity < 0) 
  39.       newCapacity = minCapacity; 
  40.    if (newCapacity - MAX_ARRAY_SIZE > 0) 
  41.       newCapacity = hugeCapacity(minCapacity); 
  42.       // minCapacity is usually close to size, so this is a win: 
  43.       elementData = Arrays.copyOf(elementData, newCapacity); 

问题二:数据遍历,fast-fail,迭代器删除为什么不越界

在如下代码注释中将找到答案

  • ArrayList 遍历支持for,foreach,迭代器遍历
  • fail-fast 机制是java集合(Collection)中的一种错误机制。当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。

例如:当某一个线程A通过iterator去遍历某集合的过程中,若该集合的内容被其他线程所改变了;那么线程A访问集合时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。

  1. //ArrayList 迭代器是通过两个游标数组的下标 
  2.  private class Itr implements Iterator<E> {         // 代表下一个元素的游标 
  3.         int cursor;       // index of next element to return 
  4.         int lastRet = -1; // index of last element returned; -1 if no such 
  5.         int expectedModCount = modCount; 
  6.         Itr() {}        // 如果游标不等于size 代表还有下一个元素        public boolean hasNext() {            return cursor != size
  7.         }       //        @SuppressWarnings("unchecked"
  8.         public E next() { 
  9.             checkForComodification();            int i = cursor
  10.             // 在此处判断当前游标是超过了数组有效元素个数 
  11.             if (i >= size
  12.                 throw new NoSuchElementException();            Object[] elementData = ArrayList.this.elementData;            //检查是否数组越界 
  13.             if (i >= elementData.length) 
  14.                 throw new ConcurrentModificationException();            cursor = i + 1; 
  15.             // 更新lastRet游标为当前数组下标 
  16.             return (E) elementData[lastRet = i]; 
  17.         }        public void remove() {         // 检查删除元素下标是否合法 
  18.             if (lastRet < 0) 
  19.                 throw new IllegalStateException();            checkForComodification();            try {            // 删除原数组中制定下标元素 
  20.                 ArrayList.this.remove(lastRet);                //更新游标 
  21.                 cursor = lastRet;                lastRet = -1; 
  22.                 expectedModCount = modCount;            } catch (IndexOutOfBoundsException ex) {                throw new ConcurrentModificationException();            }        }       } 

 

责任编辑:未丽燕 来源: 今日头条
相关推荐

2019-04-29 14:59:41

Tomcat系统架构

2023-01-17 17:54:47

MQ数据丢失

2022-05-23 08:43:02

BigIntJavaScript内置对象

2024-11-14 14:53:04

2021-07-13 07:52:03

ReactHooks组件

2020-05-22 08:11:48

线程池JVM面试

2021-07-29 07:55:20

React Fiber架构引擎

2021-09-28 13:42:55

Chrome Devwebsocket网络协议

2021-12-02 08:19:06

MVCC面试数据库

2022-04-10 18:10:24

CURD链表

2023-02-20 08:08:48

限流算法计数器算法令牌桶算法

2020-09-08 06:43:53

B+树面试索引

2022-11-15 17:45:46

数据库MySQL

2020-07-02 07:52:11

RedisHash映射

2021-05-28 07:12:58

Mybatis面试官Java

2021-03-01 18:42:02

缓存LRU算法

2019-08-23 09:20:35

Spring 5编程Java

2024-08-05 01:26:54

2024-02-26 14:07:18

2024-04-19 00:00:00

计数器算法限流算法
点赞
收藏

51CTO技术栈公众号