【JAVA复习系列】第二部分

目录

21 集合

21.1 集合的分类

21.1.1 Collection集合

21.2 泛型简述

21.3 Collection集合常用API

21.4 Collection集合遍历

21.4.1 迭代器

21.4.2 增强for循环

21.4.3 Lambda表达式

综合案例题

22 常见数据结构

22.1 栈

22.2 队列

22.3 数组

22.4 链表

22.5 二叉树

22.5.1 二叉查找树

22.5.2 平衡二叉树

22.5.3 红黑树

23 List系列集合

23.1 ArrayList集合底层原理

23.2 LinkedList

总结与补充

24 泛型

24.1 泛型类

24.2 泛型方法

24.3 泛型接口

24.4 泛型通配符

25 Set系列集合

25.1 HashSet底层原理

25.1.1  HashSet1.7版本原理解析

25.1.2  HashSet1.8版本原理解析

25.1.3 HashSet去重复原理解析

25.2 LinkedHashSet底层原理

25.3 TreeSet底层原理

25.4 Collection体系的特点、使用场景总结

25.5 可变参数

25.6 集合工具类Collections

26 Map集合

26.1 Map集合体系及其特点

26.2 Map集合常用API

26.2.1 Map集合的遍历

26.3 HashMap

26.4 LinkedHashMap集合

26.5 TreeMap集合

27 不可变集合

28 Stream流

28.1 获取Stream流

28.1.1 集合获取Stream流

28.1.2 数组获取Stream流

28.2 中间方法

28.3 终结方法

28.4 收集Stream流

29 异常

29.1 运行时异常

29.2 编译时异常

29.3 处理异常

29.3.1 默认处理流程

29.3.2 处理编译时异常

29.3.3 处理运行时异常

29.4 自定义异常

29.4.1 自定义编译时异常

30 日志

30.1 Logback日志框架

30.1.1 logback.xml

30.1.2 日志级别(重点)

31 File类

31.1 创建File类对象

31.2 File类常用方法

32 方法递归

33 字符集

33.1 常见字符集

33.1.1 ASCII字符集

33.1.2 GBK

33.1.3 Unicode字符集

33.2 字符存储和展示过程简析

33.3 使用程序进行编码解码

34 IO流

34.1 文件字节输入流:FileInputStream

34.2 文件字节输出流:FileOutputStream

34.3 try-catch-finally

34.4 文件字符输入流:Reader

34.5 文件字符输出流:FileWriter

34.6 缓冲流

34.6.1 字节缓冲流

34.6.2 字符缓冲流

34.7 转换流

34.7.1 字符输入转换流:InputStreamReader

34.7.2 字符输入转换流:OutputStreamWriter

34.8 序列化对象

34.8.1 对象序列化

34.9 打印流

34.9.1 PrintStream

34.9.2 PrintWriter

34.9.3 输出语句的重定向

35 Properties属性集对象

35.1 Properties和IO流结合的方法:

36 IO框架

36.1 FileUtils

37 线程

37.1 多线程的实现

37.1.1 多线程的实现方案一:继承Thread类

37.1.2 多线程的实现方案二:实现Runnable接口

37.1.3 多线程的实现方案三:利用Callable、FutureTask接口实现

37.2 Thread常用API

37.3 线程安全问题

37.4 线程同步

37.4.1 同步代码块

37.4.2 同步方法

37.4.3 Lock锁

37.5 线程通信

37.6 线程池

37.6.1 线程池实现的API、参数说明

38 定时器

38.1 Timer定时器

38.2 ScheduledExecutorService定时器

39 并发与并行

40 线程的生命周期


21 集合

之前我们已经使用过数组和集合了,在这里回顾一下:

数组 集合
定义后 类型确定、长度固定 大小,类型不固定
适用场景 数据个数和类型确定的场景 适合元素个数不能确定,且需要做元素的增删操作的场景。
功能 单一 强大且种类丰富
储存 基本类型和引用类型的数据 只能存储引用数据类型的数据

21.1 集合的分类

集合可分为Collection单列集合与Map双列集合

