主要内容

  • Collection集合的遍历方式:

  • 迭代器。

    • foreach(增强for循环)
    • JDK 1.8开始的新技术Lambda表达式。
  • 数据结构

    • 是集合的底层,研究数据结构是为了选择使用某种集合。
  • List接口
    • 元素是有序可重复有索引的。
  • Set接口
    • 元素是无序不重复无索引的。
  • Collections是操作集合的工具类。
  • 把学的集合用起来:斗地主的游戏。

教学目标

  • [ ] 能够使用迭代器对集合进行取元素

    1. Iterator it = names.iterator();
    2. while(it.hasNext()){
    3. String rs = it.next();
    4. System.out.println(rs);
    5. }
  • [ ] 能够说出List集合特点

  • 元素是有序,可重复的,有索引的,底层是基于数组存储元素的,查询快,增删慢!
  • 能够说出常见的数据结构
  • 队列: 先进先出
  • 栈:先进后出,后进先出
  • 数组:底层是连续内存区域,查询快,增删慢!
  • 链表:元素是游离存储的,查询慢,首尾操作快!
  • 红黑树 (HastSet ):增删改查都很好,可以排序,可以提高检索数据的性能!
  • 能够说出数组结构特点
  • 内存中的连续区域,每个区间大小固定,查询快,增删慢!
  • 能够说出栈结构特点
  • 先进后出。
  • 能够说出队列结构特点
  • 先进先出
  • 能够说出单向链表结构特点
  • 元素是游离存储的,查询慢, 一端的增删操作快!
  • 能够说出Set集合的特点
  • 元素是无序,不重复,无索引,底层是基于哈希表存储元素的,曾删查的性能都很好!!
  • 能够说出哈希表的特点
  • JDK 1.8之前是:链表+数组
  • JDK 1.8之后是:链表+数组+红黑树
  • 使用HashSet集合存储自定义元素
  • Set sets = new HashSet<>();

第一章 Iterator迭代器

1.1 Iterator接口

在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,JDK专门提供了一个接口java.util.Iterator

想要遍历Collection集合,那么就要获取该集合迭代器完成迭代操作,下面介绍一下获取迭代器的方法:

  • public Iterator iterator(): 获取集合对应的迭代器,用来遍历集合中的元素的。

下面介绍一下迭代的概念:

  • 迭代:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。

Iterator接口的常用方法如下:

  • public E next():返回迭代的下一个元素。
  • public boolean hasNext():如果仍有元素可以迭代,则返回 true。

接下来我们通过案例学习如何使用Iterator迭代集合中元素:

  1. public class IteratorDemo {
  2. public static void main(String[] args) {
  3. // 使用多态方式 创建对象
  4. Collection<String> coll = new ArrayList<String>();
  5. // 添加元素到集合
  6. coll.add("串串星人");
  7. coll.add("吐槽星人");
  8. coll.add("汪星人");
  9. //遍历
  10. //使用迭代器 遍历 每个集合对象都有自己的迭代器
  11. Iterator<String> it = coll.iterator();
  12. // 泛型指的是 迭代出 元素的数据类型
  13. while(it.hasNext()){ //判断是否有迭代元素
  14. String s = it.next();//获取迭代出的元素
  15. System.out.println(s);
  16. }
  17. }
  18. }

tips:

  1. 在进行集合元素获取时,如果集合中已经没有元素了,还继续使用迭代器的next方法,将会抛出java.util.NoSuchElementException没有集合元素异常。
  2. 在进行集合元素获取时,如果添加或移除集合中的元素 , 将无法继续迭代 , 将会抛出ConcurrentModificationException并发修改异常.

1.2 迭代器的实现原理

我们在之前案例已经完成了Iterator遍历集合的整个过程。当遍历集合时,首先通过调用t集合的iterator()方法获得迭代器对象,然后使用hashNext()方法判断集合中是否存在下一个元素,如果存在,则调用next()方法将元素取出,否则说明已到达了集合末尾,停止遍历元素。

Iterator迭代器对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素,为了让初学者能更好地理解迭代器的工作原理,接下来通过一个图例来演示Iterator对象迭代元素的过程:

迭代器原理图.bmp

