在Java集合中分为单列集合和双列集合!单列集合也就是我们常说的Collection集合下的子接口或者实现类,多列集合也就是我们常说的Map集合下的父接口或者实现类!那我们先来看一看单列集合List吧!
1单列集合
![](https://i-blog.csdnimg.cn/blog_migrate/589e8ff3f92a8a2e4e194561ef73a3a0.png)
1.1先对list集合和Set集合做一个小总结
看到List系列的集合都有一个特点:有序,可重复,有索引.
那看到Set集合那就刚好相反了吧:无序,不可重复,无索引.
有些铁子就懵逼了啊!啥是有序啥是无序啊,啥是可重复,啥是不可重复啊,啥是有索引,啥是没有索引啊!下面让丐版小杨哥给你一一道来.
1.2有序,可重复,有索引可重复补充说明
有序:有序指的是存储的顺序和取出来的顺序是一至的,在ArrayList的集合中存储的数据是"张三","李四","王五",那么取出来的顺序也是"张三","李四","王五".
可重复:可重复就是指可以存储相同元素的值,我存储了"张三",我再存储一个"张三",那当然没毛病.
有索引:我们可以理解为像数组的下标,可以通过for循环遍历出来!
1.3Collection集合
Collection是单列集合的祖宗接口,它的功能是全部单列集合可以继承使用的!
1.3.1Collection常用方法
![](https://i-blog.csdnimg.cn/blog_migrate/170fd250a7dc96dad4c51bfb8762b716.png)
add方法
public boolean add(E e) 表示返回值是一个boolean类型就是说增加一个数据,会返回一个结果,false表示添加失败,true表示添加成功,E e是一个泛型,表示可以存储的类型是你传入的类型!看代码!也就是Collection<String> collection = new ArrayList<>();这一行代码指定了String泛型,那么E就是String类型不能再存储其他类型了哟!!
public static void main(String[] args) {
//为什么要用ArrayList呢?因为我们知道Collection是一个父接口
//所以我门要new一个接口的实现类ArrayList
Collection<String> collection = new ArrayList<>();
collection.add("张三");
collection.add("李四");
collection.add("王五");
collection.add("赵六");
System.out.println("-----打印集合的内容-----");
System.out.println(collection);
System.out.println("新添加一个元素a是否添加成功:"+collection.add("a"));
}
![](https://i-blog.csdnimg.cn/blog_migrate/f72f555d3bdb374344f07f777b16dd30.png)
clear()方法
public void clear()方法这个方法用于清空集合中所有的值,记住是所有哦!
public static void main(String[] args) {
Collection<String> collection = new ArrayList<>();
collection.add("张三");
collection.add("李四");
collection.add("王五");
collection.add("赵六");
System.out.println("-----打印没有调用clear方法集合的内容-----");
System.out.println(collection);
//调用clear方法
collection.clear();
System.out.println("-----打印调用clear方法集合的内容-----");
System.out.println(collection);
}
![](https://i-blog.csdnimg.cn/blog_migrate/af93c827e9fa3de11fbfa891e88113ea.png)
remove (E e)方法
public boolean remove (E e) 同add方法类似!方法带有返回值表示删除元素是否成功(true表示删除成功,false表示删除失败)E 表示泛型!我就不再做过多的解释了,如果实在不懂的话,就去看看Java的泛型吧!
public static void main(String[] args) {
Collection<String> collection = new ArrayList<>();
collection.add("张三");
collection.add("李四");
collection.add("王五");
collection.add("赵六");
System.out.println("-----打印没有调用remove方法集合的内容-----");
System.out.println(collection);
//调用remove方法
collection.remove("张三");
System.out.println("-----打印调用clear方法集合的内容-----");
System.out.println(collection);
}
![](https://i-blog.csdnimg.cn/blog_migrate/46140b9582658a74c7d4d99ab80bffb5.png)
contains (Object obj)方法
下面是public boolean contains (Object obj)方法,这个方法表示的是这个集合中是否包含某某元素(也就是你传入的元素,有则返回true,反之返回false)
public static void main(String[] args) {
Collection<String> collection = new ArrayList<>();
collection.add("张三");
collection.add("李四");
collection.add("王五");
collection.add("赵六");
System.out.println("-----集合中的内容-----");
System.out.println(collection);
System.out.println("collection中是否存在张三元素:"+collection.contains("张三"));
System.out.println("collection中是否存在李七元素:"+collection.contains("李七"));
}
![](https://i-blog.csdnimg.cn/blog_migrate/21c54b22029a15b1a341e7a293990322.png)
isEmpty()方法
public boolean isEmpty()这个方法呢就是来判断集合中是否为空(为空则返回true,不为空返回false)
public static void main(String[] args) {
Collection<String> collection = new ArrayList<>();
collection.add("张三");
collection.add("李四");
collection.add("王五");
collection.add("赵六");
System.out.println("-----集合中的内容-----");
System.out.println(collection);
System.out.println("集合是否为空:"+collection.isEmpty());
//我们可以调用clear方法清空集合
collection.clear();
System.out.println("调用clear方法清空集合,集合是否为空:"+collection.isEmpty());
}
![](https://i-blog.csdnimg.cn/blog_migrate/5a817e3f928d948e9d42f24ed4597c93.png)
size()方法
public int size()方法用来统计集合中元素的个数并返回
public static void main(String[] args) {
Collection<String> collection = new ArrayList<>();
collection.add("张三");
collection.add("李四");
collection.add("王五");
collection.add("赵六");
System.out.println("collection集合的个数为:"+collection.size());
}
![](https://i-blog.csdnimg.cn/blog_migrate/a7128ac07479cced7522b4d5cfdb3983.png)
通过学习Collection集合中常用的6种方法,那么在使用集合是也就够用了!不管是ArrayList还是LinkedList,至于Vector集合已经被淘汰了很少有人用,我也不做过多的解释
至于学习集合那我们还需要了解集合如何遍历,哪至于为什么要学习集合遍历?用for循环不就好了吗?但是你有没有想过如果是set集合呢?或者是map集合呢?对啊!他们是没有索引的啊!那怎么办呢?所以我们需要学习集合的通用表里方法.Collection集合普遍的的遍历方法分为3种迭代器遍历,for循环遍历,增强for循环遍历.
请看 德古拉斯·纯情·幽默·深情·暖男·阳光大男孩·丐版小杨哥一一道来.
1.3.2Collection遍历的三种方式
迭代器遍历List集合
可能有些铁子又要懵逼了什么是迭代器遍历,简单的说迭代器遍历就是把集合中的每一个元素一个一个获取出来
假设我有一个长度为10的数组,绿色的箭头便是当前指向的元素,从0开始,当然下图已经指向左后一个了.
![](https://i-blog.csdnimg.cn/blog_migrate/5e40caa97a76c15339b4153c1004b667.png)
这个个箭头将会从头开始一个一个去获取集合中的元素!
来看一下迭代器的方法
![](https://i-blog.csdnimg.cn/blog_migrate/d999ddced43bff39ed88bf093db77e42.png)
我们用迭代器提供的方法加循环写一个List集合的遍历
public static void main(String[] args) {
Collection<String> collection = new ArrayList<>();
collection.add("张三");
collection.add("李四");
collection.add("王五");
collection.add("赵六");
//获取一个迭代器
Iterator<String> iterator = collection.iterator();
//判断是否有下一个元素有就返回true循环继
while(iterator.hasNext()){
//获取当前迭代器的值
String next = iterator.next();
//循环输出就可以了
System.out.println(next);
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/673323c3802aba54b0c3b881ed341d5a.png)
在使用迭代器的时候要注意了!当迭代器指向最后一个元素的时候,这个时候我们再调用迭代器的next()方法就会抛出NoSuchElementException异常,原因就是当前迭代器已经指向了最后一个元素了,下一个元素已经没有元素了!索引会抛出异常!
那么,有些睿智的同学就会问了,那我下一次遍历咋办呢?解决方法就是再获取一个迭代器不就完事了么!
public static void main(String[] args) {
Collection<String> collection = new ArrayList<>();
collection.add("张三");
collection.add("李四");
collection.add("王五");
collection.add("赵六");
//获取一个迭代器
Iterator<String> iterator = collection.iterator();
while(iterator.hasNext()){//判断是否有下一个元素有就返回true循环继
String next = iterator.next();//获取当前迭代器的值
System.out.println(next);//输出就可以了
}
Iterator<String> iterator1 = collection.iterator();
while(iterator1.hasNext()){//判断是否有下一个元素有就返回true循环继
String next = iterator1.next();//获取当前迭代器的值
System.out.println(next);//输出就可以了
}
}
增强for循环遍历List集合
增强for循环的格式为for(Object obj: collection){} 我们在循环中定义一个Object obj表示的当前的循环的变量,collection表示你需要遍历的集合!当然我们不一定需要定义Object类型的obj,类型更具你集合传入的泛型来定Iterator<String> iterator = collection.iterator();我在这里传入的String类型,所以for循环就应该是:
public static void main(String[] args) {
Collection<String> collection = new ArrayList<>();
collection.add("张三");
collection.add("李四");
collection.add("王五");
collection.add("赵六");
for (String obj:collection) {
System.out.println(obj);
}
}
![](https://i-blog.csdnimg.cn/blog_migrate/17c606f16ba0727359cf7ad4341c05f0.png)
forEach循环遍历List集合
重点来了!forEach需要对匿名内部类以及Lambda表达式有所了解
首先我们需要了解collection.forEach(这个地方该怎么写);
我们用Idea工具看看foreach
![](https://i-blog.csdnimg.cn/blog_migrate/3158fb1d4c7a931a8179108edff04333.png)
很显然forEach是采用上述的for循环来对集合经行遍历的!并且发现里面有一个Consumer接口
我们来看看这个接口发现有一个注解@FunctionalInterface
![](https://i-blog.csdnimg.cn/blog_migrate/b3f98540c1a49c9d4aa14a4003fd382a.png)
那就好办了啊!我们通过匿名内部类的方式来对集合进行遍历
public static void main(String[] args) {
Collection<String> collection = new ArrayList<>();
collection.add("张三");
collection.add("李四");
collection.add("王五");
collection.add("赵六");
collection.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
}
![](https://i-blog.csdnimg.cn/blog_migrate/0cab608c70be12ed08e638ce97df2062.png)
同时也可以使用Lambda表达式来简化遍历
public static void main(String[] args) {
Collection<String> collection = new ArrayList<>();
collection.add("张三");
collection.add("李四");
collection.add("王五");
collection.add("赵六");
collection.forEach(s -> System.out.println(s));
}
![](https://i-blog.csdnimg.cn/blog_migrate/8b0e70cd846595c288aa8527e9028e81.png)
遍历是不是变得非常简单了呀!
迭代器源码分析
![](https://i-blog.csdnimg.cn/blog_migrate/e36e3db7bb6de7a5ad5cd1ce898efaad.png)
1.4简单的数据结构
有很多铁子又懵逼了呀!学个Java集合你搁这给我扯一堆数据结构呢?
别急,我们学习了数据结构可以深入了解集合的底层原理,当然若果你是应付考试的话,该章节完可以跳过!
栈
什么栈呢?
栈的特点我们需要知道8个字:先进后出,后进先出
首先我们来看一下栈的示意图,你也可以直接理解为一个杯子
栈的顶端我们叫栈顶,栈的下端我们叫栈底
![](https://i-blog.csdnimg.cn/blog_migrate/2d7cd57d3e27e89a71abea7fae5a081e.png)
现在我有4个数据,看是如何存入取出来的存入数据时我们有个专业的名词叫做压栈或者进站
![](https://i-blog.csdnimg.cn/blog_migrate/1ec74f9766192887d5165c80abdb1927.png)
首先我们存入数据A 注意是从开口进去的就像你像杯子倒水一样,只能从顶上倒啊!
![](https://i-blog.csdnimg.cn/blog_migrate/9751a2dc375e5b9152f298bc1cc0ce3a.png)
数据B存入
![](https://i-blog.csdnimg.cn/blog_migrate/c91d942d6b4327b2440d6fefd53ef0fb.png)
数据C存入
![](https://i-blog.csdnimg.cn/blog_migrate/c7b166bc4e3c4328d8ac44241836d219.png)
数据D存入
![](https://i-blog.csdnimg.cn/blog_migrate/7b1d2140991af39d60d540abfedb46a7.png)
上述的示意图就为数据进栈!那么我们来看看数据时如何出栈的呢?我想聪明的你应该猜到了就是从栈顶取出,当然我们也有个专业的名次叫做出栈或者弹栈
数据D出栈
![](https://i-blog.csdnimg.cn/blog_migrate/c7b166bc4e3c4328d8ac44241836d219.png)
数据C出栈
![](https://i-blog.csdnimg.cn/blog_migrate/c91d942d6b4327b2440d6fefd53ef0fb.png)
数据B出栈
![](https://i-blog.csdnimg.cn/blog_migrate/9751a2dc375e5b9152f298bc1cc0ce3a.png)
数据A出栈
![](https://i-blog.csdnimg.cn/blog_migrate/e385328c5ea8700b09865ae88cfd80c7.png)
到这里你是不是发现整个过程就像手枪的弹夹一样,或者像你吃薯片一样啊!这就是栈!
先进后出,后进先出
最后做一个补充,说到栈,我们是不是想到了我们Java栈内存啊!没错Java的栈内存用的也是这个思想!先调用的方法再最下面,后调用的方法在最上面,执行完之后最上面的方法先出去.相信各位睿智的小伙伴能理解,我就不画示意图了.
队列
什么是队列呢?
首先我先说一下队列的特点,先进先出,后进后出
我们可以把队列理解成为一个水管,在前面的水先流出,在后面的水后出,就是下图这个样子
当然队列的存入,取出我们也有专属的名词,队列从后端进入我们成为:入队列,数据从前端取出我们称为:出队列
![](https://i-blog.csdnimg.cn/blog_migrate/8dfe608ab0c39d6a69fe719aaf38b252.png)
假设我还是有四个数据
![](https://i-blog.csdnimg.cn/blog_migrate/f7489cfbb96caed4e2f0f4a586ed4f12.png)
我呢么存入4个数据
数据A入队列
![](https://i-blog.csdnimg.cn/blog_migrate/416257bd535a71e63db07332f9acaf65.png)
数据B入队列
![](https://i-blog.csdnimg.cn/blog_migrate/0528d3da39e21cd7fda2321b869ebd1b.png)
数据C入队列
![](https://i-blog.csdnimg.cn/blog_migrate/8a911d7c898b65cff981b30198d5bac2.png)
数据D入队列
![](https://i-blog.csdnimg.cn/blog_migrate/ea1076625528666397eb7077b3409ec0.png)
那么我们看看数据是如何出去的!
数据A出队列
![](https://i-blog.csdnimg.cn/blog_migrate/a28d63ad06eb4868bc6e143fe997698c.png)
数据B出队列
![](https://i-blog.csdnimg.cn/blog_migrate/6e112f34c42f4aa4fd5a73e3348d79fc.png)
数据C出队列
![](https://i-blog.csdnimg.cn/blog_migrate/52390c1ca8bdfb8cad892af5c255f63e.png)
数据D出队列
![](https://i-blog.csdnimg.cn/blog_migrate/f78d40e26121d31007cf7cf4ae926bda.png)
这个过程是不是就像我们排队打饭一样,或者银行办理业务!
先进先出,后进后出
数组
什么是数组呢?就可以理解为我们Java的数组! int [ ] array这种类型
假设我有一个数组,0-6为索引,对应我们的数据A-G
![](https://i-blog.csdnimg.cn/blog_migrate/4a62c3a110a7c5fb14dd1c72daa45127.png)
通过观察我们不难发现,数组的数据结构查询数据时比较快的,因为每一个索引与数组的内容对应,想要获取内容,我们只需要获取索引即可!
但是,问题来了,那删除数据呢?
删除数据就比较麻烦了!
要将原始数据删除,然后每一个数据前移
假设我删除B
![](https://i-blog.csdnimg.cn/blog_migrate/1133b689800b3cc187c44da92249971e.png)
这就完了吗?
![](https://i-blog.csdnimg.cn/blog_migrate/670d221b8509c412d522f904a9d2d962.png)
不不不,我们还需要将删除数据之后的每一个数据前移,是不是非常麻烦
![](https://i-blog.csdnimg.cn/blog_migrate/e2b9774ec5a5292f17d12fc151f07c27.png)
加设我又要加入一个数据B再1索引处
![](https://i-blog.csdnimg.cn/blog_migrate/d849cacb2e3e993c945d3974c3e3e9bd.png)
首先是不是得把1索引后面所所有的元素后移
![](https://i-blog.csdnimg.cn/blog_migrate/670d221b8509c412d522f904a9d2d962.png)
然后再把B放入到1索引的位置
![](https://i-blog.csdnimg.cn/blog_migrate/1133b689800b3cc187c44da92249971e.png)
总结:
查询速度快:在查询时,只需要过去数据对应的索引,就能获取对应的数据,获取每一个索引所消耗的时间都是一样的,数组的每一个元素在内存总都是连续的!
增删效率底:在删除数据时,先要删除指定索引位置的数据在把这个索引之后的数据前移,如果之后数据较多,书不是效率就低了.在指定索引增加数据时需要先腾出位置,那么该索引之后所有的元素都要后移,然后再怎加数据到指定的索引!
链表
链表是由很多的结点相互连接组成
我们来看一下链表的示意图
![](https://i-blog.csdnimg.cn/blog_migrate/911329010041b7cd7115343225b754d0.png)
通过观察我们不难发现,每个节的前后或有3个值,用来记录前一个结点的存储的地址和后一个结点存储的地址.
我们来创建一个链表
当链表创建第一个元素的时候,该元素我们称为头节点,应为该节点的前一个元素的地址值为null我用^表示
![](https://i-blog.csdnimg.cn/blog_migrate/b5fd59e658e9ad0658898a1154dfdbf3.png)
添加一个数据A,首先需要创建一个新的结点,地址为11
![](https://i-blog.csdnimg.cn/blog_migrate/4fb13f7c7db6f0759d951837c3c4875b.png)
然后我们需要用头节点记录下一个元素A的地址所以^就是11形成链表
![](https://i-blog.csdnimg.cn/blog_migrate/cd382edce919762c4280f8dbf6fde2b4.png)
假设在创建一个结点c
![](https://i-blog.csdnimg.cn/blog_migrate/d32ea07c8e11027f445a53542b179ca0.png)
元素越来越多,是不是看起来就像一个链子一样,称为链表.
当我需要在添加一个元素时我们是不是只需要改变结点的地址值就可以了!
![](https://i-blog.csdnimg.cn/blog_migrate/43fb3d538aa314b313620e5d591de196.png)
当和我们要删除一个元素时,也只需要改变前后结点的地址值就ok了
删除前:
![](https://i-blog.csdnimg.cn/blog_migrate/60e13fc36040a9351434ddeedbdf4ec6.png)
删除后:
![](https://i-blog.csdnimg.cn/blog_migrate/8e6341d19adb3daefd056b8857df244e.png)
总结:链表的的结点是独立的对象,在内存中是不连续的,每个节点包含下一个结点的地址,在查询的过程中相对较慢,只能从头开始找.
这就完了吗?
当然不是,在链表刚刚开始的那张图我们发现还有一个记录上一个地址的值没用到呢,接下来也就是我想说的双向列表
![](https://i-blog.csdnimg.cn/blog_migrate/e3e1549683637fae472cc9b2f60bdfd1.png)
双向链表有什么特点呢?可以提高查询的效率,为什么呢?
当我们在双向链表中查询数据时会先判断该元素距离左右两边哪边离的近,查询就从哪边开始,
二叉树专属名词解释
我现在有一颗二叉树,我们来看一下二叉树的专属名词
首先我们了解一下结点的数据结构
结点(或者节点):里面有3地址值,分别记录着父节点地址,左子节点地址,右子结点的地址值,和自身的值.
![](https://i-blog.csdnimg.cn/blog_migrate/b4eb477d8c28909a5a54ee0097b845c8.png)
15,16,17都叫做结点:
16是18和17的父节点
15是左子结点
17是右子结点
22是根结点
![](https://i-blog.csdnimg.cn/blog_migrate/877618def159cfc961aa3a8bc64a1dd4.png)
![](https://i-blog.csdnimg.cn/blog_migrate/0e3a6b271fdb759f831aacc991fea403.png)
来看一下度和树高,下图中每个结点的度都为5,树高为4
![](https://i-blog.csdnimg.cn/blog_migrate/50ac3bb6558abcaf9c42858d8385f952.png)
下面是根结点左子树,和根节电右子树,很显然,22是跟结点
![](https://i-blog.csdnimg.cn/blog_migrate/c45b401cd345c46b6a478faea02897fe.png)
上述就是普通二叉树专属名词的内容
二叉树
正式进入二叉树,下图为一颗普通二叉树,里面的数据存储没有规律,找数据只能通过遍历的方式查找,相对来说查询效率极低,为了改进我们进入到二叉查找树
![](https://i-blog.csdnimg.cn/blog_migrate/63650e1418d9aa9c0c010fce3b7b49c3.png)
二叉查找树
二叉查查找树与普通的树相比我们返现一个规律,小的数据存左边,作为左子节点,大的数据存右边作为右子节点,
假如我们要找数据11,
先和跟结点7比较,发现比7
再和7的右子节点比较,比10大,
在和10的右子结点比较,发现相等,那么数据就找到了,是不是省事多了
![](https://i-blog.csdnimg.cn/blog_migrate/e3c2ffd62d4a56bc7fcd80aac9f556d3.png)
那么我们再来看看查找二叉树遍历的几种方式
前序遍历
![](https://i-blog.csdnimg.cn/blog_migrate/a70a47c4b3abd563fdd759a6b0fdc643.png)
中序遍历
![](https://i-blog.csdnimg.cn/blog_migrate/e6a8acce2ca20259f9b55432959f28bb.png)
后序遍历
![](https://i-blog.csdnimg.cn/blog_migrate/e8964a052a2597a8d659a8615c71b05c.png)
层序遍历
![](https://i-blog.csdnimg.cn/blog_migrate/f784c17a3029864c95183702e109b47f.png)
总结
![](https://i-blog.csdnimg.cn/blog_migrate/b52876feb2269b771790490ff6ecaf42.png)
简单的说
前序遍历就是当前结点在前面获取
中序遍历就是当前结点在中间获取
后序遍历就是当前结点在后面获取
平衡二叉树
通过上述,应该对二叉树右所了解了,但是有没有想过一个问题,
![](https://i-blog.csdnimg.cn/blog_migrate/bc8b26bc5639fccb5ab71dcdf645beda.png)
这样子的查找二叉树,算是二叉树吗?这和链表有区别么?为了解决这种情况!我们来看一看平衡二叉树.
任意子结点左右子树高不能超过1
![](https://i-blog.csdnimg.cn/blog_migrate/cb579786bba29f2a00ae38a70ca35ebd.png)
很显然该图上的二叉树不是平衡二叉树,应为10的左子节点树高为0,右子节点树高为3,左右子节点高度差超过1,所以不是平衡二叉树,记住是任意子节点
来看两个简单的平衡二叉树
![](https://i-blog.csdnimg.cn/blog_migrate/08dc08ddade03b243707a829bf9cbecf.png)
![](https://i-blog.csdnimg.cn/blog_migrate/7d20a6756278bbb02b2862ada5efede1.png)
应该就能懂了吧!
那么问题就来了,平衡二叉树是如何保持平衡的呢?
平衡二叉树左旋右旋
先看左旋机制
看一个非平衡二叉树
![](https://i-blog.csdnimg.cn/blog_migrate/9d6da27abf89f65c7405e5a5b5c7c4e5.png)
很显然这个二叉树左子节点树高是1,右子节点树高是3,两个之间的差距超过了1,所以不是平衡二叉树,那么书左旋还是右旋呢?
![](https://i-blog.csdnimg.cn/blog_migrate/e70da64f052acd092753c1b268b4382e.png)
然后变成这个样子
![](https://i-blog.csdnimg.cn/blog_migrate/500dbaf152177a8c05933531537102b8.png)
是不是又保持平衡了!
加大一下难度
![](https://i-blog.csdnimg.cn/blog_migrate/7385a4c4e5bcb154a4dbc054b3ec5e2b.png)
这棵树又不平衡了,我们还是需要左旋
![](https://i-blog.csdnimg.cn/blog_migrate/3c055b55bcee529b2f28a3bbd9d47292.png)
右旋机制
这是一个不平衡的二叉树,不难发现是左边不平衡,所以需要右旋
![](https://i-blog.csdnimg.cn/blog_migrate/9f8a876bc9ac6db1dbcce944b5ba09ba.png)
![](https://i-blog.csdnimg.cn/blog_migrate/791127b18c8081c27989db798aea83bd.png)
再来看一个复杂的情况
![](https://i-blog.csdnimg.cn/blog_migrate/9f8ca5ea185c3a42f423cb6745d0600c.png)
很显然,这棵树要保持平衡,需要发生右旋
![](https://i-blog.csdnimg.cn/blog_migrate/0d546c2c193c0e4219f7e5de16fa4375.png)
补充一下
下图这两请情况是不是一样的
![](https://i-blog.csdnimg.cn/blog_migrate/e0b50aca0f2c3383589cedeba570822e.png)
![](https://i-blog.csdnimg.cn/blog_migrate/0c5d30825cb66d874dded01c0a254384.png)
红黑树
我们来看一下红黑树的规则
![](https://i-blog.csdnimg.cn/blog_migrate/2c9ff15f5267d9f5a9a769ab40ff5603.png)
红黑树
![](https://i-blog.csdnimg.cn/blog_migrate/87326a9bf97eded71f931c3dd32ae090.png)
红黑树结点数据结构
![](https://i-blog.csdnimg.cn/blog_migrate/a9430e3695ae3e162c8e673ca5907be0.png)
我来说一下什么是简单路径:
每一个结点都又子节点,如果没接子节点我们成为nil也是子节点就是空的意思
13→8→1→nil这就是一条简单路径(黑色结点为3个)简单路径就是不能返回就比如说
13→8→13→8→1→nil这就不是一条简单路径了,通过观察我们可以发现每一条简单路径都满足(黑色结点为3个黑色结点)所以该树满足红黑树的规则.
注意:红黑树默认每个结点初始为红色
当只有一个结点时。那么这个结点就是黑色,再加入一个结点,就不满足红色与红色不能相连原则,那么根节点就会变为黑色,那为什不会时子节点变为黑色呢?下面看我的总结!
![](https://i-blog.csdnimg.cn/blog_migrate/10a4c1a63c097c34fd98937d44481cee.png)
1.5ArrayList集合
1.5.1ArrayList说明
首先我们学习了上述的Collection集合,里面有我总结的6种方法,我们可以把这6张方法拿到我们的ArrayList集合中来使用,通过下图我们可以发现ArrayList是Collection的实现类,通过Java实现类的思路来说,接口有的方法,我实现类也应该有,所以说为什么要先学Collection集合,因为Collection集合的6种方法是下述6种集合通用的,我们也不需要学习更多的方法
![](https://i-blog.csdnimg.cn/blog_migrate/589e8ff3f92a8a2e4e194561ef73a3a0.png)
那我们要对ArrayList的基本使用就应该很简单了
简单的看下代码吧!
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
//增加元素方法
System.out.println("调用add方法");
arrayList.add("--静夜思--");
arrayList.add("床前明月光");
arrayList.add("疑是地上霜");
arrayList.add("举头望明月");
arrayList.add("低头思故乡");
//采取我Lambda表达式遍历
arrayList.forEach(s-> System.out.println(s));
//调用remove
System.out.println("调用remove方法");
arrayList.remove("低头思故乡");
arrayList.forEach(s-> System.out.println(s));
//调用contains
System.out.println("是否包含 --静夜思--:"+arrayList.contains("--静夜思--"));
//调用isEmpty
System.out.println("集合是否为空:"+arrayList.isEmpty());
//调用size
System.out.println("集合共有:"+arrayList.size()+"个元素");
//调用clear
arrayList.clear();
System.out.println("调用clear之后的集合内容");
//输出集合
arrayList.forEach(s-> System.out.println(s));
System.out.println("集合是否为空:"+arrayList.isEmpty());
}
![](https://i-blog.csdnimg.cn/blog_migrate/8ab5a85f69e1dcf3006a8081dad72fbf.png)
1.5.2ArrayList底层
ArrayList扩容机制
![](https://i-blog.csdnimg.cn/blog_migrate/1c0c9330455f4959c9e56612464630b9.png)
我们来看一下源码由于源码比较乱,整理了一下
第一次添加数据
![](https://i-blog.csdnimg.cn/blog_migrate/88e469dee960e4a3123f72b048a70977.png)
再次添加数据
![](https://i-blog.csdnimg.cn/blog_migrate/7b03adad692ecc874def2f54cac8aa36.png)
1.6LinkedList
其实LinkedList也是Collection的子类,方法差不多,我也就不多说了
LinkdeList底层
![](https://i-blog.csdnimg.cn/blog_migrate/4259444d6df58abfa50dd9354ed344b3.png)
1.7Set集合
我们要学习Set类型的集合首先得先学会Set集合,然后他的子类常用方法基本和Set差不多我们还是那张图
![](https://i-blog.csdnimg.cn/blog_migrate/589e8ff3f92a8a2e4e194561ef73a3a0.png)
通过上述关系不难发现Set时Collection的子接口,那就说明Collection集合的方法在Set中同样能够用!值得注意的是Set集合没有索引,所以不能用for通过索引操作遍历.
![](https://i-blog.csdnimg.cn/blog_migrate/2585e8ff4243dd9b8be56378ad661945.png)
1.7.1HashSet
![](https://i-blog.csdnimg.cn/blog_migrate/96c187b963b360bac2bbfe8269070dd2.png)
那么问题来了,什么是哈希值呢?
简单的来说哈希值就是对象的一种整数的表现形式!
哈希表的底层是哈希数组组成的
![](https://i-blog.csdnimg.cn/blog_migrate/d38f52fe348a8fbe90c9fb0d5b1bd3f6.png)
假设我现在要添加一个数据
![](https://i-blog.csdnimg.cn/blog_migrate/df957c487d04883b59dbd6f7da10d2d0.png)
需要注意的是哈希集合他不是从0索引开始存的而是根据这个公式(int index=(数组长度-1)&哈希值)
![](https://i-blog.csdnimg.cn/blog_migrate/04f1b715099094a1f2fb417f00c8816f.png)
HashSetJDK8以前原理
首先创建一个默认长度为16,默认加载因子为0.75的数组,数组名为table
![](https://i-blog.csdnimg.cn/blog_migrate/0ffb910a65dd99dd67e73874d919f1bc.png)
根据元素的哈希值算出
2双列集合
![](https://i-blog.csdnimg.cn/blog_migrate/d1a9ccbe939f6e0a2b20d8ba80b31f13.png)
![](https://i-blog.csdnimg.cn/blog_migrate/debfac9555420d9074e0d3ea064f4ff2.png)