自己关于常用数据结构(链表、二叉树、栈和队列)、集合框架与Collections工具类的学习笔记整理

文章目录

1.链表

1.1 链表的定义

​ 链表是由一组不必相连(不必相连:可以连续也可以不连续)的内存结构(链式存储结构),按特定的顺序链接在一起的抽象数据类型。

C:\Users\82169\AppData\Roaming\Typora\typora-user-images\image-20210312102825406.png

1.2 链表的种类

​ 链表常用的有 3 类: 单链表、双向链表、循环链表。

1.3 链表和数组的区别
  • 数组是一种连续存储线性结构,元素类型相同,大小相等;链表是离散存储线性结构,n 个节点离散分配,彼此通过指针相连,每个节点只有一个前驱节点,每个节点只有一 个后续节点,首节点没有前驱节点,尾节点没有后续节点。
  • 数组存取速度快,插入删除元素很慢;链表存取速度很慢,插入删除元素很快
  • 数组的空间通常是有限制的,需要大块连续的内存块;链表空间没有限制
  • 数组事先必须知道数组的长度,链表不需要知道
1.4 单链表操作
1.4.1 链表的节点对象
Class Node<T>{
	T data;//数据域
    Node next;//指针域,如果是双向那就还有Node previous;
    
    public Node{
    }
    public Node(T data, Node node){
		this.data = data;
        this.next = node;
    }
}
1.4.2 单链表插入
//假设要插入的是节点p,位于节点q之后
Node tmp = q.next;
q.next = p;
p.next = tmp;
1.4.3 单链表删除
//假设要删除的是节点p之后的节点q
Node tmp = p.next.next;//q.next
p.next = tmp;
1.5 双向链表操作
1.5.1 双向链表插入
//假设要插入的是节点p,位于节点q之后
Node tmp = q.next;
q.next = p;
p.pre = q;
tmp.pre = p;
p.next = tmp;
1.5.2 双向链表删除
//假设要删除的是节点p之后的节点q
Node tmp = p.next.next;//q.next
p.next = tmp;
tmp.pre = p;

2.二叉树

2.1 二叉树的定义

​ 二叉树是树的一种,每个节点最多可具有两个子树,即结点的度最大为 2

​ (结点度:结点拥有的子树数)。

2.1.1 二叉树节点对象
Class Node<T>{
	T data;//数据域
    //指针域
    Node leftChild;
    Node rightChild;
    
    public Node{
    }
    public Node(T data, Node leftChild,  Node rightChild){
		this.data = data;
        this.leftChild = leftChild;
        this.rightChild = rightChild;
    }
}
2.2 二叉树的种类
2.2.1 斜树

​ 所有结点都只有左子树,或者右子树。
在这里插入图片描述

2.2.2 满二叉树

​ 所有的分支节点都具有左右节点,h层的满二叉树共有2^h-1 个结点(h≥1) 。

image-20210312101334835.png

2.2.3 完全二叉树

​ 若设二叉树的深度为 h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。

image-20210312101408047.png

2.2.4 红黑树

​ 红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。当前根节点的左边全部比根节点小,当前根节点的右边全部比根节点大。

image-20210312103252362.png

红黑树的特点:

  • 节点可以是红色的或者黑色的
  • 根节点是黑色的
  • 叶子节点(特指空节点)是黑色的
  • 每个红色节点的子节点都是黑色的
  • 任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同
  • 速度特别快,趋近平衡树,查找叶子元素最少和最多次数不多于二倍
2.3 二叉树的性质
  • 二叉树第 i 层上的结点数目最多为 2^(i-1) (i≥1)
  • 深度为 h 的二叉树至多有 2^h-1 个结点(h≥1)
  • 包含 n 个结点的二叉树的高度至少为 log2 (n+1)
  • 在任意一棵二叉树中,若终端结点的个数为 n0,度为 2 的结点数为 n2,则 n0=n2+1
2.4 二叉树的遍历
  • 先序遍历
    • 先访问根节点,然后访问左节点,最后访问右节点(根->左->右)
  • 中序遍历
    • 先访问左节点,然后访问根节点,最后访问右节点(左->根->右)
  • 后序遍历
    • 先访问左节点,然后访问右节点,最后访问根节点(左->右->根)

3.栈

3.1 栈的定义

​ 又称堆栈, 栈(stack)是限定仅在表尾进行插入和删除操作的线性表。

​ 我们把允许插入和删除的一端称为栈顶,另一端称为栈底,不含任何数据元素的栈称为空栈。栈又称为先进后出的线性表 。

​ 压栈:存元素。

​ 弹栈:取元素

3.2 栈的特点
  • 先进后出(即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素)。
  • 栈的入口、出口的都是栈的顶端位置。

image-20210312102601560.png

4.队列

4.1 队列的定义

