• 集合的笔记


    集合

    包装类

    泛型类有一个不幸的限制, 不能使用基本类型作为类型参数, 解决办法是使用这个包装类, 每一种基本类型都有对应的包装类
    基本类型和它们对应的包装类型之间的转换是自动进行的。在调用 add 方法时,个值为 42 的 Integer 类对象在一种被称为自动装箱的过程中自动构造了。
    代码片段的最后一行,get 调用返回一个 Integer 对象,在给 int 变量赋值之前, 该对象自动拆箱,内部产生一个 int 值。
    添加元素的源代码

        public class ArrayList<E> extends AbstractList<E>  //集合的开头定义
        public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    警告: 基本类型和它们的包装类之间的转换对程序员完全是透明的,但是==和!=操作符例外。==和!=操作符比较对象引用,而不是对象的内容。条件测试 if (numbers.Get (i) == numbers.Get (j))不会测试索引 i 和索引 j 的数字是否相同。就像字符串,你需要记得调用包装类的 equals 方法。
    具体介绍
    [[包装类]] obsidian 内链
    CSDN博客

    Collection 集合

    collection 的特点

    Collection代表单列集合,每个元素(数据)只包含一个值。
    Map 代表双列集合,每个元素包含两个值(键值对)。
    注意这个接口是不能够直接进行创建对象的,可以用多态的方式创建对象

    List<Integer> list1 = new ArrayList<>();
    
    • 1

    image.png

    image.png

    Collection集合特点

    由于Collection是一个泛型,所以我们在定义的时候要用确定的类型来定义这个

    • List系列集合:添加的元素是有序、可重复、有索引。
    • ArrayList、LinekdList :有序、可重复、有索引。
    • Set系列集合:添加的元素是无序、不重复、无索引。
    • HashSet: 无序、不重复、无索引;
    • LinkedHashSet: 有序、不重复、无索引。
    • TreeSet:按照大小默认升序排序、不重复、无索引。

    [!info]

    这个 collection 的集合类型很多, 但是我们常用的其实就是 hashset 和 Arraylist, 当你不知道选什么的时候就用这两个肯定没问题

    Collection集合有哪两大常用的集合体系,各自有啥特点?
    List系列集合:添加的元素是有序、可重复、有索引。
    Set系列集合:添加的元素是无序、不重复、无索引。
    根据日常使用的需求,我们来挑选不同的集合

    举例

    public class CommonFunction {
        public static void main(String[] args) {
            //简单确认一下collection集合 的特点
            ArrayList<String> arr = new ArrayList<>();
            arr.add("qiu");
            arr.add("ke");
            arr.add("hao");
            System.out.println(arr);  //[qiu, ke, hao]有序、可重复、有索引。
            HashSet<String> hah = new HashSet<>();
            hah.add("qiu");
            hah.add("ke");
            hah.add("hao");
            hah.add("hao");
            System.out.println(hah);   //[hao, ke, qiu]无序、不重复、无索引;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    colllection的常用方法

    这些方法是所有集合都能够进行使用的方法, 这个 collection 是父类, 子类可以继承这个父类的方法

    方法名说明
    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()把集合中的元素,存储到数组中
    package colletion.CommonFunction;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collection;
    
    public class collectionFunction {
        public static void main(String[] args) {
            Collection<String> c = new ArrayList<>();
            c.add("qiu");
            c.add("ke");
            c.add("hao");
            c.add("qiu");
            System.out.println(c);
            System.out.println(c.isEmpty());
            System.out.println(c.size());
            System.out.println("------------------------");
    
            c.remove("qiu");
            c.remove("k");
            System.out.println(c);
            System.out.println(c.size());
            System.out.println(c.contains("qiu"));
            System.out.println(c.contains("h"));
    
    
    
            System.out.println("------------------------");
    
            //在某些情况下,这个里面的数据类型并不一定都是字符串类型
            Object[] arr = c.toArray();
            System.out.println(Arrays.toString(arr));
    
            //如果我们就是想让其转化为字符数组呢
            String[] str = c.toArray(new String[c.size()]);
            System.out.println(Arrays.toString(str));
            
            System.out.println("------------------------");
            c.clear();
            System.out.println(c.isEmpty());
            System.out.println(c.size());
    
    
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    Collection 的遍历方法

    迭代器

    集合来调用这个迭代器的方法
    然后我们再用这个 Iterator 类来获取这个返回对象
    It.Next ()获取下一个值, 并后移
    It.Hasnext ()判断其后面有没有值 根据名字也能判断出来
    如果我们遍历的比较多的话, 我们很有可能会造成这个迭代器越界, 一定要注意
    迭代器遍历的时候我们一定要注意这个迭代遍历的 next 函数不能在一段循环代码中, 执行几次, 否则的话, 容易出错

        public static void main(String[] args) {
            ArrayList<Integer> list = new ArrayList<>();
            for(int i=0;i<10;i++){
                list.add(i);
            }
            Collections.shuffle(list);
            Iterator<Integer> iterator = list.iterator();
            while (iterator.hasNext()){
                int s = iterator.next();
                System.out.print(s+",");
            }
            System.out.println();
            System.out.println(list);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    增强型 for 循环
    可以用来遍历这个集合和数组
    
    • 1

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

            //增强for
            for(String s : c){
                System.out.println(s);
            }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    IDEA 快捷 for 循环遍历, 如果我们想用这个索引遍历就是数组/集合名字. Fori
    如果是增强 for 循环就是数组. 集合名字. For

    Lambda 表达式

    源码

    foreach方法
        default void forEach(Consumer<? super T> action) {
            Objects.requireNonNull(action);
            for (T t : this) {
                action.accept(t);
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    其实 foreach 本质还是调用这个增强 for 循环

            //lambda表达式
            c.forEach(new Consumer<String>() {
                @Override
                public void accept(String s) {
                    System.out.println(s);
                }
            });
            c.forEach(s->System.out.println(s));  
            //方法引用
    		c.forEach(System.out::println); //快速遍历方式,IDEA里的是c.foreach(sout);快速构建
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    案例

    遍历电影, 考验我们对于对象的遍历的理解, 我们还可以通过重写这个 toString 方法, 来进行快速遍历

     movies.forEach(System.out::println);  //我们可以通过重写内部类的toString方法来实现这个
    
    • 1
    package colletion.CommonFunction.movies;
    
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Iterator;
    
    public class moviesOutput {
        public static void main(String[] args) {
            Collection<movie> movies = new ArrayList<>();
            movies.add(new movie("《肖生克的救赎》", 9.7 ,  "罗宾斯"));
            movies.add(new movie("《霸王别姬》", 9.6 ,  "张国荣、张丰毅"));
            movies.add(new movie("《阿甘正传》", 9.5 ,  "汤姆.汉克斯"));
    
            for (movie mov : movies) {
                System.out.println("电影名: "+mov.getName());
                System.out.println("得分: "+mov.getScore() );
                System.out.println("演员: "+mov.getActor());
                System.out.println("--------------------");
            }
    
            Iterator<movie> iterator = movies.iterator();
            while (iterator.hasNext()){
                System.out.println("电影名: "+iterator.next().getName());
                System.out.println("得分: "+iterator.next().getScore() );
                System.out.println("演员: "+iterator.next().getActor());
                System.out.println("--------------------");
            }
    
            movies.forEach(System.out::println);  //我们可以通过重写内部类的toString方法来实现这个
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    list

    List系列集合特点: 有序,可重复,有索引
    ArrayList:有序,可重复,有索引。
    LinkedList:有序,可重复,有索引。
    image.png

    List和Collection都是接口,我们并不能够直接创建对象,我们要通过接口的方式来创建对象

    常用方法

    方法名称说明
    void add(int index,E element)在此集合中的指定位置插入指定的元素
    E remove(int index)删除指定索引处的元素,返回被删除的元素
    E set(int index,E element)修改指定索引处的元素,返回被修改的元素
    E get(int index)返回指定索引处的元素
        public static void main(String[] args) {
            ArrayList<Integer> list = new ArrayList<>();
            for(int i=0;i<10;i++){
                list.add(i);
            }
            Collections.shuffle(list);
            list.add(0,100);
            list.add(list.size(),100);
            list.forEach(integer-> System.out.print(integer+", "));//100, 9, 2, 5, 1, 3, 6, 8, 4, 7, 0, 100,
            list.remove(list.size()-1);
            System.out.println();
            list.forEach(integer-> System.out.print(integer+", "));//100, 9, 2, 5, 1, 3, 6, 8, 4, 7, 0, 移除了200
            list.set(0,200);
            System.out.println();
            list.forEach(integer-> System.out.print(integer+", "));//200, 9, 2, 5, 1, 3, 6, 8, 4, 7, 0, 将第一个元素设置为200
            System.out.println();
            System.out.println(list.get(0)); //200获取第一个元素
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    常用遍历方法

    collection 用来遍历的方式它都能遍历,初次之外它还能用 for 循环来进行遍历
    具体可以参考这个 [[#Collection 集合]]
    因为我们可以通过这个for循环索引遍历的方式
    这个是因为这个 list 是有索引的,和这个 collection 不一样

    list系列集合的底层原理

    Arraylist

    Arraylist集合的底层原理
    其是基于数组来实现的
    特点是查询速度快,注意是根据索引查询速度快

    1. 查询数据通过地址值和索引定位,查询任意数据耗时相同
    2. 删除效率低,需要把后面很多的数据进行前移它的删除和数组的删除类似
    3. 添加效率低,需要把后面的数据后移,在添加元素,或者也有可能进行数组的扩容

    构建原理

    1. 利用无参构造器创建的集合,会在底层创建一个默认长度为0的数组
    2. 添加第一个元素时,底层会创建一个新的长度为 10 的数组

    private static final int DEFAULT_CAPACITY = 10;

    1. 存满时,会扩容1.5倍
    2. 如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准

    适合根据索引查询数据,比如根据随机索引取数据,或者数据量不是很大的时候
    不适合,数据量大的同时,又要频繁的进行增删操作

    LinkList

    基于双链表实现的。
    链表中的结点是独立的对象,在内存中是不连续的,每个结点包含数据值和下一个结点的地址。

    image.png

    链表的特点1:查询慢,无论查询哪个数据都要从头开始找。
    链表的特点2:链表增删相对快
    单向链表
    双向链表, 特点:查询慢,增删相对较快,但对首尾元素进行增删改查的速度是极快的。可以根据索引位置,选择从前,从后遍历

    常用方法

    image.png

    方法使用演示
        public static void main(String[] args) {
            LinkedList<Integer> list = new LinkedList<>();
            for(int i=0;i<10;i++){
                list.add(i);
            }
            //仍然可以用这个打乱数组
            System.out.println("打乱前");
            list.forEach(s-> System.out.print(s+", "));
            System.out.println();
            System.out.println("打乱后");
            Collections.shuffle(list);
            list.forEach(s-> System.out.print(s+", "));
            System.out.println();
            System.out.println("向开头插入元素");
            list.addFirst(100);
            list.forEach(s-> System.out.print(s+", "));
            System.out.println();
            System.out.println("向结尾插入元素");
            list.addLast(200);
            list.forEach(s-> System.out.print(s+", "));
            System.out.println();
            System.out.println("开头元素");
            Integer first = list.getFirst();
            System.out.println(first);
            System.out.println("结尾元素");
            Integer last = list.getLast();
            System.out.println(last);
            System.out.println("移除开头元素");
            list.removeFirst();
            list.forEach(s-> System.out.print(s+", "));
            System.out.println();
            System.out.println("移除结尾元素");
            list.removeLast();
            list.forEach(s-> System.out.print(s+", "));
            System.out.println();
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    结果

    打乱前
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 
    打乱后
    0, 2, 6, 3, 7, 1, 4, 9, 5, 8, 
    向开头插入元素
    100, 0, 2, 6, 3, 7, 1, 4, 9, 5, 8, 
    向结尾插入元素
    100, 0, 2, 6, 3, 7, 1, 4, 9, 5, 8, 200, 
    开头元素
    100
    结尾元素
    200
    移除开头元素
    0, 2, 6, 3, 7, 1, 4, 9, 5, 8, 200, 
    移除结尾元素
    0, 2, 6, 3, 7, 1, 4, 9, 5, 8, 
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    队列

    Linkedlist 的应用场景之一,我们可以用来设计队列----先进先出
    每次都添加到最后的位置,移除最开始的元素
    队列是一种常见的数据结构系统,是一种经常用来操作首尾数据的一种系统,比如说排队系统

            LinkedList<String> queue = new LinkedList<>();
            queue.addLast("11");
            queue.addLast("22");
            queue.add("55");  //add本身也是在最后面添加元素
            queue.addLast("33");
            queue.add("44");
            System.out.println(queue);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    LinkedList的引用场景之一,可以用来设计栈
    栈的特点是:后进先出,先进后出

    每次都是往最后面添加元素,从最后面往外面拿元素

    [!warning]

    需要注意的是这个栈有的是将这个 linkedlist 的最后面的元素作为栈顶,有的是最开始的元素最为栈顶,我们一般认为这个最开始的元素作为栈顶

    数据进入栈模型的过程称为:压/进栈(push)
    数据离开栈模型的过程称为:弹/出栈(pop)

            LinkedList<String> stack = new LinkedList<>();
            stack.addFirst("11");
            stack.addFirst("22");
            stack.addFirst("33");
            System.out.println(stack);
            System.out.println(stack.removeFirst());
            System.out.println(stack.removeFirst());
            System.out.println(stack.removeFirst());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    从JDK6以后,java就在linkedlist里面添加了push和pop的API,其实就是换了个马甲

        public void push(E e) {
            addFirst(e);
        }
    
    • 1
    • 2
    • 3
        public E pop() {
            return removeFirst();
        }
    
    • 1
    • 2
    • 3

    set

    image.png

    无序:添加数据的顺序和获取出的数据顺序不一致; 不重复无索引;
    set 的特点主要是这个不重复和无索引,至于有序还是无序,这个取决于具体的 set 类型

    1. HashSet : 无序、不重复、无索引。
    2. LinkedHashSet:有序、不重复、无索引。
    3. TreeSet:排序、不重复、无索引。会对你上传的数据,会自动进行排序,升序排序

    注意:
    Set要用到的常用方法,基本上就是Collection提供的!!
    自己几乎没有额外新增一些常用功能!

    hashset

    无序、不重复、无索引。

            Set<Integer> set = new HashSet<>();
            set.add(90);
            set.add(80);
            set.add(70);
            set.add(70);
            System.out.println(set);
            //[80, 70, 90]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    [!warning]

        public static void main(String[] args) {
            HashSet<Integer> hashSet = new HashSet<>();
            for(int i=0;i<10;i++){
                hashSet.add(i);
            }
            hashSet.add(-1);
            hashSet.forEach(s-> System.out.print(s+", "));
            System.out.println();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    当我刚开始想验证这个无序性的时候, 我用了一个循环来添加元素, 但是我输出之后发现这个遍历出来的数据并不是无序的, 而是 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
    我感到困惑, 后来发现其实是因为我不太明白这个底层原理, 这个 0-9 的 hashcode 值的大小顺序跟这个 0-9 的大小顺序是一样的, 所以会出现这个类似有序现象, 你可以向其中添加一个 -1 来验证这个无序性最后的结果是 0, -1, 1, 2, 3, 4, 5, 6, 7, 8, 9,

    底层原理

    注意:在正式了解HashSet集合的底层原理前,我们需要先搞清楚一个前置知识:哈希值!
    哈希值
    就是一个int类型的数值,Java中每个对象都有一个哈希值。
    Java中的所有对象,都可以调用Obejct类提供的hashCode方法,返回该对象自己的哈希值
    对象哈希值的特点
    同一个对象多次调用 hashCode()方法返回的哈希值是相同的
    hashcode 是继承的基类 object 的一个方法, 任何对象都能使用
    不同的对象,它们的哈希值一般不相同,但也有可能会相同(哈希碰撞)。
    比如说abc和acD

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

    1. 创建一个默认长度16的数组,默认加载因子为0.75,数组名table
    2. 使用元素的哈希值对数组的长度求余计算出应存入的位置
    3. 判断当前位置是否为null,如果是null直接存入
    4. 如果不为null,表示有元素,则调用equals方法比较
      1. 相等,则不存;不相等,则存入数组
        1. JDK 8之前,新元素存入数组,占老元素位置,老元素挂下面 链表
        2. JDK 8开始之后,新元素直接挂在老元素下面 链表

    image.png

    哈希表是一种增删改查数据性能都较好的结构。
    根据数据对应的hash值,我们可以快速定位,所以查很快
    增删改由于这个虽然是数组,但是可以允许有空元素的存在再加上链表的特征,所以也很快

    无序

    这是有一个计算方法的,所以无序

    不重复

    因为重复的不存,不重复的挂链表

    无索引

    元素是无序的,自然就不支持索引,没法确定索引

    如果数组快占满了,会出什么问题
    链表会过长,导致查询性能降低
    16 * 0.75 = 12 当数组占满了12的时候就会扩容 0.75是加载因子
    哈希表
    JDK8之前,哈希表 = 数组+链表
    JDK8开始,哈希表 = 数组+链表+红黑树
    JDK8开始,当链表长度超过8,且数组长度>=64时,自动将链表转成红黑树

    去重复

    HashSet集合默认不能对内容一样的两个不同对象去重复!
    比如内容一样的两个学生对象存入到HashSet集合中去 , HashSet集合是不能去重复的!
    如何让HashSet集合能够实现对内容一样的两个不同对象也能去重复???

    结论:如果希望Set集合认为2个内容一样的对象是重复的,
    必须重写对象的hashCode()和equals()方法
    IDEA里有快速构造方法

        public static void main(String[] args) {
            HashSet<Student> stu = new HashSet<>();
            stu.add(new Student("qiu",18));
            stu.add(new Student("qiu",18));
            /*
            * 并没有去重复化
            * Student{name='qiu', age=18}
            * Student{name='qiu', age=18}
            * */
            stu.forEach(System.out::println);
            //我们需要重写这个equals和hashcode方法
            //此时只有一个结果 Student{name='qiu', age=18}
            
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    重写方法

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Student student = (Student) o;
            return age == student.age && Objects.equals(name, student.name);
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(name, age);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    [!tip]
    把里面的 equal 和 hashcode 方法进行修改使得这个学生类只要名字相等就会认为相等

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Student student = (Student) o;
            return Objects.equals(name, student.name);
        }
    
        @Override
        public int hashCode() {
            return Objects.hash(name);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    linkset

    有序、不重复、无索引。

            Set<Integer> set = new LinkedHashSet<>();
            set.add(90);
            set.add(80);
            set.add(70);
            set.add(70);
            System.out.println(set); 
            [90, 80, 70]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这个有序是按照这个添加元素的顺序进行修改的

    底层原理

    依然是基于哈希表(数组、链表、红黑树)实现的。
    但是,它的每个元素都额外的多了一个双链表的机制记录它前后元素的位置。

    image.png

    其实也是一样的,存放的位置和hashset是一样的,但是每一个元素都有一个前置节点和后置节点,可以确定这个节点的下一个元素,和这个节点的上一个元素,这样我们就能够知道这个哈希表中的第一个元素和后一个元素.

    里面记录的信息更多,这样也会导致它更占内存

    treeset

    排序、不重复、无索引。会对你上传的数据,会自动进行排序,升序排序

            Set<Integer> set = new TreeSet<>();
            set.add(90);
            set.add(80);
            set.add(70);
            set.add(70);
            System.out.println(set);
            [80, 70, 90]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    底层是基于红黑树实现的排序。

    对于数值类型:Integer , Double,默认按照数值本身的大小进行升序排序。
    对于字符串类型:默认按照首字符的编号升序排序。
    对于自定义类型如 Student 对象,TreeSet 默认是无法直接排序的。

    我们需要自己自定义比较器
    第一种方式是在学生类中添加接口在你要进行比较的对象当中添加接口
    public class Student implements Comparable
    添加 Comparable 接口然后重写这个 compareTo 方法,

        @Override
        public int compareTo(Student o) {
            //如果认为左边对象大于右边对象返回正整数
            //如果认为左边对象小于右边对象返回负整数
            //如果认为左边对象等于右边对象返回0  相等的话,就认为这个对象不存在,所以我们要在添加另一个规则
            //左边对象就是调用对象
            //按照年龄升序排序
            int num =0;
            num  = this.age-o.age;
            num = num==0?(int)(this.height-o.height):num;
            num = num==0? this.name.compareTo(o.name):num;
            return num;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    第二种方式是用匿名内部类-我们用的最多的

    Set<Student> s = new TreeSet<>(new Comparator<Student>() {
    	@Override
    	public int compare(Student o1, Student o2) {
    		return 0;
    	}
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    注意点
    接口是
    Comparable
    接口是
    Comparator

    总结

    1、如果希望记住元素的添加顺序,需要存储重复的元素,又要频繁的根据索引查询数据?
    用ArrayList集合(有序、可重复、有索引),底层基于数组的。(常用)
    2、如果希望记住元素的添加顺序,且增删首尾数据的情况较多?
    用LinkedList集合(有序、可重复、有索引),底层基于双链表实现的。
    3. 如果不在意元素顺序,也没有重复元素需要存储,只希望增删改查都快?
    用HashSet集合(无序,不重复,无索引),底层基于哈希表实现的。 (常用)
    4.如果希望记住元素的添加顺序,也没有重复元素需要存储,且希望增删改查都快?
    用LinkedHashSet集合(有序,不重复,无索引), 底层基于哈希表和双链表。
    5. 如果要对元素进行排序,也没有重复元素需要存储?且希望增删改查都快?
    用TreeSet集合,基于红黑树实现。

    集合的并发修改异常

    使用迭代器遍历集合时,又同时在删除集合中的数据,程序就会出现并发修改异常的错误

            Iterator<String> iter = list.iterator();
            while (iter.hasNext()){
                String name = iter.next();
                if(name.contains("李")){
                    list.remove(name);
                }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    这是因为:删除的时候下标会继续向后所以会漏删

            for (int i = 0; i < list.size(); i++) {
                String name = list.get(i);
                if(name.contains("李")){
                    list.remove(name);
                }
            }
            System.out.println(list);
            [王麻子, 小李子, 李爱花, 张全蛋, 晓李, 李玉刚]
    		[王麻子, 李爱花, 张全蛋, 李玉刚]
    
            for (int i = 0; i < list.size(); i++) {
                String name = list.get(i);
                if(name.contains("李")){
                    list.remove(name);
                    i--;
                }
            }
            System.out.println(list);
            [王麻子, 小李子, 李爱花, 张全蛋, 晓李, 李玉刚]
    		[王麻子, 张全蛋]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    由于增强for循环遍历集合就是迭代器遍历集合的简化写法,因此,使用增强for循环遍历集合,又在同时删除集合中的数据时,程序也会出现并发修改异常的错误
    lamda foreach表达式本身其实也是利用增强for循环
    怎么保证遍历集合同时删除数据时不出bug?
    使用迭代器遍历集合,但用迭代器自己的删除方法删除数据即可。
    迭代器自带的
    iter.remove(); 即利用遍历器进行删除
    删除当前的元素
    如果能用for循环遍历时:可以倒着遍历并删除;或者从前往后遍历,但删除元素后做i --操作。

            Iterator<String> iter = list.iterator();
            while (iter.hasNext()){
                String name = iter.next();
                if(name.contains("李")){
                    iter.remove();
                }
            }
            System.out.println(list);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    集合进阶

    可变参数

    就是一种特殊形参,定义在方法、构造器的形参列表里,格式是:数据类型…参数名称;
    传递不定数量的某种类型的参数

    public static void test(int ...nums){
    
    }
    
    • 1
    • 2
    • 3

    可变参数的特点和好处
    特点:可以不传数据给它;可以传一个或者同时传多个数据给它;也可以传一个数组给它。
    好处:常常用来灵活的接收数据。

    其实就是一个数组

    注意事项

    一个形参列表中,只能有一个可变参数,无论类型,都只能有一个
    可变参数必须放在形参列表的最后面

    public class test01 {
        public static void main(String[] args) {
            test("kehao",1,2,3,4,5,6);
        }
        public static  void test(String name,int ... num){
            System.out.println(num.length);
            System.out.println(name);
            System.out.println(Arrays.toString(num));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    collections

    image.png

    这个sort是只能对默认的整形规则来进行排序,但是对于我们自己自定义的规则,我们并不能

    斗地主的做牌,洗牌,发牌,排序,看牌

    本身我的做法有很大问题,在面对这汇总类型固定,数量固定的牌组构建,我并没有想到直接利用数组来进行构建,而是用了多层循环来遍历,导致这个代码看起来臃肿不堪

        public Room() {
            //本身在构建对象的时候我们就可以自己产生一副牌
            String[] color = new String[]{"红桃","方片","黑桃","梅花"};
            List<Card> temp = new ArrayList<>();
            //从3到10
            for (int i=3;i<11;i++){
                for (String s : color) {
                    Card card = new Card(String.valueOf(i),s,i-2);
                    temp.add(card);
                }
            }
            //3 到 j
            for (char c = 'J';c<='K';c++){
                for (String s : color) {
                    Card card = new Card(String.valueOf(c),s,(int)(c)-65);
                    temp.add(card);
                }
            }
            for (int i=1;i<=2;i++){
                for (String s : color) {
                    Card card = new Card(String.valueOf(i),s,i+11);
                    temp.add(card);
                }
            }
            Card joker = new Card("大王","红色",15);
            temp.add(joker);
            Card joker1 = new Card("小王","黑色",14);
            temp.add(joker1);
            this.cards.addAll(temp);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    较为合适的代码

        public Room() {
            //本身在构建对象的时候我们就可以自己产生一副牌
            String[] color = new String[]{"红桃","方片","黑桃","梅花"};
            String[] points = new String[]{"3","4","5","6","7","8","9","10","J","Q","K","J","2"};
            //从3到10
            int size=0;
            for (String point : points) {
                size++;
                for (String s : color) {
                    Card c= new Card(point,s,size);
                    cards.add(c);
                }
            }
            Card joker = new Card("大王","红色",++size);
            Card joker1 = new Card("小王","黑色",++size);
            Collections.addAll(cards,joker,joker1);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    随机打乱元素 shuffle,反转元素 reverse

        public static void main(String[] args) {
            ArrayList<Integer> list = new ArrayList<>();
            for(int i=0;i<10;i++){
                list.add(i);
            }
            System.out.println(list);
            Collections.shuffle(list);
    		System.out.println(list);
    		Collections.reverse(list);  
    		System.out.println(list);
        }
    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    [3, 2, 9, 8, 5, 1, 6, 7, 0, 4]
    [4, 0, 7, 6, 1, 5, 8, 9, 2, 3]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    打乱元素的另一种用法,我们可以用它来对这个数组进行随机抽取元素

    public class W1随机点名器 {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<>();
            Collections.addAll(list, "范闲", "范建", "范统", "杜子腾", "杜琦燕", "宋合泛", "侯笼藤", "朱益群", "朱穆朗玛峰", "袁明媛");
            // 思路一:
            Random r = new Random();
    		System.out.println("随机点到的是");
    		int n = r.nextInt(list.size());
    		System.out.println(list.get(n));
            System.out.println("点名结束");
            // 思路二 打乱集合,在获取第一个
            Collections.shuffle(list);
            System.out.println(list.get(0));
    		System.out.println("点名结束");
    		
            input.close();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    Map集合

    注意点

    对于这个Map对象如果我不指定这个对象那么它的键值是什么类型的
    Map map = new HashMap();
    如果没有指定泛型类型参数,则 Map 对象 map 的键和值的类型将默认为 Object 类型。
    Map集合是双列集合
    Map集合称为双列集合,格式:{key1=value1 , key2=value2 , key3=value3 , …}, 一次需要存一对数据做为一个元素.
    Map集合的每个元素“key=value”称为一个键值对/键值对对象/一个Entry对象,Map集合也被叫做“键值对集合”
    Map集合的所有键是不允许重复的,但值可以重复,键和值是一一对应的,每一个键只能找到自己对应的值
    Map集合的键不能重复,Map集合的值可以重复

    Map集合在什么业务场景下使用

    {商品1=2 , 商品2=3 , 商品3 = 2 , 商品4= 3}
    需要存储一一对应的数据时,就可以考虑使用Map集合来做
    image.png

    Map集合体系的特点

    注意:Map 系列集合的特点都是由键决定的,值只是一个附属品,值是不做要求的
    注意下面的几种常见的 Map 的实现类和 set 简直一模一样, 这是因为 set 其实是利用 map 进行创建的
    image.png

    HashMap(由键决定特点): 无序、不重复、无索引; (用的最多)
    LinkedHashMap (由键决定特点):由键决定的特点:有序、不重复、无索引。
    TreeMap (由键决定特点):按照键大小默认升序排序、不重复、无索引。
    经典代码
    Map map = new HashMap<>();

            Map<String,Integer> map = new HashMap<>();
            map.put("黑丝",2);
            map.put("白丝",1);
            map.put("白丝",3);   //后面重复的数据会覆盖前面的数据
            map.put("渔网",3);
            System.out.println(map);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    map集合的常用方法

    image.png
    返回值类型
    因为这个键值是无序,无索引,不重复,所以自然要将这个放到这个set集合中0
    因为值是可以重复的, 所以把这个值放到这个 collection 集合中
    我们可以利用 ctrl+alt+v 快速的进行操作

            Set<String> keys = map.keySet();
            System.out.println(keys);
            Collection<Integer> values = map.values();
            System.out.println(values);
    
    • 1
    • 2
    • 3
    • 4

    将一个集合的元素倒入到另一个集合中去
    aeabc734a7c7636ac5faa76dfa2818bd.jpg

        public static void main(String[] args) {
            Map<String,String> map = new HashMap<>();
            map.put("heisi","zhouyi");
            map.put("baisi","zhouer");
            System.out.println(map);
            Map<String,String> map1 = new HashMap<>();
            map1.putAll(map);
            System.out.println(map1);
        }
    {baisi=zhouer, heisi=zhouyi}
    {baisi=zhouer, heisi=zhouyi}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    map集合的遍历方式

    image.png

    键找值

    我们利用 keyset 方法来获得所有键的集合,然后根据这个 getkey 获得对应的值

    [!tip ]

    注意这个只能根据 ket 来获取值, 不能根据值来获取 key, 这是因为我们 key 是唯一的, 而值并不是唯一的

    image.png

    键值对

    本质是利用这个增强for循环来进行遍历,但是对于增强for循环,我们要有其对那个的数据类型,为了方便遍历,java封装了一个entry对象,我们可以通过这个来进行遍历
    image.png

    image.png

    [!info]
    注意这个需要用到这个 entrySet 方法先获得这个 set 类型的集合, 只有集合和数组可以用这个增强 for 循环来进行遍历
    Set< Map.Entry entries = map.entrySet();

    Set<Map.Entry<String, String>> entries = map.entrySet();
    for (Map.Entry<String, String> entry : entries) {
    	System.out.println(entry.getValue()+"的服装是"+entry.getKey());
    }
    
    • 1
    • 2
    • 3
    • 4

    lambda表达式

    image.png
    查看源码就是这个foreach 使用这个匿名内部类,然后内部的实现方式是通过第二种的entry方式来进行实现的.

            //foreach循环进行遍历
            //lambda
            map.forEach(new BiConsumer<String, String>() {
                @Override
                public void accept(String s, String s2) {
                    System.out.println(s2+"的服装是"+s);
                }
            });
            System.out.println("========");
            map.forEach((s, s2)-> System.out.println(s2+"的服装是"+s));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    只要是存储意义对应的数据的时候,我们就可以考虑用map集合

    Map的三个常见实现类

    HashMap

    HashMap集合的底层原理
    HashMap跟HashSet的底层原理是一模一样的,都是基于哈希表实现的。
    实际上:原来学的Set系列集合的底层就是基于Map实现的,只是Set集合中的元素只要键数据,不要值数据而已。

    public HashSet() {
    	map = new HashMap<>();
    }
    
    • 1
    • 2
    • 3

    哈希表

    1. JDK8之前,哈希表 = 数组+链表
    2. JDK8开始,哈希表 = 数组+链表+红黑树
    3. 哈希表是一种增删改查数据,性能都较好的数据结构。

    image.png

    HashMap 集合是一种增删改查数据,性能都较好的集合.但是它是无序,不能重复,没有索引支持的(由键决定特点
    HashMap的键依赖hashCode方法和equals方法保证键的唯一
    如果键存储的是自定义类型的对象,可以通过重写hashCode和equals方法,这样可以保证多个对象内容一样时,HashMap集合就能认为是重复的。

    LinkedMap

    image.png

    底层数据结构依然是基于哈希表实现的,只是每个键值对元素又额外的多了一个双链表的机制记录元素顺序(保证有序)。
    实际上:原来学习的LinkedHashSet集合的底层原理就是LinkedHashMap。

    TreeMap

    特点:不重复、无索引、可排序(按照键的大小默认升序排序,只能对键排序)
    原理:TreeMap跟TreeSet集合的底层原理是一样的,都是基于红黑树实现的排序
    TreeMap集合同样也支持两种方式来指定排序规则
    让类实现Comparable接口,重写比较规则。
    TreeMap集合有一个有参数构造器,支持创建Comparator比较器对象,以便用来指定比较规则

    .

  • 相关阅读:
    Android学习笔记 4. ImageView
    STM32 I2C总线锁死原因及解决方法
    【嵌入式linux】Ubuntu 修改用户名
    如何给PDF解密?建议收藏这些方法
    高防虚拟主机怎么选?
    JVM和Java体系结构
    1111111
    32 道 Spring 常见面试总结(附详细参考答案),我经常拿来面试别人
    Linux通过/etc/rcX.d(rc0~rc6)实现开机自动执行脚本的方法
    手写小程序摇树优化工具(九)——删除业务组代码
  • 原文地址:https://blog.csdn.net/everything_study/article/details/132724287