在调用Iterator的next方法之前,迭代器的索引位于第一个元素之前,不指向任何元素,当第一次调用迭代器的next方法后,迭代器的索引会向后移动一位,指向第一个元素并将该元素返回,当再次调用next方法时,迭代器的索引会指向第二个元素并将该元素返回,依此类推,直到hasNext方法返回false,表示到达了集合的末尾,终止对元素的遍历。

第二章 数据结构

2.1 数据结构介绍

数据结构 : 数据用什么样的方式组合在一起。

2.2 常见数据结构

数据存储的常用结构有:栈、队列、数组、链表和红黑树。我们分别来了解一下:

  • stack,又称堆栈,它是运算受限的线性表,其限制是仅允许在标的一端进行插入和删除操作,不允许在其他任何位置进行添加、查找、删除等操作。

简单的说:采用该结构的集合,对元素的存取有如下的特点

  • 先进后出(即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素)。例如,子弹压进弹夹,先压进去的子弹在下面,后压进去的子弹在上面,当开枪时,先弹出上面的子弹,然后才能弹出下面的子弹。
  • 栈的入口、出口的都是栈的顶端位置。

这里两个名词需要注意:

  • 压栈:就是存元素。即,把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置。
  • 弹栈:就是取元素。即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置。

队列

  • 队列queue,简称队,它同堆栈一样,也是一种运算受限的线性表,其限制是仅允许在表的一端进行插入,而在表的另一端进行删除。

简单的说,采用该结构的集合,对元素的存取有如下的特点:

  • 先进先出(即,存进去的元素,要在后它前面的元素依次取出后,才能取出该元素)。例如,小火车过山洞,车头先进去,车尾后进去;车头先出来,车尾后出来。
  • 队列的入口、出口各占一侧。例如,下图中的左侧为入口,右侧为出口。

队列.png队列图.bmp

数组

  • 数组:Array,是有序的元素序列,数组是在内存中开辟一段连续的空间,并在此空间存放元素。就像是一排出租屋,有100个房间,从001到100每个房间都有固定编号,通过编号就可以快速找到租房子的人。

简单的说,采用该结构的集合,对元素的存取有如下的特点:

  • 查找元素快:通过索引,可以快速访问指定位置的元素
    迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图4
  • 增删元素慢
  • 指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。如下图迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图5
  • 指定索引位置删除元素:需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中。如下图迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图6

链表

  • 链表:linked list,由一系列结点node(链表中每一个元素称为结点)组成,结点可以在运行时i动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。我们常说的链表结构有单向链表与双向链表,那么这里给大家介绍的是单向链表
    迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图7

简单的说,采用该结构的集合,对元素的存取有如下的特点:

  • 多个结点之间,通过地址进行连接。例如,多个人手拉手,每个人使用自己的右手拉住下个人的左手,依次类推,这样多个人就连在一起了。
  • 查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素
  • 增删元素快:
    迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图8树数据结构
    树是有很多节点组成的

2.3. 树基本结构介绍

树具有的特点:

  1. 每一个节点有零个或者多个子节点
  2. 没有父节点的节点称之为根节点,一个树最多有一个根节点。
  3. 每一个非根节点有且只有一个父节点

迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图9

名词 含义
节点 指树中的一个元素
节点的度 节点拥有的子树的个数,二叉树的度不大于2
叶子节点 度为0的节点,也称之为终端结点
高度 叶子结点的高度为1,叶子结点的父节点高度为2,以此类推,根节点的高度最高
根节点在第一层,以此类推
父节点 若一个节点含有子节点,则这个节点称之为其子节点的父节点
子节点 子节点是父节点的下一层节点
兄弟节点 拥有共同父节点的节点互称为兄弟节点

二叉树

如果树中的每个节点的子节点的个数不超过2,那么该树就是一个二叉树。

迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图10

二叉查找树/二叉排序树

二叉查找树的特点:

  1. 左子树上所有的节点的值均小于等于他的根节点的值
  2. 右子树上所有的节点值均大于或者等于他的根节点的值
  3. 每一个子节点最多有两个子树

案例演示(20,18,23,22,17,24,19)数据的存储过程;

迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图11

增删改查的性能都很高!!!

遍历获取元素的时候可以按照”左中右”的顺序进行遍历;

注意:二叉查找树存在的问题:会出现”瘸子”的现象,影响查询效率。

迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图12

平衡二叉树

