List的理解

白色的了解的,黄色重点了解,读源码,绿色很少用

1.Arraylist与Vector的区别

1、Vector是线程安全的,ArrayList不是线程安全的。
2、ArrayList在底层数组不够用时在原来的基础上扩展0.5倍,Vector是扩展1倍。

看一下Vector的源码

实现了List接口,底层和ArrayList一样,都是数组来实现的。分别看一下ArrayList的add源码

Vector的源码

preview

Vector的其它方法

只要是关键性的操作,方法前面都加了synchronized关键字,来保证线程的安全性。

当执行synchronized修饰的方法前,系统会对该方法加一把锁,方法执行完成后释放锁,加锁和释放锁的这个过程,在系统中是有开销的,因此,在单线程的环境中,Vector效率要差很多。多线程环境不允许用ArrayList,需要做处理

和ArrayList和Vector一样,同样的类似关系的类还有HashMap和HashTable,StringBuilder和StringBuffer

2.ArrayList初始化

创建对象List<Person> list1 = new ArrayList<>()

注:常量池位于方法区,方法区位于堆内存

执行完构造函数后,如下图。

static修饰的变量,常驻于方法区,我们不需要new,JVM会提前初始化,这个特性在实际开发过程经常拿来做缓存。

fianl修饰的变量,JVM也会提前初始化

关键字transient详情:https://baijiahao.baidu.com/s?id=1636557218432721275&wfr=spider&for=pc

继续执行:List<Person> list2 = new ArrayList<>();

new的时候连缓存都考虑到了,为了避免我们反复的创建无用数组,所有新new出来的ArrayList底层数组都指向缓存在方法区里的Object[]数组。

继续执行Person person1 = new Person("张三")

执行list1.add(person1)

ensureCapacityInternal方法入参size

size为成员变量,是int基本数据类型,成员变量初始化的为0

ensureCapacityInternal方法

看一下System.arraycopy()方法,这个方法只有定义,却没有实现,方法用了一个native来修饰,native的方法,是由其它语言来实现的,一般是(C或C++),详解:https://blog.csdn.net/zw6161080123/article/details/80628069

由于数组内容目前为空,相当于没有拷贝,创建一个默认长度为10的Object[]数组

继续往下执行:

size现在是0,把传进来的这个e(这里是person1),放到list1的elementData[]下标为0的数组里面,同时size加1

:list1里的elementData数组的长度是10,但是size是1,size是逻辑长度,并不是数组长度。

size()方法的源码

3.ArrayList底层数组扩容原理

先看数组

再添加一组数据,会报越界异常,因为数组一但在堆内存中创建,长度是固定的。

既然是固定的,想加数据只能new长一点的新的数组,把原来数组的元素复制过去

看一下ArrayList里的源码,当添加第11个元素时

 

int newCapacity = oldCapacity + (oldCapacity >> 1),>>是移位运算符,相当于int newCapacity = oldCapacity + (oldCapacity/2),但性能会好一些。

ArrayList还提供了其它构造方法

所以我们在写代码时候给个估计值,可以避免底层数组的多次拷贝,进而提高程序性能。

讨论一下数组的删除

要把“周八”这个人从数组中删除,如图:

我们只能循环数组,找到“周八“的下标5,由于数组没有提供删除方法,我们只能把下标为5的位置赋值为null(造成了数组空洞),“周八”这个Person对象已经没有引用指向它了,JVM的垃圾回收机制会在适当的时候回收它。但数组的长度还是10。下次当我们再循环查找某人时,稍不注意就会报空指针异常,虽然我们可以写非空去判断,但还是不太友好,我们把null后面的所有元素引用复制一下,往前拷贝一份,把null这个空给填上,如下图

复制后

null之后的ref引用都按顺序复制了一份到原来的null的位置,原有的1引用被覆盖,但perArr[9]里的引用的指向还是不变,

perArr[8],perArr[9]指向的是同一个对象,这显然不是我们所要的结果,再处理一下,我们把perArr[9]的引用赋值为null,

问题似乎解决了,但数组长度还是10,还需要自行维护了一个size来记录长度

ArrayList这个类已经实现了,只需要调用ArrayList这个类提供的remove删除元素就行

先看看删除前的元素,debug一下:

perList里面已经有了10个元素,执行一下这两句remove操作,再看一下debug的情况

下标为5的“周八”已经删除掉了,下标为5以后的元素也按照我们之前的猜想往前移了一位,数组最后一个位置也置为null了。奇怪!“孙七”居然没有删掉!打印出来的个数也是9

看一下两种删除方式的源码。

图中的分析一致,并采用size来记录元素的真实个数,这段代码里还调了一个方法rangeCheck()方法

就是检查底层数组下标是否越界

再看另一种删除方法

上面的equals()方法,就大概知道“孙七”为什么没有删掉了,如果你写了一个类(Person),你需要这个类完美的支持List,你必需按照List的规范来写代码

我们重写equals()方法试一下。

孙七已经删除掉了,孙七后面的所有人也向前复制了一格,末位置为null,size也是8了

图中的“孙七”,“周八”已经没有引用指向它们,JVM虚拟机会在适当的时候进行回收。

当我们用下标方式去删除元素时,如果删除的是最后一个元素,不会触发数组底层的复制,时间复杂度为O(1)。如果删除第i的元素,会触发底层数组复制n-i次,根据最坏情况,时间复杂度为O(n)。

由此看来,在ArrayList中删除指定元素的效率似乎不是太高,删除元素会造成底层数组复制,这个问题在LinkedList有方案解决

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值