​ queue,简称队, 队列是一种特殊的线性表,是运算受到限制的一种线性表,只允许在表的一端进行插入,而在另一端进行删除元素的线性表。(当然也有双向队列和循环队列)

​ 队尾(rear)是允许插入的一端。队头(front)是允许删除的一端。空队列是不含元素的空表。

4.2 队列的特点
  • 先进先出(即,存进去的元素,要在后它前面的元素依次取出后,才能取出该元素)。
  • 队列的入口、出口各占一侧。

image-20210312102746560.png


5.类集设置的目的

​ 普通的对象数组的最大问题在于数组中的元素个数是固定的,不能动态扩充大小,所以最早可以通过链表实现一个动态对象数组。但是这种也太复杂了。

​ 所以为了方便用户操作各个数据结构,java引入了类集,有时候可以将类集看作是java对数据结构的实现。类集中最大的几个操作接口:Collection、Map、Iterator,这三个接口为以后要使用的最重点的接口。 所有的类集操作的接口或类都在 java.util 包中。

image-20210312093640314.png

image-20210312140502307.png


6.Collection接口

6.1 Collection接口的定义

​ Collection 接口是在整个 Java 类集中保存单值的最大操作父接口,里面每次操作的时候都只能保存一个对象的数据。 此接口定义在 java.util 包中。

6.2 Collection的常用抽象方法

image-20210312103901834.png

注:

  1. 集合转数组
        必须使用集合的toArray(T[] array)方法,传入类型完全一致的数组,大小最好是集合的长度,因为如果入参分配不够大的数组空间时,toArray方法自动重新分配内存空间,并返回新的数组地址;反之,如果数组空间过大,多余的部分会被置为null。
        特别地,不要去使用去使用集合的toArray()无参方法,否则它返回的只能是Object类,若是要强转其他类型,就会报转换异常。
  2. 数组转集合     在使用工具类Arrays.asList()把数组转化成集合的时候,不能使用其修改集合相关的方法。因为这个方法返回的是Arrays的内部类,并没有实现集合的修改方法。该方法体现的是适配器模式,只是转换接口,后台的数据仍是数组,如果我们修改数组的数据,转换的集合的数据也会随之改变。
6.3 Collection接口的常用子接口
  • List接口
  • Set接口

7.List接口

7.1 List接口的定义

​ 在整个集合中 List 是 Collection 的子接口,只能存取单个元素。

7.2 List接口的特点
  • 有序的集合(存储和取出的元素顺序相同)
  • 允许存入重复的元素
  • 有索引,可以用一般的for循环
7.3 List接口在Collection接口基础上的扩充方法

image-20210312104731023.png

7.4 List接口的实现类
7.4.1 ArrayList(最常用,95%)

​ 此类继承了 AbstractList 类。AbstractList 是 List 接口的子抽象类。适配器设计模式。

注意:不要什么都想着用ArrayList!!

7.4.1.1 ArrayList的特点
  • 效率高
  • 底层是一个数组结构,元素增删慢,查取快
  • 不是同步的,线程不安全。
  • 不指定长度的话刚开始是0,然后往里添加的时候是默认的10,1.5倍左右扩容(扩容的情况可以去看源码)

注意:ArrayList的subList方法可以获取结果子集,它返回的是ArrayList的内部类SubList并不是ArrayList,因此不可以强制转换,也不要尝试去增加或删除元素。

7.4.2 Vector(ArrayList的早期实现,4%)

​ 此类与 ArrayList 类一样,都是 AbstractList 的子类,可以与ArrayList对比来看。

image-20210312110526772.png

7.4.2.1 Vector的特点
  • 是同步的,线程安全
  • 效率高,底层是一个数组结构,元素增删慢,查取快
  • 指定大小扩容,不指定就是翻一倍容量
7.4.3 LinkedList(1%)

​ 此类继承了 AbstractList类,所以是 List 的子类。

​ 但是此类也是 Queue 、Deque接口的子类,可以作为队列使用,实现了Cloneable接口可以实现克隆,实现了java.io.Serializable接口,支持序列化

7.4.3.1 LinkedList的特点
  • 不是同步的,线程不安全
  • 效率高,底层是一个双向链表,增删快,查取慢。
  • 可以作为队列或者栈去使用

8. Set接口

8.1 Set接口的定义

​ 继承自 Collection 接口,它与 Collection 接口中的方法基本一致,并没有对 Collection 接口进行功能上的扩充,只是比 Collection 接口更加严格了。

8.2 Set接口的特点
  • 元素无序;

  • 都会以某种规则保证存入的元素不出现重复,最多一个null;

  • 没有索引(不能使用普通的for循环遍历)。

8.3 Set接口的实现类
8.3.1 HashSet