(基于查找二叉树,但是让树不要太高,尽量让树的元素均衡分布。这样综合性能就高了)

概述

为了避免出现”瘸子”的现象,减少树的高度,提高我们的搜素效率,又存在一种树的结构:”平衡二叉树”

规则:它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树

如下图所示:

迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图13

如下图所示,左图是一棵平衡二叉树,根节点10,左右两子树的高度差是1,而右图,虽然根节点左右两子树高度差是0,但是右子树15的左右子树高度差为2,不符合定义,

所以右图不是一棵平衡二叉树。

旋转

在构建一棵平衡二叉树的过程中,当有新的节点要插入时,检查是否因插入后而破坏了树的平衡,如果是,则需要做旋转去改变树的结构。

左旋:

左旋就是将节点的右支往左拉,右子节点变成父节点,并把晋升之后多余的左子节点出让给降级节点的右子节点;

迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图14

右旋:

将节点的左支往右拉,左子节点变成了父节点,并把晋升之后多余的右子节点出让给降级节点的左子节点

迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图15

举个例子,像上图是否平衡二叉树的图里面,左图在没插入前”19”节点前,该树还是平衡二叉树,但是在插入”19”后,导致了”15”的左右子树失去了”平衡”,

所以此时可以将”15”节点进行左旋,让”15”自身把节点出让给”17”作为”17”的左树,使得”17”节点左右子树平衡,而”15”节点没有子树,左右也平衡了。如下图,

迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图16

由于在构建平衡二叉树的时候,当有新节点插入时,都会判断插入后时候平衡,这说明了插入新节点前,都是平衡的,也即高度差绝对值不会超过1。当新节点插入后,

有可能会有导致树不平衡,这时候就需要进行调整,而可能出现的情况就有4种,分别称作左左,左右,右左,右右

左左

左左即为在原来平衡的二叉树上,在节点的左子树的左子树下,有新节点插入,导致节点的左右子树的高度差为2,如下即为”10”节点的左子树”7”,的左子树”4”,插入了节点”5”或”3”导致失衡。

迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图17

左左调整其实比较简单,只需要对节点进行右旋即可,如下图,对节点”10”进行右旋,

迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图18

迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图19

左右

左右即为在原来平衡的二叉树上,在节点的左子树的右子树下,有新节点插入,导致节点的左右子树的高度差为2,如上即为”11”节点的左子树”7”,的右子树”9”,

插入了节点”10”或”8”导致失衡。

迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图20

左右的调整就不能像左左一样,进行一次旋转就完成调整。我们不妨先试着让左右像左左一样对”11”节点进行右旋,结果图如下,右图的二叉树依然不平衡,而右图就是接下来要

讲的右左,即左右跟右左互为镜像,左左跟右右也互为镜像。

迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图21

左右这种情况,进行一次旋转是不能满足我们的条件的,正确的调整方式是,将左右进行第一次旋转,将左右先调整成左左,然后再对左左进行调整,从而使得二叉树平衡。

即先对上图的节点”7”进行左旋,使得二叉树变成了左左,之后再对”11”节点进行右旋,此时二叉树就调整完成,如下图:

迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图22

右左

右左即为在原来平衡的二叉树上,在节点的右子树的左子树下,有新节点插入,导致节点的左右子树的高度差为2,如上即为”11”节点的右子树”15”,的左子树”13”,

插入了节点”12”或”14”导致失衡。

迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图23

前面也说了,右左跟左右其实互为镜像,所以调整过程就反过来,先对节点”15”进行右旋,使得二叉树变成右右,之后再对”11”节点进行左旋,此时二叉树就调整完成,如下图:

迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图24

右右

右右即为在原来平衡的二叉树上,在节点的右子树的右子树下,有新节点插入,导致节点的左右子树的高度差为2,如下即为”11”节点的右子树”13”,的左子树”15”,插入了节点

“14”或”19”导致失衡。

迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图25

右右只需对节点进行一次左旋即可调整平衡,如下图,对”11”节点进行左旋。

迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图26

红黑树

就是平衡的二叉查找树!!

概述

红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构,它是在1972年由Rudolf Bayer发明的,当时被称之为平衡二叉B树,后来,在1978年被

Leoj.Guibas和Robert Sedgewick修改为如今的”红黑树”。它是一种特殊的二叉查找树,红黑树的每一个节点上都有存储位表示节点的颜色,可以是红或者黑;