Collection单列集合,每个元素(数据)只包含一个值。

Map双列集合,每个元素包含两个值(键值对)。

21.1.1 Collection集合

定义:Collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的。

分类

Collection

(接口)

List

(接口)

Set

(接口)

ArrayList

(实现类)

LinkedList

(实现类)

HashSet

(实现类)

TreeSet

(实现类)

LinkedHashSet

(实现类)

特点

List系列集合:

添加的元素是有序、可重复、有索引。

ArrayList、LinekdList :有序、可重复、有索引。

Set系列集合:添加的元素是无序、不重复、无索引。

HashSet: 无序、不重复、无索引;

LinkedHashSet: 有序、不重复、无索引。

TreeSet:按照大小默认升序排序、不重复、无索引。

21.2 泛型简述

集合都是泛型的形式

作用:可以在编译阶段约束集合只能操作某种数据类型

写法:

Collection<String> lists = new ArrayList<String>();

Collection<String> lists = new ArrayList<>(); // JDK 1.7开始后面的泛型类型申明可以省略不写

注意:集合和泛型都只能支持引用数据类型,不支持基本数据类型,所以集合中存储的元素都认为是对象。

如果集合中要存储基本类型的数据,需要变为引用类型。

21.3 Collection集合常用API

方法名称

作用

public boolean add(E e)

把给定的对象添加到当前集合中 

public void clear() 

清空集合中所有的元素

public boolean remove(E e)

把给定的对象在当前集合中删除

public boolean contains(Object obj)

判断当前集合中是否包含给定的对象

public boolean isEmpty()

判断当前集合是否为空

public int size()

返回集合中元素的个数。

public Object[] toArray()

把集合中的元素,存储到数组中

//用多态定义一个集合
Collection<String> list = new ArrayList<>();
//把集合转换成数组 
//虽然用泛型规定了只能存储String类型,但是可以通过其他技术强制存储别的类型,所以用Object
Object arr[] = list.toArray();
System.out.println("数组:" + Arrays.toString(arr));
System.out.println(list);//结果是一样的

21.4 Collection集合遍历

21.4.1 迭代器

什么是遍历?

遍历就是一个一个的把容器中的元素访问一遍。

迭代器是集合的专用的遍历方式。

迭代器在Java中的代表是Iterator

Collection集合获取迭代器

方法名称

作用

Iterator<E> iterator()

返回集合中的迭代器对象,该迭代器对象默认指向当前集合的0索引

Iterator中的常用方法

方法名称

作用

boolean hasNext()

询问当前位置是否有元素存在,存在返回true ,不存在返回false

E next()

获取当前位置的元素,并同时将迭代器对象移向下一个位置,注意:防止取出越界。


小案例题

一个集合中存储着一些数据,将这些数据都取出来

Collection<String> c1 = new ArrayList<>();
//        1.得到当前集合的迭代器对象
Iterator it = c1.iterator();//迭代器先取出再移位

while(it.hasNext()){//当当前位置有元素存在时
            System.out.println(it.next());//取出当前位置的元素,之后移位
}

注意:

迭代器如果取元素越界会出现NoSuchElementException异常

21.4.2 增强for循环

书写格式:

for(元素数据类型 变量名 : 数组或者Collection集合) {         

}

【在IDEA中可以使用: 数组或者Collection集合.for 这个快捷键】

作用范围:既可以遍历集合也可以遍历数组

Collection<String> list = new ArrayList<>();
for(String ele : list) {
    System.out.println(ele);
}

注意:不能通过这种方法修改其中的元素值,没有用

21.4.3 Lambda表达式

方法名称

作用

default void forEach(Consumer<? super T> action) 

结合lambda遍历集合

书写格式与化简

Collection<String> c1 = new ArrayList<>();
 c1.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });

//可化简为
c1.forEach(s -> System.out.println(s));
//        还可以化简为
c1.forEach(System.out::println);

综合案例题

我们之前已经做过番剧的存储与展示了,再用所学知识试一次

//要点1
 Collection<Movie> arr = new ArrayList<>();
