Collections常用功能、Set与Map集合

第一章 Collections类

1.1 Collections常用功能

  • 概述:java.util.Collections是集合工具类,用来对集合进行操作。
  • 常用方法:
    • public static void shuffle(List<?> list) :打乱集合顺序。

      public class Test1_shuffle {
          public static void main(String[] args) {
              // public static void shuffle(List<?> list) :打乱集合顺序。
              // 创建List集合对象,限制集合元素类型为Integer
              List<Integer> list = new ArrayList<>();
      
              // 往集合中添加元素
              list.add(500);
              list.add(200);
              list.add(400);
              list.add(100);
              list.add(300);
      
              // 打印集合
              System.out.println("打乱顺序之前的集合:"+list);
      
              // 打乱集合元素顺序
              Collections.shuffle(list);
      
              // 打印集合
              System.out.println("打乱顺序之后的集合:"+list);
          }
      }
      
    • public static <T> void sort(List<T> list):将集合中元素按照默认规则排序。

      • 默认规则: 事先写好的规则

      • 默认规则是在哪里指定的?

        • 在集合元素所属的类中指定的
      • 默认规则如何指定?

        • 要求集合元素所属的类必须实现Comparable接口,重写compareTo方法,在compareTo方法中书写默认排序规则
      • 案例1

        public class Test2_sort默认规则排序 {
            public static void main(String[] args) {
                // public static <T> void sort(List<T> list):将集合中元素按照默认规则排序。
                // 创建List集合对象,限制集合元素类型为Integer
                List<Integer> list = new ArrayList<>();
        
                // 往集合中添加元素
                list.add(500);
                list.add(200);
                list.add(400);
                list.add(100);
                list.add(300);
        
                // 打印集合
                System.out.println("排序之前的集合:"+list);
        
                // 使用默认规则进行排序
                Collections.sort(list);
        
                // 打印集合
                System.out.println("排序之后的集合:"+list);
            }
        }
        
      • 案例2

        • Student

          public class Student implements Comparable<Student>{
              String name;
              int age;
          
              public Student(String name, int age) {
                  this.name = name;
                  this.age = age;
              }
          
              @Override
              public String toString() {
                  return "Student{" +
                          "name='" + name + '\'' +
                          ", age=" + age +
                          '}';
              }
          
              @Override
              public int compareTo(Student o) {
                  // 指定默认规则排序
                  // 升序: 前减后
                  // 降序: 后减前
                  // 前: this  后: 参数o
                  //return this.age - o.age;
                  return o.age - this.age;
              }
          }
          
          
        • 测试类

          public class Test3_sort默认规则排序 {
              public static void main(String[] args) {
                  // public static <T> void sort(List<T> list):将集合中元素按照默认规则排序。
                  // 创建List集合对象,限制集合元素类型为Student
                  List<Student> list = new ArrayList<>();
          
                  // 往集合中添加元素
                  list.add(new Student("张三1",18));
                  list.add(new Student("张三2",38));
                  list.add(new Student("张三3",28));
                  list.add(new Student("张三4",48));
                  list.add(new Student("张三5",58));
          
                  // 打印集合
                  System.out.println("排序之前的集合:"+list);
          
                  // 使用默认规则进行排序
                  Collections.sort(list);
          
                  // 打印集合
                  System.out.println("排序之后的集合:"+list);
              }
          }
          
          
    • public <T> void sort(List<T> list,Comparator<? super T> comp):将集合中元素按照指定规则排序

      • 案例1

        public class Test4_sort指定规则排序 {
            public static void main(String[] args) {
                // public <T> void sort(List<T> list,Comparator<? super T> comp):将集合中元素按照指定规则排序
                // 创建List集合对象,限制集合元素类型为Integer
                List<Integer> list = new ArrayList<>();
        
                // 往集合中添加元素
                list.add(500);
                list.add(200);
                list.add(400);
                list.add(100);
                list.add(300);
        
                // 打印集合
                System.out.println("排序之前的集合:"+list);
        
                // 使用指定规则进行排序
                Collections.sort(list, new Comparator<Integer>() {
                    @Override
                    public int compare(Integer o1, Integer o2) {
                        // 指定排序规则
                        // 升序: 前减后
                        // 降序: 后减前
                        // 前: 第一个参数o1, 后:第二个参数o2
                        return o1 - o2;
                    }
                });
        
                // 打印集合
                System.out.println("排序之后的集合:"+list);// [100, 200, 300, 400, 500]
        
                // 使用指定规则进行排序
                Collections.sort(list, new Comparator<Integer>() {
                    @Override
                    public int compare(Integer o1, Integer o2) {
                        // 指定排序规则
                        // 升序: 前减后
                        // 降序: 后减前
                        // 前: 第一个参数o1, 后:第二个参数o2
                        return o2 - o1;
                    }
                });
        
                // 打印集合
                System.out.println("排序之后的集合:"+list);// [500, 400, 300, 200, 100]
            }
        }
        
    • 案例2

        public class Test5_sort指定规则排序 {
            public static void main(String[] args) {
                // public <T> void sort(List<T> list,Comparator<? super T> comp):将集合中元素按照指定规则排序
                // 创建List集合对象,限制集合元素类型为Student
                List<Student> list = new ArrayList<>();
        
                // 往集合中添加元素
                list.add(new Student("张三1",18));
                list.add(new Student("张三2",38));
                list.add(new Student("张三3",28));
                list.add(new Student("张三4",48));
                list.add(new Student("张三5",58));
        
                // 打印集合
                System.out.println("排序之前的集合:"+list);
        
                // 使用指定规则进行排序--->按照年龄升序
               Collections.sort(list, new Comparator<Student>() {
                   @Override
                   public int compare(Student o1, Student o2) {
                       // 升序: 前减后
                       return o1.age - o2.age;
                   }
               });
                // 打印集合
                System.out.println("排序之后的集合:"+list);
        
                // 使用指定规则进行排序--->按照年龄降序
                Collections.sort(list, new Comparator<Student>() {
                    @Override
                    public int compare(Student o1, Student o2) {
                        // 降序: 后减前
                        return o2.age - o1.age;
                    }
                });
                // 打印集合
                System.out.println("排序之后的集合:"+list);
            }
        }
      

      案例3

      public static void main(String[] args) {
          //对字符串排序
              List<String> list = new ArrayList<>();
              Collections.addAll(list,"aaa","ccc","add","zzz");
              System.out.println(list);
      
              Collections.sort(list);//字符串排序按字典规则升序
              System.out.println(list);
              Collections.sort(list, new Comparator<String>() {
                  @Override
                  public int compare(String o1, String o2) {
                      //字符串不可以减
                      return o2.compareTo(o1);//降序
                  }
              });
              System.out.println(list);
      
          }
      

