集合进阶(JAVA笔记第二十九期)

p.s.这是萌新自己自学总结的笔记,如果想学习得更透彻的话还是请去看大佬的讲解

集合基础看这里

目录

  • 集合体系结构
  • 单列集合
    • Collection
      • 各个方法的注意事项
      • add()
      • remove()
      • contains()
    • Collection三种遍历方式
      • 迭代器遍历
      • 增强for遍历
      • lambda表达式遍历
        • 匿名内部类遍历
    • List系列集合
      • List集合的特有方法
        • remove()方法注意点
      • List集合五种遍历方式
        • 普通for循环遍历(因为其存在索引)
        • 列表迭代器遍历
      • ArrayList集合
      • LinkedList集合
    • Set系列集合
      • 哈希表
        • 哈希值
      • HashSet
        • HashSet的三个特点
      • LinkedHashSet
      • TreeSet
    • Collections工具类
  • 数据结构
  • 双列集合
    • Map
    • Map的三种遍历方式
      • 键找值
      • 键值对
      • lambda表达式遍历
    • 利用Map集合进行统计
    • HashMap
      • 利用HashMap存储自定义对象
      • 利用HashMap统计投票数
    • LinkedHashMap
    • TreeMap
      • 利用TreeMap进行自定义对象的排序
      • 利用TreeMap集合进行统计
  • 可变参数
  • 不可变集合


集合体系结构

集合可以分为两大类:Collection(代表单列集合)、Map(代表双列集合)
单列集合,即每次添加元素的时候只能添加一个数据
双列集合,即每次添加元素的时候添加一对(两个)数据
.
单列集合最顶层的是Collection,下面又分为两种单列集合:List、Set
在这里插入图片描述
List系列集合:添加的元素有序(存和取的顺序是一样的)、可重复、有索引
Set系列集合:添加的元素无序(存和取的顺序有可能一样)、不可重复、无索引
.
双列集合
在这里插入图片描述

单列集合

Collection

Collection是单列集合的祖宗接口,它的功能是全部单列集合都可以继承使用的
其方法如下
在这里插入图片描述

各个方法的注意事项

举例说明

add()

//使用接口的多态来创建对象
Collection<String> coll = new ArrayList<>();

System.out.println(coll.add("aaa"));

注意:如果我们往List系列集合里添加数据,那么方法永远返回true,因为List系列集合里是允许元素重复的
而如果我们往Set系列集合里添加数据,那么方法不一定返回true,因为Set系列集合里是不允许元素重复的。如果添加一个已有的数据,则会返回false,表示添加失败
想要一次添加一个集合的数据,可以使用addAll()方法

remove()

 Collection<String> coll = new ArrayList<>();
        coll.add("aaa");


        System.out.println(coll.remove("aaa"));

注意:由于Collection里面定义的是共性的方法,所以此时不能通过索引进行删除,
只能通过元素的对象进行删除

contains()

Collection<String> coll = new ArrayList<>();
        coll.add("aaa");

        
        System.out.println(coll.contains("aaa"));

注意:由于contains()方法底层是依赖equals()方法来判断元素是否存在的, 如果集合里存储的是自定义对象,那么则会根据对象的地址值来判断(不会用对象的属性值来判断)
所以,如果集合里存储的是自定义对象,也想通过由于contains()方法来判断是否存在,
那么在javabean类中,一定要重写equals()方法.此时比较的就不是对象的地址值而是属性值了

Collection三种遍历方式

不能使用普通for循环是因为普通for循环需要索引,但 Set系列集合没有索引,其只能用于list系列集合

迭代器遍历

迭代器在java中的类是
Iterator,迭代器是集合专用的迭代方式
迭代器不依赖索引

获取迭代器的方法为**iterator()**方法,其会返回一个迭代器对象(默认指向当前回合的0索引)

Iterator类中有两个常用的方法
在这里插入图片描述

以下为一种迭代器遍历方式

import java.util.ArrayList;
import java.util.Iterator;

public class D {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");

        //创建一个迭代器对象,此时指针对应的索引为0
        Iterator<String> it = list.iterator();
        while (it.hasNext()){
            //it.hasNext()判断当前指针位置是否还有元素,有为true否则为false

            //it.next()获取当前指针对应元素并将指针向右移动一次
            String str = it.next();
            
            System.out.print(str+", ");
        }

    }
}