红黑树不是高度平衡的,它的平衡是通过”红黑树的特性”进行实现的;

红黑树的特性:

  1. 每一个节点或是红色的,或者是黑色的。
  2. 根节点必须是黑色
  3. 每个叶节点(Nil)是黑色的;(如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点)
  4. 如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)
  5. 对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点;

如下图所示就是一个

迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图27

在进行元素插入的时候,和之前一样; 每一次插入完毕以后,使用黑色规则进行校验,如果不满足红黑规则,就需要通过变色,左旋和右旋来调整树,使其满足红黑规则;

第三章 List接口

我们掌握了Collection接口的使用后,再来看看Collection接口中的子类,他们都具备那些特性呢?

接下来,我们一起学习Collection中的常用几个子类(java.util.List集合、java.util.Set集合)。

3.1 List接口介绍

java.util.List接口继承自Collection接口,是单列集合的一个重要分支,习惯性地会将实现了List接口的对象称为List集合。在List集合中允许出现重复的元素,所有的元素是以一种线性方式进行存储的,在程序中可以通过索引来访问集合中的指定元素。另外,List集合还有一个特点就是元素有序,即元素的存入顺序和取出顺序一致。

看完API,我们总结一下:

List接口特点:

  1. 它是一个元素存取有序的集合。例如,存元素的顺序是11、22、33。那么集合中,元素的存储就是按照11、22、33的顺序完成的)。
  2. 它是一个带有索引的集合,通过索引就可以精确的操作集合中的元素(与数组的索引是一个道理)。
  3. 集合中可以有重复的元素,通过元素的equals方法,来比较是否为重复的元素。

tips:我们在基础班的时候已经学习过List接口的子类java.util.ArrayList类,该类中的方法都是来自List中定义。

3.2 List接口中常用方法

List作为Collection集合的子接口,不但继承了Collection接口中的全部方法,而且还增加了一些根据元素索引来操作集合的特有方法,如下:

  • public void add(int index, E element): 将指定的元素,添加到该集合中的指定位置上。
  • public E get(int index):返回集合中指定位置的元素。
  • public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。
  • public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回值的更新前的元素。

List集合特有的方法都是跟索引相关,我们在基础班都学习过。

tips:我们之前学习Colletion体系的时候,发现List集合下有很多集合,它们的存储结构不同,这样就导致了这些集合它们有各自的特点,供我们在不同的环境下使用,那么常见的数据结构有哪些呢?在下一章我们来介绍:

3.3 ArrayList集合

java.util.ArrayList集合数据存储的结构是数组结构。元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList是最常用的集合。

许多程序员开发时非常随意地使用ArrayList完成任何需求,并不严谨,这种用法是不提倡的。

3.4 LinkedList集合

java.util.LinkedList集合数据存储的结构是链表结构。方便元素添加、删除的集合。

LinkedList是一个双向链表,那么双向链表是什么样子的呢,我们用个图了解下

迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图28

实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。这些方法我们作为了解即可

  • public void addFirst(E e):将指定元素插入此列表的开头。
  • public void addLast(E e):将指定元素添加到此列表的结尾。
  • public E getFirst():返回此列表的第一个元素。
  • public E getLast():返回此列表的最后一个元素。
  • public E removeFirst():移除并返回此列表的第一个元素。
  • public E removeLast():移除并返回此列表的最后一个元素。
  • public E pop():从此列表所表示的堆栈处弹出一个元素。
  • public void push(E e):将元素推入此列表所表示的堆栈。
  • public boolean isEmpty():如果列表不包含元素,则返回true。