1.2 可变参数

  • 概述: 在JDK1.5之后,定义了可变参数,用来表示一个方法需要接受的多个同类型参数。

  • 格式:

    修饰符 返回值类型 方法名(数据类型... 变量名){
        
    }
    
  • 案例:

    public class Test {
        public static void main(String[] args) {
            // 调用method1方法
            method1();
            System.out.println("--------");
    
            method1(10);
            System.out.println("--------");
    
            method1(10,20);
            System.out.println("--------");
    
            method1(10,20,30);
            System.out.println("--------");
    
            method1(10,20,30,40);
            System.out.println("--------");
    
            int[] arr = {10,20,30,40,50};
            method1(arr);
            // ...
        }
    
        // 定义一个方法可以接收多个int类型的参数
        public static void method1(int... nums){
            // 使用: 把可变参数当成数组来使用
            for (int i = 0; i < nums.length; i++) {
                System.out.println(nums[i]);
            }
        }
    }
    
  • 注意事项:

    • 可变参数一定是定义在方法的形参位置

    • 一个方法只能有一个可变参数

    • 如果方法中有多个参数,可变参数要放到最后。

      public class Test_注意事项 {
          public static void main(String[] args) {
            method2("hello",10,20,30);
          }
          
          // - 一个方法只能有一个可变参数
         /* public static void method1(int... nums,String... strs){// 编译报错
              
          }*/
         
          // - 如果方法中有多个参数,可变参数要放到最后。
          /*public static void method2(int... nums,String str){// 编译报错
              
          }*/
      
          public static void method2(String str,int... nums){
      
          }
      }
      
  • 应用场景:

    • Collections工具类中的批量添加元素的静态方法:

    • static <T> boolean addAll(Collection<T> c, T... elements) :往集合中添加一些元素。

      public static void main(String[] args) {
              // 可变参数应用场景:
              // static <T> boolean addAll(Collection<T> c, T... elements)  :往集合中添加一些元素。
              ArrayList<String> list = new ArrayList<>();
              Collections.addAll(list, "2", "A", "K", "Q", "J", "10", "9", "8", "7", "6", "5", "4", "3");
              System.out.println("list:" + list);
          }
      

第二章 Set接口

2.1 Set接口介绍

  • 概述: java.util.Set接口继承自Collection接口,是单列集合的一个重要分支。
  • 特点: 元素没有索引,元素唯一(不重复)
  • 注意事项:
    • Set集合元素没有索引,只能使用迭代器或者增强for循环进行遍历元素
    • Set集合没有特殊的方法,都是使用Collection的方法
    • Set接口就是Set集合,但凡实现了Set接口的类也叫做Set集合
  • 常用实现类:
    • HashSet类: 元素没有索引,元素唯一,元素存取顺序不一致
      • 存储结构采用的是哈希表结构,由哈希表保证元素唯一
    • LinkedHashSet类:元素没有索引,元素唯一,元素存取顺序一致
      • 存储结构采用的是哈希表+链表结构,由哈希表保证元素唯一,由链表保证元素存取顺序一致
    • TreeSet类: 元素没有索引,元素唯一,可以对元素进行排序
      • 存储结构采用的是红黑树结构,由红黑树保证元素唯一,由比较器来对元素进行排序

2.2 HashSet集合

  • 概述:java.util.HashSet是Set接口的一个实现类, 底层的实现其实是一个java.util.HashMap支持

  • 特点: 元素没有索引,元素唯一,元素存取顺序不一致

  • 案例演示:

    public class Test {
        public static void main(String[] args) {
             // 创建HashSet集合,限制集合元素的类型String
            HashSet<String> set = new HashSet<>();
    
            // 添加元素
            set.add("nba");
            set.add("bac");
            set.add("cba");
            set.add("abc");
            set.add("nba");
    
            // 打印集合
            System.out.println("set:" + set);//set:[cba, abc, bac, nba]
            System.out.println(set);//同上,读取顺序不一致仅限第一次读取
            System.out.println(set);
        }
    

}




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