注意点

  1. 当指针指向不存在的元素时再调用next()方法的时候(即当前位置没有元素还要强行获取),会出现NoSuchElementException异常(即没有这个元素异常)
  2. 迭代器遍历后,指针不会复位。想要在遍历一次的话就只能重新创建一个新的迭代器的对象
  3. 循环中只能用一次next方法(原因为第一点)
  4. 迭代器遍历时,不能用集合的方法进行增加和删除,只能调用迭代器的remove()方法进行删除,且暂时没有方法进行添加

增强for遍历

增强for的底层就是迭代器,是为了简化迭代器的代码书写的
它是在JDK5以后出现的,其内部原理就是一个Iterator迭代器
只有单列集合和数组才能用增强for进行遍历

格式

for(集合或数组的数据类型 变量名: 集合名/数组名) 
	System.out.println(变量名)};

举例

ArrayList<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");

        for (String s: list) {
            System.out.println(s);
        }

s其实就是一个第三方变量,在循环的过程中依次表示集合中的每一个数据
所以增强for循环不改变集合或数组原来的数据
快捷键:list.for然后回车

lambda表达式遍历

匿名内部类遍历
ArrayList<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        
        list.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });

底层原理:
其实也会自己遍历集合,一次得到每一个元素
把得到的每一个元素传递给上面的accept方法
s依次表示集合中的每一个数据

通过将匿名内部类遍历简化后得到 lambda表达式遍历

ArrayList<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");


        list.forEach( s -> System.out.println(s));

总结:如果只想遍历的话可以用增强for和lambda表达式遍历
如果想在遍历的过程中删除元素,则使用迭代器遍历

List系列集合

List系列集合:添加的元素有序(存和取的顺序是一样的)、可重复、有索引

List集合的特有方法

由于List集合中有索引,所以其有很多特定的需要使用索引的方法
在这里插入图片描述

remove()方法注意点
ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);

        //此时删除的不是1这个元素,而是1索引对应的元素
        list.remove(1);
        //这是因为在调用方法的时候,如果方法出现了重载现象的时候
        //优先调用实参和新参类型一致的那个方法

        //如果想根据元素删除的话

        //使用手动装箱的办法:手动将基本数据类型的1,变成Integer类型
        
        //原理:在调用方法的时候,如果方法出现了重载现象的时候,
        //优先调用实参和新参类型一致的那个方法

        Integer i = Integer.valueOf(1);

        list.remove(i);
        //这时删除的就是元素1了

List集合五种遍历方式

由于List集合继承于Collection,所以Collection的三种遍历方式List集合都能使用
下面为List集合特有的两种遍历方式

普通for循环遍历(因为其存在索引)
ArrayList<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");

        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }

        System.out.print(list+" ");
列表迭代器遍历
ArrayList<String> list = new ArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");

        //获取一个列表迭代器的对象,里面的指针默认也是指向0索引的
        ListIterator<String> it = list.listIterator();

        while (it.hasNext()){
            String str = it.next();

            //在遍历的过程中添加元素
            if ("b".equals(str)){
                it.add("x");
            }

        }
        System.out.println(list);

注意事项: 列表迭代器是迭代器的子接口。其在迭代器的基础上额外添加了一个方法,就是在遍历的过程中可以添加元素

总结:如果只想遍历的话可以用增强for和lambda表达式遍历
如果想在遍历的过程中删除元素,则使用迭代器遍历
如果想在遍历的过程中添加元素,则使用列表迭代器遍历
如果想在遍历的过程中操作索引,则使用普通for遍历

ArrayList集合

底层原理:ArrayList是利用空参创建的集合,在底层创建一个默认长度为0的数组,其名字为elementData。有一个成员变量size用来记录成员个数
当添加第一个元素的时候,底层会创建一个新的长度为10的数组。size不仅记录了元素个数,还指定了元素的下次存入位置
当数组存满的时候,会扩容1.5倍,即创建一个新的长度为原数组1.5倍的数组,然后再将所有元素拷贝到新数组当中
如果一次添加多个元素,1.5倍还放不下,则新创建的数组以实际情况为准

LinkedList集合

底层数据结构是双链表,查询慢,增删快。
如果操作的是首尾元素,速度也是极快的。所以其多了很多操作首尾数据的API
在这里插入图片描述

Set系列集合

Set系列集合:添加的元素无序(存和取的顺序有可能一样)、不可重复、无索引
无序:存取顺序不一致
不重复:可以去除重复
无索引:没有带索引的方法。所以不能通过普通for循环遍历,也不能通过索引获取元素
.
Set系列的实现类
HashSet:无序、不重复、无索引
LinkHashSet:有序、不重复、无索引
TreeSet:可排序、不重复、无索引
.
Set接口的方法基本上与Collection的API一致 。因此其方法的注意事项与上方一致
由于其没有索引,所以其遍历方式只有增强for遍历、迭代器遍历、lambda表达式遍历三种方式