​ java.util.HashSet 是 Set 接口的一个实现类,底层的实现其实是一个 java.util.HashMap 支持。

8.3.1.1 HashSet的特点
  • 元素无序

  • 都会以某种规则保证存入的元素不出现重复,最多一个null;

  • 根据对象的哈希值来确定元素在集合中的存储位置,具有良好的存取和查找性能;

  • 如果需要使用HashSet存储自定义类型元素,那么必须保证存储的元素重写hashCode 与 equals 方法。

因为set集合需要保证元素唯一),如果不重写HashCode方法,那么Object类默认的HashCode方法会根据对象的存储地址来得出散列值,那散列值基本就不可能相等,set可能会被存入同一个数据,但是它认为是不同的。

8.3.2 TreeSet

​ java.util.TreeSet是Set接口的一个实现类,采用有序的二叉树进行存储,底层的实现其实是一个 java.util.TreeMap 支持。

​ 所提供的方法和HashSet的方法基本一致,只不过存储的方式不一样。

​ 需要注意的是,如果要比较自定义元素,需要对这个元素类型实现Comparble接口定义compareTo方法,或者传入一个Comparator比较器。

8.3.3 LinkedHashSet

​ java.util.LinkedHashSet集合继承于HashSet集合,是Set接口的实现类的子类。

8.3.3.1 LinkedHashSet的特点
  • 不允许存储重复的元素;

  • 底层是一个哈希表(数组+链表or红黑树)+链表结构

  • 比HashSet多了一条双向链表(记录元素的存储顺序,保证元素有序,如果插入同样的值,那么这个key的插入顺序就变了)。

    如果元素已存在,重新插入后,会被重新插入的set中,比如一共5个元素,原来的元素1是第2个插入的,现在再插入元素1,元素1的插入顺序就是第5.

8.4 安全失败和快速失败
  • 安全失败: Iterator的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响,java.util.concurrent包下面的所有的类都是安全失败的。
  • 快速失败: 操作的是底层集合本身,java.util包下面的所有的集合类都是快速失败的。

9. Iterator接口

9.1 Iterator接口的定义

​ 在程序开发中,经常需要遍历集合中的所有元素。Iterator 接口也是Java集合中的一员,但它与 Collection 、 Map 接口有所不同, Collection 接口与 Map 接口主要用于存储元素,而 Iterator 主要用于迭代访问(即遍历) Collection 中的元素,因此 Iterator 对象也被称为迭代器。

9.2 迭代器操作
  • public Iterator iterator() :

    获取集合对应的迭代器,用来遍历集合中的元素的。

  • public boolean hasNext() :

    如果仍有元素可以迭代,则返回 true。

  • public E next() :

    返回迭代的下一个元素。

注意:

​ 不同的集合可以获取对应的不同的迭代器,这些迭代器实现了Iterator接口,拥有更多的操作方法:add()、previous()……

9.3 增强for

​ 最早出现在C#,增强for循环(也称for each循环)是JDK 1.5以后出来的一个高级for循环,专门用来遍历数组和集合的。

它的内部原理其实是个Iterator迭代器,所以在遍历的过程中,不能对集合中的元素进行增删操作。

注意:不要在foreach里面循环中进行元素的增加或删除操作,remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。参考一位仁兄的解释


10. Comparator比较器

10.1 什么是Comparator?

​ 排序用来比较的,简单的说就是两个对象之间比较大小。那么在JAVA中提供了两种比较实现的方式:

  • 一种是 比较死板的采用 java.lang.Comparable 接口去实现;(每次排序都按照既定规则去走)
  • 一种是灵活的,当我需要做排序的时候在去选择的 java.util.Comparator 接口完成。(排序的时候给他传过去即可,每次排序都可以重新定义排序规则)
10.2 Comparable和Comparator两个接口的区别?
  1. Comparable强行对实现它的类对象(这个类实现了Comparable,重写了compareTo方法)进行整体排序;Comparator强行对某些对象(对象所属的类可以不实现,用的时候传入即可)进行整体排序

    (也就是说Comparble对实现了它的这个类的所有对象都定义一套规则去排序,Comparator只是设计一套规则对于这个类的某些对象去排序)

  2. Comparable只能在类中实现compareTo()一次可以将Comparator 传递给sort方法(如Collections.sort 或 Arrays.sort,也就是可以在不同使用时定义不同规则),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构 (如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。


11. Map接口

11.1 Map接口的定义

​ Collection 中,每次操作的都是一个对象,如果现在假设要操作一对对象(键值对),则就必须使用 Map 了。里面的所有内容都按照 key->value 的形式保存,也称为二元偶对象。

11.2 Map接口的特点
  • 键KEY唯一,值VALUE可以重复,每个键对应一个值(Set接口的实现类底层实际调用了Map接口的实现类,用键去存数据)
  • 键只允许一个为null
  • 无序
  • Map 接口本身是不能直接使用 Iterator 进行输出的,而是使用 Map 接口中的 entrySet()方法将 Map 接口的全部内容变为 Set 集合,然后使用 Set 接口中定义的 iterator()方法为 Iterator 接口进行实例化
11.3 Map接口的常用方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LGoySUv7-1616253311918)(C:\Users\82169\AppData\Roaming\Typora\typora-user-images\image-20210312180647314.png)]