//泛型的类型是我们自己写的Movie类

//要点2:输出时需要在Moive类中重写toString方法,否则仅输出地址

总结:

List集合的遍历方式:

迭代器

增强for循环

Lambda表达式

for循环(因为List集合存在索引)


22 常见数据结构

数据结构定义:计算机底层存储、组织数据的方式。即数据相互之间是以什么方式排列在一起的。

存在意义:精心选择的数据结构可以带来更高的运行或者存储效率

包含:栈 队列 数组 链表 二叉树 二叉查找树 平衡二叉树 红黑树 等

22.1 栈

执行特点:后进先出,先进后出

可以想象为弹匣,最上面的子弹是最后进入的,却最早发射

数据进入栈模型的过程称为:压/进栈

数据离开栈模型的过程称为:弹/出栈

22.2 队列

执行特点:先进先出,后进后出

就想象普通排队

数据从后端进入队列模型的过程称为:入队列

数据从前端离开队列模型的过程称为:出队列

22.3 数组

查询速度快:查询数据通过地址值和索引定位,查询任意数据耗时相同。(元素在内存中是连续存储的)

删除效率低:要将原始数据删除,同时后面每个数据前移。

添加效率极低:添加位置后的每个数据后移,再添加元素。

小结:数组是一种查询快,增删慢的模型

22.4 链表

链表中的元素是在内存中不连续存储的,每个元素节点包含数据值和下一个元素的地址。

链表分类:单项列表,双向链表(双向链表包含前一个和下一个元素的地址)

链表查询慢。无论查询哪个数据都要从头开始找

链表增删相对快,只用修改“下一个元素的地址”即可

小结:相比数组而言,链表查询慢,增删快

22.5 二叉树

二叉树的一个节点包含父节点地址,自身值,左子节点,右子节点地址

二叉树的特点:

只能有一个根节点,每个节点最多支持2个直接子节点。

节点的度: 节点拥有的子树的个数,二叉树的度不大于2

叶子节点:度为0的节点,也称之为终端结点。

高度:叶子结点的高度为1,叶子结点的父节点高度为2,以此类推,根节点的高度最高。

层:根节点在第一层,以此类推

兄弟节点 :拥有共同父节点的节点互称为兄弟节点

22.5.1 二叉查找树

又称二叉排序树或者二叉搜索树

特点:

1,每一个节点上最多有两个子节点

2,左子树上所有节点的值都小于根节点的值

3,右子树上所有节点的值都大于根节点的值

目的:提高检索数据的性能。

二叉树查找树添节点

规则:       小的存左边       大的存右边       一样的不存

22.5.2 平衡二叉树

由于二叉树添加节点的规则,有可能导致左子树或右子树过于长

因此平衡二叉树是在满足查找二叉树的大小规则下,让树尽可能矮小,以此提高查数据的性能。

定义:任意节点的左右两个子树的高度差不超过1,任意节点的左右两个子树都是一颗平衡二叉树

当平衡二叉树在添加元素后不平衡时,基本策略是进行左旋,或者右旋保证平衡。

平衡二叉树-旋转的四种情况:左左 左右 右右 右左

22.5.3 红黑树

定义:红黑树是一种自平衡的二叉查找树,是计算机科学中用到的一种数据结构。

每一个节点可以是红或者黑;红黑树不是通过高度平衡的,它的平衡是通过“红黑规则”进行实现的。

其节点包含:父节点地址,自身值,左子节点右子节点地址,颜色

红黑规则:

每一个节点或是红色的,或者是黑色的,根节点必须是黑色

如果一个节点没有子节点或者父节点,则该节点相应的指针属性值为Nil,这些Nil视为叶节点,每个叶节点(Nil)是黑色的;

如果某一个节点是红色,那么它的子节点必须是黑色(不能出现两个红色节点相连的情况)

对每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色节点。

添加节点:

添加的节点的颜色,可以是红色的,也可以是黑色的。默认用红色效率高。

小结:

红黑树不是高度平衡的,它的平衡是通过"红黑规则"进行实现的