哈希表

在学HashSet集合以前我们要先学习哈希表

哈希表是一种对于增删改查数据性能都较好的结构
JDK8以前哈希表是以数组+链表组成的
JDK8开始哈希表是以数组+链表+红黑树组成的
.

哈希值

哈希表有一个很重要的值:哈希值
哈希值,即对象的整数表现形式
哈希表的底层是以数组形式存在的,存储数据对应的索引是根据int index = (数组长度-1) & 哈希值;计算出数据在数组中对应的索引。因此想要存储一个对象,我们就要获取其整数形式才能计算出它对应的索引,而这个整数形式即为哈希值

哈希值是对象根据hashCode方法算出来的int类型的整数
hashCode方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算
一般情况下我们会重写hashCode方法,利用对象内部的属性值计算哈希值
.
注意:
如果没有重写hashCode方法,不同对象计算出来的哈希值是不一样的
如果已经重写hashCode方法,不同对象只要属性值相同,计算出来的哈希值就是一样的
在小部分情况下,不同的属性值或地址值计算出来的哈希值也有可能一样,这种情况称为哈希碰撞

举例说明

import java.util.Objects;

public class Demo1 {
    public static void main(String[] args) {
        //创建对象
        Student student1 = new Student("张三",18);
        Student student2 = new Student("张三",18);

        //如果没有重写hashCode方法,不同对象计算出来的哈希值是不一样的
        //如果已经重写hashCode方法,不同对象只要属性值相同,计算出来的哈希值就是一样的
        System.out.println(student1.hashCode());
        System.out.println(student2.hashCode());


        //在小部分情况下,不同的属性值或地址值计算出来的哈希值也有可能一样,这种情况称为哈希碰撞
        System.out.println("abc".hashCode());
        System.out.println("acD".hashCode());

    }
}
//==============================================
class Student{
    private String name;
    private int age;


    //重写equals()方法和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 age == student.age && Objects.equals(name, student.name);
    }

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

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

HashSet

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

在这里插入图片描述
新元素挂在老元素下面形成链表
当数组存储的元素等于加载因子*数组长度的数量时,数组会扩容成原来的两倍
当链表的长度大于8且数组长度大于等于64的时候,那么该链表则会自动转换为红黑树,从而提高了查找效率
如果集合中存储的是自定义对象,则必须要重写hashCode()和equals()方法,通过属性值来计算哈希值或者进行比较

HashSet的三个特点
  1. HashSet为什么存或取的顺序不一样(即无序)?
    解释:HashSet在遍历的时候时从0索引开始遍历的,而存储数据则是通过哈希值来存储的
  1. HashSet为什么没有索引
    因为其底层是由数组、链表、红黑树组成的,于是不好规定索引
  1. HashSet是利用什么机制保证去重的?
    hashCode()方法和equals()方法。先利用hashCode()方法获得哈希值,再用equals()方法来比较对象内部的属性值是否相同

LinkedHashSet

LinkedHashSet是HashSet的子类,其特点为有序、不重复、无索引
这里的有序是保证存储和取出的数据顺序是一致的

其底层数据结构依然是哈希表,只是每个元素又额外多了一个双向链表的机制记录存储顺序
红色双向箭头即为双向链表在这里插入图片描述
在以后要数据去重时,默认使用HashSet。如果要求去重且存取有序,这时才使用LinkedHashSet(因为HashSet用来去重效率更高)

TreeSet

TreeSet:可排序、不重复、无索引
可排序:按照元素的默认规则排序
其底层是基于红黑树的数据结构实现排序的,增删改查性能都较好

TreeSet集合排序的默认规则
整数:从小到大
字符、字符串:根据字符在ASCII码表中的数字升序进行排序,字符串则是从首字符开始比较然后依次向后比较,而且长度较小的在前面
自定义对象:

        //存储整数并排序
        TreeSet<Integer> ts = new TreeSet<>();
        ts.add(1);ts.add(3);ts.add(2);
        ts.add(6);ts.add(4);ts.add(5);
        ts.add(9);

        System.out.println(ts);

对于自定义对象,TreeSet有两种排序方式
第一种:默认排序、自然排序:使javabean类实现Comparable接口指定比较规则
如下

package study.TreeSet;

import java.util.TreeSet;

