Java最全【JDK源码】ArrayList 源码分析,华为工程师面试经历

总结

至此,文章终于到了尾声。总结一下,我们谈论了简历制作过程中需要注意的以下三个部分,并分别给出了一些建议:

  1. 技术能力:先写岗位所需能力,再写加分能力,不要写无关能力;
  2. 项目经历:只写明星项目,描述遵循 STAR 法则;
  3. 简历印象:简历遵循三大原则:清晰,简短,必要,要有的放矢,不要海投;

以及最后为大家准备的福利时间:简历模板+Java面试题+热门技术系列教程视频

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

实现这些接口有什么作用呢?

  • ArrayList实现了List,提供了基础的添加、删除、遍历等操作。

  • ArrayList实现了RandomAccess,提供了随机访问的能力。

  • ArrayList实现了Cloneable,可以被克隆。

  • ArrayList实现了Serializable,可以被序列化。

2.属性


//默认容量,10。也就是通过new ArrayList()创建时的默认容量。

private static final int DEFAULT_CAPACITY = 10;

//空数组,用于空实例。这种是通过new ArrayList(0)创建时用的是这个空数组。

private static final Object[] EMPTY_ELEMENTDATA = {};

//1.用于默认大小空实例的共享空数组实例。通过new ArrayList()创建时用的是这个空数组

//2.我们把它与EMPTY_ELEMENTDATA数组区分出来,以知道在添加第一个元素时容量需要增加多少。与EMPTY_ELEMENTDATA的区别是在添加第一个元素时使用这个空数组的会初始化为DEFAULT_CAPACITY(10)个元素。

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//1.数组列表中存储元素的数组缓冲区。数组列表的容量是数组缓冲区的长度

//2.将在添加第一个元素时,长度扩展为DEFAULT_CAPACITY。

//3.使用transient是为了不序列化这个字段

transient Object[] elementData;

//ArrayList 所包含的元素个数,而不是elementData数组的长度。

private int size;

  • DEFAULT_CAPACITY:默认容量为10,也就是通过new ArrayList()创建时的默认容量。

  • EMPTY_ELEMENTDATA:空的数组,这种是通过new ArrayList(0)创建时用的是这个空数组。

  • DEFAULTCAPACITY_EMPTY_ELEMENTDATA:也是空数组,这种是通过new ArrayList()创建时用的是这个空数组,与EMPTY_ELEMENTDATA的区别是在添加第一个元素时使用这个空数组的会初始化为DEFAULT_CAPACITY(10)个元素。

  • elementData:真正存放元素的地方,使用transient是为了不序列化这个字段。

  • size:真正存储元素的个数,而不是elementData数组的长度。

3.构造方法


ArrayList(int initialCapacity)

传入初始容量,如果大于0就初始化elementData为对应大小,如果等于0就使用EMPTY_ELEMENTDATA空数组,如果小于0抛出异常。

//带初始容量的构造函数(用户可以在创建ArrayList对象时自己指定集合的初始大小)

public ArrayList(int initialCapacity) {

if (initialCapacity > 0) {

//如果传入的初始容量大于0,创建initialCapacity大小的数组

this.elementData = new Object[initialCapacity];

} else if (initialCapacity == 0) {

//如果传入的初始容量等于0,使用空数组EMPTY_ELEMENTDATA

this.elementData = EMPTY_ELEMENTDATA;

} else {

//如果传入的初始容量小于0,抛出异常

throw new IllegalArgumentException("Illegal Capacity: "+

initialCapacity);

}

}

ArrayList()

不传初始容量,初始化为DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组,会在添加第一个元素的时候扩容为默认的大小,即10

//默认无参构造函数

public ArrayList() {

//DEFAULTCAPACITY_EMPTY_ELEMENTDATA 为0.初始化为10,也就是说初始其实是空数组 当添加第一个元素的时候数组容量才变成10

this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

}

ArrayList(Collection c)

传入集合并初始化elementData,这里会使用拷贝把传入集合的元素拷贝到elementData数组中,如果元素个数为0,则初始化为EMPTY_ELEMENTDATA空数组。

//构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。

public ArrayList(Collection<? extends E> c) {

//将集合转换为数组

elementData = c.toArray();

//如果elementData数组的长度不为0

if ((size = elementData.length) != 0) {

// 检查c.toArray()返回的是不是Object[]类型

if (elementData.getClass() != Object[].class)

//如果不是,重新拷贝成Object[].class类型

elementData = Arrays.copyOf(elementData, size, Object[].class);

} else {

//如果c的空集合,则初始化为空数组EMPTY_ELEMENTDATA

this.elementData = EMPTY_ELEMENTDATA;

}

}

为什么 c.toArray()返回的有可能不是Object[]类型呢?

这里我们看一个例子:

/**

  • @author xppll

  • @date 2021/12/28 10:01

*/

public class ArrayListTest {

public static void main(String[] args) {

Father[] fathers = new Son[]{};

//打印结果为:class [Lcom.itheima.test.Son;

System.out.println(fathers.getClass());

List strList = new MyList();

//打印结果为:class [Ljava.lang.String;

System.out.println(strList.toArray().getClass());

}

}

class Father {

}

class Son extends Father {

}

class MyList extends ArrayList {

/**

  • 子类重写父类的方法,返回值可以不一样

  • 但这里只能用数组类型,换成Object就不行

  • 这应该算是java本身的bug

  • @return

*/

@Override

public String[] toArray() {

// 为了方便举例直接写死

return new String[]{“a”, “b”, “c”};

}

}

4.相关操作方法


add(E e)