LinkedList是List的子类,List中的方法LinkedList都是可以使用,这里就不做详细介绍,我们只需要了解LinkedList的特有方法即可。在开发时,LinkedList集合也可以作为堆栈,队列的结构使用。

  1. public class Demo04LinkedList {
  2. public static void main(String[] args) {
  3. method4();
  4. }
  5. /*
  6. * void push(E e): 压入。把元素添加到集合的第一个位置。
  7. * E pop(): 弹出。把第一个元素删除,然后返回这个元素。
  8. */
  9. public static void method4() {
  10. //创建LinkedList对象
  11. LinkedList<String> list = new LinkedList<>();
  12. //添加元素
  13. list.add("达尔文");
  14. list.add("达芬奇");
  15. list.add("达尔优");
  16. System.out.println("list:" + list);
  17. //调用push在集合的第一个位置添加元素
  18. //list.push("爱迪生");
  19. //System.out.println("list:" + list);//[爱迪生, 达尔文, 达芬奇, 达尔优]
  20. //E pop(): 弹出。把第一个元素删除,然后返回这个元素。
  21. String value = list.pop();
  22. System.out.println("value:" + value);//达尔文
  23. System.out.println("list:" + list);//[达芬奇,达尔优]
  24. }
  25. /*
  26. * E removeFirst():删除第一个元素
  27. * E removeLast():删除最后一个元素。
  28. */
  29. public static void method3() {
  30. //创建LinkedList对象
  31. LinkedList<String> list = new LinkedList<>();
  32. //添加元素
  33. list.add("达尔文");
  34. list.add("达芬奇");
  35. list.add("达尔优");
  36. //删除集合的第一个元素
  37. // String value = list.removeFirst();
  38. // System.out.println("value:" + value);//达尔文
  39. // System.out.println("list:" + list);//[达芬奇,达尔优]
  40. //删除最后一个元素
  41. String value = list.removeLast();
  42. System.out.println("value:" + value);//达尔优
  43. System.out.println("list:" + list);//[达尔文, 达芬奇]
  44. }
  45. /*
  46. * E getFirst(): 获取集合中的第一个元素
  47. * E getLast(): 获取集合中的最后一个元素
  48. */
  49. public static void method2() {
  50. //创建LinkedList对象
  51. LinkedList<String> list = new LinkedList<>();
  52. //添加元素
  53. list.add("达尔文");
  54. list.add("达芬奇");
  55. list.add("达尔优");
  56. System.out.println("list:" + list);
  57. //获取集合中的第一个元素
  58. System.out.println("第一个元素是:" + list.getFirst());
  59. //获取集合中的最后一个元素怒
  60. System.out.println("最后一个元素是:" + list.getLast());
  61. }
  62. /*
  63. * void addFirst(E e): 在集合的开头位置添加元素。
  64. * void addLast(E e): 在集合的尾部添加元素。
  65. */
  66. public static void method1() {
  67. //创建LinkedList对象
  68. LinkedList<String> list = new LinkedList<>();
  69. //添加元素
  70. list.add("达尔文");
  71. list.add("达芬奇");
  72. list.add("达尔优");
  73. //打印这个集合
  74. System.out.println("list:" + list);//[达尔文, 达芬奇, 达尔优]
  75. //调用addFirst添加元素
  76. list.addFirst("曹操");
  77. System.out.println("list:" + list);//[曹操, 达尔文, 达芬奇, 达尔优]
  78. //调用addLast方法添加元素
  79. list.addLast("大乔");
  80. System.out.println("list:" + list);//[曹操, 达尔文, 达芬奇, 达尔优, 大乔]
  81. }
  82. }

第四章 Set接口

java.util.Set接口和java.util.List接口一样,同样继承自Collection接口,它与Collection接口中的方法基本一致,并没有对Collection接口进行功能上的扩充,只是比Collection接口更加严格了。与List接口不同的是,Set接口都会以某种规则保证存入的元素不出现重复。

Set集合有多个子类,这里我们介绍其中的java.util.HashSetjava.util.LinkedHashSetjava.util.TreeSet这两个集合。

tips:Set集合取出元素的方式可以采用:迭代器、增强for。

4.1 HashSet集合介绍

java.util.HashSetSet接口的一个实现类,它所存储的元素是不可重复的,并且元素都是无序的(即存取顺序不能保证不一致)。java.util.HashSet底层的实现其实是一个java.util.HashMap支持,由于我们暂时还未学习,先做了解。

HashSet是根据对象的哈希值来确定元素在集合中的存储位置,因此具有良好的存储和查找性能。保证元素唯一性的方式依赖于:hashCodeequals方法。

我们先来使用一下Set集合存储,看下现象,再进行原理的讲解:

  1. public class HashSetDemo {
  2. public static void main(String[] args) {
  3. //创建 Set集合
  4. HashSet<String> set = new HashSet<String>();
  5. //添加元素
  6. set.add(new String("cba"));
  7. set.add("abc");
  8. set.add("bac");
  9. set.add("cba");
  10. //遍历
  11. for (String name : set) {
  12. System.out.println(name);
  13. }
  14. }
  15. }

