史上最全的Java容器集合之Vector和LinkedList(源码解读)

简介: 史上最全的Java容器集合之Vector和LinkedList(源码解读)

Vector 源码分析

其实Vector要讲的东西不多了,因为它和ArrayList的代码很像,就是再每个方法上加了锁,如下图

image.png

image.png

因为大部分和前面差不多,我来说说不同的点吧


image.png

看图上面的 这个是Vetor和ArrayList不同的另一个点 它的增长因子是可以自己定义的。我们来看grow方法


image.png


这段代码是扩容代码,可以看如果定义了曾长因子就每次扩容增长因子,不然就是扩容2倍

其他的增删改查,我就不说了,自己也没细看,但是底层是数组,所以大多数是相同了 只是加了一个synchronized锁

这边提一下其实ArrayList也可以变成线程安全

如果想要ArrayList实现同步,可以使用Collections的方法:List list=Collections.synchronizedList(new ArrayList(...));,就可以实现同步了

LinkedList源码分析

概念

  • LinkedList是基于链表实现的,所以先讲解一下什么是链表。链表原先是C/C++的概念,是一种线性的存储结构,意思是将要存储的数据存在一个存储单元里面,这个存储单元里面除了存放有待存储的数据以外,还存储有其下一个存储单元的地址(下一个存储单元的地址是必要的,有些存储结构还存放有其前一个存储单元的地址),每次查找数据的时候,通过某个存储单元中的下一个存储单元的地址寻找其后面的那个存储单元。
  • 理解:
  • LinkedList是一个双向链表
  • 也就是说list中的每个元素,在存储自身值之外,还额外存储了其前一个和后一个元素的地址,所以 也就可以很方便地根据当前元素获取到其前后的元素
  • 链表的尾部元素的后一个节点是链表的头节点;而链表的头结点前一个节点则是则是链表的尾节点(是不是有点像贪吃蛇最后 头吃到自己尾巴的样子,脑补下)
  • 既然是一个双向链表,那么必然有一个基本的存储单元,让我们来看LinkedList的最基础的存储单元。

对于单项链表 自己手撕了一个,有兴趣的可以去看看

🔥史上最全的Java容器集合之基础数据结构(手撕链表)

继承结构和层次关系


image.png


从这个图来对 和底层是数组结构的2个集合的区别 少实现了一个随机访问的RandomAccess接口 多实现了一个Deque接口 所以linkedList并不具备随机访问的功能,它也必须一个个去遍历,结论是: ArrayList用for循环遍历比iterator迭代器遍历快,LinkedList用iterator迭代器遍历比for循环遍历

还有可以看出LinkedList与ArrayList的另外不同之处,ArrayList直接继承自AbstractList,而LinkedList继承自AbstractSequentialList,然后再继承自AbstractList。另外,LinkedList还实现了Dequeu接口,双端队列。


image.png

Deque双端队列

Deque 双端队列,这个之前没讲过,唉 一个个坑,自己填吧

双端队列(deque,全名double-ended queue),是一种具有队列和栈的性质的数据结构。队列是一个有序列表,可以用数组或是链表来实现。

双端队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行。双端队列可以在队列任意一端入队和出队,


image.png

遵循先入先出的原则:

  • 先存入队列的数据,要先取出。
  • 后存入的要后取出

image.png

来看看队列的方法吧

  • add和offer 都表示插入 区别是一个失败会报错,一个失败返回false
  • remove 和 poll 都表示删除 都是删除成功返回队首元素 当队列为空时它会抛出异常。remove失败的话会报错 poll返回null
  • element peek 返回队首元素,但是不删除队首元素,element 当队列为空时它会抛出异常。peek返回null

总结一下 其实队列很简单,特别像这种简单 只有三个操作 加入 删除 查看 且全部是针对队首的操作。

接下来我们看看deque吧

image.png

有点多 哈哈 其实操作也差不多,队列就是只能操作队首,双向队列就是可以操作队首和队尾

手撕一个简单的队列

我们知道队列它的底层可以是数组或者是链表, 我们今天就用数组来实现一个简单的队列

package com.atguigu.ct.producer.controller;
/**
 * 六脉神剑
 * 1.使用数组实现队列功能,使用int数组保存数据特点:先进先出,后进后出
 */
public class QueueTest1 {
    public static void main(String[] args){
        //测试队列
        System.out.println("测试队列");
        Queue queue = new Queue();
        queue.in("六脉神剑");
        queue.in("七卖神剑");
        queue.in("八面玲珑");
        System.out.println(queue.out());
        System.out.println(queue.out());
    }
}
//使用数组定义一个队列
class Queue {
    String[] a = new String[5];
    int i = 1; //数组下标
    //入队
    public void in(String m){
        a[i++] = m;
    }
    //出队
    public String out(){
        int index = 0;
        String temp = a[1];
        for(int j=1;j<i;j++){
            a[j-1] = a[j];
            index++;
        }
        i = index;
        return temp;
    }
}
复制代码