public class Student implements Comparable<Student>{
//Comparable<Student>:指明比较的数据类型

    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int compareTo(Student o) {
        //指定排序的规则
        //只看年龄,想要通过年龄的升序进行排列
        int result = this.age - o.age;
        //this表示当前要添加的元素
        //o表示已经在红黑树中的元素
        return result;
        //返回值若为负数:认为当前要添加的元素是较小的,存在左边
        //返回值若为负数:认为当前要添加的元素是较大的,存在右边
        //返回值若为0:认为当前要添加的元素存在的,舍弃
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    
}
//=====================================================
class Test{
    public static void main(String[] args) {
        TreeSet<Student> ts = new TreeSet<>();

        Student student1 = new Student("a", 18);
        Student student2 = new Student("b", 25);
        Student student3 = new Student("c", 15);

        ts.add(student1);
        ts.add(student2);
        ts.add(student3);

        System.out.println(ts);
    }
}

输出

[Student{name='c', age=15}, Student{name='a', age=18}, Student{name='b', age=25}]

第二种排序方式:比较器排序。即在创建TreeSet方向的时候,传递比较器Comparator制定规则
使用原则:默认使用第一种,如果无法满足需求就用第二种(比如想要自定义先比较长度在比较首字母)

import java.util.Comparator;
import java.util.TreeSet;

public class Demo1 {
    public static void main(String[] args) {
        //存储整数并排序
        TreeSet<String> ts = new TreeSet<>(new Comparator<String>() {
            //创建了一个比较器对象


            @Override
            //o1:当前要添加的元素
            //o2:已经在红黑树中存在的元素
            public int compare(String o1, String o2) {
                //按照长度排序
                int i = o1.length()-o2.length();

                 i = i==0 ? o1.compareTo(o2):i;



                //返回值若为负数:认为当前要添加的元素是较小的,存在左边
                //返回值若为负数:认为当前要添加的元素是较大的,存在右边
                //返回值若为0:认为当前要添加的元素存在的,舍弃
                return i;
            }
        });
        ts.add("aaa");ts.add("aba");ts.add("aa");
        ts.add("cd");ts.add("cbd");


        System.out.println(ts);
    }
}

Collections工具类

Collections是一个集合工具类
其有如下API在这里插入图片描述

数据结构

要想分清楚底层的实现类有什么不同,我们先要了解底层的数据结构

数据结构是计算机存储、组织数据的方式
数据结构是计算机底层存储、组织数据的方式,是指数据之间是以什么方式排列在一起的
数据结构是为了更加方便的管理和使用数据,需要结合具体的业务场景来进行选择
使用合理的数据结构可以带来更高的运行效率或者存储效率
常见的数据结构包括

栈、队列、数组、链表、二叉树、二叉查找树、平衡二叉树、红黑树

双列集合

双列集合,即每次添加元素的时候添加一对(两个)数据
一对数据的左边数据称为(不可重复),右边称为(可以重复)
键和值之间是一一对应的关系。这一对数据称为键值对或键值对对象或Entry对象
在这里插入图片描述

Map

Map是双列集合的顶层接口,它的功能是全部双列集合都可以继承使用的
所有Map集合的特点都是由键决定的,与值无关
常用方法
在这里插入图片描述

Put()方法

 //创建Map集合的对象
        Map<String,String> map = new HashMap<>();

        //添加元素
        map.put("张三","学生");
        map.put("李四","老师");

        //put方法的细节
        //put有两个作用:添加/覆盖

        //在添加数据的时候,如果键不存在,那么直接把键值对对象添加到集合中,方法返回null
        //在添加数据的时候,如果键存在,那么会把原有的键值对对象覆盖,并把被覆盖的值进行返回

        String str = map.put("李四","辅导员");
        System.out.println(str);


        System.out.println(map);

remove()方法

//创建Map集合的对象
        Map<String,String> map = new HashMap<>();

        //添加元素
        map.put("张三","学生");
        map.put("李四","老师");


        //成功删除返回true,否则返回false
        Boolean b = map.remove("张三","学");
        System.out.println(b);

        System.out.println(map);

Map的三种遍历方式

键找值

思路:把所有的键都获取出来放到一个单列集合中,再遍历单列集合依次得到每一个键,再通过get()方法依次获取每一个键所对应的值

//创建Map集合的对象
        Map<String,String> map = new HashMap<>();

        //添加元素
        map.put("张三","学生");
        map.put("李四","老师");


        //获取所有的键,把这些键放到一个单列集合中
        Set<String> keys = map.keySet();