红黑树增删改查的性能都很好


总结:

队列:先进先出,后进后出。

栈:后进先出,先进后出。

数组:内存连续区域,查询快,增删慢。

链表:元素是游离的,查询慢,首尾操作极快。

二叉树:永远只有一个根节点, 每个结点不超过2个子节点的树。

查找二叉树:小的左边,大的右边,但是可能树很高,查询性能变差。

平衡查找二叉树:让树的高度差不大于1,增删改查都提高了。

红黑树(就是基于红黑规则实现了自平衡的排序二叉树)


23 List系列集合

回顾:

List系列集合特点  

ArrayList、LinekdList :有序,可重复,有索引。

有序:存储和取出的元素顺序一致

有索引:可以通过索引操作元素

可重复:存储的元素可以重复

List集合特有方法

List集合因为支持索引,所以多了很多索引操作的独特api

方法名称

作用

void add(int index,E element)

在此集合中的指定位置插入指定的元素

E remove(int index)

删除指定索引处的元素,返回被删除的元素

E set(int index,E element)

修改指定索引处的元素,返回被修改的元素

E get(int index)

返回指定索引处的元素

23.1 ArrayList集合底层原理

ArrayList底层是基于数组实现的:根据索引定位元素快,增删需要做元素的移位操作。  

第一次创建集合并添加第一个元素的时候,在底层创建一个默认长度为10的数组。

添加进去之后,size向后移动一个位置。

当10存满之后需要扩容,【当size等于当前数组长度时就会扩容至当前长度的1.5倍】

23.2 LinkedList

特点:

底层数据结构是双链表,查询慢,首尾操作的速度是极快的,所以多了很多首尾操作的特有API。

LinkedList集合的特有功能:

方法名称

作用

public void addFirst​(E e)

在该列表开头插入指定的元素

public void addLast​(E e)

将指定的元素追加到此列表的末尾

public E getFirst​()

返回此列表中的第一个元素

public E getLast​()

返回此列表中的最后一个元素

public E removeFirst​()

从此列表中删除并返回第一个元素

public E removeLast​()

从此列表中删除并返回最后一个元素

LinkedList可以完成队列结构和栈结构(双链表)