输出结果如下,说明集合中不能存储重复元素:

  1. cba
  2. abc
  3. bac

tips:根据结果我们发现字符串”cba”只存储了一个,也就是说重复的元素set集合不存储。

4.2 HashSet集合存储数据的结构(哈希表)

什么是哈希表呢?

JDK1.8之前,哈希表底层采用数组+链表实现,即使用数组处理冲突,同一hash值的链表都存储在一个数组里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。

简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,如下图所示。迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图29

看到这张图就有人要问了,这个是怎么存储的呢?

为了方便大家的理解我们结合一个存储流程图来说明一下:

迭代器,数据结构,List,Set ,TreeSet集合,Collections工具类 - 图30

总而言之,JDK1.8引入红黑树大程度优化了HashMap的性能,那么对于我们来讲保证HashSet集合元素的唯一,其实就是根据对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。

4.3 HashSet存储自定义类型元素

给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一.

创建自定义Student类:

  1. public class Student {
  2. private String name;
  3. private int age;
  4. //get/set
  5. @Override
  6. public boolean equals(Object o) {
  7. if (this == o)
  8. return true;
  9. if (o == null || getClass() != o.getClass())
  10. return false;
  11. Student student = (Student) o;
  12. return age == student.age &&
  13. Objects.equals(name, student.name);
  14. }
  15. @Override
  16. public int hashCode() {
  17. return Objects.hash(name, age);
  18. }
  19. }

创建测试类:

  1. public class HashSetDemo2 {
  2. public static void main(String[] args) {
  3. //创建集合对象 该集合中存储 Student类型对象
  4. HashSet<Student> stuSet = new HashSet<Student>();
  5. //存储
  6. Student stu = new Student("于谦", 43);
  7. stuSet.add(stu);
  8. stuSet.add(new Student("郭德纲", 44));
  9. stuSet.add(new Student("于谦", 43));
  10. stuSet.add(new Student("郭麒麟", 23));
  11. stuSet.add(stu);
  12. for (Student stu2 : stuSet) {
  13. System.out.println(stu2);
  14. }
  15. }
  16. }
  17. 执行结果:
  18. Student [name=郭德纲, age=44]
  19. Student [name=于谦, age=43]
  20. Student [name=郭麒麟, age=23]

4.4 LinkedHashSet

我们知道HashSet保证元素唯一,可是元素存放进去是没有顺序的,那么我们要保证有序,怎么办呢?

在HashSet下面有一个子类java.util.LinkedHashSet,它是链表和哈希表组合的一个数据存储结构。

演示代码如下:

  1. public class LinkedHashSetDemo {
  2. public static void main(String[] args) {
  3. Set<String> set = new LinkedHashSet<String>();
  4. set.add("bbb");
  5. set.add("aaa");
  6. set.add("abc");
  7. set.add("bbc");
  8. Iterator<String> it = set.iterator();
  9. while (it.hasNext()) {
  10. System.out.println(it.next());
  11. }
  12. }
  13. }
  14. 结果:
  15. bbb
  16. aaa
  17. abc
  18. bbc

4.5 TreeSet集合

1. 特点

TreeSet集合是Set接口的一个实现类,底层依赖于TreeMap,是一种基于红黑树的实现,其特点为:

  1. 元素唯一
  2. 元素没有索引
  3. 使用元素的自然顺序对元素进行排序,或者根据创建 TreeSet 时提供的 [Comparator](../../java/util/Comparator.html) 比较器
    进行排序,具体取决于使用的构造方法:
  1. public TreeSet(): 根据其元素的自然排序进行排序
  2. public TreeSet(Comparator<E> comparator): 根据指定的比较器进行排序

2. 演示

案例演示自然排序(20,18,23,22,17,24,19):

  1. public static void main(String[] args) {
  2. //无参构造,默认使用元素的自然顺序进行排序
  3. TreeSet<Integer> set = new TreeSet<Integer>();
  4. set.add(20);
  5. set.add(18);
  6. set.add(23);
  7. set.add(22);
  8. set.add(17);
  9. set.add(24);
  10. set.add(19);
  11. System.out.println(set);
  12. }
  13. 控制台的输出结果为:
  14. [17, 18, 19, 20, 22, 23, 24]