11.4 Map接口的实现类
11.4.1 HashMap

​ 在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。 但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。

​ 而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。

扩容机制:如果对象数组长度不到64,链表的长度超过8的时候,会去扩容对象数组;只有数组长度到达64之后,链表的长度再超过8的时候,会去把链表转换成红黑树。

​ 注意:

  • JDK1.8之前,HashMap插入链表是采用的头插法,后来用的是尾插法,头插法效率高于尾插法。之所以改成尾插法,是因为需要判断链表的长度来决定是否树化。而且头插法在多线程中可能会造成链表回环(多线程情况下)。
  • HashMap的默认长度是16(注意不是说一开始就是,如果没有指定初始长度,那么一开始长度是0,往里添加数据的时候才会触发扩容机制),默认扩充因子是0.75F(超过75%的桶使用时),扩充一倍的空间
  • 如果HashMap的对象数组中某个链表的数据量减少到6的时候,原来是红黑树的,则会从红黑树转换成链表,如果原来不是红黑树就不会。
  • 如果HashMap的键设置的数据类型是自定义类型的时候一定要注意,存入后这个自定义类型的对象不要轻易改变:
    • 因为一改变,再去找的时候,hashcode发生了变化,就没办法再去根据hashcode去找到原来存储的位置了
    • 如果再创建一个内容相同的对象,用这个对象作为键去找,确实可以找到,但是内部会调用equals方法,也就是说hashcode一样了,equals不一样(原来的对象里的数据已改变,和新创建的这个对象匹配不上)
  • 在集合初始化的时候,能确定初始化大小的,就指定集合初始化大小,因为扩容resize需要重建hash表,严重影响性能。
  • 使用entrySet或者Map.forEach(jdk1.8)方法遍历Map类集合,而不是使用keySet,因为keySet方法其实遍历了2次,一次是转为Iterator对象,另一次是从集合中取出key对应的value。
    //源码
	static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16,默认长度
    static final int MAXIMUM_CAPACITY = 1 << 30;//2^30,int是四个字节
    static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认扩容因子
    static final int TREEIFY_THRESHOLD = 8;//树化的阈值
    static final int UNTREEIFY_THRESHOLD = 6;//重新链化的阈值
    static final int MIN_TREEIFY_CAPACITY = 64;//只有数组长度到达64之后,链表的长度超过8的时候,会去把链表转换成红黑树

image-20210312180810271.png
image-20210312180821119.png

:ConcurrentHashMap的key和Value都不能为null
补充:https://blog.csdn.net/weixin_44460333/article/details/86770169

11.4.2 Hashtable

image-20210312190524356.png

11.4.3 LinkedHashMap

​ 与HashMap最大的不同在于,后者维护了一个运行于所有条目的双向链表,此链表定义了迭代顺序,如果重新插入一个键值对(键已经存在,那么这个键的插入顺序就会改变)

11.4.4 ConcurrentHashMap
  • JDK 1.7 数组+Segment+分段锁的方式实现

    ​ ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表,同时又是一个ReentrantLock(Segment继承了ReentrantLock)

    • 坏处是这一种结构的带来的副作用是Hash的过程要比普通的HashMap要长

    • 好处是写操作的时候可以只对元素所在的Segment进行加锁即可,不会影响到其他的Segment,这样,在最理想的情况下,ConcurrentHashMap可以最高同时支持Segment数量大小的写操作(刚好这些写操作都非常平均地分布在所有的Segment上)。
      所以,通过这一种结构,ConcurrentHashMap的并发能力可以大大的提高。

  • JDK 1.8 彻底放弃了Segment转而采用的是Node,采用了数组+链表+红黑树的实现方式来设计,内部大量采用CAS操作。

    • Node:保存key,value及key的hash值的数据结构。其中value和next都用volatile修饰,保证并发的可见性。
    • CAS是compare and swap的缩写,cas是一种基于锁的操作,而且是乐观锁。在java中锁分为乐观锁和悲观锁。悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源
    • CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值和A的值是一样的,那么就将内存里面的值更新成B。CAS是通过无限循环来获取数据的,如果在第一轮循环中,a线程获取地址里面的值被b线程修改了,那么a线程需要自旋,到下次循环才有可能机会执行。

image-20210312192408364.png


12. Collections工具类

image-20210312195612066.png

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值