        //遍历单列集合,得到每一个键
        for (String key : keys) {
            //利用键获取对应的值
            System.out.println(key + "="+map.get(key));
        }

键值对

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Demo2 {
    public static void main(String[] args) {
    //创建Map集合的对象
    Map<String,String> map = new HashMap<>();

    //添加元素
    map.put("张三","学生");
    map.put("李四","老师");


    //通过键值对对象进行遍历
    //通过一个方法获取所有的键值对对象,返回一个Set集合
        Set<Map.Entry<String, String>> entries = map.entrySet();
        //遍历entries这个集合,去得到里面的每一个键值对对象
        for (Map.Entry<String, String> entry : entries) {
            //利用entry调用get()方法获取键和值
            System.out.println(entry.getKey()+" "+entry.getValue());
        }

    }
}

lambda表达式遍历

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;

public class Demo3 {
    public static void main(String[] args) {
        //创建Map集合的对象
        Map<String,String> map = new HashMap<>();

        //添加元素
        map.put("张三","学生");
        map.put("李四","老师");


        map.forEach(new BiConsumer<String, String>() {
            @Override
            public void accept(String key, String value) {
                System.out.println(key+" "+value);
            }
        });

        System.out.println("=============================");


        map.forEach((String key, String value)-> {
                System.out.println(key+" "+value);
            }
        );


        System.out.println("=============================");


        //lambda表达式遍历
        map.forEach(( key,  value)-> System.out.println(key+" "+value));

    }
}

利用Map集合进行统计

当我们想统计一些数据时,最简便的可以使用计数器思想
但是当要统计的数据比较多的时候,使用计数器思想会非常低效率
这时我们便可以用HashMap或TreeMap进行统计
两种集合的使用情况有所不同
如果题目没有要求对结果进行排序:那么优先使用HashMap(效率更高)
如果题目要求对结果进行排序:那么使用TreeMap

HashMap

HashMap是Map里面的一个实现类。其没有特有的方法,只继承了Map里面的方法
特点:无序、不重复、无索引
其底层与HashSet一样,都是哈希表结构
其哈希值的计算与键有关,与值无关

利用HashMap存储自定义对象

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

public class Demo4 {
    public static void main(String[] args) {
        //创建一个HshMap集合,键是学生对象,值是籍贯
        //存储后遍历
        //要求:同姓名、同年龄的被认为是一个学生

        //核心:HashMap的键位置如果存储的是自定义对象,需要重写hasnCode和equals方法

        Student student1 = new Student("张三",18);
        Student student2 = new Student("李四",23);
        Student student3 = new Student("张三",18);

        HashMap<Student,String> hm = new HashMap<>();

        hm.put(student1,"四川");
        hm.put(student2,"上海");
        hm.put(student3,"北京");

        //--------------------------------------
        
        Set<Student> keys = hm.keySet();
        for (Student key : keys) {
            System.out.println(key+" "+hm.get(key));
        }

        System.out.println("=========================");

        Set<Map.Entry<Student, String>> entries = hm.entrySet();
        for (Map.Entry<Student, String> entry : entries) {
            System.out.println(entry.getKey()+" "+entry.getValue());
        }

        System.out.println("=========================");

        hm.forEach(( student,  s)-> System.out.println(student+" "+s));
    }
}

//==================================================

class Student{
    private String name;
    private int age;

    public Student() {
    }