1.创建一个栈对象[想使用独有功能,就不能用多态啦
        LinkedList<String> stack = new LinkedList<>();
//        压栈,入栈
        stack.addFirst("第一颗子弹");
        stack.addFirst("第2颗子弹");
        stack.addFirst("第3颗子弹");
        stack.addFirst("第4颗子弹");
        stack.addFirst("第5颗子弹");
        System.out.println(stack);

//        出栈,弹栈
        System.out.println(stack.getFirst());
        System.out.println(stack);
        System.out.println(stack.removeFirst());//把第一个位置的元素拿到并移走
        System.out.println(stack);
//        所以如果打三枪
        System.out.println(stack.removeFirst());
        System.out.println(stack.removeFirst());
        System.out.println(stack.removeFirst());
        System.out.println(stack);


//      队列
        LinkedList<String> queue = new LinkedList<>();
//        入队
        queue.addLast("1号");
        queue.addLast("2号");
        queue.addLast("3号");
        queue.addLast("4号");
        System.out.println(queue);
//      出队
        System.out.println(queue.getFirst());
        System.out.println(queue);
        System.out.println(queue.removeFirst());//出来之后从队伍中删掉
        System.out.println(queue);

//      但是使用addFirst之类的并不专业。
//        压栈:
        stack.push("最后一颗子弹");
        System.out.println(stack);
//      弹栈:
        stack.pop();
        System.out.println(stack);

//        入队
        queue.offerLast("最后号");
        System.out.println(queue);

总结与补充

从集合中的一批元素中找出某些数据并删除时可能存在异常

也就是并发修改异常

迭代器遍历集合且直接用集合删除元素的时候可能出现。

//这个集合中存放着一些数据
ArrayList<String> list = new ArrayList<>();
//假设我们要删除这个集合中的所有“早安”
Iterator<String> it = list.iterator();//创建一个迭代器
while (it.hasNext()){
           String ele = it.next();
            if("早安".equals(ele)){
                // 删除早安
               // list.remove(ele); // 集合删除会出毛病
               it.remove(); // 删除迭代器所在位置的元素值,不会后移,所以使用这个更好
           }
       }

增强for循环遍历集合且直接用集合删除元素的时候可能出现。

//这个集合中存放着一些数据
ArrayList<String> list = new ArrayList<>();
//假设我们要删除这个集合中的所有“早安”
 for (String s : list) {
            if("Java".equals(s)){
                list.remove(s);
            }
        }

这种无法解决的,foreach不能边遍历边删除,会出bug,所有不要用增强for

另外我们之前使用的for循环也要记得倒着遍历删除

因此:

迭代器遍历集合但是用迭代器自己的删除方法操作

for循环倒着遍历删除

这两种方式都是可以的


24 泛型

定义:可以在编译阶段约束操作的数据类型,并进行检查

是JDK5中引入的特性

格式:<数据类型>;

 泛型只能支持引用数据类型。

集合体系的全部接口和实现类都是支持泛型的使用的。

优点:

统一数据类型。

把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常。

定义泛型:

在   类                 后        定义就是                 泛型类

在 方法申明        上          定义就是            泛型方法

在   接口             后          定义就是            泛型接口

24.1 泛型类

定义:定义类时同时定义了泛型的类就是泛型类。

书写格式:

public class MyArrayList<T> {  }

此处泛型变量T可以随便写为任意标识,常见的如E、T、K、V等。

案例:

public class MyArrayList<T> {  
}

作用:编译阶段可以指定数据类型,类似于集合的作用。

原理:把出现泛型变量的地方全部替换成传输的真实数据类型。


小案例题:自定义一个集合,加入“泛型”这个要素

//想写一个泛型类的话,这样写就足够了
public class MyArrayList<E> {
    //但是最好还是有一些功能比较好
 private ArrayList list = new ArrayList();//像饿汉单例

//添加数据
public void add(E e) {
        list.add(e);
    }
//删除数据
public void remove(E e) {
        list.remove(e);
    }
//不要忘记重写toString

}

24.2 泛型方法

定义:定义方法时同时定义了泛型的方法就是泛型方法

书写格式:修饰符 <泛型变量> 方法返回值 方法名称(形参列表){}

作用:方法中可以使用泛型接收一切实际类型的参数,方法更具备通用性。

案例:

public <T> void show(T t) {
}

原理:把出现泛型变量的地方全部替换成传输的真实数据类型。


小案例题:

给出任何一个类型的数组,都能返回它的内容

接收类型就是泛型
 之后就正常写了
public static <T> void printArray(T[] arr){
//        要考虑数组为空的情况
        if(arr != null){
            StringBuilder sb = new StringBuilder("[");
            for (int i =0;i<arr.length;i++ ) {
                sb.append(arr[i]).append(i == arr.length - 1?"":",");
            }
            sb.append("]");
            System.out.println(sb);

        }else {
            System.out.println(arr);
        }
    }
//    假设这里有一个add方法,我们可以通过泛型让他返回的值也是T,不需要强转
    public static <T> T[] add(T[] arr){
        return arr;
    }
}

24.3 泛型接口

定义:使用了泛型定义的接口就是泛型接口。

书写格式:修饰符 interface 接口名称<泛型变量>{}

作用:泛型接口可以约束实现类,可以让实现类选择当前功能需要操作的数据类型

原理:实现类可以在实现接口的时候传入自己操作的数据类型,这样重写的方法都将是针对于该类型的操作。

案例

public interface Data<E>{
}

小案例题:

设计一个泛型的人类接口,让老师类和学生类都能继承

public interface Data<E> {
//    使用泛型接口,不管是老师类还是学生类都可以使用增删改查
    void add(E e);

    void delete(int id);

    void update(E e);

    E queryById(int id);//这里的E就是返回值

}

24.4 泛型通配符

定义:“?”就是通配符

作用:“使用泛型”的时候代表一切类型。