案例演示比较器排序(20,18,23,22,17,24,19):

  1. public static void main(String[] args) {
  2. //有参构造,传入比较器,使用比较器对元素进行排序
  3. TreeSet<Integer> set = new TreeSet<Integer>(new Comparator<Integer>() {
  4. @Override
  5. public int compare(Integer o1, Integer o2) {
  6. //元素前 - 元素后 : 升序
  7. //元素后 - 元素前 : 降序
  8. return o2 - o1;
  9. }
  10. });
  11. set.add(20);
  12. set.add(18);
  13. set.add(23);
  14. set.add(22);
  15. set.add(17);
  16. set.add(24);
  17. set.add(19);
  18. System.out.println(set);
  19. }
  20. 控制台的输出结果为:
  21. [24, 23, 22, 20, 19, 18, 17]

第五章 Collections类

5.1 Collections常用功能

  • java.utils.Collections是集合工具类,用来对集合进行操作。
    常用方法如下:
  • public static void shuffle(List<?> list):打乱集合顺序。
  • public static <T> void sort(List<T> list):将集合中元素按照默认规则排序。
  • public static <T> void sort(List<T> list,Comparator<? super T> ):将集合中元素按照指定规则排序。

代码演示:

  1. public class CollectionsDemo {
  2. public static void main(String[] args) {
  3. ArrayList<Integer> list = new ArrayList<Integer>();
  4. list.add(100);
  5. list.add(300);
  6. list.add(200);
  7. list.add(50);
  8. //排序方法
  9. Collections.sort(list);
  10. System.out.println(list);
  11. }
  12. }
  13. 结果:
  14. [50,100, 200, 300]

我们的集合按照默认的自然顺序进行了排列,如果想要指定顺序那该怎么办呢?

5.2 Comparator比较器

创建一个学生类,存储到ArrayList集合中完成指定排序操作。

Student 类

  1. public class Student{
  2. private String name;
  3. private int age;
  4. //构造方法
  5. //get/set
  6. //toString
  7. }

测试类:

  1. public class Demo {
  2. public static void main(String[] args) {
  3. // 创建四个学生对象 存储到集合中
  4. ArrayList<Student> list = new ArrayList<Student>();
  5. list.add(new Student("rose",18));
  6. list.add(new Student("jack",16));
  7. list.add(new Student("abc",20));
  8. Collections.sort(list, new Comparator<Student>() {
  9. @Override
  10. public int compare(Student o1, Student o2) {
  11. return o1.getAge()-o2.getAge();//以学生的年龄升序
  12. }
  13. });
  14. for (Student student : list) {
  15. System.out.println(student);
  16. }
  17. }
  18. }
  19. Student{name='jack', age=16}
  20. Student{name='rose', age=18}
  21. Student{name='abc', age=20}

5.3 可变参数

JDK1.5之后,如果我们定义一个方法需要接受多个参数,并且多个参数类型一致,我们可以对其简化.

格式:

  1. 修饰符 返回值类型 方法名(参数类型... 形参名){ }

代码演示:

  1. public class ChangeArgs {
  2. public static void main(String[] args) {
  3. int sum = getSum(6, 7, 2, 12, 2121);
  4. System.out.println(sum);
  5. }
  6. public static int getSum(int... arr) {
  7. int sum = 0;
  8. for (int a : arr) {
  9. sum += a;
  10. }
  11. return sum;
  12. }
  13. }

注意:

  1. 1.一个方法只能有一个可变参数
  2. 2.如果方法中有多个参数,可变参数要放到最后。

应用场景: Collections

  1. Collections中也提供了添加一些元素方法:
  2. `public static <T> boolean addAll(Collection<T> c, T... elements)`:往集合中添加一些元素。

