ArrayList扩容机制的相关面试题

简介: ArrayList扩容机制的相关面试题

最近耗时5个月整理的10w字Java面试手册,涵盖了Java面试几乎都会问的面试题目,直达链接10w字Java面试手册

1、底层数据结构

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * 默认的初始化容量
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     *底层采用的是数组
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * 元素个数
     *
     * @serial
     */
    private int size;
}
ArrayList底层采用的是数组

2、构造函数

//指定容量构造函数初始化
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);
    }
}

//无参构造 初始化为空数组
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

//集合构造初始化
public ArrayList(Collection<? extends E> c) {
    Object[] a = c.toArray();
    if ((size = a.length) != 0) {
        if (c.getClass() == ArrayList.class) {
            elementData = a;
        } else {
            elementData = Arrays.copyOf(a, size, Object[].class);
        }
    } else {
        // replace with empty array.
        elementData = EMPTY_ELEMENTDATA;
    }
}
  • ArrayList() 会使用长度为0的数组
  • ArrayList(int initialCapacity) 会初始化指定容量的数组
  • ArrayList(Collection<? extends E> c) 会初始化c的大小作为数组容量

3、扩容规则

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

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

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

/**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

/**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    //扩容后的规则是原来容量的1.5倍
    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);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
    MAX_ARRAY_SIZE;
}

1、Add(Object o)扩容

public boolean add(E e) {
    //调用上面的扩容规则
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

/**
     * Inserts the specified element at the specified position in this
     * list. Shifts the element currently at that position (if any) and
     * any subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
public void add(int index, E element) {
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}
add()详解
  1. 初始化ArrayList,size =0,DEFAULT_CAPACITY = 10,elementData = {},
  2. 调用add(Object o)时,调用ensureCapacityInternal(1)

    • 调用calculateCapacity(elementData, 1),返回数组容量,elementData为空,所以返回初始化容量和指定容量1的最大值Math.max(DEFAULT_CAPACITY, 1),返回10
    • 调用ensureExplicitCapacity方法,判断是否需要初始化数组容量,若minCapacity - elementData.length > 0,即10 - 1 > 0,调用grow方法,增加容量以确保它至少可以容纳最小容量参数指定的元素数量
    • grow方法做了哪些事,首先会获取数组的大小oldCapacity,然后进行扩容,采用的移位运算>>,返回一个新的容量newCapacity,然后跟所需的最小容量minCapacity比较

      • newCapacity - minCapacity < 0:说明需要的最小容量大,返回minCapacity
      • newCapacity - MAX_ARRAY_SIZE > 0:大于最大值,返回最大值
    • 最后把容量和元素赋值给数组
验证扩容规则
/**
     * 计算arraylist的扩容规则
     * @param n
     * @return
     */
private static List<Integer> arrayListGrowRule(int n) {
    List<Integer> list = new ArrayList<>();
    int init = 0;
    list.add(init);
    //初始容量
    if (n >= 1) {
        init = 10;
        list.add(init);
    }
    //进行扩容
    for (int i = 1; i < n; i++) {
        init += (init) >> 1;
        list.add(init);
    }
    return list;
}
[0, 10, 15, 22, 33, 49, 73, 109, 163, 244, 366]
结论
add(Object o):首次(空数组的情况下)扩容为10,之后为元容量的1.5倍

2、addAll(Collection<? extends E> c)扩容规则

/**
按照指定集合的​​迭代器返回的顺序,将指定集合中的所有元素附加到此列表的末尾。如果在操作正在进行时修改了指定的集合,则此操作的行为是未定义的。 (这意味着如果指定的集合是这个列表,并且这个列表是非空的,那么这个调用的行为是未定义的。)
参数:c - 包含要添加到此列表的元素的集合
返回:如果此列表因调用而更改,则为true
抛出:NullPointerException – 如果指定的集合为空
     */
public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}

/**
将指定集合中的所有元素插入此列表,从指定位置开始。将当前位于该位置的元素(如果有)和任何后续元素向右移动(增加它们的索引)。新元素将按照指定集合的​​迭代器返回的顺序出现在列表中。
参数:index – 插入指定集合中第一个元素的索引   c - 包含要添加到此列表的元素的集合
返回:如果此列表因调用而更改,则为true
抛出:IndexOutOfBoundsException –NullPointerException – 如果指定的集合为空
     */
public boolean addAll(int index, Collection<? extends E> c) {
    rangeCheckForAdd(index);

    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount

    int numMoved = size - index;
    if (numMoved > 0)
        System.arraycopy(elementData, index, elementData, index + numNew,
                         numMoved);

    System.arraycopy(a, 0, elementData, index, numNew);
    size += numNew;
    return numNew != 0;
}
addAll详解
  • 初始化ArrayList,size =0,DEFAULT_CAPACITY = 10,elementData = {},
  • 调用addAll(Object o)时,获取数组长度n,调用ensureCapacityInternal(size+n)

    • 调用calculateCapacity(elementData, size+n),返回数组容量,elementData为空,所以返回初始化容量和指定容量1的最大值Math.max(DEFAULT_CAPACITY, size+n),返回10
    • 调用ensureExplicitCapacity方法,判断是否需要初始化数组容量,若minCapacity - elementData.length > 0,即10 - 1 > 0,调用grow方法,增加容量以确保它至少可以容纳最小容量参数指定的元素数量
    • grow方法做了哪些事,首先会获取数组的大小oldCapacity,然后进行扩容,采用的移位运算>>,返回一个新的容量newCapacity,然后跟所需的最小容量minCapacity比较

      • newCapacity - minCapacity < 0:说明需要的最小容量大,返回minCapacity
      • newCapacity - MAX_ARRAY_SIZE > 0:大于最大值,返回最大值
    • 最后把容量和元素赋值给数组