而E T K V 是在定义泛型的时候使用的。

我们需要一个小案例题来帮助理解


小案例题:

模拟赛车,让不同牌子的车都可以参加

解题思路:

不同牌子的车就意味着不同“类”

我们需要一个“车”类作为父类

再写一些子类去继承它,例如奔驰宝马什么的,随你喜欢

有了具体的“奔驰”类,我们就创建一些具体的车对象出来

最后有了车,就该写“比赛”方法了

这时

//所有车比赛,使用?代表所有类型
public  static  void go(ArrayList<?> cars){

    }

我们可以注意到,这里的泛型就用的是通配符,代表一个车的类型集合仅存放这一个类型


泛型的上下限:

? extends XXX(类名): ?必须是XXX或者其子类   泛型上限  

? super XXX(类名) : ?必须是XXX或者其父类   泛型下限

这有什么作用呢?

在上面的案例中,即便不是车,也能够参与比赛,这样是不行的,因此

// 所以应该这样
    public  static  void go(ArrayList< ? extends Car> cars){

    }

在尖括号中的?extends Car 就是泛型上限,代表传入的类必须继承自车类才行。

这样就能够防止不是车的别的类也加入比赛。


25 Set系列集合

回顾一下set集合的特点:

1.无序:存取顺序不一致

这里的无序只无序一次,不是每一次都是打乱的

2.不重复:可以去除重复

3.无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素。

Set集合实现类特点:

 HashSet : 无序、不重复、无索引。

LinkedHashSet:有序、不重复、无索引。

这里的有序指:保证存储和取出的元素顺序一致

TreeSet:可排序、不重复、无索引。

可排序指天然对存储的数据升序排序

Set集合的功能上基本上与Collection的API一致

25.1 HashSet底层原理

HashSet集合底层采取哈希表存储的数据。

其中:

哈希表的组成:

JDK8之前的,底层使用数组+链表组成

JDK8开始后,底层采用数组+链表+红黑树组成。

哈希表是一种对于增删改查数据性能都较好的结构。

我们先了解一下“哈希值”

定义:是JDK根据对象的地址,按照某种规则算出来的int类型的数值。

可以将“这个数值”理解为地址的另一种表示方法

Object类的API:

public int hashCode​():返回对象的哈希值

对象的哈希值特点:

同一个对象多次调用hashCode()方法返回的哈希值是相同的

默认情况下,不同对象的哈希值是不同的。

哈希值的特点在下方会详细解释。

我们通过案例来了解一下哈希值的模样

String name = "桂小太郎";
System.out.println(name.hashCode());//818514353

通过这个案例,我们发现name对象的哈希值为818514353,当然哈希值的长度有变化不过都是int类型


25.1.1  HashSet1.7版本原理解析

底层使用数组+链表组成

1.在创建对象的时候

Set<String> sets = new HashSet<>();

创建一个默认长度16的数组,数组名table ;

2.根据元素的哈希值跟数组的长度求余计算出应存入的位置(哈希算法)

假设元素的哈希值为16,而数组的长度也为16,那么余数为0,则应存入索引为0的位置

也因此这个值一定会在0到15之间

3.判断当前位置是否为null,如果是null直接存入。

如果位置不为null,表示有元素,则调用equals方法比较

如果一样,则不存,如果不一样,则存入数组

JDK 7新元素占老元素位置,指向老元素【也就是说新元素存入数组,而以链表的形式与老元素连接】

JDK 8中新元素挂在老元素下面【也就是说新元素直接以链表的形式与老元素连接】

当数组存满到16*0.75=12时,就自动扩容,每次扩容原先的两倍

因此:哈希表是一种对于增删改查数据性能都较好的结构。

25.1.2  HashSet1.8版本原理解析

底层采用数组+链表+红黑树组成。

由于:挂在元素下面的数据过多时,查询性能降低,从JDK8开始后,当链表长度超过8的时候,自动转换为红黑树。

其余与1.7版本相同

哈希表对于红黑树的引入进一步提高了操作数据的性能。