    @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);
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

利用HashMap统计投票数

import java.util.*;

public class Demo5 {
    public static void main(String[] args) {
        //一个班级80名学生,现在需要组成秋游活动,班长提供了四个景点(A、B、C、D)
        //每个学生只能选择一个景点,请统计最终哪个经典想去的人数最多

        //定义一个数组,存储4个景点
        String[]arr = {"A","B","C","D"};

        //利用随机数模拟88个同学的投票,并把投票的结果存储起来
        ArrayList<String> list = new ArrayList<>();
        Random random = new Random();
        for (int i = 0; i < 80; i++) {
            int index = random.nextInt(arr.length);
            list.add(arr[index]);
        }

        //如果要统计的东西比较多,此时不方便使用计数器思想
        //这时我们可以定义map集合,利用集合进行统计
        HashMap<String,Integer> hm = new HashMap<>();

        for (String name : list) {
            //判断当前景点在map集合里是否存在
            if (hm.containsKey(name)){
                //存在
                //先获取当前景点已经被投票的次数
                int count = hm.get(name);
                //表示当前景点又被投了一次
                count++;
                //把投的次数再次添加到集合
                hm.put(name,count);
            }else {
                hm.put(name,1);
            }
        }

        System.out.println(hm);

        //求最大值
        int max = 0;
        Set<Map.Entry<String, Integer>> entries = hm.entrySet();
        for (Map.Entry<String, Integer> entry : entries) {
            int count = entry.getValue();
            max = Math.max(count,max);
        }


        //判断哪个景点的次数和最大值一样,若一样则打印出来
        for (Map.Entry<String, Integer> entry : entries) {
            int count = entry.getValue();
            if (count==max){
                System.out.println(entry.getKey());
            }
        }
    }
}

LinkedHashMap

LinkedHashMap是HashMap的子类
特点:有序、不重复、无索引
有序是指元素取出的顺序和存储的顺序一致
其底层数据结构依然是哈希表,只是每个键值对元素之间有额外多了一个双链表的机制记录存储的顺序

TreeMap

TreeMap和TreeSet集合一样,底层都是红黑树结构
特点:可排序、不重复、无索引
可排序:对键进行排序
其排序规则和TreeSet一致

利用TreeMap进行自定义对象的排序

import java.util.*;

public class Demo6 {
    public static void main(String[] args) {
        //键:学生对象
        //值:籍贯
        //需求:按照学生年龄的升序排列,年龄一样的按照姓名的字母排列
        //同姓名年龄的视为同一个人
        TreeMap<Student1,String> tm = new TreeMap<>();

        Student1 student1 = new Student1(18,"张三");
        Student1 student2 = new Student1(20,"李四");
        Student1 student3 = new Student1(20,"王五");
        Student1 student4 = new Student1(18,"张三");

        tm.put(student1,"北京");
        tm.put(student2,"上海");
        tm.put(student3,"四川");
        tm.put(student4,"广东");

        Set<Map.Entry<Student1, String>> entries = tm.entrySet();
        for (Map.Entry<Student1, String> entry : entries) {
            System.out.println(entry.getKey()+" "+entry.getValue());
        }


    }

}
class Student1 implements Comparable<Student1>{
    private int age;
    private String name;

    public Student1() {
    }

    @Override
    public String toString() {
        return "Student1{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student1 student1 = (Student1) o;
        return age == student1.age && Objects.equals(name, student1.name);
    }

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