结果

测试队列
六脉神剑
七卖神剑
Process finished with exit code 0
复制代码

其实很简单 就是每次出来的时候 把所有的元素的下标往前移一位。队列深入,这边就不深入了,靠各位大佬自己了,我也是黄婆卖瓜。接下来我们讲LinkedList

LinkedList 常量

image.png

三个常量 一个表示这个集合的大小,一个表示队列的元素的前一个元素,一个表示队列元素的后一个元素

image.png

来看一个Node 元素 它要存三个数据,一个是自己本身,一个是它的前驱,一个是它的后继

构造方法

image.png

LinkedList 有两个构造函数,第一个是默认的空的构造函数,第二个是将已有元素的集合Collection 的实例添加到 LinkedList 中,调用的是 addAll() 方法,这个方法下面我们会介绍。

  注意:LinkedList 是没有初始化链表大小的构造函数,因为链表不像数组,一个定义好的数组是必须要有确定的大小,然后去分配内存空间,而链表不一样,它没有确定的大小,通过指针的移动来指向下一个内存地址的分配。

添加元素

image.png

  • addFirst(E e)
  • 添加元素到链表的头部 只需要替换链表头部的后继指针就好了
  • addLast(E e)和add(E e)
  •  将指定元素添加到链表尾
  • add(int index, E element)
  • 讲元素拆入到指定的位置,这个要先找到这个元素的前后的元素,然后再修改。
  • addAll(Collection<? extends E> c)
  • 按照指定集合的迭代器返回的顺序,将指定集合中的所有元素追加到此列表的末尾

删除元素

image.png


删除元素和添加元素一样,也是通过更改指向上一个节点和指向下一个节点的引用即可。

  • remove()和removeFirst()
  • 从此列表中移除并返回第一个元素
  • removeLast()
  • 从该列表中删除并返回最后一个元素
  • remove(int index)
  • 删除此列表中指定位置的元素
  • remove(Object o)
  • 如果存在,则从该列表中删除指定元素的第一次出现,需要注意的是,这个是删除第一个出现的,并不是删除所有的这个元素

修改元素

通过调用 set(int index, E element) 方法,用指定的元素替换此列表中指定位置的元素。

image.png


这里主要是通过 node(index) 方法获取指定索引位置的节点,然后修改此节点位置的元素即可。

查找元素

因为截图截不全我就没截图了,大家可以对着源码看,具体的实现确实也不难,前面我们手撕链表大部分也实现了

  • getFirst()
  • 返回此列表中的最后一个元素
  • getLast()
  • 返回此列表中的最后一个元素
  • get(int index)
  •  返回指定索引处的元素
  • indexOf(Object o)
  •  返回此列表中指定元素第一次出现的索引,如果此列表不包含元素,则返回-1。

遍历集合

普通 for 循环

代码很简单,我们就利用 LinkedList 的 get(int index) 方法,遍历出所有的元素。  但是需要注意的是, get(int index) 方法每次都要遍历该索引之前的所有元素,这句话这么理解:比如上面的一个 LinkedList 集合,我放入了 A,B,C,D是个元素。

  • 总共需要四次遍历:
  • 第一次遍历打印 A:只需遍历一次。
  • 第二次遍历打印 B:需要先找到 A,然后再找到 B 打印。
  • 第三次遍历打印 C:需要先找到 A,然后找到 B,最后找到 C 打印。
  • 第四次遍历打印 D:需要先找到 A,然后找到 B,然后找到 C,最后找到 D。  
  • 这样如果集合元素很多,越查找到后面(当然此处的get方法进行了优化,查找前半部分从前面开始遍历,查找后半部分从后面开始遍历,但是需要的时间还是很多)花费的时间越多。那么如何改进呢?

迭代器 这个比较适合 迭代器的另一种形式就是使用 foreach 循环,底层实现也是使用的迭代器,这里我们就不做介绍了

总结

  • List继承了Collection,是有序的列表,可重复。
  • 实现类有ArrayList、LinkedList、Vector、Stack等
  • ArrayList是基于数组实现的,是一个数组队列。可以动态的增加容量!,线程不安全。基于数组所以查快,增删慢,因为如果要删除的话,它后面的元素就要重新改版索引。
  • LinkedList是基于链表实现的,是一个双向循环列表。可以被当做堆栈使用!,它的查慢,每次查都要遍历整个集合,但是它的增删快,特别是再头尾添加,特别的快。
  • Vector是基于数组实现的,是一个矢量队列,是线程安全的!基本差不多和ArrayList差不多,但是它是线程安全的,意味着性能没有那么好。