验证扩容规则
//空数组情况,测试增加
private static void testAddAllGrowEmpty() {
    ArrayList<Integer> list = new ArrayList<>();
    //        list.addAll(Arrays.asList(1,2,3));
    list.addAll(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11));
    System.out.println(length(list));
}

//不为空情况下增加
private static void testAddAllGrowNotEmpty() {
    ArrayList<Integer> list = new ArrayList<>();
    for (int i = 0; i < 10; i++) {
        list.add(i);
    }
    //        list.addAll(Arrays.asList(1, 2, 3));
    list.addAll(Arrays.asList(1, 2, 3, 4, 5, 6));
    System.out.println(length(list));
}
//反射获取长度
public static int length(ArrayList<Integer> list) {
    try {
        Field field = ArrayList.class.getDeclaredField("elementData");
        field.setAccessible(true);
        return ((Object[]) field.get(list)).length;
    } catch (Exception e) {
        e.printStackTrace();
        return 0;
    }
}
  • 空数组添加结合

    • 添加长度小于10,数组的初始容量扩容为10
    • 添加长度大于10,数组的初始容量扩容为Math.max(10,实际个数)
  • 不为空添加集合

    • 添加长度小于扩容后的容量,数组的容量为原容量的1.5倍
    • 添加长度大于扩容后的容量,数组的容量为Math.max(原容量的1.5倍,实际个数)
结论
addAll(Object c)没有元素时,扩容为Math.max(10,实际个数),有元素时,Math.max(原容量的1.5倍,实际个数)

总结

  • ArrayList() 会使用长度为0的数组
  • ArrayList(int initialCapacity) 会初始化指定容量的数组
  • ArrayList(Collection<? extends E> c) 会初始化c的大小作为数组容量
  • add(Object o):首次(空数组的情况下)扩容为10,之后为元容量的1.5倍
  • addAll(Object c)没有元素时,扩容为Math.max(10,实际个数),有元素时,Math.max(原容量的1.5倍,实际个数)
相关文章
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
73 2
|
2月前
|
存储 Java 程序员
Java面试加分点!一文读懂HashMap底层实现与扩容机制
本文详细解析了Java中经典的HashMap数据结构,包括其底层实现、扩容机制、put和查找过程、哈希函数以及JDK 1.7与1.8的差异。通过数组、链表和红黑树的组合,HashMap实现了高效的键值对存储与检索。文章还介绍了HashMap在不同版本中的优化,帮助读者更好地理解和应用这一重要工具。
68 5
|
2月前
|
消息中间件 存储 Java
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
|
2月前
|
消息中间件 存储 Java
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
Android面试高频知识点(2) 详解Android消息处理机制(Handler)
58 1
|
2月前
|
监控 架构师 Java
从蚂蚁金服面试题窥探STW机制
在Java虚拟机(JVM)中,垃圾回收(GC)是一个至关重要的机制,它负责自动管理内存的分配和释放。然而,垃圾回收过程并非没有代价,其中最为显著的一个影响就是STW(Stop-The-World)机制。STW机制是指在垃圾回收过程中,JVM会暂停所有应用线程的执行,以确保垃圾回收器能够正确地遍历和回收对象。这一机制虽然保证了垃圾回收的安全性和准确性,但也可能对应用程序的性能产生显著影响。
39 2
|
2月前
|
架构师 Java 开发者
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
在40岁老架构师尼恩的读者交流群中,近期多位读者成功获得了知名互联网企业的面试机会,如得物、阿里、滴滴等。然而,面对“Spring Boot自动装配机制”等核心面试题,部分读者因准备不足而未能顺利通过。为此,尼恩团队将系统化梳理和总结这一主题,帮助大家全面提升技术水平,让面试官“爱到不能自已”。
得物面试:Springboot自动装配机制是什么?如何控制一个bean 是否加载,使用什么注解?
|
3月前
|
存储 缓存 Android开发
Android RecyclerView 缓存机制深度解析与面试题
本文首发于公众号“AntDream”,详细解析了 `RecyclerView` 的缓存机制,包括多级缓存的原理与流程,并提供了常见面试题及答案。通过本文,你将深入了解 `RecyclerView` 的高性能秘诀,提升列表和网格的开发技能。
77 8
|
2月前
|
存储 算法 安全
HashMap常见面试题(超全面):实现原理、扩容机制、链表何时升级为红黑树、死循环
HashMap常见面试题:红黑树、散列表,HashMap实现原理、扩容机制,HashMap的jd1.7与jdk1.8有什么区别,寻址算法、链表何时升级为红黑树、死循环
|
4月前
|
Java 索引
【Java集合类面试九】、介绍一下HashMap的扩容机制
HashMap的扩容机制包括初始容量16,以2的次方进行扩充,使用负载因子0.75判断是否扩容,以及链表长度达到阈值时转换为红黑树,以优化性能。
【Java集合类面试九】、介绍一下HashMap的扩容机制

热门文章

最新文章