    public Student1(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public int compareTo(Student1 o) {
        //根据学生的年龄的升序排列,年龄一样的按照姓名的字母排列,同姓名同年龄的视为同一个人
        //this:表示当前要添加的元素
        //o:表示已经在红黑树中存在的元素

        //返回值
        //负数:表示当前要添加的元素是小的,在左边
        //正数:表示当前要添加的元素是大的,在右边
        //0:表示当前要添加的元素已经存在,舍去

        int i = this.getAge() - o.getAge();
        i = i == 0 ? this.getName().compareTo(o.getName()) : i;


        return i;
    }
}

利用TreeMap集合进行统计

import java.util.ArrayList;
import java.util.StringJoiner;
import java.util.TreeMap;
import java.util.function.BiConsumer;

public class Demo7 {
    public static void main(String[] args) {
        //字符串"aababcabcdabcde"
        //统计字符串中每一个字符出现的次数,并进行排序

        String str = "aababcabcdabcde";

        TreeMap<Character,Integer> tm = new TreeMap<>();


        for (int i = 0; i < str.length(); i++) {
            char ch = str.charAt(i);
            if (tm.containsKey(ch)){
                //存在
                //先获取当前字符出现的次数
                int count = tm.get(ch);
                //表示当前景点又被投了一次
                count++;
                //把投的次数再次添加到集合
                tm.put(ch,count);
            }else {
                tm.put(ch,1);
            }
        }

        //遍历集合,并按照指定格式进行拼接

        //第一种
        StringBuilder sb = new StringBuilder();
       tm.forEach(( key,  value)-> sb.append(key).append("(").append(value).append(") "));
        System.out.println(sb);

        System.out.println();
        
        //第二种
        StringJoiner sj = new StringJoiner("","","");
        tm.forEach(((key, value) -> sj.add(key+"").add("(").add(value+"").add(") ")));
        System.out.println(sj);

    }
}

可变参数

可变参数,即参数的数量是不固定的
格式:数据类型...参数名

举例

public class Demo8 {
    public static void main(String[] args) {

        System.out.println(getSum(1,2));
        System.out.println(getSum(1,2,3,5));
        System.out.println(getSum());


    }
    public static int getSum(int...num){
        int sum = 0;
        for (int i : num) {
             sum = sum +i;
        }
        return sum;
    }
}

注意事项
在方法的形参处最多只能写一个形参变量
在方法当中如果除了可变参数外还有别的形参,那么可变参数要写在最后
.
可变参数其实就是一个数组
其作用为在形参中接收多个数据

不可变集合

顾名思义,不可变集合就是不可以被修改的集合。
一旦创建完毕后,长度不能改、内容也不能改

应用场景
1.如果某个数据不能被修改,把它防御性地拷贝到不可变集合中是一个很好的实践
2.当集合对象被不可信的库调用的时候,不可变形式是安全的
说白了,就是我不相信其他人,我不想让别人修改集合的内容,别人只能进行只读操作

书写格式
在这里插入图片描述

注意:
当我们要获取一个不可变的Set集合的时候,里面的参数要保证唯一性,否则对该集合进行只读操作会报错
Map的不可变集合的键是不能重复的。并且Map里面的of方法是有参上限的,最多只能传递20个参数,10个键值对(因为一个方法的形参里面只能有两个可变参数)
当我们想传递多于10个键值对对象的时候,我们可以使用ofEntries方法
在这里插入图片描述
我们也可以使用copyof方法将一个可变集合变成不可变集合

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/912657.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用LlamaIndex框架构建RAG应用的基础实践指南

前言 上一篇文章[检索增强生成 Retrieval-Augmented Generation]介绍了什么是 RAG &#xff0c;并详细对比了和大模型微调方式的区别。 目前实现 RAG 的主流框架就是 [LangChain] 和 [LlamaIndex]&#xff0c;LangChain 更适合需要复杂对话流程、上下文管理、以及多步骤任务的…

小程序开发进阶之路-AI编程助手

之前&#xff0c;我独自一人开发了一个名为“心情追忆”的小程序&#xff0c;旨在帮助用户记录日常的心情变化及重要时刻。从项目的构思、设计、前端&#xff08;小程序&#xff09;开发、后端搭建到最终部署&#xff0c;所有环节都由我一人包办。经过一个月的努力&#xff0c;…

Typora导出pdf手动分页和设置字体样式

手动分页 <div style"page-break-after: always;"></div>鼠标点击代码才会显示&#xff0c;不点击会隐藏。导出pdf时&#xff0c;该位置会分页 设置字体大小、加粗、居中、空格 <p style"font-size:30px; font-weight: bold; text-align: cen…

【GCN】 代码详解 (1) 如何运行【pytorch】可运行版本

Graph Convolutional Networks 代码详解 前言0.引言1.环境配置2. 代码的运行2.1 报错处理2.2 运行结果 3.总结 前言 在前文中&#xff0c;已经对图卷积神经网络&#xff08;Graph Convolutional Neural Networks, GCN&#xff09;的理论基础进行了深入探讨。接下来的章节将会进…

基于 PyTorch 从零手搓一个GPT Transformer 对话大模型

一、从零手实现 GPT Transformer 模型架构 近年来&#xff0c;大模型的发展势头迅猛&#xff0c;成为了人工智能领域的研究热点。大模型以其强大的语言理解和生成能力&#xff0c;在自然语言处理、机器翻译、文本生成等多个领域取得了显著的成果。但这些都离不开其背后的核心架…

做口播博主:真人出镜还是用数字人

做口播博主&#xff1a;真人出镜还是用数字人&#xff1f; 背景&#xff1a;数字人“风口”与流量的冷现实 数字人生成的视频逐渐流行&#xff0c;但真正拥有爆款流量的案例却寥寥无几&#xff1b;另一方面&#xff0c;真人出镜的创作又面临镜头感不足和成本高的难题。创作者究…

【C++篇】在秩序与混沌的交响乐中: STL之map容器的哲学探寻

文章目录 C map 容器详解&#xff1a;高效存储与快速查找前言第一章&#xff1a;C map 的概念1.1 map 的定义1.2 map 的特点 第二章&#xff1a;map 的构造方法2.1 常见构造函数2.1.1 示例&#xff1a;不同构造方法 2.2 相关文档 第三章&#xff1a;map 的常用操作3.1 插入操作…

基于Redis缓存机制实现高并发接口调试

创建接口 这里使用的是阿里云提供的接口服务直接做的测试&#xff0c;接口地址 curl http://localhost:8080/initData?tokenAppWithRedis 这里主要通过参数cacheFirstfalse和true来区分是否走缓存&#xff0c;正常的业务机制可能是通过后台代码逻辑自行控制的&#xff0c;这…

STM32ZET6-USART使用

一、原理说明 STM32自带通讯接口 通讯目的 通信方式&#xff1a; 全双工&#xff1a;通信时可以双方同时通信。 半双工&#xff1a;通信时同一时间只能一个设备发送数据&#xff0c;其他设备接收。 单工&#xff1a;只能一个设备发送到另一个设备&#xff0c;例如USART只有…

深度学习-张量相关

一. 张量的创建 张量简介 张量是pytorch的基本数据结构 张量&#xff0c;英文为Tensor&#xff0c;是机器学习的基本构建模块&#xff0c;是以数字方式表示数据的形式。 例如&#xff0c;图像可以表示为形状为 [3, 224, 224] 的张量&#xff0c;这意味着 [colour_channels, h…

图片表格文字模糊转电子版Excel的解决之道

在面对图片中的表格文字需要转化为电子版Excel或其它格式文本时&#xff0c;当前的主流方法是借助OCR&#xff08;光学字符识别&#xff09;技术。然而&#xff0c;OCR技术的识别效果深受成像质量&#xff0c;即图像文字的清晰度影响。图像越模糊&#xff0c;识别的难度越大&am…

LC:二分查找——杂记

文章目录 268. 丢失的数字162. 寻找峰值 268. 丢失的数字 LC将此题归类为二分查找&#xff0c;并且为简单题&#xff0c;下面记一下自己对这道题目的思考。 题目链接&#xff1a;268.丢失的数字 第一次看到这个题目&#xff0c;虽然标注的为简单&#xff0c;但肯定不能直接排…

增删改查基础项目总结

上篇中主要负责后端部分完成了一个简单的学习辅助系统部分界面&#xff0c;主要针对增删改查进行了练习&#xff0c;过程中遇到了一些细节上的问题以及当时做的时候去查阅的一些之前没有太注意到的额外知识&#xff0c;所以还需要进行进一步梳理&#xff0c;像登录校验的方法以…

YOLOv11融合[ECCV2024]自调制特征聚合SMFA模块及相关改进思路|YOLO改进最简教程

YOLOv11v10v8使用教程&#xff1a; YOLOv11入门到入土使用教程 YOLOv11改进汇总贴&#xff1a;YOLOv11及自研模型更新汇总 《SMFANet: A Lightweight Self-Modulation Feature Aggregation Network for Efficient Image Super-Resolution》 一、 模块介绍 论文链接&#xff1…

【51单片机】I2C总线详解 + AT24C02

学习使用的开发板&#xff1a;STC89C52RC/LE52RC 编程软件&#xff1a;Keil5 烧录软件&#xff1a;stc-isp 开发板实图&#xff1a; 文章目录 AT24C02介绍存储器 I2C总线介绍I2C时序结构数据帧AT24C02数据帧 编程实例 —— 按键控制数据大小&存储器写入读出 AT24C02介绍 …

Kafka 可观测性最佳实践

Kafka 概述 Kafka 是由 LinkedIn 开发一个分布式的基于发布订阅模式的消息队列&#xff0c;是一个实时数据处理系统&#xff0c;可以横向扩展。与 RabbitMQ、RockerMQ 等中间件一样拥有几大特点&#xff1a; 异步处理服务解耦流量削峰 监控 Kafka 是非常重要的&#xff0c;因…

智能制造基础- TPM(全面生产维护)

TPM 前言一、TPM二、TPM实施步骤三、 消除主要问题3.1 实施指南3.2 如何进行“主要问题”的消除&#xff1f; 四、自主维护4.1 实施指南4.2 主要工作内容4.3 如何进行“自主维护“ 五、计划维护5.1 实施指南5.2 如何实施计划维护 六、TPM 适当的 设备 设计5.1 实施指南5.2 如何…

数据库SQLite的使用

SQLite是一个C语言库&#xff0c;实现了一个小型、快速、独立、高可靠性、功能齐全的SQL数据库引擎。SQLite文件格式稳定、跨平台且向后兼容。SQLite源代码属于公共领域(public-domain)&#xff0c;任何人都可以免费将其用于任何目的。源码地址&#xff1a;https://github.com/…

openpyxl处理Excel模板,带格式拷贝行和数据填入

本文中用openpyxl操作Excell 模板,进行行拷贝和数据填充. 主要涉及单元格格式的拷贝,合并单元格的拷贝,行高和列宽的处理. 将模板表格分为三部分,头部,中间循环填充部分,尾部.模板参数中设置头部高度,循环部分高度,剩余为尾部. 拷贝时先拷贝填充头部 ,然后根据数据循环拷贝填…

【FPGA开发】AXI-Lite总线协议解读、Verilog逻辑开发与仿真、Alex Forencich代码解读

目录 AXI是什么AXI是如何工作的AXI-Lite定义AXI-Lite的关键特性AXI-Lite信号列表AXI-Lite信号时序时钟和复位握手机制写请求通道&#xff08;AW&#xff09;写数据通道&#xff08;W&#xff09;写响应通道&#xff08;B&#xff09;读请求通道&#xff08;AR&#xff09;读数据…