添加元素到末尾,平均时间复杂度为O(1)。

public boolean add(E e) {

//检查是否需要扩容

ensureCapacityInternal(size + 1);

//把元素插到最后一位

elementData[size++] = e;

return true;

}

检查是否需要扩容ensureCapacityInternal(int minCapacity)

private void ensureCapacityInternal(int minCapacity) {

ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));

}

接着看calculateCapacity(Object[] elementData, int minCapacity)

private static int calculateCapacity(Object[] elementData, int minCapacity) {

//如果是空数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,就初始化为默认大小10

//获取“默认的容量”和“传入参数”两者之间的最大值

if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {

return Math.max(DEFAULT_CAPACITY, minCapacity);

}

return minCapacity;

}

接着看ensureExplicitCapacity(int minCapacity)

private void ensureExplicitCapacity(int minCapacity) {

modCount++;

if (minCapacity - elementData.length > 0)

//扩容

grow(minCapacity);

}

扩容方法grow(minCapacity)

private void grow(int minCapacity) {

//oldCapacity为旧容量,newCapacity为新容量

int oldCapacity = elementData.length;

//将oldCapacity 右移一位,其效果相当于oldCapacity /2,

//我们知道位运算的速度远远快于整除运算,整句运算式的结果就是将新容量更新为旧容量的1.5倍,

int newCapacity = oldCapacity + (oldCapacity >> 1);

///检查新容量是否大于最小需要容量,若还是小于最小需要容量,那么就把最小需要容量当作数组的新容量,

if (newCapacity - minCapacity < 0)

newCapacity = minCapacity;

//再检查新容量是否超出了ArrayList所定义的最大容量,

//若超出了,则调用hugeCapacity()来比较minCapacity和 MAX_ARRAY_SIZE,

//如果minCapacity大于MAX_ARRAY_SIZE,则新容量则为Interger.MAX_VALUE,否则,新容量大小则为 MAX_ARRAY_SIZE。

if (newCapacity - MAX_ARRAY_SIZE > 0)

newCapacity = hugeCapacity(minCapacity);

// 以新容量拷贝出来一个新数组

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. 检查是否需要扩容

  2. 如果elementData等于DEFAULTCAPACITY_EMPTY_ELEMENTDATA则初始化容量大小为DEFAULT_CAPACITY

  3. 新容量是老容量的1.5倍(oldCapacity + (oldCapacity >> 1)),如果加了这么多容量发现比需要的容量还小,则以需要的容量为准

  4. 创建新容量的数组并把老数组拷贝到新数组

add(int index, E element)

添加元素到指定位置,平均时间复杂度为O(n)。

/**

  • 添加元素到指定位置,平均时间复杂度为O(n)

  • @param index 指定元素插入的位置

  • @param element 要插入的元素

*/

public void add(int index, E element) {

// 检查是否越界

rangeCheckForAdd(index);

// 检查是否需要扩容

ensureCapacityInternal(size + 1); // Increments modCount!!

// 将index及其以后的元素都往后移一位,此时inex位置就空出来了

System.arraycopy(elementData, index, elementData, index + 1,

size - index);

//将元素插入到index位置

elementData[index] = element;

//元素数量加一

size++;

}

//检查是否越界

private void rangeCheckForAdd(int index) {

if (index > size || index < 0)

throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

}

整个流程:

  1. 检查索引是否越界

  2. 检查是否需要扩容

  3. 把插入索引位置后的元素都往后挪一位

  4. 在插入索引位置放置插入的元素

  5. 元素数量加1

addAll(Collection c)

求两个集合的并集。

/**

  • 将集合c中所有元素添加到当前ArrayList中

  • @param c

  • @return

*/

public boolean addAll(Collection<? extends E> c) {

//将集合c转为数组

Object[] a = c.toArray();

int numNew = a.length;

//检查是否需要扩容

ensureCapacityInternal(size + numNew); // Increments modCount

//将c中的元素全部拷贝到数组的最后

System.arraycopy(a, 0, elementData, size, numNew);

//集合中元素的大小增加c的大小

size += numNew;

//如果c不为空就返回true,否则返回false

return numNew != 0;

}

整个流程:

  1. 拷贝c中的元素到数组a中

  2. 检查是否需要扩容

  3. 把数组a中的元素拷贝到elementData的尾部

  4. 元素数量加c的大小

get(int index)

获取指定索引位置的元素,时间复杂度为O(1)。

public E get(int index) {

//检查是否越界

rangeCheck(index);

//返回数组index位置的元素

return elementData(index);

}

//检查是否越界

private void rangeCheck(int index) {

if (index >= size)

throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

}

//返回指定位置的元素

@SuppressWarnings(“unchecked”)

E elementData(int index) {

return (E) elementData[index];

}

1200页Java架构面试专题及答案

小编整理不易,对这份1200页Java架构面试专题及答案感兴趣劳烦帮忙转发/点赞

百度、字节、美团等大厂常见面试题

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

private void rangeCheck(int index) {

if (index >= size)

throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

}

//返回指定位置的元素

@SuppressWarnings(“unchecked”)

E elementData(int index) {

return (E) elementData[index];

}

1200页Java架构面试专题及答案

小编整理不易,对这份1200页Java架构面试专题及答案感兴趣劳烦帮忙转发/点赞

[外链图片转存中…(img-5BbMqHCZ-1715330813975)]

[外链图片转存中…(img-aeaJxKGJ-1715330813976)]

百度、字节、美团等大厂常见面试题

[外链图片转存中…(img-4P2bf1gD-1715330813976)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 20
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值