25.1.3 HashSet去重复原理解析

我们已经知道了原理,这里更多的是对代码的展示

如果希望Set集合认为2个内容一样的对象是重复的, 必须重写对象的hashCode()和equals()方法

我们在之前的学习中已经了解了equals()

我们在一个小案例题中具体了解


小案例题:

创建一个存储角色对象的集合,存储多个角色对象,使用程序实现在控制台遍历该集合,要求:学生对象的成员变量值相同,我们就认为是同一个对象

解题思路:定义角色类,创建HashSet集合对象, 创建角色对象,之后把角色添加到集合里,在学生类中重写两个方法,hashCode()和equals(),可以自动生成,使用增强for遍历集合【直接输出也行】

 @Override
    public int hashCode() {
        return Objects.hash(name, age, sex);
    }

 hashCode的源代码比较复杂,但我们可以理解,因为入参name, age, sex是一样的,因此结果是一样的,因此哈希值是一样的。

有了一样的哈希值,再通过equals判断内容是否一样,就可以达到去重的目的


25.2 LinkedHashSet底层原理

LinkedHashSet的特点是:有序、不重复、无索引。

有序是如何实现的呢?

原理:底层数据结构是依然哈希表,只是每个元素又额外的多了一个双链表的机制记录存储的顺序。

比如第一个数据会记录第二个加入进来的数据的地址。

25.3 TreeSet底层原理

特点:不重复、无索引、可排序

TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。

注意:TreeSet集合是一定要排序的,可以将元素按照指定的规则进行排序。

排序情况:

对于数值类型:Integer , Double,官方默认按照大小进行升序排序

对于字符串类型:默认按照首字符的编号升序排序。【ACS2码的编号,所以中文一般都更靠后】

对于自定义类型:如Student对象,TreeSet无法直接排序,需要制定排序规则。

自定义排序规则方式:

方式一  让自定义的类(如学生类)实现Comparable接口重写里面的compareTo方法来定制比较规则。

方式二 TreeSet集合有参数构造器,可以设置Comparator接口对应的比较器对象,来定制比较规则。

其返回值:

如果认为第一个元素大于第二个元素返回正整数即可。

如果认为第一个元素小于第二个元素返回负整数即可。

如果认为第一个元素等于第二个元素返回0即可,此时Treeset集合只会保留一个元素,认为两者重复。

如果TreeSet集合存储的对象有实现比较规则,集合也自带比较器,默认使用集合自带的比较器排序。

这两种方式我们之前已经使用过了


小案例题:

假设你开了一家苹果店,卖不同种类的苹果,在展示的时候按照某种排序顺序展示你的苹果们

解题思路:定义苹果类,设置相关属性,例如商品名,重量,颜色,价格等属性

创建多个苹果对象,存入TreeSet集合,并设定排序规则,最后输出

 /**
     方式一:类自定义比较规则。写在苹果类中
     o1.compareTo(o2) 使用的时候是这样的
     * @param o
     * @return
     */
    @Override
    public int compareTo(Apple o) {
        // 按照重量进行比较的
        return this.weight - o.weight ; // 去除重量重复的元素
        // return this.weight - o.weight >= 0 ? 1 : -1; // 保留重量重复的元素(肯定有重量相同的苹果的)
    }

 方式二:集合自带比较器对象进行规则定制

        Set<Apple> apples = new TreeSet<>(new Comparator<Apple>() {//匿名内部类也可以快捷生成
            @Override
            public int compare(Apple o1, Apple o2) {
                // return o1.getWeight() - o2.getWeight(); // 升序
                // return o2.getWeight() - o1.getWeight(); // 降序
                // 注意:浮点型建议直接使用Double.compare进行比较
                // return Double.compare(o1.getPrice() , o2.getPrice()); // 升序
                return Double.compare(o2.getPrice() , o1.getPrice()); // 降序
            }
        });
        方式二可化简为
        Set<Apple> apples = new TreeSet<>(( o1, o2) ->Double.compare(o2.getPrice() , o1.getPrice()));
    

25.4 Collection体系的特点、使用场景总结