代码演示:

  1. public class CollectionsDemo {
  2. public static void main(String[] args) {
  3. ArrayList<Integer> list = new ArrayList<Integer>();
  4. //原来写法
  5. //list.add(12);
  6. //list.add(14);
  7. //list.add(15);
  8. //list.add(1000);
  9. //采用工具类 完成 往集合中添加元素
  10. Collections.addAll(list, 5, 222, 12);
  11. System.out.println(list);
  12. }

第六章 集合综合案例

6.1 案例介绍

按照斗地主的规则,完成洗牌发牌的动作。
具体规则:

使用54张牌打乱顺序,三个玩家参与游戏,三人交替摸牌,每人17张牌,最后三张留作底牌。

6.2 案例分析

  • 准备牌:
    牌可以设计为一个ArrayList,每个字符串为一张牌。
    每张牌由花色数字两部分组成,我们可以使用花色集合与数字集合嵌套迭代完成每张牌的组装。
    牌由Collections类的shuffle方法进行随机排序。
  • 发牌
    将每个人以及底牌设计为ArrayList,将最后3张牌直接存放于底牌,剩余牌通过对3取模依次发牌。
  • 看牌
    直接打印每个集合。

6.3 代码实现

  1. Poker.java
  1. public class Poker {
  2. private String name;
  3. public Poker() {
  4. }
  5. public Poker(String name) {
  6. this.name = name;
  7. }
  8. public String getName() {
  9. return name;
  10. }
  11. public void setName(String name) {
  12. this.name = name;
  13. }
  14. @Override
  15. public String toString() {
  16. return "{" + name + "}";
  17. }
  18. }

2 测试类

  1. public class Demo12 {
  2. public static void main(String[] args) {
  3. // 创建一个ArrayList用于存放一副牌
  4. ArrayList<Poker> pokers = new ArrayList<>();
  5. pokers.add(new Poker("大王", ""));
  6. pokers.add(new Poker("小王", ""));
  7. String[] colors = new String[] {"♠", "♥", "♣", "♦"};
  8. String[] numbers = new String[] {"2", "A", "K", "Q", "J", "10", "9", "8", "7", "6", "5", "4", "3"};
  9. // 组合牌, 嵌套循环的流程:外循环一次,内循环所有次
  10. // 2.使用嵌套循环生成一副牌
  11. for (String n : numbers) {
  12. // "2", "A"
  13. for (String c : colors) {
  14. // "♠", "♥", "♣", "♦"
  15. Poker p = new Poker(c, n);
  16. // 3.将54张牌放到集合
  17. pokers.add(p);
  18. }
  19. }
  20. // 打印
  21. // System.out.println(pokers);
  22. // 洗牌: Collections,集合工具类
  23. // static void shuffle(List<?> list) 将集合中元素的顺序打乱
  24. Collections.shuffle(pokers);
  25. System.out.println("洗牌后:" + pokers);
  26. // 发牌
  27. // 1.创建3个玩家集合,创建底牌集合
  28. ArrayList<Poker> player01 = new ArrayList<>();
  29. ArrayList<Poker> player02 = new ArrayList<>();
  30. ArrayList<Poker> player03 = new ArrayList<>();
  31. ArrayList<Poker> diPai = new ArrayList<>();
  32. // 2.遍历牌的集合
  33. // 0 1 2 3 4 5 6 7 8 9 10 ...51 52 53
  34. // pokers = [♦5], [♣4], [♦8], [♣A], [♣7], [♦2], [♠6], [♣J], [♥A], [♥7], [♥6], [♣5], [♦7], [♥10]
  35. // 玩家1: 索引0,3,6 索引 % 3 == 0
  36. // 玩家2: 索引1,4,7 索引 % 3 == 1
  37. // 玩家3: 索引2,5,8 索引 % 3 == 2
  38. // 3.根据索引将牌发给不同的玩家
  39. for (int i = 0; i < pokers.size(); i++) {
  40. // i表示索引,poker就是i索引对应的poker
  41. Poker poker = pokers.get(i);
  42. if (i >= 51) { // 最后3张给底牌
  43. diPai.add(poker);
  44. } else if (i % 3 == 0) { // 玩家1
  45. player01.add(poker);
  46. } else if (i % 3 == 1) { // 玩家2
  47. player02.add(poker);
  48. } else if (i % 3 == 2) { // 玩家3
  49. player03.add(poker);
  50. }
  51. }
  52. // 看牌
  53. System.out.println("玩家1: " + player01);
  54. System.out.println("玩家2: " + player02);
  55. System.out.println("玩家3: " + player03);
  56. System.out.println("底牌: " + diPai);
  57. // 还要创建一副牌
  58. // 创建一个ArrayList用于存放一副牌
  59. }
  60. }