版本说明

  • 这里的源码是JDK8版本,不同版本可能会有所差异,但是基本原理都是一样的。

结尾

List的三个实现,讲完了,是不是感觉也不是很难呢?博主跟着学,发现以前只是用,但是现在确实熟悉很多了。下节开始讲Map,因为Set的底层是基于Map,它放最后

因为博主也是一个开发萌新 我也是一边学一边写 我有个目标就是一周 二到三篇 希望能坚持个一年吧 希望各位大佬多提意见,让我多学习,一起进步。

目录
相关文章
|
2月前
|
存储 Java 容器
HashMap 的基本操作【集合容器知识回顾 ⑤】
本文介绍了HashMap的基本操作,包括创建对象、添加、获取、删除和替换元素、获取所有key的集合、遍历HashMap,以及如何存储自定义类型键值对,并强调了当使用自定义对象作为键时需要重写equals和hashCode方法以确保正确的行为。
HashMap 的基本操作【集合容器知识回顾 ⑤】
|
3月前
|
Kubernetes Cloud Native Java
云原生之旅:从容器到微服务的演进之路Java 内存管理:垃圾收集器与性能调优
【8月更文挑战第30天】在数字化时代的浪潮中,企业如何乘风破浪?云原生技术提供了一个强有力的桨。本文将带你从容器技术的基石出发,探索微服务架构的奥秘,最终实现在云端自由翱翔的梦想。我们将一起见证代码如何转化为业务的翅膀,让你的应用在云海中高飞。
|
15天前
|
存储 Java 索引
Java中的数据结构:ArrayList和LinkedList的比较
【10月更文挑战第28天】在Java编程世界中,数据结构是构建复杂程序的基石。本文将深入探讨两种常用的数据结构:ArrayList和LinkedList,通过直观的比喻和实例分析,揭示它们各自的优势与局限,帮助你在面对不同的编程挑战时做出明智的选择。
|
25天前
|
安全 Java 程序员
Java集合之战:ArrayList vs LinkedList,谁才是你的最佳选择?
本文介绍了 Java 中常用的两个集合类 ArrayList 和 LinkedList,分析了它们的底层实现、特点及适用场景。ArrayList 基于数组,适合频繁查询;LinkedList 基于链表,适合频繁增删。文章还讨论了如何实现线程安全,推荐使用 CopyOnWriteArrayList 来提升性能。希望帮助读者选择合适的数据结构,写出更高效的代码。
49 3
|
1月前
|
存储 编译器 C++
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
53 2
|
1月前
|
存储 Java 索引
Java LinkedList详解
`LinkedList`是Java集合框架中的一个重要类,实现了`List`、`Deque`和`Cloneable`接口。它基于双向链表,支持动态扩展,允许重复元素。虽然通过索引访问元素的时间复杂度为O(n),但在插入和删除操作上表现优异,时间复杂度为O(1)。常用操作包括创建、添加、获取、删除和查找元素,以及使用迭代器遍历。适用于频繁插入和删除的场景,如队列和栈的实现。
|
2月前
|
存储 Java 容器
HashSet 的基本操作【集合容器知识回顾 ④】
本文介绍了HashSet的基本操作,包括创建和初始化、添加和删除元素、判断元素存在性、获取集合大小、遍历、求交集差集、转换为数组和其他集合类型、比较两个HashSet,以及如何将自定义对象作为HashSet的元素时重写hashCode和equals方法,最后总结了HashSet的性能特点和使用注意事项。
HashSet 的基本操作【集合容器知识回顾 ④】
|
2月前
|
存储 安全 Java
ArrayList的基本操作【集合容器知识回顾 ②】
这篇文章详细介绍了ArrayList的基本操作,包括创建对象、添加和删除元素、获取和更新元素、遍历、判断元素存在性、集合的空值检查、批量操作、转换为数组、截取子集合、查找元素索引、克隆拷贝、清空集合以及容量管理等,同时指出了使用ArrayList时的注意事项,如线程安全性、容量管理、删除元素的性能、遍历时的修改、空值处理和性能优化。
ArrayList的基本操作【集合容器知识回顾 ②】
|
2月前
|
存储 安全 Java
集合概览【集合容器知识回顾 ①】
这篇文章是关于Java集合框架的全面介绍,包括集合的层次结构、常见集合类的特点、如何学习集合框架、泛型的应用、基本操作以及一些常用的集合操作技巧和注意事项。
集合概览【集合容器知识回顾 ①】
|
2月前
|
Java API 索引
LinkedList的基本操作【集合容器知识回顾 ③】
本文详细介绍了LinkedList的基本操作,包括初始化、添加、获取、删除、替换元素、遍历,以及LinkedList独有的队列和栈相关操作,同时指出了LinkedList在插入和删除操作方面的优势以及在随机访问元素时的性能劣势。