如果希望元素可以重复,又有索引,索引查询要快

用ArrayList集合,基于数组的。(用的最多)

如果希望元素可以重复,又有索引,增删首尾操作快

用LinkedList集合,基于链表的。

如果希望增删改查都快,但是元素不重复、无序、无索引。

用HashSet集合,基于哈希表的。

如果希望增删改查都快,但是元素不重复、有序、无索引。

用LinkedHashSet集合,基于哈希表和双链表。

如果要对对象进行排序。

用TreeSet集合,基于红黑树。后续也可以用List集合实现排序。

25.5 可变参数

作用:可变参数用在形参中可以接收多个数据。

也就是说不规定只能接受多少个数据,而是任意

书写格式:数据类型...参数名称

优点:接收参数非常灵活,方便。可以不接收参数,可以接收1个或者多个参数,也可以接收一个数组

可变参数在方法内部本质上就是一个数组。

注意事项:

1.一个形参列表中可变参数只能有一个

2.可变参数必须放在形参列表的最后面


小案例题:

定义一个方法求和,该方法可以灵活的完成如下需求: 计算1个数据的和。 计算2个数据的和。 计算3个数据的和。 计算n个数据的和,甚至可以支持不接收参数进行调用。

//    写一个求和的方法
    public static void sum(int...nums){
//        注意,可变参数在方法内部其实就是一个数组
        System.out.println("元素个数" + nums.length);
        System.out.println("元素内容" + Arrays.toString(nums));

public static void main(String[] args) {
//        1.不传参数
        sum();
//        2.传一个参数
        sum(10);
//        3.传输多个参数
        sum(10,30,38);
//        4.传输一个数组
        int[] arr = {10,30,30};
        sum(arr);

    }


25.6 集合工具类Collections

java.utils.Collections:是集合工具类。Collections并不属于集合

作用:用来操作集合的工具类。

Collections常用的API

方法名称

作用

public static <T> boolean addAll(Collection<? super T> c, T... elements)

给集合对象批量添加元素

public static void shuffle(List<?> list) 

打乱List集合元素的顺序

shuffle:每次随机。原理就是随机数随机索引之后交换位置。具体使用例如洗牌

Collections排序相关API

使用范围:只能对于List集合的排序。

排序方式1:

public static <T> void sort(List<T> list)

将集合中元素按照默认规则排序

注意:本方式不可以直接对自定义类型的List集合排序,除非自定义类型实现了比较规则Comparable接口。

排序方式2:

public static <T> void sort(List<T> list,Comparator<? super T> c)

将集合中元素按照指定规则排序


综合案例题:

模拟制作斗地主游戏,仅仅制作到发牌完毕的阶段即可

解题思路:

我们曾经模拟过斗地主,仍旧应定义一个静态集合存储原始的54张扑克牌

之后使用静态代码块创建扑克牌:即通过循环嵌套制作出“牌”,并通过index变量存放牌的大小以便比较

之后在主方法中进行发牌:首先应该洗牌,使用api打乱顺序,并通过循环给三位玩家发牌。

使用api截取最后三张牌作为保留

最好单独写一个排序方法,帮助玩家整理手牌。


26 Map集合

定义:Map集合是一种双列集合,每个元素包含两个数据。也被称为“键值对集合”

书写格式:

Collection集合的格式: [元素1,元素2,元素3..]

Map集合的完整格式:{key1=value1 , key2=value2 , key3=value3 , ...}

其中:Map集合的键:无序、不重复的

           Map集合的值 值不做要求 可以重复

Map集合体系特点:

Map集合的特点都是由键决定的。

Map集合的键是无序,不重复的,无索引的,值不做要求(可以重复)。

Map集合后面重复的键对应的值会覆盖前面重复键的值。

Map集合的键值对都可以为null。

Map集合使用场景:例如购物车,商品对象作为键,购买数量作为值。

26.1 Map集合体系及其特点

Map

(接口)

HashMap

(实现类)

HashTable

(实现类)

\(接口)

LinkedHashMap

(实现类)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值