##### 哈希表底层结构

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

简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的

##### HashSet保证元素唯一原理

```java
HashSet 底层是HashMap 初始容量为 长度为16的数组,加载因子是0.75(当存储到16*0.75时扩容 2倍)
保证元素唯一的原理: 依靠hashCode和equals方法
1.存储元素的时候,会调用该元素的hashCode方法计算该元素的哈希值
2.判断该哈希值对应的位置上是否有元素 (位置计算方式  哈希值%数组长度 结果在0到数组长度-1范围内)
3.如果该哈希值对应的位置上没有元素,就直接存储
4.如果该哈希值对应的位置上有元素,说明产生了哈希冲突
5.产生了哈希冲突就会调用该元素的equals方法与该哈希值对应的位置上的所有元素进行一一比较 (链表)
5.1 如果比较完之后,没有一个元素与该元素相等,就直接存储 (以链表的方式存在同一个数组空间里如索引为5的空间,如果链表长度大于八,就会把链表转成红黑树)
5.2 如果比较完之后,有任意一个元素与该元素相等,就不存储
  
注意:
1.hashCode和equals方法属于Object类的
2.任意类的对象都拥有hashCode和equals方法
3.Object类中的hashCode方法是主要根据地址值计算哈希值
4.Object类中的equals方法是比较地址值

扩展 HashSet的源码分析

3.4.1 HashSet的成员属性及构造方法

public class HashSet<E> extends AbstractSet<E>
    					implements Set<E>, Cloneable, java.io.Serializable{
    
	//内部一个HashMap——HashSet内部实际上是用HashMap实现的
    private transient HashMap<E,Object> map;
    // 用于做map的值
    private static final Object PRESENT = new Object();
    /**
     * 构造一个新的HashSet,
     * 内部实际上是构造了一个HashMap
     */
    public HashSet() {
        map = new HashMap<>();
    }  
}
  • 通过构造方法可以看出,HashSet构造时,实际上是构造一个HashMap

3.4.2 HashSet的add方法源码解析

public class HashSet{
    //......
    public boolean add(E e) {
       return map.put(e, PRESENT)==null;//内部实际上添加到map中,键:要添加的对象,值:Object对象
    }
    //......
}

3.4.3 HashMap的put方法源码解析

public class HashMap{
    //......
    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    //......
    static final int hash(Object key) {//根据参数,产生一个哈希值
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
    //......
    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; //临时变量,存储"哈希表"——由此可见,哈希表是一个Node[]数组
        Node<K,V> p;//临时变量,用于存储从"哈希表"中获取的Node
        int n, i;//n存储哈希表长度;i存储哈希表索引
        
        if ((tab = table) == null || (n = tab.length) == 0)//判断当前是否还没有生成哈希表
            n = (tab = resize()).length;//resize()方法用于生成一个哈希表,默认长度:16,赋给n
        if ((p = tab[i = (n - 1) & hash]) == null)//(n-1)&hash等效于hash % n,转换为数组索引
            tab[i] = newNode(hash, key, value, null);//此位置没有元素,直接存储
        else {//否则此位置已经有元素了
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))//判断哈希值和equals
                e = p;//将哈希表中的元素存储为e
            else if (p instanceof TreeNode)//判断是否为"树"结构
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {//排除以上两种情况,将其存为新的Node节点
                for (int binCount = 0; ; ++binCount) {//遍历链表
                    if ((e = p.next) == null) {//找到最后一个节点
                        p.next = newNode(hash, key, value, null);//产生一个新节点,赋值到链表
                        if (binCount >= TREEIFY_THRESHOLD - 1) //判断链表长度是否大于了8
                            treeifyBin(tab, hash);//树形化
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))//跟当前变量的元素比较,如果hashCode相同,equals也相同
                        break;//结束循环
                    p = e;//将p设为当前遍历的Node节点
                }
            }
            if (e != null) { // 如果存在此键
                V oldValue = e.value;//取出value
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;//设置为新value
                afterNodeAccess(e);//空方法,什么都不做
                return oldValue;//返回旧值
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
}

2.4 HashSet存储自定义类型元素

  • 需求: 使用HashSet集合存储学生对象

  • 结论: HashSet存储自定义类型元素,要求该元素所属的类要重写hashCode和equals方法

  • 实现:

    public class Student {
        private String name;
        private int age;
    
        public Student(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    
        @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 class Test {
        public static void main(String[] args) {
            /*
                开发中,往往认为2个对象的所有属性值相同,就认为是2个相同的对象
                所以HashSet集合存储自定义类型的元素需要重写hashCode和equals方法
                因为你不重写,使用的是Object类的hashCode和equals方法
             */
            // 需求:使用HashSet集合存储学生对象
            // 1.创建HashSet集合对象,限制集合元素类型为Student
            HashSet<Student> set = new HashSet<>();
    
            // 2.创建学生对象
            Student stu1 = new Student("张三",18);
            Student stu2 = new Student("李四",28);
            Student stu3 = new Student("王五",38);
            Student stu4 = new Student("赵六",48);
            Student stu5 = new Student("张三",18);
    
            // 3.把学生对象添加到集合中
            set.add(stu1);
            set.add(stu2);
            set.add(stu3);
            set.add(stu4);
            set.add(stu5);
    
            // 4.循环遍历集合
            for (Student stu : set) {
                System.out.println(stu);
            }
        }
    }
    

2.5 LinkedHashSet

  • 概述: java.util.LinkedHashSet 是HashSet的一个子类,底层采用链表+哈希表

  • 特点:

    • 元素没有索引,元素唯一,元素存取顺序一致
    • 存储结构采用的是哈希表+链表结构,由哈希表保证元素唯一,由链表保证元素存取顺序一致
    • 如果集合中存储的是自定义类型的元素,那么就要求该元素所属的类要重写hashCode和equals方法
  • 案例:

    public class Test {
        public static void main(String[] args) {
            // 创建LinkedHashSet集合,限制集合元素的类型为String
            LinkedHashSet<String> set = new LinkedHashSet<>();
    
            // 往集合中添加元素
            set.add("nba");
            set.add("cba");
            set.add("bac");
            set.add("abc");
            set.add("nba");
    
            // 打印集合
            System.out.println("set:" + set);
    
            System.out.println("========");
    
            // 1.创建LinkedHashSet集合对象,限制集合元素类型为Student
            LinkedHashSet<Student> set2 = new LinkedHashSet<>();
    
            // 2.创建学生对象
            Student stu1 = new Student("张三",18);
            Student stu2 = new Student("李四",28);
            Student stu3 = new Student("王五",38);
            Student stu4 = new Student("赵六",48);
            Student stu5 = new Student("张三",18);
    
            // 3.把学生对象添加到集合中
            set2.add(stu1);
            set2.add(stu2);
            set2.add(stu3);
            set2.add(stu4);
            set2.add(stu5);
    
            // 4.循环遍历集合
            for (Student stu : set2) {
                System.out.println(stu);
            }
        }
    }
    

2.6 TreeSet集合

  • 概述: TreeSet集合是Set接口的一个实现类,底层依赖于TreeMap,是一种基于红黑树的实现

  • 特点:

    • 元素没有索引,元素唯一,可以对元素进行排序
    • 存储结构采用的是红黑树结构,由红黑树保证元素唯一,由比较器来对元素进行排序
  • 排序:

    • 默认规则排序: public TreeSet(); 创建TreeSet集合对象,该集合对象使用默认规则对元素进行排序

      • 默认规则是在元素所属的类中指定的

      • 要求集合元素所属的类必须实现Comparable接口,重写compareTo方法,在compareTo方法中指定排序规则

      • 案例1:

        public class Test1 {
            public static void main(String[] args) {
                // 按照默认规则进行排序
                // 创建TreeSet集合,限制集合元素的类型为Integer类型
                TreeSet<Integer> set1 = new TreeSet<>();
                // 往集合中添加元素
                set1.add(500);
                set1.add(200);
                set1.add(400);
                set1.add(100);
                set1.add(300);
                // 打印集合
                System.out.println("set1:"+set1);// set1:[100, 200, 300, 400, 500]
            }
        }
        
      • 案例2:

         */
        public class Student implements Comparable<Student>{
            int age;
            String name;
        
            public Student (String name,int age) {
                this.age = age;
                this.name = name;
            }
        
            @Override
            public String toString() {
                return "Student{" +
                        "age=" + age +
                        ", name='" + name + '\'' +
                        '}';
            }
        
            @Override
            public int compareTo(Student o) {
                // 升序: 前减后
                // 降序: 后减前
                // 前: this  后:参数o
                //return this.age - o.age;
                return o.age - this.age;
            }
        }
        
        
        public class Test1 {
            public static void main(String[] args) {
                // 按照默认规则进行排序
                // 创建TreeSet集合,限制集合元素的类型为Student类型
                TreeSet<Student> set2 = new TreeSet<>();
        
                // 往集合中添加元素
                set2.add(new Student("张三1",18));
                set2.add(new Student("张三2",38));
                set2.add(new Student("张三3",28));
                set2.add(new Student("张三4",58));
                set2.add(new Student("张三5",48));
        
                // 循环遍历
                for (Student stu : set2) {
                    System.out.println(stu);
                }
            }
        }
        
        
    • 指定规则排序: public TreeSet(Comparator<? super E> comparator); 创建TreeSet集合对象,该集合对象使用指定规则对元素进行排序

      public class Test2_指定规则排序 {
          public static void main(String[] args) {
              // 指定规则排序
              // 创建TreeSet集合,限制集合元素的类型为Integer类型
              TreeSet<Integer> set1 = new TreeSet<>(new Comparator<Integer>() {
                  @Override
                  public int compare(Integer o1, Integer o2) {
                      // 升序: 前减后
                      // 降序: 后减前
                      // 前: 第一个参数o1,后:第二个参数: o2
                      return o2 - o1;
                  }
              });
      
              // 往集合中添加元素
              set1.add(500);
              set1.add(200);
              set1.add(400);
              set1.add(100);
              set1.add(300);
              // 打印集合
              System.out.println("set1:"+set1);// set1:[500, 400, 300, 200, 100]
      
              // 指定规则排序
              // 创建TreeSet集合,限制集合元素的类型为Student类型
              TreeSet<Student> set2 = new TreeSet<>(new Comparator<Student>() {
                  @Override
                  public int compare(Student o1, Student o2) {
                      return o1.age - o2.age;
                  }
              });
      
              // 往集合中添加元素
              set2.add(new Student("张三1",18));
              set2.add(new Student("张三2",38));
              set2.add(new Student("张三3",28));
              set2.add(new Student("张三4",58));
              set2.add(new Student("张三5",48));
      
              // 循环遍历
              for (Student stu : set2) {
                  System.out.println(stu);
              }
          }
      }
      
      

第三章 Map集合

3.1 Map概述

  • 概述: java.util.Map双列集合的顶层接口,用来存储具备映射关系对象的集合接口定义

  • 单列集合: 以单个单个元素进行存储数据

  • 双列集合: 以键值对的形式进行存储数据

  • 特点:

    • Map<K,V>,K用来限制键的类型,V用来限制值的类型
    • Map集合以键值对的形式来存储数据
    • Map集合的键是唯一的,值可以重复,但键如果重复,值就会覆盖
    • Map集合是根据键来找值
  • 实现类:

    实现类都有的特点: 键是唯一的,值可以重复,但键如果重复,值就会覆盖
    HashMap: 键值对存取顺序不一致
        	 底层哈希表结构,由哈希表保证键唯一(保证键唯一需要重写hashcode和equales方法)
    LinkedHashMap:键值对存取顺序一致
        	 底层哈希表+链表结构,由哈希表保证键唯一,由链表保证存取顺序一致
    TreeMap: 可以对键进行排序,从而实现键值对排序
        	 底层红黑树结构,由红黑树保证键唯一,由比较器对象对元素进行排序
    

3.2 Map的常用方法

  • public V put(K key, V value): 把指定的键与指定的值添加到Map集合中。
  • public V remove(Object key): 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。
  • public V get(Object key) 根据指定的键,在Map集合中获取对应的值。
  • public boolean containsKey(Object key):判断该集合中是否有此键
  • public boolean containsValue(Object value):判断该集合中是否有此值
  • public Set<K> keySet(): 获取Map集合中所有的键,存储到Set集合中。
  • public Set<Map.Entry<K,V>> entrySet(): 获取到Map集合中所有的 键值对对象 的集合(Set集合)。

3.3 Map的遍历

  • 方式1:键找值方式

    • 获取Map集合的所有键—>keySet()方法

    • 循环遍历所有的键

    • 根据键找值—>get(K k)方法

    • 案例:

      public class Test1_键找值的方式 {
          public static void main(String[] args) {
      
              // 创建Map集合,限制键的类型String,值的类型String
              Map<String, String> map = new HashMap<>();
      
              // 添加键值对
              map.put("1", "张三");
              map.put("2", "李四");
              map.put("3", "王五");
              map.put("4", "王五");
      
      
              // - 获取Map集合的所有键
              Set<String> keys = map.keySet();
      
              // - 循环遍历所有的键
              for (String key : keys) {
                  // - 根据键找值
                  String value = map.get(key);
                  System.out.println("key:"+key+",value:"+value);
              }
          }
      }
      
  • 方式2:键值对对象方式

    • 获取所有的键值对对象---->entrySet()方法

    • 循环遍历所有的键值对对象

    • 使用键值对对象获取键和值—>使用Entry接口的方法

    • Entry<K,V>接口:

      • Entry接口是Map接口的成员内部接口,使用的方式是Map.Entry<K,V>
      • Entry表示键值对对象,也就是说Entry是用来封装键值对的
      • Entry接口里面的常用方法:
        • K getKey(); 获取键值对对象封装的键
        • V getValue(); 获取键值对对象封装的值
    • 案例:

      public class Test2_键值对对象的方式 {
          public static void main(String[] args) {
      
              // 创建Map集合,限制键的类型String,值的类型String
              Map<String, String> map = new HashMap<>();
      
              // 添加键值对
              map.put("1", "张三");
              map.put("2", "李四");
              map.put("3", "王五");
              map.put("4", "王五");
      
              // - 获取所有的键值对对象---->entrySet()方法
              Set<Map.Entry<String, String>> set = map.entrySet();
      
              // - 循环遍历所有的键值对对象
              for (Map.Entry<String, String> entry : set) {
                  // - 使用键值对对象获取键和值--->使用Entry接口的方法
                  String key = entry.getKey();
                  String value = entry.getValue();
                  System.out.println("key:"+key+",value:"+value);
              }
          }
      }
      

3.5 HashMap存储自定义类型

  • 结论: 如果键是自定义类型的元素,要保证键唯一,那么该键所属的类需要重写hashCode和equals方法

练习:每位学生(姓名,年龄)都有自己的家庭住址。那么,既然有对应关系,则将学生对象和家庭住址存储到map集合中。学生作为键, 家庭住址作为值。

注意,学生姓名相同并且年龄相同视为同一名学生。

实现:

public class Student {
    String name;
    int age;

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

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

    @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 class Test {
    public static void main(String[] args) {
        // 创建HashMap集合,限制键的类型为Student,值的类型为String
        HashMap<Student,String> map = new HashMap<>();

        // 创建学生对象
        Student stu1 = new Student("张三",18);
        Student stu2 = new Student("李四",28);
        Student stu3 = new Student("王五",38);
        Student stu4 = new Student("赵六",48);
        Student stu5 = new Student("张三",18);

        // 添加学生对象到集合中
        map.put(stu1,"深圳");
        map.put(stu2,"北京");
        map.put(stu3,"上海");
        map.put(stu4,"广州");
        map.put(stu5,"厦门");

        // 遍历map集合,打印输出
        Set<Student> keys = map.keySet();
        for (Student key : keys) {
            String value = map.get(key);
            System.out.println(key+" = "+value);
        }
    }
}

3.6 LinkedHashMap

  • 概述:LinkedHashMap是HashMap子类,底层由链表和哈希表组合,由哈希表保证键唯一,由链表保证键值对存取顺序一致

  • 结论: 如果键是自定义类型的元素,要保证键唯一,那么该键所属的类需要重写hashCode和equals方法

  • 代码:

    public class Test {
        public static void main(String[] args) {
            // 创建LinkedHashMap集合,限制键的类型为Student,值的类型为String
            LinkedHashMap<Student,String> map = new LinkedHashMap<>();
    
            // 创建学生对象
            Student stu1 = new Student("张三",18);
            Student stu2 = new Student("李四",28);
            Student stu3 = new Student("王五",38);
            Student stu4 = new Student("赵六",48);
            Student stu5 = new Student("张三",18);
    
            // 添加学生对象到集合中
            map.put(stu1,"深圳");
            map.put(stu2,"北京");
            map.put(stu3,"上海");
            map.put(stu4,"广州");
            map.put(stu5,"厦门");
    
            // 遍历map集合,打印输出
            Set<Student> keys = map.keySet();
            for (Student key : keys) {
                String value = map.get(key);
                System.out.println(key+" = "+value);
            }
        }
    }
    

3.7 TreeMap集合

  • 概述: TreeMap是Map实现类,底层由红黑树实现,可以对元素的键进行排序

  • 构造方法:

    • public TreeMap();创建TreeMap集合对象,使用默认规则对键进行排序

      public class Test1_默认规则排序 {
          public static void main(String[] args) {
              // 创建TreeMap集合,限制键的类型为Integer,值的类型为String
              TreeMap<Integer, String> map = new TreeMap<>();
      
              // 添加键值对
              map.put(500, "深圳");
              map.put(100, "北京");
              map.put(400, "广州");
              map.put(200, "上海");
              map.put(300, "杭州");
      
              // 遍历map集合,打印输出
              Set<Integer> keys = map.keySet();
              for (Integer key : keys) {
                  String value = map.get(key);
                  System.out.println(key + " = " + value);
              }
              
              // 结论: 如果键是自定义类型的元素,那么就要求键所属的类需要实现Comparable接口,重写compareTo方法,指定默认排序规则
          }
      }
      
      
    • public TreeMap(Comparator<? super K> comparator);创建TreeMap集合对象,使用指定规则对键进行排序

      public class Test2_指定规则排序 {
          public static void main(String[] args) {
              // 指定规则排序: 降序
              // 创建TreeMap集合,限制键的类型为Integer,值的类型为String
              TreeMap<Integer, String> map = new TreeMap<>(new Comparator<Integer>() {
                  @Override
                  public int compare(Integer o1, Integer o2) {
                      return o2 - o1;
                  }
              });
      
              // 添加键值对
              map.put(500, "深圳");
              map.put(100, "北京");
              map.put(400, "广州");
              map.put(200, "上海");
              map.put(300, "杭州");
      
              // 遍历map集合,打印输出
              Set<Integer> keys = map.keySet();
              for (Integer key : keys) {
                  String value = map.get(key);
                  System.out.println(key + " = " + value);
              }  
          }
      }
      

3.8 Map集合练习

需求

  • 输入一个字符串,统计该字符串中每个字符出现次数。

分析

  • 思路: 键唯一,如果键重复了,值就会覆盖

    1.创建Map集合,限制键的类型为Character,值的类型为Integer
    2.循环遍历字符串
    3.在循环中,获取遍历出来的字符
    4.在循环中,判断遍历出来的字符在Map集合中是否有相同的键
    5.如果没有相同的键,那么遍历出来的字符作为键,值为1
    6.如果有相同的键,那么就获取该键对应的值,然后+1,作为新的值,存储进去(覆盖了之前的值)
    7.打印结果

实现

public class Test {
    public static void main(String[] args) {
        //  需求: 输入一个字符串,统计该字符串中每个字符出现次数。
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入一个字符串:");
        String str = sc.nextLine();

        // 1.创建Map集合,限制键的类型为Character,值的类型为Integer
        HashMap<Character, Integer> map = new HashMap<>();

        // 2.循环遍历字符串
        for (int i = 0; i < str.length(); i++) {
            // 3.在循环中,获取遍历出来的字符
            char c = str.charAt(i);
            // 4.在循环中,判断遍历出来的字符在Map集合中是否有相同的键
            if (map.containsKey(c)) {
                // 6.如果有相同的键,那么就获取该键对应的值,然后+1,作为新的值,存储进去(覆盖了之前的值)
                Integer value = map.get(c);
                value++;
                map.put(c, value);
            } else {
                // 5.如果没有相同的键,那么遍历出来的字符作为键,值为1
                map.put(c, 1);
            }
        }
        // 7.打印结果
        System.out.println(map);
    }
}

第四章 集合的嵌套

  • 总述:任何集合内部都可以存储其它任何集合

4.1 集合的嵌套

List嵌套List

public class Test1_List嵌套List {
    public static void main(String[] args) {
        // List嵌套List: List集合的元素类型是List
        // 创建List集合,并存储元素
        List<String> list1 = new ArrayList<>();
        list1.add("张三");
        list1.add("李四");

        // 创建List集合,并存储元素
        List<String> list2 = new ArrayList<>();
        list2.add("王五");
        list2.add("赵六");

        // 需求: 定义一个List集合,存储以上2个集合
        List<List<String>>  list = new ArrayList<>();
        list.add(list1);
        list.add(list2);

        // 循环遍历取出所有的姓名
        for (List<String> nameList : list) {
            for (String name : nameList) {
                System.out.println("name:"+name);
            }
        }
    }
}

List嵌套Map

public class Test2_List嵌套Map {
    public static void main(String[] args) {
        // List嵌套Map: List集合元素的类型是Map
        // 创建Map集合,添加键值对
        HashMap<String,String> map1 = new HashMap<>();
        map1.put("001","德玛西亚");
        map1.put("002","马尔扎哈");

        // 创建Map集合,添加键值对
        HashMap<String,String> map2 = new HashMap<>();
        map2.put("001","无畏先锋");
        map2.put("002","黑色玫瑰");

        // 创建List集合,存储以上2个map集合
        List<HashMap<String,String>> list = new ArrayList<>();
        // 添加元素
        list.add(map1);
        list.add(map2);

        // 取出元素的键值对
        // 循环遍历list集合,得到map集合对象
        for (HashMap<String, String> map : list) {
            // System.out.println(map);
            // 循环遍历map集合
            Set<String> keys = map.keySet();
            for (String key : keys) {
                String value = map.get(key);
                System.out.println(key +" = " + value);
            }
        }
    }
}

Map嵌套Map

public class Test3_Map嵌套Map {
    public static void main(String[] args) {
        // Map嵌套Map: Map集合中存储Map集合
        // 创建Map集合,添加键值对
        HashMap<String, String> map1 = new HashMap<>();
        map1.put("001", "德玛西亚");
        map1.put("002", "马尔扎哈");

        // 创建Map集合,添加键值对
        HashMap<String, String> map2 = new HashMap<>();
        map2.put("001", "无畏先锋");
        map2.put("002", "黑色玫瑰");

        // 创建Map集合,存储以上2个Map集合
        Map<String, HashMap<String, String>> map = new HashMap<>();
        map.put("1", map1);
        map.put("2", map2);

        // 获取所有的键值对
        // 获取map集合所有的键
        Set<String> keys = map.keySet();
        // 循环遍历所有的键
        for (String key : keys) {
            // 根据键找值
            HashMap<String, String> valueMap = (HashMap<String, String>) map.get(key);// map1,map2
            // 获取valueMap的所有键
            Set<String> keys2 = valueMap.keySet();
            for (String key2 : keys2) {
                String value2 = valueMap.get(key2);
                System.out.println(key2 + " = " + value2);
            }
        }
    }
}

第五章 模拟斗地主洗牌发牌

需求

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

具体规则:

  1. 组装54张扑克牌
  2. 54张牌顺序打乱
  3. 三个玩家参与游戏,三人交替摸牌,每人17张牌,最后三张留作底牌。
  4. 查看三人各自手中的牌(按照牌的大小排序)、底牌

规则:手中扑克牌从大到小的摆放顺序:大王,小王,2,A,K,Q,J,10,9,8,7,6,5,4,3

分析

实现

public class Test {
    public static void main(String[] args) {
        // 造牌:
        // 1.创建Map集合,限制键的类型为Integer,值的类型为String
        HashMap<Integer, String> pokerBox = new HashMap<>();

        // 2.创建花色集合,存储4个花色
        ArrayList<String> colors = new ArrayList<>();
        colors.add("♥");
        colors.add("♠");
        colors.add("♣");
        colors.add("♦");

        // 3.创建牌面值集合,存储13个牌面值
        ArrayList<String> numbers = new ArrayList<>();
        Collections.addAll(numbers, "2", "A", "K", "Q", "J", "10", "9", "8", "7", "6", "5", "4", "3");

        // 4.定义一个int类型的标记,初始值为0
        int mark = 0;

        // 5.标记作为键,大王作为值,存储到map集合中
        // 6.标记+1
        pokerBox.put(mark++, "大王");

        // 7.标记作为键,小王作为值,存储到map集合中
        // 8.标记+1
        pokerBox.put(mark++, "小王");

        // 9.牌面值集合和花色集合循环嵌套,生成54张牌,添加到map集合中
        for (String number : numbers) {
            for (String color : colors) {
                String pai = color + number;
                pokerBox.put(mark++, pai);
            }
        }
        System.out.println("pokerBox:" + pokerBox);
        System.out.println("pokerBox:" + pokerBox.size());

        // 洗牌:
        // 1.获取Map集合的所有键(标记)-->Set集合
        Set<Integer> keys = pokerBox.keySet();

        // 2.把Set集合转换为List集合
        ArrayList<Integer> marks = new ArrayList<>();
        marks.addAll(keys);

        // 3.打乱所有的键的顺序
        Collections.shuffle(marks);
        System.out.println("marks:" + marks);
        System.out.println("marks:" + marks.size());

        // 发牌:
        // 1.创建4个集合,分别表示玩家1,玩家2,玩家3,底牌的牌的标记
        ArrayList<Integer> play1Mark = new ArrayList<>();
        ArrayList<Integer> play2Mark = new ArrayList<>();
        ArrayList<Integer> play3Mark = new ArrayList<>();
        ArrayList<Integer> diPaiMark = new ArrayList<>();

        // 2.循环遍历打乱顺序之后的所有标记
        for (int i = 0; i < marks.size(); i++) {
            // 取出标记
            Integer mk = marks.get(i);

            // 3.在循环中,判断遍历出来的标记
            if (i >= 51) {
                // 4.如果该标记的索引>=51, 该标记给底牌
                diPaiMark.add(mk);
            } else if (i % 3 == 0) {
                // 4.如果该标记的索引%3==0,该标记给玩家1
                play1Mark.add(mk);
            } else if (i % 3 == 1) {
                // 4.如果该标记的索引%3==1,该标记给玩家2
                play2Mark.add(mk);
            } else if (i % 3 == 2) {
                // 4.如果该标记的索引%3==2,该标记给玩家3
                play3Mark.add(mk);
            }

        }
        System.out.println("mark1:"+play1Mark);
        System.out.println("mark2:"+play2Mark);
        System.out.println("mark3:"+play3Mark);
        System.out.println("mark4:"+diPaiMark);
        // 5.对标记进行升序排序
        Collections.sort(play1Mark);
        Collections.sort(play2Mark);
        Collections.sort(play3Mark);
        Collections.sort(diPaiMark);

        System.out.println("mark1:"+play1Mark);
        System.out.println("mark2:"+play2Mark);
        System.out.println("mark3:"+play3Mark);
        System.out.println("mark4:"+diPaiMark);

        // 6.创建4个集合,分别表示玩家1,玩家2,玩家3,底牌的牌
        ArrayList<String> play1 = new ArrayList<>();
        ArrayList<String> play2 = new ArrayList<>();
        ArrayList<String> play3 = new ArrayList<>();
        ArrayList<String> diPai = new ArrayList<>();

        // 7.根据排序之后的标记去取牌
        for (Integer mk : play1Mark) {
            // 根据标记取牌
            String pai = pokerBox.get(mk);
            play1.add(pai);
        }

        for (Integer mk : play2Mark) {
            // 根据标记取牌
            String pai = pokerBox.get(mk);
            play2.add(pai);
        }

        for (Integer mk : play3Mark) {
            // 根据标记取牌
            String pai = pokerBox.get(mk);
            play3.add(pai);
        }

        for (Integer mk : diPaiMark) {
            // 根据标记取牌
            String pai = pokerBox.get(mk);
            diPai.add(pai);
        }

        // 8.展示牌
        System.out.println("玩家1:"+play1+",数量:"+play1.size());
        System.out.println("玩家2:"+play2+",数量:"+play2.size());
        System.out.println("玩家3:"+play3+",数量:"+play3.size());
        System.out.println("底牌:"+diPai);
    }
}

总结

	1.Collections工具类的三个方法
    2.可变参数
    3.所有Set集合的特点
    4.HashSet\HashMap集合保证元素唯一的原理
    5.所有Map集合的特点
    6.Map集合的常用方法
    7.TreeMap的使用
    8.HashSet,HashMap存储自定义类型的元素或者键
        
- 能够使用集合工具类
    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> ):将集合中元素按照指定规则排序
    public static <T> boolean addAll(Collection<T> c, T... elements)  :往集合中添加一些元素。

- 能够使用Comparator比较器进行排序
- 能够使用可变参数
   格式:参数类型... 形参名
   注意事项:
      一个方法只能有一个可变参数
      如果方法中有多个参数,可变参数要放到最后。

- 能够说出Set集合的特点
   元素唯一,元素无索引
          
- 能够说出哈希表的特点
   哈希表可以保证元素唯一,哈希表底层由数组+链表+红黑树组成
          
- 使用HashSet集合存储自定义元素
   该元素所属的类必须重写hashCode和equals方法
          
- 能够说出Map集合特点
   键唯一,值可以重复,键重复了,值就可以覆盖,根据键找值
          
- 使用Map集合添加方法保存数据
    public V put(K key, V value):  把指定的键与指定的值添加到Map集合中。
    public V remove(Object key): 把指定的键所对应的键值对元素在Map集合中删除,返回被删除元素的值。
    public V get(Object key) 根据指定的键,在Map集合中获取对应的值。
    public Set<K> keySet(): 获取Map集合中所有的键,存储到Set集合中。
    public Set<Map.Entry<K,V>> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)。
    public boolean containKey(Object key):判断该集合中是否有此键。

          
- 使用”键找值”的方式遍历Map集合
    获取所有的键
    循环遍历所有的键
    根据键找值
          
- 使用”键值对”的方式遍历Map集合
    获取所有的键值对对象
    循环遍历所有的键值对对象
    根据键值对对象获取键和获取值
          
- 能够使用HashMap存储自定义键值对的数据
     键所属的类必须重写hashCode和equals方法
上一篇:Jedis


下一篇:Apoll