本章主要是想详细的介绍以下List接口实现类之间的区别!
01、List简介
List 的数据结构就是一个序列,存储内容时直接在内存中开辟一块连续的空间,然后将空间地址与索引对应。
以下是List集合简易架构图
![f8f1ba569357c84c936ea7a2ff0f2239.png](https://i-blog.csdnimg.cn/blog_migrate/ad55daae60047b8967230e3e5ea50a0d.jpeg)
由图中的继承关系,可以知道,ArrayList、LinkedList、Vector、Stack都是List的四个实现类。
- AbstractCollection 是一个抽象类,它唯一实现Collection接口的类。AbstractCollection主要实现了toArray()、toArray(T[] a)、remove()等方法。
- AbstractList 也是一个抽象类,它继承于AbstractCollection。AbstractList实现List接口中除size()、get(int location)之外的函数,比如特定迭代器ListIterator。
- AbstractSequentialList 是一个抽象类,它继承于AbstractList。AbstractSequentialList 实现了“链表中,根据index索引值操作链表的全部函数”。
- ArrayList 是一个动态数组,它由数组实现。随机访问效率高,随机插入、随机删除效率低。
- LinkedList 是一个双向链表。它也可以被当作堆栈、队列或双端队列进行操作。LinkedList随机访问效率低,但随机插入、随机删除效率高。
- Vector 也是一个动态数组,和ArrayList一样,也是由数组实现。但是ArrayList是非线程安全的,而Vector是线程安全的。
- Stack 是栈,它继承于Vector。它的特性是:先进后出(FILO, First In Last Out)。
下面对各个实现类进行方法剖析!
02、ArrayList
ArrayList实现了List接口,也是顺序容器,即元素存放的数据与放进去的顺序相同,允许放入null元素,底层通过数组实现。 除该类未实现同步外,其余跟Vector大致相同。
在Java1.5之后,集合还提供了泛型,泛型只是编译器提供的语法糖,方便编程,对程序不会有实质的影响。因为所有的类都默认继承至Object,所以这里的数组是一个Object数组,以便能够容纳任何类型的对象。
![b6d36cab4da0236d5ff74a8c8ba0737e.png](https://i-blog.csdnimg.cn/blog_migrate/5ed2254ca0c08abd14336d251c64d08b.jpeg)
常用方法介绍
2.1、get方法
get()方法同样很简单,先判断传入的下标是否越界,再获取指定元素。
![bf0206b09daffe83cca8522d536c99c9.png](https://i-blog.csdnimg.cn/blog_migrate/1a4c71c75bd33770692ced7b8bd07f8b.jpeg)
2.2、set方法
set()方法也非常简单,直接对数组的指定位置赋值即可。
![fc82d94d1adfc18410c446ceffc809a2.png](https://i-blog.csdnimg.cn/blog_migrate/1dea0499cd293449e8f7b55e6933fc08.jpeg)
2.3、add方法
ArrayList添加元素有两个方法,一个是add(E e),另一个是add(int index, E e)。 这两个方法都是向容器中添加新元素,可能会出现容量(capacity)不足,因此在添加元素之前,都需要进行剩余空间检查,如果需要则自动扩容。扩容操作最终是通过grow()方法完成的。
![9fe6be802332e9e15672f8100339a428.png](https://i-blog.csdnimg.cn/blog_migrate/d828f5a9812bd3531f95258310ecfc7b.jpeg)
grow方法实现
![0bfc8f9263a3c4ac98813eae2bef8b69.png](https://i-blog.csdnimg.cn/blog_migrate/909ec8e34bc5a68b09164275a09daf68.jpeg)
添加元素还有另外一个addAll()方法,addAll()方法能够一次添加多个元素,根据位置不同也有两个方法,一个是在末尾添加的addAll(Collection extends E> c)方法,一个是从指定位置开始插入的addAll(int index, Collection extends E> c)方法。
不同点:addAll()的时间复杂度不仅跟插入元素的多少有关,也跟插入的位置相关,时间复杂度是线性增长!
2.4、remove方法
remove()方法也有两个版本,一个是remove(int index)删除指定位置的元素;另一个是remove(Object o),通过o.equals(elementData[index])来删除第一个满足的元素。
需要将删除点之后的元素向前移动一个位置。需要注意的是为了让GC起作用,必须显式的为最后一个位置赋null值。
- remove(int index)方法
![2641dd549e82c8f36e563dd63e1df990.png](https://i-blog.csdnimg.cn/blog_migrate/b2499d90cffdc306f9a24ebba8e504fc.jpeg)
- remove(Object o)方法
![3cbb102e8bd68c35f23e3f20f2503fc4.png](https://i-blog.csdnimg.cn/blog_migrate/c847684a38d09b1fd3cb7bbea01872af.jpeg)
03、LinkedList
在上篇文章中,我们知道LinkedList同时实现了List接口和Deque接口,也就是说它既可以看作一个顺序容器,又可以看作一个队列(Queue),同时又可以看作一个栈(Stack)。
LinkedList底层通过双向链表实现,通过first和last引用分别指向链表的第一个和最后一个元素,注意这里没有所谓的哑元(某个参数如果在子程序或函数中没有用到,那就被称为哑元),当链表为空的时候first和last都指向null。
![f9ee1463e8846d252b9ce1c6b67a9048.png](https://i-blog.csdnimg.cn/blog_migrate/2c9be87a45020156ebe6b8aa5ec61a20.jpeg)
![3058ac48748c924b72bbd44e178fef86.png](https://i-blog.csdnimg.cn/blog_migrate/379eb73ee0d6c73a457a86184c002a3a.jpeg)
![ed4c3964105378115d2ea1b8bc14eda1.png](https://i-blog.csdnimg.cn/blog_migrate/dbcfa5f796e53d8a8db1367feb213449.jpeg)
常用方法介绍
3.1、get方法
get()方法同样很简单,先判断传入的下标是否越界,再获取指定元素。
![643f6eb2ed813f47ea162e1b1030572b.png](https://i-blog.csdnimg.cn/blog_migrate/05aaa250f0ef06945b2f78a0822b8060.jpeg)
3.2、set方法
set(int index, E element)方法将指定下标处的元素修改成指定值,也是先通过node(int index)找到对应下表元素的引用,然后修改Node中item的值。
![94c97321176bcf36f2639a27eaf0b762.png](https://i-blog.csdnimg.cn/blog_migrate/7ab02498052c040ff9c33124b285c333.jpeg)
3.3、add方法
同样的,add()方法有两方法,一个是add(E e),另一个是add(int index, E element)。
![6c50d4e1b4707e356748c41761650824.png](https://i-blog.csdnimg.cn/blog_migrate/953c975f833fa0539486d114bec3bb0f.jpeg)
- add(E e)方法
该方法在LinkedList的末尾插入元素,因为有last指向链表末尾,在末尾插入元素的花费是常数时间,只需要简单修改几个相关引用即可。
![8fc4618039370b23cf49a26ab702e241.png](https://i-blog.csdnimg.cn/blog_migrate/c68646f9b5ba29a093c88d2f30747847.jpeg)
- add(int index, E element)方法
该方法是在指定下表处插入元素,需要先通过线性查找找到具体位置,然后修改相关引用完成插入操作。
具体分成两步,1.先根据index找到要插入的位置;2.修改引用,完成插入操作。
![18af21e3e61832719b7e0fee65381f46.png](https://i-blog.csdnimg.cn/blog_migrate/f73d28533d528a712933b126458d8187.jpeg)
同样的,添加元素还有另外一个addAll()方法,addAll()方法能够一次添加多个元素,根据位置不同也有两个方法,一个是在末尾添加的addAll(Collection extends E> c)方法,另一个是从指定位置开始插入的addAll(int index, Collection extends E> c)方法。
里面也for循环添加元素,addAll()的时间复杂度不仅跟插入元素的多少有关,也跟插入的位置相关,时间复杂度是线性增长!
3.4、remove方法
同样的,remove()方法也有两个方法,一个是删除指定下标处的元素remove(int index),另一个是删除跟指定元素相等的第一个元素remove(Object o)。
![d3093ede3c9efe56fc429e0dae4296a7.png](https://i-blog.csdnimg.cn/blog_migrate/3c80841ebd2988eeff9ab1a4864b3d08.jpeg)
两个删除操作都是,1.先找到要删除元素的引用;2.修改相关引用,完成删除操作。
- remove(int index)方法
通过下表,找到对应的节点,然后将其删除
public E remove(int index) { checkElementIndex(index); return unlink(node(index));}
- remove(Object o)方法
通过equals判断找到对应的节点,然后将其删除
![112f5e6d46b0df96fcaf58f082315d0c.png](https://i-blog.csdnimg.cn/blog_migrate/64bab1a0723e9153023c04ff6db6758d.jpeg)
删除操作都是通过unlink(Node x)方法完成的。这里需要考虑删除元素是第一个或者最后一个时的边界情况。
![2b0fd7ac7a4522eb6fe75f5f508844d6.png](https://i-blog.csdnimg.cn/blog_migrate/ffa19180fbf6a5dfeb59c0f749ead49e.jpeg)
04、Vector
Vector类属于一个挽救的子类,早在jdk1.0的时候,就已经存在此类,但是到了jdk1.2之后重点强调了集合的概念,所以,先后定义了很多新的接口,比如ArrayList、LinkedList,但考虑到早期大部分已经习惯使用Vector类,所以,为了兼容性,java的设计者,就让Vector多实现了一个List接口,这才将其保留下来。
在使用方面,Vector的get、set、add、remove方法实现,与ArrayList基本相同,不同的是Vector在方法上加了线程同步锁synchronized,所以,执行效率方面,会比较慢!
4.1、get方法
![f03b2b67c5d9c05f58df8be44e0b2eff.png](https://i-blog.csdnimg.cn/blog_migrate/875d2223c0c2a0cd6febf4da4c655590.jpeg)
4.2、set方法
![9168cefc9dbc67f46a8b2a722edd350d.png](https://i-blog.csdnimg.cn/blog_migrate/54f7a0c7c42d9685c95eff57b01299ce.jpeg)
4.3、add方法
![8ac1b9f599ac3021c70b9a8001aa82d9.png](https://i-blog.csdnimg.cn/blog_migrate/7d28be52cb3562f8edf55fe1005a476e.jpeg)
4.4、remove方法
![09907ecf0d18348a24a25c6f79651b1e.png](https://i-blog.csdnimg.cn/blog_migrate/5210a6883889e2ca51eafb0e46b0b3e7.jpeg)
05、Stack
在 Java 中 Stack 类表示后进先出(LIFO)的对象堆栈。栈是一种非常常见的数据结构,它采用典型的先进后出的操作方式完成的;在现实生活中,手枪弹夹的子弹就是一个典型的后进先出的结构。
在使用方面,主要方法有push 、peek 、pop 。
5.1、push方法
push方法表示,向栈中添加元素
![614a803424aa4bdc51e853903467b3a4.png](https://i-blog.csdnimg.cn/blog_migrate/1ab815cb3dbb658633d052f881881090.jpeg)
5.2、peek方法
peek方法表示,查看栈顶部的对象,但不从栈中移除它
![0a9b7e7c28efb90bbfa02cd117b2c741.png](https://i-blog.csdnimg.cn/blog_migrate/8eb30d69cfeebb762d5175f8bca3d988.jpeg)
5.3、pop方法
pop方法表示,移除元素,并将要移除的元素方法
![856acee6dd24362e93961455dc6e6453.png](https://i-blog.csdnimg.cn/blog_migrate/461e71c7ffadafdc088573ed4e3c92cd.jpeg)
关于 Java 中 Stack 类,有很多的质疑声,栈更适合用队列结构来实现,这使得Stack在设计上不严谨,因此,官方推荐使用Deque下的类来是实现栈!
06、总结
![86bdf2332b292b2a22ff8cbf11d1dad3.png](https://i-blog.csdnimg.cn/blog_migrate/756758040a47c3e8b73b7112911e3e9c.jpeg)
- ArrayList(动态数组结构),查询快(随意访问或顺序访问),增删慢,但在末尾插入,速度与LinkedList相差无几!
- LinkedList(双向链表结构),查询慢,增删快!
- Vector(动态数组结构),相比ArrayList都慢,被ArrayList替代,基本不在使用。优势是线程安全(函数都是synchronized),如果需要在多线程下使用,推荐使用并发容器中的工具类来操作,效率高!
- Stack(栈结构)继承于Vector,数据是先进后出,基本不在使用,如果要实现栈,推荐使用Deque下的ArrayDeque,效率比Stack高!