【Java】ArrayList实现原理浅析,java面试常见问题与答案pdf

  1. 判断当前插入的元素位置是否为LinkedList的头节点;若不是的话,则将当前操作元素节点的next指为new的要插入进来的那个节点。(将操作节点的next指向新插入的元素的节点)

在某个位置插入某个元素的大概流程如图所示: 在这里插入图片描述 也就是说,我们通过LinkedList向某个位置插入一个数据,我们只需要改变两个数据节点的pre和next指向就完成了。

ArrayList和LinkedList的插入效率比较

ArrayList的add是尾部效率ArrayList>LinkedList; ArrayList的add是头部效率ArrayList<LinkedList

原因:ArrayList的内存空间连续,且不需要复制数组。LinkedList需要创建一个新的节点,前后引用进行重新排列。

我不知道你们有没有恍然大悟,但是我恍然大悟了。

接下来就是揭开扩容的真面目了,扩容和构造器有什么关系?

2.4 触发扩容以及扩容大小

上面说扩容机制是在add(e)时触发的,来看看add(e)的源码:

add(E e)

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

ensureCapacityInternal(int minCapacity) :确保插入元素容器最小的值; minCapacity:当前数组的长度+1

构造函数为空参数构造函数,则给minCapacity设置默认的值为minCapacity=DEFAULT_CAPACITY=10; 若不为空参数构造函数,则minCapacity=数组已存在数据size+1

private static final int DEFAULT_CAPACITY = 10;
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}

ensureExplicitCapacity(int minCapacity) :判断是否需要扩容 elementData.length是现有数据的长度。

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

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

grow(int minCapacity) :实现扩容

  1. 获取旧容量
  2. 现将原元素数组的长度增大1.5倍,随后和newCapacity比较。
  3. 新容量小于参数指定容量,修改新容量:newCapacity(新增容量)<minnewCapacity:newCapacity=minCapacity
  4. 新容量大于最大容量:newCapacity>minnewCapacity:newCapacity:将就数组拷贝到扩容后的新数组中。

private void grow(int minCapacity) {
int oldCapacity = elementData.length; //旧容量
int newCapacity = oldCapacity + (oldCapacity >> 1); //新容量为旧容量的1.5倍
if (newCapacity - minCapacity < 0) //新容量小于参数指定容量,修改新容量
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0) //新容量大于最大容量
newCapacity = hugeCapacity(minCapacity); //指定新容量
//拷贝扩容
elementData = Arrays.copyOf(elementData, newCapacity);
}

第一次调用添加元素的add和addAll,size=0,则minCapacity=1 如果ArrayList给了特定的初始值,则需要根据数组实际长度和数组容量差来判断是否调用扩容;如果没有指定初始容量,第一次调用add则一定需要调用grow()

三.ArrayList的线程安全性

在多线程下,ArrayList不能保证原子性(即同一时刻只能有一个线程来对它进行操作)。

举个栗子:线程A对ArrayList进行++处理,期待100;线程B对ArrayList进行–处理,期待98。多线程进行时,可能本应该为100,因为又有–操作,可能++后,结果仍然为99.

多线程环境下,ArrayList线程是不安全的。

保证线程安全性的方法

1.使用synchronized关键字;

2.用Collection类中的静态方法synchronizedList(),对Arraylist进行调用

四.ArrayList常用方法介绍

arrayList.get(position):根据数组下标进行取值,set同理

arrayList.add(postion):判断是否扩容,根据数组下标进行赋值

arrayList.remove(index)步骤: 1.在目标元素的位置设置赋值的起始位置; 2.将目标位置开始到数组最后一位的数组进行复制; 3.然后覆盖拷贝到要移除的位置上,将要移除的位置进行覆盖; 4.再将最后一个位置的数据进行null设置,等待回收。

remove(index)源码:

public E remove(int index) {
//第一步先判断是否有越界,如果越界直接IndexOutOfBoundsException
rangeCheck(index);
modCount++;
//把该元素从数组中提出
E oldValue = elementData(index);
//需要复制的长度
int numMoved = size - index - 1;
if (numMoved > 0)
//原数组,从哪开始复制,目标数组,复制起始位置,长度。过程如下图:
System.arraycopy(elementData, index+1, elementData, index,numMoved);
//赋值null等待回收
elementData[–size] = null;
return oldValue;
}

五.ArrayList的去重处理

1.循环list中所有的元素然后删除

public static ArrayList removeDuplicate_1(ArrayList list){
for(int i =0;i<list.size()-1;i++){
for(int j=list.size()-1;j>i;j–){
if(list.get(i).equals(list.get(j)))
list.remove(j);
}
}

return list;
}

2.利用hashSet剔除重复元素,无序

public static ArrayList removeDuplicate_2(ArrayList list){
HashSet set = new HashSet(list);
//使用LinkedHashSet可以保证输入的顺序
//LinkedHashSet set2 = new LinkedHashSet(list);
list.clear();
list.addAll(set);
return list;
}

3.利用list的contains方法去重

public static ArrayList removeDuplicate_3(ArrayList list){
ArrayList tempList = new ArrayList(list.size());
for(int i=0;i<list.size();i++){
if(!tempList.contains(list.get(i)))
tempList.add(list.get(i));
}
return tempList;
}

contains是根据什么原理来进行比较的呢? 可以看出contains其实也是用equals来比较的,而equals是比较的地址

public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}

六.什么时候选择ArrayList?

6.1 ArrayList的for和Iterator遍历效率差别

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
img

言尽于此,完结

无论是一个初级的 coder,高级的程序员,还是顶级的系统架构师,应该都有深刻的领会到设计模式的重要性。

  • 第一,设计模式能让专业人之间交流方便,如下:

程序员A:这里我用了XXX设计模式

程序员B:那我大致了解你程序的设计思路了

  • 第二,易维护

项目经理:今天客户有这样一个需求…

程序员:明白了,这里我使用了XXX设计模式,所以改起来很快

  • 第三,设计模式是编程经验的总结

程序员A:B,你怎么想到要这样去构建你的代码

程序员B:在我学习了XXX设计模式之后,好像自然而然就感觉这样写能避免一些问题

  • 第四,学习设计模式并不是必须的

程序员A:B,你这段代码使用的是XXX设计模式对吗?

程序员B:不好意思,我没有学习过设计模式,但是我的经验告诉我是这样写的

image

从设计思想解读开源框架,一步一步到Spring、Spring5、SpringMVC、MyBatis等源码解读,我都已收集整理全套,篇幅有限,这块只是详细的解说了23种设计模式,整理的文件如下图一览无余!

image

搜集费时费力,能看到此处的都是真爱!

习过设计模式,但是我的经验告诉我是这样写的

[外链图片转存中…(img-w70l1RlR-1711016277124)]

从设计思想解读开源框架,一步一步到Spring、Spring5、SpringMVC、MyBatis等源码解读,我都已收集整理全套,篇幅有限,这块只是详细的解说了23种设计模式,整理的文件如下图一览无余!

[外链图片转存中…(img-hQS2mWJq-1711016277125)]

搜集费时费力,能看到此处的都是真爱!

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

  • 12
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值