十、Java集合 ★ ✔(模块18-20)【泛型、通配符、List、Set、TreeSet、自然排序和比较器排序、Collections、可变参数、Map】

news2024/11/15 18:25:26

day05 泛型,数据结构,List,Set

今日目标

1 泛型

1.1 泛型的介绍 ★

  • 泛型是一种类型参数,专门用来保存类型用的
    • 最早接触泛型是在ArrayList,这个E就是所谓的泛型了。使用ArrayList时,只要给E指定某一个类型,里面所有用到泛型的地方都会被指定对应的类型

1.2 使用泛型的好处 ★

  • 不用泛型带来的问题
    • 集合若不指定泛型,默认就是Object。存储的元素类型自动提升为Object类型。获取元素时得到的都是Object,若要调用特有方法需要转型,给我们编程带来麻烦.
  • 使用泛型带来的好处
    • 可以在编译时就对类型做判断,避免不必要的类型转换操作,精简代码,也避免了因为类型转换导致的代码异常
 //泛型没有指定类型,默认就是Object
ArrayList list = new ArrayList();
list.add("Hello");
list.add("World");
list.add(100);
list.add(false);
//集合中的数据就比较混乱,会给获取数据带来麻烦
for (Object obj : list) {
    String str = (String) obj;
    //当遍历到非String类型数据,就会报异常出错
    System.out.println(str + "长度为:" + str.length());
}

1.3 泛型的注意事项

  • 泛型在代码运行时,泛型会被擦除。后面学习反射的时候,可以实现在代码运行的过程中添加其他类型的数据到集合

    • 泛型只在编译时期限定数据的类型 , 在运行时期会被擦除

1.4 自定义泛型类 ★

  • 当一个类定义其属性的时候,不确定具体是什么类型时,就可以使用泛型表示该属性的类型

  • 定义的格式
    • 在类型名后面加上一对尖括号,里面定义泛型。一般使用一个英文大写字母表示,如果有多个泛型使用逗号分隔
    • public class 类名<泛型名>{ … }
    举例 : 
    public class Student<X,Y>{  
        X  xObj; 
    } 
    
  • 泛型的确定
    • 当创建此泛型类是 , 确定泛型类中泛型的具体数据类型
  • 练习
package com.itheima.genericity_demo.genericity_class;

import java.time.Period;

/*
    需求 : 定义一个人类,定义一个属性表示爱好,但是具体爱好是什么不清楚,可能是游泳,乒乓,篮球。
 */
public class GenericityDemo {
    public static void main(String[] args) {
        Person<BasketBall> person = new Person<>();
        person.setHobby(new BasketBall());

        Person<Swim> person2 = new Person<>();
        person2.setHobby(new Swim());

        Person person3 = new Person<>();// 如果没有指定泛型 , 那么默认使用Object数据类型
    }
}

class Person<H> {
    // 定义属性表达爱好
    private H hobby;


    public H getHobby() {
        return hobby;
    }

    public void setHobby(H hobby) {
        this.hobby = hobby;
    }
}

class Swim {
}

class PingPang {
}

class BasketBall {
}

1.3 自定义泛型接口

  • 当定义接口时,内部方法中其参数类型,返回值类型不确定时,就可以使用泛型替代了。

  • 定义泛型接口
    • 在接口后面加一对尖括号 , 尖括号中定义泛型 , 一般使用大写字母表示, 多个泛型用逗号分隔
    • public interface<泛型名> { … }
    • 举例 :
    public interface Collection<E>{
    	public boolean add(E e);
    } 
    
    
  • 泛型的确定
    • 实现类去指定泛型接口的泛型
    • 实现了不去指定泛型接口的泛型 , 进行延续泛型 , 回到泛型类的使用
package com.itheima.genericity_demo.genericity_interface;
/*
    需求:
    模拟一个Collection接口,表示集合,集合操作的数据不确定。
    定义一个接口MyCollection具体表示。

 */
// 泛型接口
public interface MyCollection<E> {
    // 添加功能
    public abstract void add(E e);
    // 删除功能
    public abstract void remove(E e);
}

// 指定泛型的第一种方式 : 让实现类去指定接口的泛型
class MyCollectionImpl1 implements MyCollection<String>{
    @Override
    public void add(String s) {

    }
    @Override
    public void remove(String s) {

    }
}
// 指定泛型的第二种方式 : 实现类不确定泛型,延续泛型,回到泛型类的使用
class MyCollectionImpl2<E> implements MyCollection<E>{

    @Override
    public void add(E a) {

    }

    @Override
    public void remove(E a) {

    }
}

1.4 自定义泛型方法

  • 当定义方法时,方法中参数类型,返回值类型不确定时,就可以使用泛型替代了

  • 泛型方法的定义
    • 可以在方法的返回值类型前定义泛型
    • 格式 : public <泛型名> 返回值类型 方法名(参数列表){ … }
    • 举例 : public void show(T t) { … }
  • 泛型的确定
    • 当调用一个泛型方法 , 传入的参数是什么类型, 那么泛型就会被确定
  • 练习
    package com.itheima.genericity_demo.genericity_method;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    
    public class Test {
        public static void main(String[] args) {
            // Collection集合中 : public <T> T[] toArray(T[] a) : 把集合中的内容存储到一个数组中 , 进行返回
            ArrayList<String> list = new ArrayList<>();
            list.add("abc");
            list.add("ads");
            list.add("qwe");
            String[] array = list.toArray(new String[list.size()]);
            System.out.println(Arrays.toString(array));
        }
    
        // 接收一个集合 , 往集合中添加三个待指定类型的元素
        public static <X> void addElement(ArrayList<X> list, X x1, X x2, X x3) {
            list.add(x1);
            list.add(x2);
            list.add(x3);
        }
    }
    

1.5 通配符 ★

  • 当我们对泛型的类型确定不了,而是表达的可以是任意类型,可以使用泛型通配符给定

    符号就是一个问号:? 表示任意类型,用来给泛型指定的一种通配值。如下

public static void shuffle(List<?> list){
   //…
} 

说明:该方法时来自工具类Collections中的一个方法,用来对存储任意类型数据的List集合进行乱序
  • 泛型通配符结合集合使用

    • 泛型通配符搭配集合使用一般在方法的参数中比较常见。在集合中泛型是不支持多态的,如果为了匹配任意类型,我们就会使用泛型通配符了。
    • 方法中的参数是一个集合,集合如果携带了通配符,要特别注意如下
      • 集合的类型会提升为Object类型
      • 方法中的参数是一个集合,集合如果携带了通配符,那么此集合不能进行添加和修改操作 , 可以删除和获取
    package com.itheima.genericity_demo;
    
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class Demo {
        public static void main(String[] args) {
            ArrayList<String> list = new ArrayList<>();
            list.add("abc");
            list.add("asd");
            list.add("qwe");
            // 方法的参数是一个集合 , 集合的泛型是一个通配符 , 可以接受任意类型元素的集合
            show(list);
        }
    
        public static void show(List<?> list) {
            // 如果集合的泛型是一个通配符 , 那么集合中元素以Object类型存在
            Object o = list.get(0);
    
            // 如果集合的泛型是一个通配符 , 那么此集合不能进行添加和修改操作 , 可以删除和获取
            // list.add(??);
    
            // 删除可以
            list.remove(0);
            // 获取元素可以
            for (Object o1 : list) {
                System.out.println(o1);
            }
        }
    }
    
    package com.itheima.genericity_demo;
    
    import java.util.ArrayList;
    
    /*
        已知存在继承体系:Integer继承Number,Number继承Object。
        定义一个方法,方法的参数是一个ArrayList。
        要求可以接收ArrayList<Integer>,ArrayList<Number>,ArrayList<Object>,ArrayList<String>这些类型的数据。
    
        结论 : 具体类型的集合,不支持多态 , 要想接收任意类型集合 , 需要使通配符集合
     */
    public class Test1 {
        public static void main(String[] args) {
            ArrayList<Integer> list1 = new ArrayList<>();
            ArrayList<Number> list2 = new ArrayList<>();
            ArrayList<String> list3 = new ArrayList<>();
            ArrayList<Object> list4 = new ArrayList<>();
    
            useList5(list1);
            useList5(list2);
            useList5(list3);
            useList5(list4);
        }
    
        // 此方法只能接收存储Integer类型数据的集合
        public static void useList1(ArrayList<Integer> list) {
    
        }
    
        // 此方法只能接收存储Number类型数据的集合
        public static void useList2(ArrayList<Number> list) {
    
        }
    
        // 此方法只能接收存储String类型数据的集合
        public static void useList3(ArrayList<String> list) {
    
        }
    
        // 此方法只能接收存储Object类型数据的集合
        public static void useList4(ArrayList<Object> list) {
    
        }
    
        public static void useList5(ArrayList<?> list) {
    
        }
    
    }
    

1.6 受限泛型

  • 受限泛型是指,在使用通配符的过程中 , 对泛型做了约束,给泛型指定类型时,只能是某个类型父类型或者子类型

  • 分类 :

    • 泛型的下限 :
      • <? super 类型> //只能是某一类型,及其父类型,其他类型不支持
    • 泛型的上限 :
      • <? extends 类型> //只能是某一个类型,及其子类型,其他类型不支持
    package com.itheima.genericity_demo.wildcard_demo;
    
    import java.util.ArrayList;
    
    /*
      wildcardCharacter
    
      基于上一个知识点,定义方法
        show1方法,参数只接收元素类型是Number或者其父类型的集合
        show2方法,参数只接收元素类型是Number或者其子类型的集合
    
     */
    public class Test2 {
        public static void main(String[] args) {
            ArrayList<Integer> list1 = new ArrayList<>();
            ArrayList<Number> list2 = new ArrayList<>();
            ArrayList<Object> list3 = new ArrayList<>();
    
            show1(list3);
            show1(list2);
    
            show2(list2);
            show2(list1);
        }
        // 此方法可以接受集合中存储的是Number或者Number的父类型 , 下限泛型
        public static void show1(ArrayList<? super Number> list) {
    
        }
        // 此方法可以接受集合中存储的是Number或者Number的子类型 , 上限泛型
        public static void show2(ArrayList<? extends Number> list) {
    
        }
    }
    

2 数据结构

  • 栈结构 : 先进后出

  • 队列结构 : 先进先出

  • 数组结构 : 查询快 , 增删慢

  • 链表结构 : 查询慢 , 增删快

  • 二叉树

    • 二叉树 : 每个节点最多有两个子节点

    • 二茬查找树 : 每个节点的左子节点比当前节点小 , 右子节点比当前节点大

    • 二茬平衡树 : 在查找树的基础上, 每个节点左右子树的高度不超过1

    • 红黑树 :

      • 每一个节点或是红色的,或者是黑色的

      • 根节点必须是黑色

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

      • 不能出现两个红色节点相连的情况

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

      • 添加元素 :

  • 哈希表结构 :

    • 哈希值:是JDK根据对象的地址或者字符串或者数字算出来的int类型的数值
    • Object类中有一个方法可以获取对象的哈希值
      public int hashCode():返回对象的哈希码值
    • 对象的哈希值特点
      • 同一个对象多次调用hashCode()方法返回的哈希值是相同的
      • 默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同

3 List集合(有序、有索引) ★

  • List集合是Collection集合子类型,继承了所有Collection中功能,同时List增加了带索引的功能

  • 特点 :
    • 元素的存取是有序的【有序】
    • 元素具备索引 【有索引】
    • 元素可以重复存储【可重复】
  • 常见的子类
    • ArrayList:底层结构就是数组【查询快,增删慢】
    • Vector:底层结构也是数组(线程安全,同步安全的,低效,用的就少)
    • LinkedList:底层是链表结构(双向链表)【查询慢,增删快】
  • List中常用的方法
    • public void add(int index, E element): 将指定的元素,添加到该集合中的指定位置上。
    • public E get(int index):返回集合中指定位置的元素
    • public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。\
    • public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回值的更新前的元素
  • LinkedList类
    • LinkedList底层结构是双向链表。每个节点有三个部分的数据,一个是保存元素数据,一个是保存前一个节点的地址,一个是保存后一个节点的地址。可以双向查询,效率会比单向链表高。
    • LinkedList特有方法
      • public void addFirst(E e):将指定元素插入此列表的开头。
      • public void addLast(E e):将指定元素添加到此列表的结尾。
      • public E getFirst():返回此列表的第一个元素。
      • public E getLast():返回此列表的最后一个元素。
      • public E removeFirst():移除并返回此列表的第一个元素。
      • public E removeLast():移除并返回此列表的最后一个元素。

4 Set集合(无序、无索引) ★

  • Set集合也是Collection集合的子类型,没有特有方法。Set比Collection定义更严谨
  • 特点 :
    • 元素不能保证插入和取出顺序(无序)
    • 元素是没有索引的(无索引)
    • 元素唯一(元素唯一)
  • Set常用子类
    • HashSet:底层由HashMap,底层结构哈希表结构。
      去重,无索引,无序。
      哈希表结构的集合,操作效率会非常高。
    • LinkedHashSet:底层结构链表加哈希表结构。
      具有哈希表表结构的特点,也具有链表的特点。
    • TreeSet:底层是有TreeMap,底层数据结构 红黑树。
      去重,让存入的元素具有排序(升序排序)

day06-排序查找算法,Map集合,集合嵌套,斗地主案例

今日目标 :

1 TreeSet集合

1.1 集合体系

  • Collection

    • List接口

      • ArrayList类
      • LinkedList类
    • Set接口

      • HashSet集合

      • TreeSet集合

1.2 TreeSet特点 【去重、并排序】

  • 不包含重复元素的集合[元素唯一]
  • 没有带索引的方法[无索引]
  • 可以按照指定的规则进行排序[可以排序]

1.3 TreeSet集合的练习

package com.itheima.treeset_demo;

import java.util.Iterator;
import java.util.TreeSet;

/*
      1 TreeSet集合练习
            存储Integer类型的整数,并遍历
 */
public class TreeSetDemo {
    public static void main(String[] args) {
        TreeSet<Integer> ts = new TreeSet<>();
        ts.add(10);
        ts.add(10);
        ts.add(20);
        ts.add(10);
        ts.add(30);

        // 迭代器
        Iterator<Integer> it = ts.iterator();
        while(it.hasNext()){
            Integer s = it.next();
            System.out.println(s);
        }

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

        // 增强for
        for (Integer t : ts) {
            System.out.println(t);
        }
    }
}

  • 如果TreeSet存储自定义对象 , 需要对自定义类进行指定排序规则

    下列代码没有指定排序规则 , 所以运行会报出错误

package com.itheima.treeset_demo;

public class Student {
    private String name;
    private int 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;
    }

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

import java.util.Iterator;
import java.util.TreeSet;

/*
      TreeSet集合练习
         存储学生对象,并遍历

 */
public class TreeSetDemo2 {
    public static void main(String[] args) {
        TreeSet<Student> ts = new TreeSet<>();

        Student s1 = new Student("dilireba", 19);
        Student s2 = new Student("gulinazha", 20);
        Student s3 = new Student("maerzhaha", 18);

        ts.add(s1);
        ts.add(s2);
        ts.add(s3);

        System.out.println(ts);
    }
}

1.4 排序规则

1.4.1 ★自然排序【对象实现Comparable、重写compareTo】
  • 使用步骤
    • 使用空参构造创建TreeSet集合对象
    • 存储元素所在的类需要实现Comparable接口
    • 重写Comparable接口中的抽象方法 compareTo方法,指定排序规则
  • compareTo方法如何指定排序规则 :
    • 此方法如果返回的是小于0 , 代表的是当前元素较小 , 需要存放在左边
    • 此方法如果返回的是大于0 , 代表的是当前元素较大, 需要存放在右边
    • 此方法如果返回的是0 , 代表的是当前元素在集合中已经存在 , 不存储
  • 练习 : 存储学生对象, 按照年龄的升序排序,并遍历
    package com.itheima.treeset_demo;
    
    public class Student implements Comparable<Student>{
        private String name;
        private int 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;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    
        /*
    
         */
        @Override
        public int compareTo(Student o) {
            /*
                this : 当前要添加的元素
                o    : 集合中已经存在的元素
    
                如果方法返回值为负数 : 当前元素放左边
                如果方法的返回值为正数  : 当前元素放右边
                如果方法的返回值为0  : 说明当前元素在集合中存在,不存储
             */
            int result = this.age - o.age;
            return result;
        }
    }
    =====================================================
        
    package com.itheima.treeset_demo;
    import java.util.Iterator;
    import java.util.TreeSet;
    
    /*
          TreeSet集合练习
             存储学生对象,并遍历
    
     */
    public class TreeSetDemo2 {
        public static void main(String[] args) {
            TreeSet<Student> ts = new TreeSet<>();
    
            Student s1 = new Student("dilireba", 19);
            Student s2 = new Student("gulinazha", 20);
            Student s3 = new Student("maerzhaha", 18);
    
            ts.add(s1);
            ts.add(s2);
            ts.add(s3);
    
            System.out.println(ts);
        }
    }
    
    • 对刚才的案例进行改进 , 如果年龄相等 , 那么对学生的名字在字典的顺序排序
    package com.itheima.treeset_demo;
    
    public class Student implements Comparable<Student> {
        private String name;
        private int 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;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    
        /*
    
         */
        @Override
        public int compareTo(Student o) {
            /*
                this : 当前要添加的元素
                o    : 集合中已经存在的元素
    
                如果方法返回值为负数 : 当前元素放左边
                如果方法的返回值为正数  : 当前元素放右边
                如果方法的返回值为0  : 说明当前元素在集合中存在,不存储
             */
            int result = this.age - o.age;
            // 如果年龄相等 , 那么按照名字进行排序
            return result == 0 ? this.name.compareTo(o.name) : age;
        }
    }
    
    =============================================================
        
        
    package com.itheima.treeset_demo;
    
    import java.util.Iterator;
    import java.util.TreeSet;
    
    /*
          TreeSet集合练习
             存储学生对象, 按照年龄的升序排序,并遍历
    
     */
    public class TreeSetDemo2 {
        public static void main(String[] args) {
            TreeSet<Student> ts = new TreeSet<>();
    
            Student s1 = new Student("dilireba", 19);
            Student s2 = new Student("gulinazha", 20);
            Student s3 = new Student("maerzhaha", 18);
            Student s4 = new Student("ouyangnanan", 18);
    
            ts.add(s1);
            ts.add(s2);
            ts.add(s3);
            ts.add(s4);
    
            System.out.println(ts);
        }
    }
    
    
1.4.2 ★比较器排序【比较器类实现Comparator,并重写compare】
  • 使用步骤
    • TreeSet的带参构造方法使用的是 “比较器排序” 对元素进行排序的
    • 比较器排序,就是让TreeSet集合构造方法接收Comparator接口的实现类对象
    • 重写Comparator接口中的 compare(T o1,T o2)方法 , 指定排序规则
    • 注意 : o1代表的是当前往集合中添加的元素 , o2代表的是集合中已经存在的元素,排序原理与自然排序相同!
  • 排序规则
    • 排序原理与自然排序相同!
  • 练习
package com.itheima.treeset_demo.comparator_demo;

public class Teacher  {
    private String name;
    private int age;

    public Teacher() {
    }

    public Teacher(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;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
======================================
    
package com.itheima.treeset_demo.comparator_demo;

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

/*
      TreeSet集合练习
         存储学生对象, 按照年龄的升序排序,并遍历

 */
public class TreeSetDemo2 {
    public static void main(String[] args) {
        TreeSet<Teacher> ts = new TreeSet<>(new ComparatorImpl());

        Teacher s1 = new Teacher("dilireba", 19);
        Teacher s2 = new Teacher("gulinazha", 20);
        Teacher s3 = new Teacher("maerzhaha", 18);
        Teacher s4 = new Teacher("ouyangnanan", 18);

        ts.add(s1);
        ts.add(s2);
        ts.add(s3);
        ts.add(s4);

        System.out.println(ts);
    }
}

// 比较器排序
class ComparatorImpl implements Comparator<Teacher> {
    @Override
    public int compare(Teacher o1, Teacher o2) {
        int result = o1.getAge() - o2.getAge();
        return result == 0 ? o1.getName().compareTo(o2.getName()) : result;
    }
}
1.4.3 两种排序的区别
  • 自然排序:自定义类实现Comparable接口,重写compareTo方法,根据返回值进行排序。

  • 比较器排序:创建TreeSet对象的时候传递Comparator的实现类对象,重写compare方法,根据返回值进行排序。

  • 如果Java提供好的类已经定义了自然排序排序规则 , 那么我们可以使用比较器排序进行替换

  • 注意 : 如果自然排序和比较器排序都存在 , 那么会使用比较器排序

  • 两种方式中,关于返回值的规则:

    • 如果返回值为负数,表示当前存入的元素是较小值,存左边
    • 如果返回值为0,表示当前存入的元素跟集合中元素重复了,不存
    • 如果返回值为正数,表示当前存入的元素是较大值,存右边

2 Collections单列集合工具类 ★

  • Collections工具类介绍
    • java.util.Collections 是集合的工具类,里面提供了静态方法来操作集合,乱序,排序…

2.1 shuffle方法

  • public static void shuffle(List<?> list) 对集合中的元素进行打乱顺序。

  • 集合中元素类型可以任意类型

    package com.itheima.collections_demo;
    
    import java.util.ArrayList;
    import java.util.Collections;
    
    /*
        Collections类 : 操作单列集合的工具类
            public static void shuffle(List<?> list) 对集合中的元素进行打乱顺序
            1 乱序只能对List集合进行乱序
            2 集合中元素类型可以任意类型
    
            需求 : 定义一个List集合,里面存储若干整数。对集合进行乱序
     */
    public class ShuffleDemo {
        public static void main(String[] args) {
            ArrayList<Integer> list = new ArrayList<>();
            list.add(10);
            list.add(30);
            list.add(50);
            list.add(40);
            list.add(20);
    
            Collections.shuffle(list);
    
            System.out.println(list);// 打印集合中的元素
        }
    }
    
    

2.2 sort方法(只对List结合排序)

  • public static void sort (List list): 对集合中的元素自然排序

    • 该方法只能对List集合进行排序
    • 从方法中泛型分析可知,集合中元素类型必须是Comparable的子类型
    package com.itheima.collections_demo;
    
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.Comparator;
    
    /*
        Collections类 : 单列集合的工具类
            sort方法是一个重载的方法,可以实现自然排序及比较器排序。
            要特别注意的是sort方法只能对List集合进行排序,方法如下:
    
            public static <T extends Comparable> void sort (List<T> list)
    
            练习:定义List集合,存储若干整数,进行排序
     */
    public class SortDemo1 {
        public static void main(String[] args) {
            ArrayList<Integer> list = new ArrayList<>();
            list.add(3);
            list.add(2);
            list.add(4);
            list.add(1);
    
            // 使用此方法 , 需要集合中存储的元素实现Comparable接口
            Collections.sort(list);
    
            System.out.println(list);
        }
    }
    
  • public static void sort (List list, Comparator<? super T> c)

    • 方法只能对List集合排序

    • 对元素的类型没有要求

    • 需要定义一个比较器Comparator (规则和之前TreeSet时一样)

    • 使用场景:

      • List集合中的元素类型不具备自然排序能力(元素类型没有实现结果 Comparable)
      • List集合中的元素类型具备自然排序能力,但是排序规则不是当前所需要的。
      package com.itheima.collections_demo;
      
      import java.util.ArrayList;
      import java.util.Collections;
      import java.util.Comparator;
      
      /*
          Collections类 : 单列集合的工具类
              sort方法是一个重载的方法,可以实现自然排序及比较器排序。
              要特别注意的是sort方法只能对List集合进行排序,方法如下:
      
              public static <T extends Comparable> void sort (List<T> list) : 只能对集合中的元素自然排序
      
              需求1:定义一个List集合,存储若干整数,要求对集合进行降序排序
              分析:整数类型Integer具备自然排序能力,但是题目要求降序排序
      
              需求2:定义一个学生类,属性有姓名,年龄。创建若干对象放到List集合中。要求对List集合中学生对象进行年龄的升序排序。
              分析:自定义类型默认是没有自然排序能力的,我们使用自定义比较器方式排序。
      
      
       */
      public class SortDemo2 {
          public static void main(String[] args) {
      
              /*
                  需求2:定义一个学生类,属性有姓名,年龄。创建若干对象放到List集合中。要求对List集合中学生对象进行年龄的升序排序。
                  分析:自定义类型默认是没有自然排序能力的,我们使用自定义比较器方式排序
              */
              ArrayList<Student> list = new ArrayList<>();
              list.add(new Student("lisi", 24));
              list.add(new Student("zhangsan", 23));
      
              Collections.sort(list, new Comparator<Student>() {
                  @Override
                  public int compare(Student o1, Student o2) {
                      return o1.getAge() - o2.getAge();
                  }
              });
              System.out.println(list);
          }
      
          /*
              需求1:定义一个List集合,存储若干整数,要求对集合进行降序排序
              分析:整数类型Integer具备自然排序能力,但是题目要求降序排序
           */
          private static void method1() {
              ArrayList<Integer> list = new ArrayList<>();
              list.add(3);
              list.add(2);
              list.add(4);
              list.add(1);
      
              // 使用此方法 , 需要集合中存储的元素实现Comparable接口
              Collections.sort(list, new Comparator<Integer>() {
                  @Override
                  public int compare(Integer o1, Integer o2) {
                      return o2 - o1;
                  }
              });
      
              System.out.println(list);
          }
      }
      

3 可变参数

3.1 可变参数介绍

  • 在 JDK5 中提供了可变参数,允许在调用方法时传入任意个参数。可变参数原理就是一个数组形式存在
  • 格式 : 修饰符 返回值类型 方法名(数据类型… 变量名) { }
  • 举例 : public void show(int … num) : 表达式该方法可以接收任意个整数值 , 原理 : 其实就是一个int类型的数组

3.2 可变参数注意

  • 可变参数只能作为方法的最后一个参数,但其前面可以有或没有任何其他参数。
  • 可变参数本质上是数组,不能作为方法的重载。如果同时出现相同类型的数组和可变参数方法,是不能编译通过的。

3.3 可变参数的使用

  • 调用可变参数方法,可以给出零到任意多个参数,编译器会将可变参数转化为一个数组,也可以直接传递一个数组。
    方法内部使用时直接当做数组使用即可
package com.itheima.changevariable_demo;

/*
    1 什么是可变参数
        JDK5中,允许在调用方法时传入任意个参数。可变参数原理就是一个数组形式存在
        格式 : 修饰符 返回值类型 方法名(数据类型… 变量名) {  }
        举例 : public void show(int... num){}

    2 可变参数注意 :
        1) 可变参数只能作为方法的最后一个参数,但其前面可以有或没有任何其他参数。
        2) 可变参数本质上是数组,不能作为方法的重载。如果同时出现相同类型的数组和可变参数方法,是不能编译通过的。

    3 可变参数的使用 :
        调用可变参数的方法 , 可以传入0个到任意个数据 , 编译器会将可变参数转换成一个数组 , 也可以直接传递一个数组
        方法中把可变参数当做一个数组去使用即可


    练习:定义方法可以求任意个整数的和

 */
public class VariableDemo1 {
    public static void main(String[] args) {
        sum();
        sum(1, 2);
        sum(1, 2, 3, 4);
    }
    
    public static void sum(int... num) {// 方法的参数是一个int类型的可变参数
        int sum = 0;// 定义求和变量
        for (int i : num) {
            sum += i;
        }
        System.out.println(sum);
    }
}

3.4 addAll方法(就是一个含有可变参数的方法)

  • static boolean addAll(Collection<? super T> c , T… elements) : 添加任意多个数据到集合中

    • 该方法就是一个含有可变参数的方法,使用时可以传入任意多个实参,实用方便!

    • 分析 : Collection<? super T> , ? 可以是 T 的类型或者父类型 , 反过来 , T是?类型或者子类型

      那么当你确定?的类型,也就是集合的类型 , 就可以往集合中添加此类型或者子类型

    package com.itheima.changevariable_demo;
    
    import java.util.ArrayList;
    import java.util.Collections;
    
    /*
        Collections的addAll方法
            static <T> boolean  addAll(Collection<? super T> c , T... elements) : 添加任意多个数据到集合中
            分析: 该方法就是一个含有可变参数的方法,使用时可以传入任意多个实参,实用方便!
    
            练习:创建一个List集合,使用addAll方法传入若干字符串
    
     */
    public class VariableDemo2 {
        public static void main(String[] args) {
            ArrayList<Integer> list = new ArrayList<>();
            Collections.addAll(list, 10, 20, 30, 40);
            System.out.println(list);// [10, 20, 30, 40]
        }
    }
    
    

4 排序,查找算法

4.1 冒泡排序

  • 冒泡排序 : 将一组数据按照从小到大的顺序进行排序
  • 冒泡排序原理 : 相邻元素两两作比较 , 大的元素往后放
package com.itheima.arraysort_demo.bubblesort_demo;

import java.util.Arrays;

/*
    冒泡排序 : 将一组数据按照从小到大的顺序进行排序
    冒泡排序的原理 : 相邻元素两两作比较 , 大的元素往后放

    需求 : 将数组中的元素 {3,5,2,1,4} 进行升序排序
 */
public class SortDemo {
    public static void main(String[] args) {
        int[] arr = {3, 5, 2, 1, 4};

//        // 第一轮排序
//        for (int i = 0; i < arr.length - 1; i++) {
//            if (arr[i] > arr[i + 1]) {
//                int temp = arr[i];
//                arr[i] = arr[i + 1];
//                arr[i + 1] = temp;
//            }
//        }
//
//        System.out.println("第一轮排序:" + Arrays.toString(arr));
//
//
//        // 第二轮排序
//        for (int i = 0; i < arr.length - 2; i++) {
//            if (arr[i] > arr[i + 1]) {
//                int temp = arr[i];
//                arr[i] = arr[i + 1];
//                arr[i + 1] = temp;
//            }
//        }
//
//        System.out.println("第二轮排序:" + Arrays.toString(arr));
//
//
//        // 第三轮排序
//        for (int i = 0; i < arr.length - 2; i++) {
//            if (arr[i] > arr[i + 1]) {
//                int temp = arr[i];
//                arr[i] = arr[i + 1];
//                arr[i + 1] = temp;
//            }
//        }
//
//        System.out.println("第三轮排序:" + Arrays.toString(arr));
//
//
//        // 第四轮排序
//        for (int i = 0; i < arr.length - 2; i++) {
//            if (arr[i] > arr[i + 1]) {
//                int temp = arr[i];
//                arr[i] = arr[i + 1];
//                arr[i + 1] = temp;
//            }
//        }
//
//        System.out.println("第四轮排序:" + Arrays.toString(arr));

		// 优化代码
        for (int j = 0; j < arr.length - 1; j++) {// 比较的轮次
            // 每轮相邻元素比较的次数
            for (int i = 0; i < arr.length - 1 - j; i++) {
                if (arr[i] > arr[i + 1]) {
                    int temp = arr[i];
                    arr[i] = arr[i + 1];
                    arr[i + 1] = temp;
                }
            }
            System.out.println("第" + (j + 1) + "轮排序:" + Arrays.toString(arr));
        }
    }
}

4.2 选择排序

  • 选择排序原理 : 它的工作原理是每一次从待排序的数据元素中选出最小的一个元素,存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到已排序序列的末尾。以此类推,直到全部待排序的数据元素排完。
  • 注意事项 :
    • 有n个元素,那么就要比较n-1轮次。
    • 每一趟中都会选出一个最值元素,较前一趟少比较一次
package com.itheima.arraysort_demo.selectionsort_demo;
/*
    选择排序工作原理 :
        它的工作原理是每一次从待排序的数据元素中选出最小的一个元素,
        存放在序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到已排序序列的末尾。
        以此类推,直到全部待排序的数据元素排完。

    注意 :
        1 有n个元素,那么就要比较n-1趟。
        2 每一趟中都会选出一个最值元素,较前一趟少比较一次
 */

import java.util.Arrays;

public class SortDemo {
    public static void main(String[] args) {
        int[] arr = {4, 1, 5, 3, 2};

        // 遍历数组
        for (int i = 0; i < arr.length - 1; i++) {
            // 记录当前元素和其之后所有元素的最小值索引
            int minIndex = i;

            int min = arr[i];
            for (int j = i; j < arr.length; j++) {
                if (arr[j] < min) {
                    minIndex = j; //  把当前最小值的索引赋值给minIndex
                    min = arr[j];// 替换最小值
                }
            }

            if (i != minIndex) {
                int temp = arr[i];
                arr[i] = arr[minIndex];
                arr[minIndex] = temp;
            }
        }

        System.out.println(Arrays.toString(arr));
    }
}

4.3 二分查找

  • 原理 : 每次去掉一般的查找范围
  • 前提 : 数组必须有序
package com.itheima.arraysort_demo.binarysearch_demo;

/*
    二分查找 :
        原理 : 每次去掉一般的查找范围
        前提 : 数组必须有序
        步骤 :
            1,定义两个变量,表示要查找的范围。默认min = 0 , max = 最大索引
            2,循环查找,但是min <= max
            3,计算出mid的值
            4,判断mid位置的元素是否为要查找的元素,如果是直接返回对应索引
            5,如果要查找的值在mid的左半边,那么min值不变,max = mid -1.继续下次循环查找
            6,如果要查找的值在mid的右半边,那么max值不变,min = mid + 1.继续下次循环查找
            7,当 min > max 时,表示要查找的元素在数组中不存在,返回-1.
 */
public class BinarySearchDemo {
    public static void main(String[] args) {

        int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

        int i = binarySearch(arr, 8);
        System.out.println(i);

    }

    public static int binarySearch(int[] arr, int num) {
        // 定义两个变量,表示要查找的范围。默认min = 0 , max = 最大索引
        int max = arr.length - 1;
        int min = 0;

        // 2,循环查找,但是min <= max
        while (min <= max) {
            // 3,计算出mid的值
            int mid = (min + max) / 2;

            if (arr[mid] == num) {
                //  4,判断mid位置的元素是否为要查找的元素,如果是直接返回对应索引
                return mid;
            } else if (arr[mid] > num) {
                // 5,如果要查找的值在mid的左半边,那么min值不变,max = mid -1.继续下次循环查找
                max = mid - 1;
            } else if (arr[mid] < num) {
                // 6,如果要查找的值在mid的右半边,那么max值不变,min = mid + 1.继续下次循环查找
                min = mid + 1;
            }
        }

        return -1;
    }
}

5 Map集合

5.1 Map集合的介绍

  • java.util.Map<K,V> 集合,里面保存的数据是成对存在的,称之为双列集合。存储的数据,我们称为键值对。 之前所学的Collection集合中元素单个单个存在的,称为单列集合

5.2 特点

  • Map<K,V> K:键的数据类型;V:值的数据类型

  • 特点 :

    • 键不能重复,值可以重复
    • 键和值是 一 一 对应的,通过键可以找到对应的值
    • (键 + 值) 一起是一个整体 我们称之为“键值对” 或者 “键值对对象”,在Java中叫做“Entry对象”
  • 使用场景

    • 凡是要表示一一对应的数据时就可以Map集合
      • 举例 : 学生的学号和姓名 — (itheima001 小智)
      • 举例 : 夫妻的关系 ---- (王宝强 马蓉 ) (谢霆锋 张柏芝)

5.3 常用实现类

  • HashMap:
    • 此前的HashSet底层实现就是HashMap完成的,HashSet保存的元素其实就是HashMap集合中保存的键,底层结构是哈希表结构,具有键唯一,无序,特点。
  • LinkedHashMap:
    • 底层结构是有链表和哈希表结构,去重,有序
  • TreeMap:
    • 底层是有红黑树,去重,通过键排序

5.4 常用的方法

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

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

/*
    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 boolean containKey(Object key): 判断该集合中是否有此键。

     需求 : 将以下数据保存到Map集合中 , 进行测试以上方法
        “文章“       "马伊琍
        “谢霆锋”     “王菲”
        “李亚鹏”     “王菲”

 */
public class MapDemo {
    public static void main(String[] args) {
        //  创建双列集合对象
        Map<String, String> hm = new HashMap<>();

        // 添加元素
        // public V put(K key, V value):  把指定的键与指定的值添加到Map集合中
        hm.put("文章", "马伊琍");
        hm.put("谢霆锋", "王菲");
        hm.put("李亚鹏", "王菲");

        // public V remove(Object key): 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值
        // System.out.println(hm.remove("谢霆锋"));

        // public V get(Object key) 根据指定的键,在Map集合中获取对应的值
        // System.out.println(hm.get("李亚鹏"));

        // public Set<K> keySet(): 获取Map集合中所有的键,存储到Set集合中
//        Set<String> set = hm.keySet();
//        for (String key : set) {
//            System.out.println(key);
//        }

        // public boolean containKey(Object key): 判断该集合中是否有此键。
        // System.out.println(hm.containsKey("李亚鹏"));

        System.out.println(hm);// 打印集合 , 打印的是集合中的元素
    }
}

5.5 Map集合的遍历

  • 第一种方式 : 键找值
  package com.itheima.map_demo.map_test;


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

/*
    创建Map集合对象 , 往集合中添加以下四对元素 , 使用键找值遍历集合

    周瑜 -- 小乔
    孙策 -- 大乔
    刘备 -- 孙尚香
    诸葛亮 -- 黄月英

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

        // 添加元素
        hm.put("周瑜", "小乔");
        hm.put("孙策", "大乔");
        hm.put("刘备", "孙尚香");
        hm.put("诸葛亮", "黄月英");
    
        // 获取健集合
        Set<String> set = hm.keySet();
        
        // 遍历健集合 , 通过键找值
        for (String key : set) {
            String value = hm.get(key);
            System.out.println(key + "---" + value);
        }
    }
}

-第二种方式 : 获取键值对对象 , 在找到键和值
package com.itheima.map_demo.map_test;

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

/*
    需求 :  创建Map集合对象 , 往集合中添加以下三对元素
            使用获取Entry对象集合,在找到键和值 遍历集合

        张无忌 -- 赵敏
        张翠山 -- 殷素素
        张三丰 -- 郭芙

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

        // 添加元素
        hm.put("张无忌", "赵敏");
        hm.put("张翠山", "殷素素");
        hm.put("张三丰", "郭芙");

        // 获取键值对对象集合
        Set<Map.Entry<String, String>> set = hm.entrySet();

        // 遍历键值对对象集合 , 获取每一个键值对对象
        for (Map.Entry<String, String> entry : set) {
            // 通过entry对象获取键
            String key = entry.getKey();
            // 通过entry对象获取值
            String value = entry.getValue();
            System.out.println(key + "--" + value);
        }
    }
}

5.6 HashMap集合

  • 注意 : HashMap集合 , 要想保证键唯一 , 那么键所在的类必须重写hashCode和equals方法
package com.itheima.map_demo.map_test;

public class Student {
    private String name;
    private int 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;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return name != null ? name.equals(student.name) : student.name == null;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }

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

package com.itheima.map_demo.map_test;
import java.util.HashMap;
import java.util.Set;

/*
    HashMap类 :
        存储数据,每位学生(姓名,年龄)都有自己的家庭住址。
        学生和地址有对应关系,将学生对象和家庭住址存储到map集合中。学生作为键, 家庭住址作为值。
        要求:学生姓名相同并且年龄相同视为同一名学生,不能重复存储
 */
public class HashMapTest {
    public static void main(String[] args) {
        // 学生作为键, 家庭住址作为值。
        HashMap<Student, String> hm = new HashMap<>();

        hm.put(new Student("迪丽热巴", 18) , "新疆");
        hm.put(new Student("迪丽热巴", 18) , "中国");

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

5.7 LinkedHashMap集合(元素唯一、元素有序)

  • LinkedHashMap类 , 在最底层采用的数据结构 : 是链表+哈希表。
  • 特点 :
    • 元素唯一
    • 元素有序
package com.itheima.map_demo.map_test;

import java.util.LinkedHashMap;
import java.util.Set;

/*
    LinkedHashMap类 , 在最底层采用的数据结构 : 是链表+哈希表。
    特点 :
        1 元素唯一
        2 有序

    需求 : 创建LinkedHashMap对象 , 添加元素进行验证 元素唯一 , 有序的特点
 */
public class LinkedHashMapTest {
    public static void main(String[] args) {
        // 创建集合对象
        LinkedHashMap<Integer, String> linkedHashMap = new LinkedHashMap<>();

        linkedHashMap.put(1, "张三");
        linkedHashMap.put(1, "李四");
        linkedHashMap.put(2, "王五");
        linkedHashMap.put(3, "赵六");

        System.out.println(linkedHashMap);
    }
}

5.8 TreeMap集合

  • TreeMap的底层是红黑树实现的,有排序的能力,键去重。

  • 可以自然排序(键所在的类要实现Comparable)

  • 若自定义类没有自然排序功能,或自然排序功能不满足要求时。可以自定义比较器排序(Comparator)

package com.itheima.map_demo.map_test;
import java.util.TreeMap;

/*
    需求 :
        定义TreeMap集合存储键值对,键使用Integer,值使用String,存储若干键值对,遍历集合观察结果是否有排序。
 */
public class TreeMapTest1 {
    public static void main(String[] args) {
        //  定义TreeMap集合存储键值对,键使用Integer,值使用String
        // 创建集合对象
        TreeMap<Integer, String> treeMap = new TreeMap<>();

        treeMap.put(1, "张三");
        treeMap.put(3, "赵六");
        treeMap.put(2, "王五");

        System.out.println(treeMap);
    }
}

package com.itheima.map_demo.map_test;

import java.util.Comparator;
import java.util.TreeMap;

/*
    需求:创建一个TreeMap集合,键是学生对象(Student),值是籍贯(String)。
    学生属性姓名和年龄, 要求按照年龄进行升序排序并遍历
 */
public class TreeMapTest2 {
    public static void main(String[] args) {
        // 学生作为键, 家庭住址作为值。
        TreeMap<Student, String> tm = new TreeMap<>(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o1.getAge() - o2.getAge();
            }
        });

        tm.put(new Student("迪丽热巴", 18), "新疆");
        tm.put(new Student("迪丽热巴", 16), "中国");

        System.out.println(tm);
    }
}

6 集合嵌套

6.1 List嵌套List

package com.itheima.Collection_nested_demo;

import java.util.ArrayList;
import java.util.List;

/*
    使用场景举例:一年级有多个班级,每个班级有多名学生。要求保存每个班级的学生姓名,保存一个年级所有的班级信息

    思路:
    可以使用List集合保存一个班级的学生
    可以使用List集合保存所有班级

    因此我们可以定义集合如下:
    班级:List<String>
        举例 :
            List<String> 三年一班 = {迪丽热巴 , 古力娜扎 ,马尔扎哈 ,欧阳娜娜}
            List<String> 三年二班 = {李小璐 , 白百何 , 马蓉}
            List<String> 三年三班 = {林丹 ,文章, 陈赫}

    年级:List<List<String>>
        举例 :
            List<List<String>> 年级 = {三年一班 , 三年二班 , 三年三班}
 */
public class Test3 {
    public static void main(String[] args) {
        List<String> 三年一班 = new ArrayList<>();
        三年一班.add("迪丽热巴");
        三年一班.add("古力娜扎");
        三年一班.add("马尔扎哈");
        三年一班.add("欧阳娜娜");

        List<String> 三年二班 = new ArrayList<>();
        三年二班.add("李小璐");
        三年二班.add("白百何");
        三年二班.add("马蓉");

        List<String> 三年三班 = new ArrayList<>();
        三年三班.add("林丹");
        三年三班.add("文章");
        三年三班.add("陈赫");

        List<List<String>> 年级 = new ArrayList<>();
        年级.add(三年一班);
        年级.add(三年二班);
        年级.add(三年三班);

        for (List<String> 班级 : 年级) {
            for (String name : 班级) {
                System.out.println(name);
            }
            System.out.println("-----------------");
        }
    }
}

6.2 List嵌套Map

package com.itheima.Collection_nested_demo;

import java.util.*;

/*
    List嵌套Map :

    使用场景举例:一年级有多个班级,每个班级有多名学生。要求保存每个班级的学生姓名,姓名有与之对应的学号,保存一年级所有的班级信息。

    思路:
        1 可以使用Map集合保存一个班级的学生(键是学号,值是名字)
        2 可以使用List集合保存所有班级

    因此我们可以定义集合如下:

    班级:Map<String,String> 键是学号,值是姓名
        举例 :
            Map<String,String> 三年一班 = {it001 = 迪丽热巴 , it002 = 古力娜扎 ,it003 = 马尔扎哈 ,it004 = 欧阳娜娜}
            Map<String,String> 三年二班 = {it001 = 李小璐 , it002 = 白百何 , it003 = 马蓉}
            Map<String,String> 三年三班 = {it001 = 林丹 ,it002 = 文章, it003 = 陈赫}

    年级:List<Map<String,String>>保存每个班级的信息
        举例 :
              List<Map<String,String>> 年级 = {三年一班 , 三年二班 , 三年三班}
 */
public class Test2 {
    public static void main(String[] args) {
        Map<String, String> 三年一班 = new HashMap<>();
        三年一班.put("it001", "迪丽热巴");
        三年一班.put("it002", "古力娜扎");
        三年一班.put("it003", "马尔扎哈");
        三年一班.put("it004", "欧阳娜娜");


        Map<String, String> 三年二班 = new HashMap<>();
        三年二班.put("it001", "李小璐");
        三年二班.put("it002", "白百何");
        三年二班.put("it003", "马蓉");

        Map<String, String> 三年三班 = new HashMap<>();
        三年三班.put("it001", "林丹");
        三年三班.put("it002", "文章");
        三年三班.put("it003", "陈赫");


        List<Map<String, String>> 年级 = new ArrayList<>();
        年级.add(三年一班);
        年级.add(三年二班);
        年级.add(三年三班);

        for (Map<String, String> 班级 : 年级) {
            Set<String> studentId = 班级.keySet();
            for (String id : studentId) {
                String name = 班级.get(id);
                System.out.println(id + "---" + name);
            }
            System.out.println("=================");
        }
    }
}

6.3 Map嵌套Map

package com.itheima.Collection_nested_demo;

import java.util.*;

/*
    Map嵌套Map

    使用场景举例:一个年级有多个班级,每个班级有多名学生。要求保存每个班级的学生姓名,姓名有与之对应的学号,保存一年级所有的班级信息,班级有与之对应的班级名称。

    思路:
    可以使用Map集合保存一个班级的学生(键是学号,值是名字)

    可以使用Map集合保存所有班级(键是班级名称,值是班级集合信息)

    因此我们可以定义集合如下:
    班级: Map<String,String> 键:学号,值:姓名
        举例 :
            Map<String,String> 三年一班 = {it001 = 迪丽热巴 , it002 = 古力娜扎 ,it003 = 马尔扎哈 ,it004 = 欧阳娜娜}
            Map<String,String> 三年二班 = {it001 = 李小璐 , it002 = 白百何 , it003 = 马蓉}
            Map<String,String> 三年三班 = {it001 = 林丹 ,it002 = 文章, it003 = 陈赫}

    年级: Map<String , Map<String,String>> 键:班级名称,值:具体班级信息
        举例:
            Map<String, Map<String,String>> 年级 = {"三年一班" = 三年一班 , "三年二班" = 三年二班 , "三年三班" = 三年三班 }

 */
public class Test3 {
    public static void main(String[] args) {

        Map<String, String> 三年一班 = new HashMap<>();
        三年一班.put("it001", "迪丽热巴");
        三年一班.put("it002", "古力娜扎");
        三年一班.put("it003", "马尔扎哈");
        三年一班.put("it004", "欧阳娜娜");


        Map<String, String> 三年二班 = new HashMap<>();
        三年二班.put("it001", "李小璐");
        三年二班.put("it002", "白百何");
        三年二班.put("it003", "马蓉");

        Map<String, String> 三年三班 = new HashMap<>();
        三年三班.put("it001", "林丹");
        三年三班.put("it002", "文章");
        三年三班.put("it003", "陈赫");


        Map<String, Map<String, String>> 年级 = new HashMap<>();
        年级.put("三年一班", 三年一班);
        年级.put("三年二班", 三年二班);
        年级.put("三年三班", 三年三班);

        Set<String> 班级名字集合 = 年级.keySet();

        for (String 班级名字 : 班级名字集合) {
            Map<String, String> 班级信息 = 年级.get(班级名字);
            Set<String> 学生学号 = 班级信息.keySet();
            for (String 学号 : 学生学号) {
                String 姓名 = 班级信息.get(学号);
                System.out.println("班级名字:" + 班级名字 + " ,学号:" + 学号 + " , 名字:" + 姓名);
            }
            System.out.println("============");
        }

    }
}

7 斗地主案例

package com.itheima.doudizhu;

import java.util.*;

/*
    按照斗地主的规则,完成洗牌发牌的动作。
    要求完成以下功能:
    准备牌:组装54张扑克牌
    洗牌:54张牌顺序打乱
    发牌:三个玩家参与游戏,三人交替摸牌,每人17张牌,最后三张留作底牌。
    看牌:查看三人各自手中的牌(按照牌的大小排序)、底牌

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

 */
public class DouDiZhu {
    public static void main(String[] args) {
        // 准备牌
        // 键我牌的序号 , 值为牌面
        HashMap<Integer, String> pokers = new HashMap<>();

        // 牌的颜色
        String[] colors = {"♠", "♥", "♣", "♦"};
        String[] numbers = {"2", "A", "K", "Q", "J", "10", "9", "8", "7", "6", "5", "4", "3"};
        int count = 2;
        for (String number : numbers) {// 3
            for (String color : colors) {
                // System.out.println(count + " = " + color + number);
                pokers.put(count, color + number);
                count++;
            }
        }
        pokers.put(0, "大王");
        pokers.put(1, "小王");

        System.out.println(pokers);

        // 洗牌
        Set<Integer> set = pokers.keySet();
        // 创建存储编号的list集合
        List<Integer> list = new ArrayList<>();// {10 , 6 , 8 , 20 , 22 , 11 ... }
        // 把set集合中的元素存储到list集合中
        list.addAll(set);
        // 打乱集合中编号
        Collections.shuffle(list);
        System.out.println(list);

        // 发牌
        TreeSet<Integer> 赌神 = new TreeSet<>();
        TreeSet<Integer> 赌圣 = new TreeSet<>();
        TreeSet<Integer> 赌侠 = new TreeSet<>();
        TreeSet<Integer> 底牌 = new TreeSet<>();
        // 遍历牌的编号
        for (int i = 0; i < list.size() - 3; i++) {
            if (i % 3 == 0) {
                赌神.add(list.get(i));
            } else if (i % 3 == 1) {
                赌圣.add(list.get(i));
            } else {
                赌侠.add(list.get(i));
            }
        }

        底牌.add(list.get(51));
        底牌.add(list.get(52));
        底牌.add(list.get(53));
        System.out.println("赌神:" + 赌神);
        System.out.println("赌圣:" + 赌圣);
        System.out.println("赌侠:" + 赌侠);
        System.out.println("底牌:" + 底牌);


        // 看牌
        // 赌神
        lookPoker(pokers, 赌神, "赌神:  ");

        // 赌圣
        lookPoker(pokers, 赌圣, "赌圣:  ");

        // 赌侠
        lookPoker(pokers, 赌侠, "赌侠:  ");

        // 底牌
        System.out.print("底牌:  ");
        for (Integer integer : 底牌) {
            String poker = pokers.get(integer);
            System.out.print(poker + "\t");
        }

    }

    private static void lookPoker(HashMap<Integer, String> pokers, TreeSet<Integer> 赌神, String s) {
        System.out.print(s);
        for (Integer integer : 赌神) {
            String poker = pokers.get(integer);
            System.out.print(poker + "\t");
        }
        System.out.println();
    }
}

模块18.集合

模块17重点回顾:
  1.wait和notify
    a.wait:线程等待,在等待过程中释放锁,需要其他线程调用notify唤醒
    b.notify:唤醒一条等待的线程,如果多条线程等待,随机一条唤醒
    c.notifyAll:唤醒所有等待线程
  2.Lock:锁
    方法:lock()获取锁   unlock()释放锁
  3.线程池:Executors
    a.获取:static ExecutorService newFixedThreadPool(int nThread)
    b.提交线程任务:
      submit(Runnable r) 
      submit(Callable c)
    c.Future:接收run或者call方法的返回值
    d.shutDown()关闭线程池
  4.Callable:类似于Runnable
    a.方法:call()设置线程任务,类似于run方法,但是call可以throws异常,以及有返回值
    b.接收返回值:FutureTask 实现了Future接口
      get()获取call方法的返回值
        
模块18重点:
  1.知道集合的特点以及作用
  2.会使用Collection接口中的方法
  3.会使用迭代器迭代集合
  4.ArrayList以及LinkedList的使用
  5.会使用增强for遍历集合

第一章.集合框架(单列集合)

1.之前我们学了保存数据的有:变量,数组,但是数组定长,所以如果添加一个数据或者删除一个数据,数组并不好使,需要创建新数组,所以接下来我们学一个长度可变的容器,集合
    
2.集合的特点
  a.只能存储引用数据类型的数据
  b.长度可变
  c.集合中有大量的方法,方便我们操作
    
3.分类:
  a.单列集合:一个元素就一个组成部分:
    list.add("张三")
  b.双列集合:一个元素有两部分构成:  key 和 value
    map.put("涛哥","金莲") -> key,value叫做键值对  
    

第二章.Collection接口

1.概述:单列集合的顶级接口
2.使用:
  a.创建:
    Collection<E> 对象名 = new 实现类对象<E>()
  b.<E>:泛型,决定了集合中能存储什么类型的数据,可以统一元素类型
        泛型中只能写引用数据类型,如果不写,默认Object类型,此时什么类型的数据都可以存储了
        <int> 不行
        <Integer><Person> 行
      
  c.泛型细节:
    我们等号前面的泛型必须写,等号后面的泛型可以不写,jvm会根据前面的泛型推导出后面的泛型是啥
      
3.常用方法:
  boolean add(E e) : 将给定的元素添加到当前集合中(我们一般调add时,不用boolean接收,因为add一定会成功)
  boolean addAll(Collection<? extends E> c) :将另一个集合元素添加到当前集合中 (集合合并)
  void clear():清除集合中所有的元素
  boolean contains(Object o)  :判断当前集合中是否包含指定的元素
  boolean isEmpty() : 判断当前集合中是否有元素->判断集合是否为空
  boolean remove(Object o):将指定的元素从集合中删除
  int size() :返回集合中的元素个数。
  Object[] toArray(): 把集合中的元素,存储到数组中  
public class Demo01Collection {
    public static void main(String[] args) {
        Collection<String> collection = new ArrayList<>();
        //boolean add(E e) : 将给定的元素添加到当前集合中(我们一般调add时,不用boolean接收,因为add一定会成功)
        collection.add("萧炎");
        collection.add("萧薰儿");
        collection.add("彩鳞");
        collection.add("小医仙");
        collection.add("云韵");
        collection.add("涛哥");
        System.out.println(collection);
        //boolean addAll(Collection<? extends E> c) :将另一个集合元素添加到当前集合中 (集合合并)
        Collection<String> collection1 = new ArrayList<>();
        collection1.add("张无忌");
        collection1.add("小昭");
        collection1.add("赵敏");
        collection1.add("周芷若");
        collection1.addAll(collection);
        System.out.println(collection1);

        //void clear():清除集合中所有的元素
        collection1.clear();
        System.out.println(collection1);
        //boolean contains(Object o)  :判断当前集合中是否包含指定的元素
        boolean result01 = collection.contains("涛哥");
        System.out.println("result01 = " + result01);
        //boolean isEmpty() : 判断当前集合中是否有元素->判断集合是否为空
        System.out.println(collection1.isEmpty());
        //boolean remove(Object o):将指定的元素从集合中删除
        collection.remove("涛哥");
        System.out.println(collection);
        //int size() :返回集合中的元素个数。
        System.out.println(collection.size());
        //Object[] toArray(): 把集合中的元素,存储到数组中
        Object[] arr = collection.toArray();
        System.out.println(Arrays.toString(arr));
    }
}

第三章.迭代器

1.迭代器基本使用

1.概述:Iterator接口
2.主要作用:遍历集合
3.获取:Collection中的方法:
  Iterator<E> iterator()
4.方法:
  boolean hasNext()  -> 判断集合中有没有下一个元素
  E next()  ->获取下一个元素    
public class Demo01Iterator {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("楚雨荨");
        list.add("慕容云海");
        list.add("端木磊");
        list.add("上官瑞谦");
        list.add("叶烁");
        //获取迭代器对象
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()){
            String element = iterator.next();
            System.out.println(element);
        }
    }
}

注意:next方法在获取的时候不要连续使用多次

public class Demo02Iterator {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("楚雨荨");
        list.add("慕容云海");
        list.add("端木磊");
        list.add("上官瑞谦");
        list.add("叶烁");
        //获取迭代器对象
        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()){
            String element = iterator.next();
            System.out.println(element);
            //String element2 = iterator.next();
            //System.out.println(element2);
        }
    }
}

NoSuchElementException:没有可操作的元素异常

2.迭代器迭代过程

int cursor; //下一个元素索引位置
int lastRet = -1;//上一个元素索引位置

3.迭代器底层原理

1.获取Iterator的时候怎么获取的:
  Iterator iterator = list.iterator()
  我们知道Iterator是一个接口,等号右边一定是它的实现类对象
  问题:Iterator接收的到底是哪个实现类对象呢? -> ArrayList中的内部类Itr对象    

注意:只有ArrayList使用迭代器的时候Iterator接口才会指向Itr,其他的集合使用迭代器Iterator就指向的不是Itr了

HashSet<String> set = new HashSet<>();
Iterator<String> iterator1 = set.iterator();

4.并发修改异常

需求:定义一个集合,存储 唐僧,孙悟空,猪八戒,沙僧,遍历集合,如果遍历到猪八戒,往集合中添加一个白龙马
public class Demo03Iterator {
    public static void main(String[] args) {
        //需求:定义一个集合,存储 唐僧,孙悟空,猪八戒,沙僧,遍历集合,如果遍历到猪八戒,往集合中添加一个白龙马
        ArrayList<String> list = new ArrayList<>();
        list.add("唐僧");
        list.add("孙悟空");
        list.add("猪八戒");
        list.add("沙僧");

        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()){
            String element = iterator.next();
            if ("猪八戒".equals(element)){
                list.add("白龙马");
            }
        }
        System.out.println(list);
    }
}

String element = iterator.next();
=========================================
private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
    
        /*
          modCount: 实际操作次数
          expectedModCount:预期操作次数
        */
        int expectedModCount = modCount;
    
        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
        } 
    
        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
}    
结论:当预期操作次数和实际操作次数不相等了,会出现"并发修改异常"
我们干了什么事儿,让实际操作次数和预期操作次数不相等了
list.add("白龙马")
====================================
public boolean add(E e) {
    modCount++;//实际操作次数+1
}  
====================================
最终结论:我们调用了add方法,而add方法底层只给modCount++,但是再次调用next方法的时候,并没有给修改后的modCount重新赋值给expectedModCount,导致next方法底层的判断判断出实际操作次数和预期操作次数不相等了,所以抛出了"并发修改异常"    

ArrayList中的方法:ListIterator listIterator()

public class Demo03Iterator {
    public static void main(String[] args) {
        //需求:定义一个集合,存储 唐僧,孙悟空,猪八戒,沙僧,遍历集合,如果遍历到猪八戒,往集合中添加一个白龙马
        ArrayList<String> list = new ArrayList<>();
        list.add("唐僧");
        list.add("孙悟空");
        list.add("猪八戒");
        list.add("沙僧");

        //Iterator<String> iterator = list.iterator();
        ListIterator<String> listIterator = list.listIterator();
        while(listIterator.hasNext()){
            String element = listIterator.next();
            if ("猪八戒".equals(element)){
                listIterator.add("白龙马");
            }
        }
        System.out.println(list);
    }
}

使用迭代器迭代集合的过程中,不要随意修改集合长度,容易出现并发修改异常

第四章.数据结构

数据结构是一种具有一定逻辑关系,在计算机中应用某种存储结构,并且封装了相应操作的数据元素集合。它包含三方面的内容,逻辑关系、存储关系及操作。

为什么需要数据结构

随着应用程序变得越来越复杂和数据越来越丰富,几百万、几十亿甚至几百亿的数据就会出现,而对这么大对数据进行搜索、插入或者排序等的操作就越来越慢,数据结构就是用来解	决这些问题的。

数据的逻辑结构指反映数据元素之间的逻辑关系,而与他们在计算机中的存储位置无关:

  • 集合(数学中集合的概念):数据结构中的元素之间除了“同属一个集合” 的相互关系外,别无其他关系;
  • 线性结构:数据结构中的元素存在一对一的相互关系;
  • 树形结构:数据结构中的元素存在一对多的相互关系;
  • 图形结构:数据结构中的元素存在多对多的相互关系。

数据的物理结构/存储结构:是描述数据具体在内存中的存储(如:顺序结构、链式结构、索引结构、哈希结构)等,一种数据逻辑结构可表示成一种或多种物理存储结构。

数据结构是一门完整并且复杂的课程,那么我们今天只是简单的讨论常见的几种数据结构,让我们对数据结构与算法有一个初步的了解。

1.栈

1.特点:
  先进后出
2.好比:手枪压子弹      

2.队列

1.特点:先进先出
2.好比:过安检    

3.数组

1.特点:查询快,增删慢
2.查询快:因为有索引,我们可以直接通过索引操作元素
  增删慢:因为数组定长
        a.添加元素:创建新数组,将老数组中的元素复制到新数组中去,在最后添加新元素;要是从中间插入就更麻烦了
                  插入完新元素,后面的元素都要往后移动
        b.删除元素:创建新数组,将老数组中的元素复制到新数组中去,被删除的元素就不复制了;如果要是从之间删除
                  被删除的元素后面的元素都要往前移动

4.链表

1.在集合中涉及到了两种链表
2.单向链表
  a.节点:一个节点分为两部分
    第一部分:数据域(存数据)
    第二部分:指针域(保存下一个节点地址)
  b.特点:前面节点记录后面节点的地址,但是后面节点地址不记录前面节点地址
      
3.双向链表:
  a.节点:一个节点分为三部分
    第一部分:指针域(保存上一个节点地址)
    第二部分:数据域(保存的数据)
    第三部分:指针域(保存下一个节点地址)
  b.特点:
    前面节点记录后面节点地址,后面节点也记录前面节点地址
        
4.链表结构特点:查询慢,增删快        

4.1单向链表

a.节点:一个节点分为两部分
  第一部分:数据域(存数据)
  第二部分:指针域(保存下一个节点地址)
b.特点:前面节点记录后面节点的地址,但是后面节点地址不记录前面节点地址

4.2双向链表

  a.节点:一个节点分为三部分
    第一部分:指针域(保存上一个节点地址)
    第二部分:数据域(保存的数据)
    第三部分:指针域(保存下一个节点地址)
  b.特点:
    前面节点记录后面节点地址,后面节点也记录前面节点地址

第五章.List接口

1.概述:Collection接口的子接口
2.常见的实现类:
  ArrayList LinkedList Vector

第六章.List集合下的实现类

1.ArrayList集合

1.概述:ArrayListList接口的实现类
2.特点:
  a.元素有序-> 按照什么顺序存的,就按照什么顺序取
  b.元素可重复
  c.有索引-> 可以利用索引去操作元素
  d.线程不安全
      
3.数据结构:数组       
4.常用方法:
  boolean add(E e)  -> 将元素添加到集合中->尾部(add方法一定能添加成功的,所以我们不用boolean接收返回值)
  void add(int index, E element) ->在指定索引位置上添加元素
  boolean remove(Object o) ->删除指定的元素,删除成功为true,失败为false
  E remove(int index) -> 删除指定索引位置上的元素,返回的是被删除的那个元素
  E set(int index, E element) -> 将指定索引位置上的元素,修改成后面的element元素
  E get(int index) -> 根据索引获取元素
  int size()  -> 获取集合元素个数

1.1.ArrayList集合使用

public class Demo01ArrayList {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        //boolean add(E e)  -> 将元素添加到集合中->尾部(add方法一定能添加成功的,所以我们不用boolean接收返回值)
        list.add("铁胆火车侠");
        list.add("喜洋洋");
        list.add("火影忍者");
        list.add("灌篮高手");
        list.add("网球王子");
        System.out.println(list);
        //void add(int index, E element) ->在指定索引位置上添加元素
        list.add(2,"涛哥");
        System.out.println(list);
        //boolean remove(Object o) ->删除指定的元素,删除成功为true,失败为false
        list.remove("涛哥");
        System.out.println(list);
        //E remove(int index) -> 删除指定索引位置上的元素,返回的是被删除的那个元素
        String element = list.remove(0);
        System.out.println(element);
        System.out.println(list);
        //E set(int index, E element) -> 将指定索引位置上的元素,修改成后面的element元素
        String element2 = list.set(0, "金莲");
        System.out.println(element2);
        System.out.println(list);
        //E get(int index) -> 根据索引获取元素
        System.out.println(list.get(0));
        //int size()  -> 获取集合元素个数
        System.out.println(list.size());
    }
}

public class Demo02ArrayList {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("铁胆火车侠");
        list.add("喜洋洋");
        list.add("火影忍者");
        list.add("灌篮高手");
        list.add("网球王子");

        Iterator<String> iterator = list.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

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

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

        System.out.println("=====================");
        /*
           遍历带有索引集合的快捷键
             集合名.fori
         */
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }

    }
}

public class Demo03ArrayList {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<>();
        list.add(2);
        System.out.println(list);

        /*
          需求:删除2
          remove(Object o) -> 直接删除指定元素
          remove(int index) -> 删除指定索引位置上的元素

          如果remove中直接传递整数,默认调用按照指定索引删除元素的remove
          但是此时list中没有2索引,所以越界

          解决:我们可以将2包装成包装类,变成包装类之后,其父类就是Object了,


         */
        //list.remove(2);
        list.remove(Integer.valueOf(2));
        System.out.println(list);
    }
}

1.2.底层源码分析

1.ArrayList构造方法:
  a.ArrayList() 构造一个初始容量为十的空列表
  b.ArrayList(int initialCapacity) 构造具有指定初始容量的空列表 
      
2.ArrayList源码总结:
  a.不是一new底层就会创建初始容量为10的空列表,而是第一次add的时候才会创建初始化容量为10的空列表
  b.ArrayList底层是数组,那么为啥还说集合长度可变呢?
    ArrayList底层会自动扩容-> Arrays.copyOf    
  c.扩容多少倍?
    1.5
ArrayList() 构造一个初始容量为十的空列表
=========================================
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
Object[] elementData; ->ArrayList底层的那个数组
    
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}   

=========================================
list.add("a");

public boolean add(E e) {
    modCount++;
    add(e, elementData, size);// e->要存的元素  elementData->集合数组,长度开始为0,size->0
    return true;
}

private void add(E e->元素, Object[] elementData->集合数组, int s->0) {
    if (s == elementData.length)
        elementData = grow();
    elementData[s] = e;
    size = s + 1;
}

private Object[] grow() {
    return grow(size + 1);
}

private Object[] grow(int minCapacity->1) {
    int oldCapacity = elementData.length;//0
    if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        int newCapacity = ArraysSupport.newLength(oldCapacity,
                minCapacity - oldCapacity, /* minimum growth */
                oldCapacity >> 1           /* preferred growth */);
        return elementData = Arrays.copyOf(elementData, newCapacity);
    } else {
        return elementData = new Object[Math.max(DEFAULT_CAPACITY->10, minCapacity->1)];
    }
}
==========================================
假设ArrayList中存了第11个元素,会自动扩容-> Arrays.copyOf

private Object[] grow(int minCapacity) {//11
    int oldCapacity = elementData.length;//10
    if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        int newCapacity(15) = ArraysSupport.newLength(oldCapacity->10,
                minCapacity - oldCapacity->1, /* minimum growth */
                oldCapacity >> 1 ->5          /* preferred growth */);
        return elementData = Arrays.copyOf(elementData, newCapacity);
    } else {
        return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)];
    }
}    


public static int newLength(int oldLength->10, int minGrowth->1, int prefGrowth->5) {
        // preconditions not checked because of inlining
        // assert oldLength >= 0
        // assert minGrowth > 0

        int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // 15
        if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
            return prefLength;
        } else {
            // put code cold in a separate method
            return hugeLength(oldLength, minGrowth);
        }
}
 ArrayList(int initialCapacity) 构造具有指定初始容量的空列表 
ArrayList<String> list = new ArrayList<>(5);
==============================================
public ArrayList(int initialCapacity->5) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];//直接创建长度为5的数组
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}    

ArrayList list = new ArrayList() -> 现在我们想用都是new

但是将来开发不会想使用就new集合,都是调用一个方法,查询出很多数据来,此方法返回一个集合,自动将查询出来的数据放到集合中,我们想在页面上展示数据,遍历集合

而且将来调用方法,返回的集合类型,一般都是接口类型

List<泛型> list = 对象.查询方法()

第七章.LinkedList集合

1.概述:LinkedListList接口的实现类
2.特点:
  a.元素有序
  b.元素可重复
  c.有索引 -> 这里说的有索引仅仅指的是有操作索引的方法,不代表本质上具有索引
  d.线程不安全
      
3.数据结构:双向链表      
    
4.方法:有大量直接操作首尾元素的方法
  - public void addFirst(E e):将指定元素插入此列表的开头。
  - public void addLast(E e):将指定元素添加到此列表的结尾。
  - public E getFirst():返回此列表的第一个元素。
  - public E getLast():返回此列表的最后一个元素。
  - public E removeFirst():移除并返回此列表的第一个元素。
  - public E removeLast():移除并返回此列表的最后一个元素。
  - public E pop():从此列表所表示的堆栈处弹出一个元素。
  - public void push(E e):将元素推入此列表所表示的堆栈。
  - public boolean isEmpty():如果列表没有元素,则返回true
public class Demo05LinkedList {
    public static void main(String[] args) {
        LinkedList<String> linkedList = new LinkedList<>();
        linkedList.add("吕布");
        linkedList.add("刘备");
        linkedList.add("关羽");
        linkedList.add("张飞");
        linkedList.add("貂蝉");
        System.out.println(linkedList);

        linkedList.addFirst("孙尚香");
        System.out.println(linkedList);

        linkedList.addLast("董卓");
        System.out.println(linkedList);

        System.out.println(linkedList.getFirst());
        System.out.println(linkedList.getLast());

        linkedList.removeFirst();
        System.out.println(linkedList);

        linkedList.removeLast();
        System.out.println(linkedList);

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

        Iterator<String> iterator = linkedList.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

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

public E pop():从此列表所表示的堆栈处弹出一个元素。
public void push(E e):将元素推入此列表所表示的堆栈。
public class Demo06LinkedList {
    public static void main(String[] args) {
        LinkedList<String> linkedList = new LinkedList<>();
        linkedList.add("吕布");
        linkedList.add("刘备");
        linkedList.add("关羽");
        linkedList.add("张飞");
        linkedList.add("貂蝉");

        //public E pop():从此列表所表示的堆栈处弹出一个元素。
        linkedList.pop();
        System.out.println(linkedList);
        //public void push(E e):将元素推入此列表所表示的堆栈。
        linkedList.push("涛哥");
        System.out.println(linkedList);
    }
}

1.1 LinkedList底层成员解释说明

1.LinkedList底层成员
  transient int size = 0;  元素个数
  transient Node<E> first; 第一个节点对象
  transient Node<E> last;  最后一个节点对象
  
2.Node代表的是节点对象
   private static class Node<E> {
        E item;//节点上的元素
        Node<E> next;//记录着下一个节点地址
        Node<E> prev;//记录着上一个节点地址

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

1.2 LinkedList中add方法源码分析

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

void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
}

1.3.LinkedList中get方法源码分析

public E get(int index) {
    checkElementIndex(index);
    return node(index).item;
} 

Node<E> node(int index) {
    // assert isElementIndex(index);

    if (index < (size >> 1)) {
        Node<E> x = first;
        for (int i = 0; i < index; i++)
            x = x.next;
        return x;
    } else {
        Node<E> x = last;
        for (int i = size - 1; i > index; i--)
            x = x.prev;
        return x;
    }
}
index < (size >> 1)采用二分思想,先将index与长度size的一半比较,如果index<size/2,就只从位置0往后遍历到位置index处,而如果index>size/2,就只从位置size往前遍历到位置index处。这样可以减少一部分不必要的遍历

第八章.增强for

1.基本使用

1.作用:
  遍历集合或者数组
2.格式:
  for(元素类型 变量名:要遍历的集合名或者数组名){
      变量名就是代表的每一个元素
  }

3.快捷键:集合名或者数组名.for
public class Demo01ForEach {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("张三");
        list.add("李四");
        list.add("王五");
        list.add("赵六");
        for (String s : list) {
            System.out.println(s);
        }

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

        int[] arr = {1,2,3,4,5};
        for (int i : arr) {
            System.out.println(i);
        }
    }
}

2.注意

1.增强for遍历集合时,底层实现原理为迭代器
2.增强for遍历数组时,底层实现原理为普通for

所以不管是用迭代器还是使用增强for,在遍历集合的过程中都不要随意修改集合长度,否则会出现并发修改异常

模块19.集合

模块18回顾:
  1.Collection集合:单列集合的顶级接口
    a.add  addAll  clear size  isEmpty  remove  toArray  contains
  2.迭代器:Iterator
    a.获取:iterator方法
    b.方法:
      hasNext()
      next()
    c.并发修改异常:在迭代集合的时候,不能随意修改集合长度
      原因:调用add,只给实际操作次数+1.后面调用next的时候,没有给预期操作次数重新赋值,导致预期操作次数和实际操作次数不相等了
          
   3.数据结构::先进后出
     队列:先进先出
     数组:查询快,增删慢
     链表:查询慢,增删快
         
   4.ArrayList
     a.特点:
       元素有序,有索引,元素可重复,线程不安全
     b.数据结构:
       数组
     c.方法:
       add(元素)直接在最后添加元素
       add(索引,元素)在指定索引位置上添加元素
       remove(元素)删除指定元素
       remove(索引)按照指定索引删除元素
       size()获取元素个数
       get(索引)根据索引获取元素
       set(索引,元素)将指定索引位置上的元素修改成我们指定的元素
     d.利用无参构造创建集合对象,第一次add时,会创建一个长度为10的空数组
       超出范围,自动扩容->Arrays.copyOf
       扩容1.55.LinkedList
     a.特点: 元素有序 有索引(java提供了按照索引操作元素的方法,并不代表本质上拥有索引),元素可重复,线程不安全
         
     b.数据结构:双向链表
     c.方法:有大量直接操作收尾元素的方法
         
   6.增强for:
     a.格式:
       for(元素类型 变量名:集合名或者数组名){
           变量就代表每一个元素
       }

     b.原理:
       遍历集合时,原理为迭代器
       遍历数组时,原理为普通for
           
模块19重点:
  1.Collections集合工具类的常用方法
  2.会使用泛型
  3.知道HashSetLinkedHashSet的特点以及使用
  4.知道HashSet将元素去重复的过程       

第一章.Collections集合工具类

1.概述:集合工具类
2.特点:
  a.构造私有
  b.方法都是静态的
      
3.使用:类名直接调用
    
4.方法:
  static <T> boolean addAll(Collection<? super T> c, T... elements)->批量添加元素 
  static void shuffle(List<?> list) ->将集合中的元素顺序打乱
  static <T> void sort(List<T> list) ->将集合中的元素按照默认规则排序
  static <T> void sort(List<T> list, Comparator<? super T> c)->将集合中的元素按照指定规则排序 
public class Demo01Collections {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        //static <T> boolean addAll(Collection<? super T> c, T... elements)->批量添加元素
        Collections.addAll(list,"张三","李四","王五","赵六","田七","朱八");
        System.out.println(list);
        //static void shuffle(List<?> list) ->将集合中的元素顺序打乱
        Collections.shuffle(list);
        System.out.println(list);
        //static <T> void sort(List<T> list) ->将集合中的元素按照默认规则排序-> ASCII码表
        ArrayList<String> list1 = new ArrayList<>();
        list1.add("c.举头望明月");
        list1.add("a.床前明月光");
        list1.add("d.低头思故乡");
        list1.add("b.疑是地上霜");
        Collections.sort(list1);
        System.out.println(list1);
    }
}

1.方法:static <T> void sort(List<T> list, Comparator<? super T> c)->将集合中的元素按照指定规则排序
    
2.Comparator比较器
  a.方法:
    int compare(T o1,T o2)
                o1-o2 -> 升序
                o2-o1 -> 降序    
public class Person {
    private String name;
    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

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

public class Demo02Collections {
    public static void main(String[] args) {
        ArrayList<Person> list = new ArrayList<>();
        list.add(new Person("柳岩",18));
        list.add(new Person("涛哥",16));
        list.add(new Person("金莲",20));

        Collections.sort(list, new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge()-o2.getAge();
            }
        });

        System.out.println(list);
    }
}

1.接口:Comparable接口
2.方法: int compareTo(T o) -> this-o (升序)   o-this(降序)
public class Student implements Comparable<Student>{
    private String name;
    private Integer score;

    public Student() {
    }

    public Student(String name, Integer score) {
        this.name = name;
        this.score = score;
    }

    public String getName() {
        return name;
    }

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

    public Integer getScore() {
        return score;
    }

    public void setScore(Integer score) {
        this.score = score;
    }

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

    @Override
    public int compareTo(Student o) {
        return this.getScore()-o.getScore();
    }
}

public class Demo03Collections {
    public static void main(String[] args) {
        ArrayList<Student> list = new ArrayList<>();
        list.add(new Student("涛哥",100));
        list.add(new Student("柳岩",150));
        list.add(new Student("三上",80));
        Collections.sort(list);
        System.out.println(list);
    }
}

Arrays中的静态方法:
  static <T> List<T> asList(T...a) -> 直接指定元素,转存到list集合中
public class Demo04Collections {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("张三", "李四", "王五");
        System.out.println(list);
    }
}

第二章.泛型

1.泛型:<>
2.作用:
  统一数据类型,防止将来的数据转换异常
3.注意:
  a.泛型中的类型必须是引用类型
  b.如果泛型不写,默认类型为Object    

1.为什么要使用泛型

1.从使用层面上来说:
  统一数据类型,防止将来的数据类型转换异常
2.从定义层面上来看:
  定义带泛型的类,方法等,将来使用的时候给泛型确定什么类型,泛型就会变成什么类型,凡是涉及到泛型的都会变成确定的类型,代码更灵活
public class Demo01Genericity {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add("1");
        list.add(1);
        list.add("abc");
        list.add(2.5);
        list.add(true);

        //获取元素中为String类型的字符串长度
        for (Object o : list) {
            String s = (String) o;
            System.out.println(s.length());//ClassCastException
           
        }
    }
}

![](https://gitee.com/zdawdaw/img/raw/master/202407141655484.png" alt=“1706504536249” style=“zoom:80%;” />

2.泛型的定义

2.1含有泛型的类

1.定义:
   public class 类名<E>{
       
   }

2.什么时候确定类型
  new对象的时候确定类型  
public class MyArrayList <E>{
    //定义一个数组,充当ArrayList底层的数组,长度直接规定为10
    Object[] obj = new Object[10];
    //定义size,代表集合元素个数
    int size;

    /**
     * 定义一个add方法,参数类型需要和泛型类型保持一致
     *
     * 数据类型为E  变量名随便取
     */
    public boolean add(E e){
        obj[size] = e;
        size++;
        return true;
    }

    /**
     * 定义一个get方法,根据索引获取元素
     */
    public E get(int index){
        return (E) obj[index];
    }

    @Override
    public String toString() {
        return Arrays.toString(obj);
    }
}
public class Demo02Genericity {
    public static void main(String[] args) {
        MyArrayList<String> list1 = new MyArrayList<>();
        list1.add("aaa");
        list1.add("bbb");
        System.out.println(list1);//直接输出对象名,默认调用toString

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

        MyArrayList<Integer> list2 = new MyArrayList<>();
        list2.add(1);
        list2.add(2);
        Integer element = list2.get(0);
        System.out.println(element);
        System.out.println(list2);
    }
}

2.2含有泛型的方法

1.格式:
  修饰符 <E> 返回值类型 方法名(E e)
      
2.什么时候确定类型
  调用的时候确定类型
public class ListUtils {
    //定义一个静态方法addAll,添加多个集合的元素
    public static <E> void addAll(ArrayList<E> list,E...e){
        for (E element : e) {
            list.add(element);
        }
    }

}
public class Demo03Genericity {
    public static void main(String[] args) {
        ArrayList<String> list1 = new ArrayList<>();
        ListUtils.addAll(list1,"a","b","c");
        System.out.println(list1);

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

        ArrayList<Integer> list2 = new ArrayList<>();
        ListUtils.addAll(list2,1,2,3,4,5);
        System.out.println(list2);
    }
}

2.3含有泛型的接口

1.格式:
  public interface 接口名<E>{
      
  }
2.什么时候确定类型:
  a.在实现类的时候还没有确定类型,只能在new实现类的时候确定类型了 ->比如 ArrayList
  b.在实现类的时候直接确定类型了 -> 比如Scanner    
public interface MyList <E>{
    public boolean add(E e);
}
public class MyArrayList1<E> implements MyList<E>{
    //定义一个数组,充当ArrayList底层的数组,长度直接规定为10
    Object[] obj = new Object[10];
    //定义size,代表集合元素个数
    int size;

    /**
     * 定义一个add方法,参数类型需要和泛型类型保持一致
     *
     * 数据类型为E  变量名随便取
     */
    public boolean add(E e){
        obj[size] = e;
        size++;
        return true;
    }

    /**
     * 定义一个get方法,根据索引获取元素
     */
    public E get(int index){
        return (E) obj[index];
    }

    @Override
    public String toString() {
        return Arrays.toString(obj);
    }
}

public class Demo04Genericity {
    public static void main(String[] args) {
        MyArrayList1<String> list1 = new MyArrayList1<>();
        list1.add("张三");
        list1.add("李四");
        System.out.println(list1.get(0));

    }
}
public interface MyIterator <E>{
    E next();
}

public class MyScanner implements MyIterator<String>{
    @Override
    public String next() {
        return "涛哥和金莲的故事";
    }
}

public class Demo05Genericity {
    public static void main(String[] args) {
        MyScanner myScanner = new MyScanner();
        String result = myScanner.next();
        System.out.println("result = " + result);
    }
}

3.泛型的高级使用

3.1 泛型通配符 ?

public class Demo01Genericity {
    public static void main(String[] args) {
        ArrayList<String> list1 = new ArrayList<>();
        list1.add("张三");
        list1.add("李四");

        ArrayList<Integer> list2 = new ArrayList<>();
        list2.add(1);
        list2.add(2);
        
        method(list1);
        method(list2);
    }
    
    public static void method(ArrayList<?> list){
        for (Object o : list) {
            System.out.println(o);
        }
    }

}

3.2 泛型的上限下限

1.作用:可以规定泛型的范围
2.上限:
  a.格式:<? extends 类型>
  b.含义:?只能接收extends后面的本类类型以及子类类型    
3.下限:
  a.格式:<? super 类型>
  b.含义:?只能接收super后面的本类类型以及父类类型    
/**
 * Integer -> Number -> Object
 * String -> Object
 */
public class Demo02Genericity {
    public static void main(String[] args) {
        ArrayList<Integer> list1 = new ArrayList<>();
        ArrayList<String> list2 = new ArrayList<>();
        ArrayList<Number> list3 = new ArrayList<>();
        ArrayList<Object> list4 = new ArrayList<>();

        get1(list1);
        //get1(list2);错误
        get1(list3);
        //get1(list4);错误

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

        //get2(list1);错误
        //get2(list2);错误
        get2(list3);
        get2(list4);
    }

    //上限  ?只能接收extends后面的本类类型以及子类类型
    public static void get1(Collection<? extends Number> collection){

    }

    //下限  ?只能接收super后面的本类类型以及父类类型
    public static void get2(Collection<? super Number> collection){

    }
}

应用场景:

1.如果我们在定义类,方法,接口的时候,如果类型不确定,我们可以考虑定义含有泛型的类,方法,接口

2.如果类型不确定,但是能知道以后只能传递某个类的继承体系中的子类或者父类,就可以使用泛型的通配符

第三章.斗地主案例(扩展案例)

3.1 案例介绍

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

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

3.2 案例分析

  • 准备牌:

    牌可以设计为一个ArrayList,每个字符串为一张牌。
    每张牌由花色数字两部分组成,我们可以使用花色集合与数字集合嵌套迭代完成每张牌的组装。
    牌由Collections类的shuffle方法进行随机排序。

  • 发牌

    将每个人以及底牌设计为ArrayList,将最后3张牌直接存放于底牌,剩余牌通过对3取模依次发牌。

  • 看牌

    直接打印每个集合。

3.3 代码实现

1.创建ArrayList集合-> color -> 专门存花色
2.创建一个ArrayList集合 -> number -> 专门存牌号
3.创建一个ArrayList集合 -> poker -> 专门存花色和牌号组合好的牌面
4.打乱poker
5.创建4ArrayList集合,分别代表三个玩家,以及存储一个底牌
6.如果index为最后三张,往dipai集合中存
7.如果index%3==0 给p1
8.如果index%3==1 给p2
9.如果index%3==2 给p3
10.遍历看牌
public class Poker {
    public static void main(String[] args) {
        //1.创建ArrayList集合-> color -> 专门存花色
        ArrayList<String> color = new ArrayList<>();
        //2.创建一个ArrayList集合 -> number -> 专门存牌号
        ArrayList<String> number = new ArrayList<>();
        //3.创建一个ArrayList集合 -> poker -> 专门存花色和牌号组合好的牌面
        ArrayList<String> poker = new ArrayList<>();

        color.add("♠");
        color.add("♥");
        color.add("♣");
        color.add("♦");

        for (int i = 2; i <= 10; i++) {
            number.add(i + "");
        }

        number.add("J");
        number.add("Q");
        number.add("K");
        number.add("A");

        //System.out.println(color);
        //System.out.println(number);

        for (String num : number) {
            for (String huaSe : color) {
                String pokerNumber = huaSe + num;
                poker.add(pokerNumber);
            }
        }

        poker.add("😊");
        poker.add("☺");
        //System.out.println(poker);

        //4.打乱poker
        Collections.shuffle(poker);
        //System.out.println(poker);
        //5.创建4个ArrayList集合,分别代表三个玩家,以及存储一个底牌
        ArrayList<String> p1 = new ArrayList<>();
        ArrayList<String> p2 = new ArrayList<>();
        ArrayList<String> p3 = new ArrayList<>();
        ArrayList<String> dipai = new ArrayList<>();
        for (int i = 0; i < poker.size(); i++) {
            String s = poker.get(i);
            //6.如果index为最后三张,往dipai集合中存
            if (i >= 51) {
                dipai.add(s);
                //7.如果index%3==0 给p1
            } else if (i % 3 == 0) {
                p1.add(s);
                //8.如果index%3==1 给p2
            } else if (i % 3 == 1) {
                p2.add(s);
                //9.如果index%3==2 给p3
            }else if (i%3==2){
                p3.add(s);
            }
        }

        //10.遍历看牌
        System.out.println("涛哥:"+p1);
        System.out.println("三上:"+p2);
        System.out.println("金莲:"+p3);
        System.out.println("底牌:"+dipai);
    }
}
public class Poker1 {
    public static void main(String[] args) {
        //1.创建数组-> color -> 专门存花色
        String[] color = "♠-♥-♣-♦".split("-");
        //2.创建数组 -> number -> 专门存牌号
        String[] number = "2-3-4-5-6-7-8-9-10-J-Q-K-A".split("-");
        //3.创建一个ArrayList集合 -> poker -> 专门存花色和牌号组合好的牌面
        ArrayList<String> poker = new ArrayList<>();

        //System.out.println(color);
        //System.out.println(number);

        for (String num : number) {
            for (String huaSe : color) {
                String pokerNumber = huaSe + num;
                poker.add(pokerNumber);
            }
        }

        poker.add("😊");
        poker.add("☺");
        //System.out.println(poker);

        //4.打乱poker
        Collections.shuffle(poker);
        //System.out.println(poker);
        //5.创建4个ArrayList集合,分别代表三个玩家,以及存储一个底牌
        ArrayList<String> p1 = new ArrayList<>();
        ArrayList<String> p2 = new ArrayList<>();
        ArrayList<String> p3 = new ArrayList<>();
        ArrayList<String> dipai = new ArrayList<>();
        for (int i = 0; i < poker.size(); i++) {
            String s = poker.get(i);
            //6.如果index为最后三张,往dipai集合中存
            if (i >= 51) {
                dipai.add(s);
                //7.如果index%3==0 给p1
            } else if (i % 3 == 0) {
                p1.add(s);
                //8.如果index%3==1 给p2
            } else if (i % 3 == 1) {
                p2.add(s);
                //9.如果index%3==2 给p3
            }else if (i%3==2){
                p3.add(s);
            }
        }

        //10.遍历看牌
        System.out.println("涛哥:"+p1);
        System.out.println("三上:"+p2);
        System.out.println("金莲:"+p3);
        System.out.println("底牌:"+dipai);
    }
}

第四章.红黑树(了解)

集合加入红黑树的目的:提高查询效率
HashSet集合:
  数据结构:哈希表
      jdk8之前:哈希表 = 数组+链表
      jdk8之后:哈希表 = 数组+链表+红黑树 ->为的是查询效率    

1. 每一个节点或是红色的,或者是黑色的

2. 根节点必须是黑色

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

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

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

https://www.cs.usfca.edu/~galles/visualization/RedBlack

第五章.Set集合

1.涛哥总说:Set接口并没有对Collection接口进行功能上的扩充,而且所有的Set集合底层都是依靠Map实现

1.Set集合介绍

SetMap密切相关的
Map的遍历需要先变成单列集合,只能变成set集合

2.HashSet集合的介绍和使用

1.概述:HashSetSet接口的实现类
2.特点:
  a.元素唯一
  b.元素无序
  c.无索引
  d.线程不安全
3.数据结构:哈希表
  a.jdk8之前:哈希表 = 数组+链表
  b.jdk8之后:哈希表 = 数组+链表+红黑树
            加入红黑树目的:查询快
4.方法:Collection一样
5.遍历:
  a.增强for
  b.迭代器
public class Demo01HashSet {
    public static void main(String[] args) {
        HashSet<String> set = new HashSet<>();
        set.add("张三");
        set.add("李四");
        set.add("王五");
        set.add("赵六");
        set.add("田七");
        set.add("张三");
        System.out.println(set);

        //迭代器
        Iterator<String> iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

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

        //增强for
        for (String s : set) {
            System.out.println(s);
        }
    }
}

3.LinkedHashSet的介绍以及使用

1.概述:LinkedHashSet extends HashSet
2.特点:
  a.元素唯一
  b.元素有序
  c.无索引
  d.线程不安全
3.数据结构:
  哈希表+双向链表
4.使用:HashSet一样      
public class Demo02LinkedHashSet {
    public static void main(String[] args) {
        LinkedHashSet<String> set = new LinkedHashSet<>();
        set.add("张三");
        set.add("李四");
        set.add("王五");
        set.add("赵六");
        set.add("田七");
        set.add("张三");
        System.out.println(set);

        //迭代器
        Iterator<String> iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

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

        //增强for
        for (String s : set) {
            System.out.println(s);
        }
    }
}

4.哈希值

1.概述:是由计算机算出来的一个十进制数,可以看做是对象的地址值
2.获取对象的哈希值,使用的是Object中的方法
  public native int hashCode()
    
3.注意:如果重写了hashCode方法,那计算的就是对象内容的哈希值了
    
4.总结:
  a.哈希值不一样,内容肯定不一样
  b.哈希值一样,内容也有可能不一样    
public class Person {
    private String name;
    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

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

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

public class Demo01Hash {
    public static void main(String[] args) {
        Person p1 = new Person("涛哥", 18);
        Person p2 = new Person("涛哥", 18);
        System.out.println(p1);//com.atguigu.f_hash.Person@4eec7777
        System.out.println(p2);//com.atguigu.f_hash.Person@3b07d329

        System.out.println(p1.hashCode());
        System.out.println(p2.hashCode());

        //System.out.println(Integer.toHexString(1324119927));//4eec7777
        //System.out.println(Integer.toHexString(990368553));//3b07d329

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

        String s1 = "abc";
        String s2 = new String("abc");
        System.out.println(s1.hashCode());//96354
        System.out.println(s2.hashCode());//96354

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

        String s3 = "通话";
        String s4 = "重地";
        System.out.println(s3.hashCode());//1179395
        System.out.println(s4.hashCode());//1179395

    }
}

如果不重写hashCode方法,默认计算对象的哈希值
如果重写了hashCode方法,计算的是对象内容的哈希值

5.字符串的哈希值时如何算出来的

String s = "abc"
byte[] value = {97,98,99}    
public int hashCode() {
    int h = hash;
    if (h == 0 && !hashIsZero) {
        h = isLatin1() ? StringLatin1.hashCode(value)
                       : StringUTF16.hashCode(value);
        if (h == 0) {
            hashIsZero = true;
        } else {
            hash = h;
        }
    }
    return h;
}
====================================================
StringLatin1.hashCode(value)底层源码,String中的哈希算法
    
public static int hashCode(byte[] value) {
    int h = 0;
    for (byte v : value) {
        h = 31 * h + (v & 0xff);
    }
    return h;
}
直接跑到StringLatin1.hashCode(value)底层源码,计算abc的哈希值-> 0xff这个十六进制对应的十进制255
任何数据和255&运算,都是原值
    
第一圈:
  h = 31*0+97 = 97
第二圈:
  h = 31*97+98 = 3105
第三圈:
  h = 31*3105+99 = 96354
问题:在计算哈希值的时候,有一个定值就是31,为啥?
     31是一个质数,31这个数通过大量的计算,统计,认为用31,可以尽量降低内容不一样但是哈希值一样的情况
    
     内容不一样,哈希值一样(哈希冲突,哈希碰撞)

6.HashSet的存储去重复的过程

1.先计算元素的哈希值(重写hashCode方法),再比较内容(重写equals方法)
2.先比较哈希值,如果哈希值不一样,3.如果哈希值一样,再比较内容
  a.如果哈希值一样,内容不一样,存
  b.如果哈希值一样,内容也一样,去重复  
public class Test02 {
    public static void main(String[] args) {
        HashSet<String> set = new HashSet<>();
        set.add("abc");
        set.add("通话");
        set.add("重地");
        set.add("abc");
        System.out.println(set);//[通话, 重地, abc]

    }
}

7.HashSet存储自定义类型如何去重复

public class Person {
    private String name;
    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

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

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

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

public class Test03 {
    public static void main(String[] args) {
        HashSet<Person> set = new HashSet<>();
        set.add(new Person("涛哥",16));
        set.add(new Person("金莲",24));
        set.add(new Person("涛哥",16));
        System.out.println(set);
    }
}
总结:
1.如果HashSet存储自定义类型,如何去重复呢?重写hashCode和equals方法,HashSet比较属性的哈希值以及属性的内容
2.如果不重写hashCode和equals方法,默认调用的是Object中的,不同的对象,肯定哈希值不一样,equals比较对象的地址值也不一样,所以此时即使对象的属性值一样,也不能去重复    

Map嵌套Map

- JavaSE  集合 存储的是 学号 键,值学生姓名
  - 1  张三
  - 2  李四
- JavaEE 集合 存储的是 学号 键,值学生姓名
  - 1  王五
  - 2  赵六
public class Demo03MapInMap {
    public static void main(String[] args) {
        //1.创建两个map集合
        HashMap<Integer, String> map1 = new HashMap<>();
        map1.put(1,"张三");
        map1.put(2,"李四");

        HashMap<Integer, String> map2 = new HashMap<>();
        map2.put(1,"王五");
        map2.put(2,"赵六");

        HashMap<String, HashMap<Integer, String>> map = new HashMap<>();
        map.put("javase",map1);
        map.put("javaee",map2);

        Set<Map.Entry<String, HashMap<Integer, String>>> set = map.entrySet();
        for (Map.Entry<String, HashMap<Integer, String>> entry : set) {
            HashMap<Integer, String> hashMap = entry.getValue();
            Set<Integer> set1 = hashMap.keySet();
            for (Integer key : set1) {
                System.out.println(key+"..."+hashMap.get(key));
            }
        }
    }
}

模块20.Map集合

模块19回顾:
  1.Collections集合工具类
    方法:
        addAll-> 批量添加元素
        shuffle-> 元素打乱
        sort->排序-> ascii
        sort(集合,比较器)-> 按照指定的顺序排序
  2.泛型:
    a.含有泛型的类:
      public class 类名<E>{}
      new对象的时候确定类型
    b.含有泛型的方法:
      修饰符 <E> 返回值类型 方法名(E e){}
      调用的时候确定类型
    c.含有泛型的接口
      public interface 接口名<E>{}
      在实现类的时候确定类型
      在实现类的时候还没有确定类型,只能new对象的时候确定
    d.泛型通配符
      <? extends 类型> ?接收的泛型类型是后面类的本类以及子类
      <? super 类型> ?接收的泛型类型是后面类的本类以及父类
          
  3.哈希值:计算机计算出来的十进制数,可以看成是对象的地址值
    a.要是没有重写hashCode方法,默认调用Object中的hashCode方法,计算的是对象的哈希值
    b.要是重写了hashCode方法,计算的是对象内容的哈希值
  4.HashSet集合
    特点:  元素唯一  无序 无索引 线程不安全
    数据结构: 哈希表 = 数组+链表+红黑树
   
  5.LinkedHashSet
    特点:元素唯一  有序 无索引 线程不安全  
    数据结构: 哈希表+双向链表
  6.set存储自定义对象怎么去重复 -> 重写hashCode和equals方法
        
  7.去重复过程:先比较元素哈希值,再比较内容
    如果哈希值不一样,存
    如果哈希值一样,再比较内容->哈希值一样,内容不一样,;哈希值一样,内容一样,去重复
        
模块20重点:
  1.会使用HashMapLinkedHashMap以及知道他们的特点
  2.会使用Properties属性集
  3.会操作集合嵌套
  4.知道哈希表结构存储元素过程    

第一章.Map集合

1.Map的介绍

1.概述:是双列集合的顶级接口
2.元素特点:
  元素都是由key(),value()组成 -> 键值对

2.HashMap的介绍和使用

1.概述:HashMapMap的实现类
2.特点:
  a.key唯一,value可重复 -> 如果key重复了,会发生value覆盖
  b.无序
  c.无索引
  d.线程不安全
  e.可以存nullnull3.数据结构:
  哈希表
4.方法:
  V put(K key, V value)  -> 添加元素,返回的是
  V remove(Object key)  ->根据key删除键值对,返回的是被删除的value
  V get(Object key) -> 根据key获取value
  boolean containsKey(Object key)  -> 判断集合中是否包含指定的key
  Collection<V> values() -> 获取集合中所有的value,转存到Collection集合中
      
  Set<K> keySet()->Map中的key获取出来,转存到Set集合中  
  Set<Map.Entry<K,V>> entrySet()->获取Map集合中的键值对,转存到Set集合中
public class Demo01HashMap {
    public static void main(String[] args) {
        HashMap<String, String> map = new HashMap<>();
        //V put(K key, V value)  -> 添加元素,返回的是被覆盖的value
        String value1 = map.put("猪八", "嫦娥");
        System.out.println(value1);
        String value2 = map.put("猪八", "高翠兰");
        System.out.println(value2);
        System.out.println(map);

        map.put("后裔","嫦娥");
        map.put("二郎神","嫦娥");
        map.put("唐僧","女儿国国王");
        map.put("涛哥","金莲");
        map.put(null,null);
        System.out.println(map);

        //V remove(Object key)  ->根据key删除键值对,返回的是被删除的value
        String value3 = map.remove("涛哥");
        System.out.println(value3);
        System.out.println(map);
        //V get(Object key) -> 根据key获取value
        System.out.println(map.get("唐僧"));
        //boolean containsKey(Object key)  -> 判断集合中是否包含指定的key
        System.out.println(map.containsKey("二郎神"));
        //Collection<V> values() -> 获取集合中所有的value,转存到Collection集合中
        Collection<String> collection = map.values();
        System.out.println(collection);
    }
}

1.概述:LinkedHashMap extends HashMap
2.特点:
  a.key唯一,value可重复 -> 如果key重复了,会发生value覆盖
  b.有序
  c.无索引
  d.线程不安全
  e.可以存nullnull3.数据结构:
  哈希表+双向链表
4.使用:HashMap一样      
public class Demo02LinkedHashMap {
    public static void main(String[] args) {
        LinkedHashMap<String, String> map = new LinkedHashMap<>();
        map.put("八戒","嫦娥");
        map.put("涛哥","金莲");
        map.put("涛哥","三上");
        map.put("唐僧","女儿国国王");
        System.out.println(map);
    }
}

3.HashMap的两种遍历方式

3.1.方式1:获取key,根据key再获取value

Set<K> keySet()->Map中的key获取出来,转存到Set集合中  
public class Demo03HashMap {
    public static void main(String[] args) {
        HashMap<String, String> map = new HashMap<>();
        map.put("猪八", "嫦娥");
        map.put("猪八", "高翠兰");
        map.put("后裔","嫦娥");
        map.put("二郎神","嫦娥");
        map.put("唐僧","女儿国国王");
        map.put("涛哥","金莲");

        Set<String> set = map.keySet();//获取所有的key,保存到set集合中
        for (String key : set) {
            //根据key获取value
            System.out.println(key+".."+map.get(key));
        }

    }
}

3.2.方式2:同时获取key和value

Set<Map.Entry<K,V>> entrySet()->获取Map集合中的键值对,转存到Set集合中
public class Demo04HashMap {
    public static void main(String[] args) {
        HashMap<String, String> map = new HashMap<>();
        map.put("猪八", "嫦娥");
        map.put("猪八", "高翠兰");
        map.put("后裔","嫦娥");
        map.put("二郎神","嫦娥");
        map.put("唐僧","女儿国国王");
        map.put("涛哥","金莲");

        /*
          Set集合中保存的都是"结婚证"-> Map.Entry
          我们需要将"结婚证"从set集合中遍历出来
         */
        Set<Map.Entry<String, String>> set = map.entrySet();
        for (Map.Entry<String, String> entry : set) {
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key+"..."+value);
        }
    }
}

1.Map存储自定义对象时如何去重复

public class Person {
    private String name;
    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

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

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

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

public class Demo05HashMap {
    public static void main(String[] args) {
        HashMap<Person, String> map = new HashMap<>();
        map.put(new Person("涛哥",18),"河北省");
        map.put(new Person("三上",26),"日本");
        map.put(new Person("涛哥",18),"北京市");
        System.out.println(map);
    }
}
如果key为自定义类型,去重复的话,重写hashCode和equals方法,去重复过程和set一样一样的
因为set集合的元素到了底层都是保存到了map的key位置上

2.Map的练习

需求:Map集合统计字符串中每一个字符出现的次数
步骤:
  1.创建ScannerHashMap
  2.遍历字符串,将每一个字符获取出来
  3.判断,map中是否包含遍历出来的字符 -> containsKey
  4.如果不包含,证明此字符第一次出现,直接将此字符和1存储到map中
  5.如果包含,根据字符获取对应的value,让value++
  6.将此字符和改变后的value重新保存到map集合中
  7.输出

public class Demo06HashMap {
    public static void main(String[] args) {
        //1.创建Scanner和HashMap
        Scanner sc = new Scanner(System.in);
        HashMap<String, Integer> map = new HashMap<>();
        String data = sc.next();
        //2.遍历字符串,将每一个字符获取出来
        char[] chars = data.toCharArray();
        for (char aChar : chars) {
            String key = aChar+"";
            //3.判断,map中是否包含遍历出来的字符 -> containsKey
            if (!map.containsKey(key)){
            //4.如果不包含,证明此字符第一次出现,直接将此字符和1存储到map中
                map.put(key,1);
            }else{
                //5.如果包含,根据字符获取对应的value,让value++
                //6.将此字符和改变后的value重新保存到map集合中
                Integer value = map.get(key);
                value++;
                map.put(key,value);
            }
        }

        //7.输出
        System.out.println(map);
    }
}

3.斗地主_Map版本

public class Demo07Poker {
    public static void main(String[] args) {
        //1.创建数组-> color -> 专门存花色
        String[] color = "♠-♥-♣-♦".split("-");
        //2.创建数组 -> number -> 专门存牌号
        String[] number = "2-3-4-5-6-7-8-9-10-J-Q-K-A".split("-");
        //3.创建map集合,key为序号,value为组合好的牌面
        HashMap<Integer, String> poker = new HashMap<>();
        //4.创建一个ArrayList,专门存储key
        ArrayList<Integer> list = new ArrayList<>();
        list.add(0);
        list.add(1);

        //5.组合牌,存储到map中
        int key = 2;
        for (String num : number) {
            for (String huaSe : color) {
                String pokerNumber = huaSe+num;
                poker.put(key,pokerNumber);
                list.add(key);
                key++;
            }
        }

        poker.put(0,"😊");
        poker.put(1,"☺");

        //6.洗牌,打乱list集合中的key
        Collections.shuffle(list);
        //7.创建四个list集合
        ArrayList<Integer> p1 = new ArrayList<>();
        ArrayList<Integer> p2 = new ArrayList<>();
        ArrayList<Integer> p3 = new ArrayList<>();
        ArrayList<Integer> dipai = new ArrayList<>();

        //8.发牌
        for (int i = 0; i < list.size(); i++) {
            Integer key1 = list.get(i);
            if (i>=51){
                dipai.add(key1);
            }else if (i%3==0){
                p1.add(key1);
            }else if (i%3==1){
                p2.add(key1);
            }else if (i%3==2){
                p3.add(key1);
            }
        }

        //9.排序
        Collections.sort(p1);
        Collections.sort(p2);
        Collections.sort(p3);
        Collections.sort(dipai);

        lookPoker("涛哥",p1,poker);
        lookPoker("三上",p2,poker);
        lookPoker("金莲",p3,poker);
        lookPoker("大郎",dipai,poker);
    }

    private static void lookPoker(String name, ArrayList<Integer> list, HashMap<Integer, String> map) {
        System.out.print(name+":");

        for (Integer key : list) {
            String value = map.get(key);
            System.out.print(value+" ");
        }

        System.out.println();
    }
}

第二章.哈希表结构存储过程

1.HashMap底层数据数据结构:哈希表
2.jdk7:哈希表 = 数组+链表
  jdk8:哈希表 = 数组+链表+红黑树
3.
  先算哈希值,此哈希值在HashMap底层经过了特殊的计算得出
  如果哈希值不一样,直接存
  如果哈希值一样,再去比较内容,如果内容不一样,也存
  如果哈希值一样,内容也一样,直接去重复(后面的value将前面的value覆盖)
  
  哈希值一样,内容不一样->哈希冲突(哈希碰撞)
4.要知道的点:
  a.在不指定长度时,哈希表中的数组默认长度为16,HashMap创建出来,一开始没有创建长度为16的数组
  b.什么时候创建的长度为16的数组呢?在第一次put的时候,底层会创建长度为16的数组
  c.哈希表中有一个数据加[加载因子]->默认为0.75(加载因子)->代表当元素存储到百分之75的时候要扩容了->2倍
  d.如果对个元素出现了哈希值一样,内容不一样时,就会在同一个索引上以链表的形式存储,当链表长度达到8并且当前数组长度>=64,链表就会改成使用红黑树存储
    如果后续删除元素,那么在同一个索引位置上的元素个数小于6,红黑树会变回链表
  e.加入红黑树目的:查询快
外面笔试时可能会问到的变量
default_initial_capacity:HashMap默认容量  16
default_load_factor:HashMap默认加载因子   0.75f
threshold:扩容的临界值   等于   容量*0.75 = 12  第一次扩容
treeify_threshold:链表长度默认值,转为红黑树:8
min_treeify_capacity:链表被树化时最小的数组容量:64

1.问题:哈希表中有数组的存在,但是为啥说没有索引呢?

​ 哈希表中虽然有数组,但是set和map却没有索引,因为存数据的时候有可能在同一个索引下形成链表,如果2索引上有一条链表,那么我们要是按照索引2获取,咱们获取哪个元素呢?所以就取消了按照索引操作的机制

2.问题:为啥说HashMap是无序的,LinkedHashMap是有序的呢?

​ 原因:HashMap底层哈希表为单向链表

​ LinkedHashMap底层在哈希表的基础上加了一条双向链表

1.HashMap无参数构造方法的分析

//HashMap中的静态成员变量
static final float DEFAULT_LOAD_FACTOR = 0.75f;
public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

解析:使用无参数构造方法创建HashMap对象,将加载因子设置为默认的加载因子,loadFactor=0.75F。

2.HashMap有参数构造方法分析

HashMap(int initialCapacity, float loadFactor) ->创建Map集合的时候指定底层数组长度以及加载因子
    
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
    	throw new IllegalArgumentException("Illegal initial capacity: " +
    initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
    	initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
    	throw new IllegalArgumentException("Illegal load factor: " +
    loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);//10
}

解析:带有参数构造方法,传递哈希表的初始化容量和加载因子

  • 如果initialCapacity(初始化容量)小于0,直接抛出异常。
  • 如果initialCapacity大于最大容器,initialCapacity直接等于最大容器
    • MAXIMUM_CAPACITY = 1 << 30 是最大容量 (1073741824)
  • 如果loadFactor(加载因子)小于等于0,直接抛出异常
  • tableSizeFor(initialCapacity)方法计算哈希表的初始化容量。
    • 注意:哈希表是进行计算得出的容量,而初始化容量不直接等于我们传递的参数。

3.tableSizeFor方法分析

static final int tableSizeFor(int cap) {
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

8  4  2  1规则->无论指定了多少容量,最终经过tableSizeFor这个方法计算之后,都会遵循8421规则去初始化列表容量为了存取高效,尽量较少碰撞

解析:该方法对我们传递的初始化容量进行位移运算,位移的结果是 8 4 2 1 码

  • 例如传递2,结果还是2,传递的是4,结果还是4。
  • 例如传递3,结果是4,传递5,结果是8,传递20,结果是32。

4.Node 内部类分析

哈希表是采用数组+链表的实现方法,HashMap中的内部类Node非常重要,证明HashSet是一个单向链表

 static class Node<K,V> implements Map.Entry<K,V> {
     final int hash;
     final K key;
     V value;
     Node<K,V> next;
 Node(int hash, K key, V value, Node<K,V> next) {
     this.hash = hash;
     this.key = key;
     this.value = value;
     this.next = next;
}

解析:内部类Node中具有4个成员变量

  • hash,对象的哈希值
  • key,作为键的对象
  • value,作为值得对象(讲解Set集合,不牵扯值得问题)
  • next,下一个节点对象

5.存储元素的put方法源码

public V put(K key, V value) {
	return putVal(hash(key), key, value, false, true);
}

解析:put方法中调研putVal方法,putVal方法中调用hash方法。

  • hash(key)方法:传递要存储的元素,获取对象的哈希值
  • putVal方法,传递对象哈希值和要存储的对象key

6.putVal方法源码

Node<K,V>[] tab; Node<K,V> p; int n, i;
	if ((tab = table) == null || (n = tab.length) == 0)
		n = (tab = resize()).length;

解析:方法中进行Node对象数组的判断,如果数组是null或者长度等于0,那么就会调研resize()方法进行数组的扩容。

7.resize方法的扩容计算

if (oldCap > 0) {
     if (oldCap >= MAXIMUM_CAPACITY) {
         threshold = Integer.MAX_VALUE;
         return oldTab;
     }
     else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
              oldCap >= DEFAULT_INITIAL_CAPACITY)
         newThr = oldThr << 1; // double threshold
}

解析:计算结果,新的数组容量=原始数组容量<<1,也就是乘以2。

8.确定元素存储的索引

if ((p = tab[i = (n - 1) & hash]) == null)
	 tab[i] = newNode(hash, key, value, null);

解析:i = (数组长度 - 1) & 对象的哈希值,会得到一个索引,然后在此索引下tab[i],创建链表对象。

不同哈希值的对象,也是有可能存储在同一个数组索引下。

其中resize()扩容的方法,默认是16
 tab[i] = newNode(hash, key, value, null);->将元素放在数组中  i就是索引

 i = (n - 1) & hash
     0000 0000 0000 0000 0000 0000 0000 1111->15
                                                    &   0&0=0 0&1=0 1&1=1
     0000 0000 0000 0001 0111 1000 0110 0011->96355
--------------------------------------------------------
     0000 0000 0000 0000 0000 0000 0000 0011->3
     0000 0000 0000 0000 0000 0000 0000 1111->15
                                                    &   0&0=0 0&1=0 1&1=1
     0000 0000 0001 0001 1111 1111 0001 0010->1179410
--------------------------------------------------------
     0000 0000 0000 0000 0000 0000 0000 0010->2

9.遇到重复哈希值的对象

 Node<K,V> e; K k;
 if (p.hash == hash &&
 	((k = p.key) == key || (key != null && key.equals(k))))
		 e = p;

解析:如果对象的哈希值相同,对象的equals方法返回true,判断为一个对象,进行覆盖操作。

else {
     for (int binCount = 0; ; ++binCount) {
     	if ((e = p.next) == null) {
     		p.next = newNode(hash, key, value, null);
     	if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
     		treeifyBin(tab, hash);
     	break;
 }

解析:如果对象哈希值相同,但是对象的equals方法返回false,将对此链表进行遍历,当链表没有下一个节点的时候,创建下一个节点存储对象.

第三章.TreeSet

1.概述:TreeSetSet的实现类
2.特点:
  a.对元素进行排序
  b.无索引
  c.不能存null
  d.线程不安全
  e.元素唯一
3.数据结构:红黑树      
构造:
  TreeSet() -> 构造一个新的空 set,该 set 根据其元素的自然顺序进行排序 -> ASCII 
  TreeSet(Comparator<? super E> comparator)构造一个新的空 TreeSet,它根据指定比较器进行排序     
public class Person {
    private String name;
    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

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

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

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}
public class Demo01TreeSet {
    public static void main(String[] args) {
        TreeSet<String> set1 = new TreeSet<>();
        set1.add("c.白毛浮绿水");
        set1.add("a.鹅鹅鹅");
        set1.add("b.曲项向天歌");
        set1.add("d.红掌拨清波");
        System.out.println(set1);

        System.out.println("=====================");
        TreeSet<Person> set2 = new TreeSet<>(new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge()-o2.getAge();
            }
        });
        set2.add(new Person("柳岩",18));
        set2.add(new Person("涛哥",12));
        set2.add(new Person("三上",20));
        System.out.println(set2);
    }
}

第四章.TreeMap

1.概述:TreeMapMap的实现类
2.特点:
  a.对key进行排序
  b.无索引
  c.key唯一
  d.线程不安全
  e.不能存null
3.数据结构:红黑树      
构造:
  TreeMap() -> 使用键的自然顺序构造一个新的、空的树映射 -> ASCII 
  TreeMap(Comparator<? super E> comparator)构造一个新的、空的树映射,该映射根据给定比较器进行排序
public class Person {
    private String name;
    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

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

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

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

public class Demo02TreeMap {
    public static void main(String[] args) {
        TreeMap<String, String> map1 = new TreeMap<>();
        map1.put("c","白毛浮绿水");
        map1.put("a","鹅鹅鹅");
        map1.put("b","曲项向天歌");
        map1.put("d","红掌拨清波");
        System.out.println(map1);

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

        TreeMap<Person, String> map2 = new TreeMap<>(new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge()-o2.getAge();
            }
        });

        map2.put(new Person("柳岩",18),"北京");
        map2.put(new Person("涛哥",12),"北京");
        map2.put(new Person("三上",20),"北京");
        System.out.println(map2);

    }
}

第五章.Hashtable和Vector集合(了解)

1.Hashtable集合

1.概述:HashtableMap的实现类
2.特点:
  a.key唯一,value可重复
  b.无序
  c.无索引
  d.线程安全   
  e.不能存储null,null3.数据结构:哈希表      
public class Demo01Hashtable {
    public static void main(String[] args) {
        Hashtable<String, String> table = new Hashtable<>();
        table.put("迪丽热巴","马尔扎哈");
        table.put("古力娜扎","涛哥思密达");
        table.put("古力娜扎","雷霆嘎巴");
        table.put("玛卡巴卡","哈哈哈哈");
        //table.put(null,null);
        System.out.println(table);
    }
}

HashMap和Hashtable区别:

相同点:元素无序,无索引,key唯一

不同点:HashMap线程不安全,Hashtable线程安全

​ HashMap可以存储null键null值;Hashtable不能

2.Vector集合

1.概述:VectorList接口的实现类
2.特点:
  a.元素有序
  b.有索引
  c.元素可重复
  d.线程安全
3.数据结构:数组
    
4.源码分析:
  a.如果用空参构造创建对象,数组初始容量为10,如果超出范围,自动扩容,2倍
  b.如果用有参构造创建对象,如果超出了范围,自动扩容,扩的是老数组长度+指定的容量增强
public class Demo02Vector {
    public static void main(String[] args) {
        Vector<String> vector = new Vector<>();
        vector.add("张三");
        vector.add("李四");
        for (String s : vector) {
            System.out.println(s);
        }
    }
}

Vector底层源码分析

Vector() 构造一个空向量,使其内部数据数组的大小为 10,其标准容量增量为零
Vector(int initialCapacity, int capacityIncrement)使用指定的初始容量和容量增量构造一个空的向量 
Vector<String> vector = new Vector<>();
public Vector() {
    this(10);
}
public Vector(int initialCapacity->10) {
    this(initialCapacity, 0);
}
public Vector(int initialCapacity->10, int capacityIncrement->0) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    this.elementData = new Object[initialCapacity];//长度为0的数组
    this.capacityIncrement = capacityIncrement;//0
}
=====================================================
vector.add("李四");-> 假设李四是第11个元素
public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}  
private void ensureCapacityHelper(int minCapacity->11) {
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity->11);
}
private void grow(int minCapacity->11) {
    // overflow-conscious code
    int oldCapacity = elementData.length;//10
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                     capacityIncrement : oldCapacity);//10+10=20
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}
Vector<String> vector = new Vector<>(10,5);
public Vector(int initialCapacity->10, int capacityIncrement->5) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    this.elementData = new Object[initialCapacity];
    this.capacityIncrement = capacityIncrement;//5
}

======================================================
vector.add("李四");-> 假设李四是第11个元素
public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}    

private void ensureCapacityHelper(int minCapacity->11) {
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

private void grow(int minCapacity->11) {
    // overflow-conscious code
    int oldCapacity = elementData.length;//10
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                     capacityIncrement : oldCapacity);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

第六章.Properties集合(属性集)

1.概述:Properties 继承自 Hashtable
2.特点:
  a.key唯一,value可重复
  b.无序
  c.无索引
  d.线程安全
  e.不能存null,nullf.Properties的key和value类型默认为String
3.数据结构:哈希表
4.特有方法:
  Object setProperty(String key, String value)  -> 存键值对
  String getProperty(String key)  ->根据key获取value的
  Set<String> stringPropertyNames()  -> 获取所有的key,保存到set集合中,相当于keySet方法
  void load(InputStream inStream) -> 将流中的数据加载到Properties集合中(IO部分讲)    
public class Demo01Properties {
    public static void main(String[] args) {
        Properties properties = new Properties();
        //Object setProperty(String key, String value)  -> 存键值对
        properties.setProperty("username","root");
        properties.setProperty("password","1234");
        System.out.println(properties);
        //String getProperty(String key)  ->根据key获取value的
        System.out.println(properties.getProperty("username"));
        //Set<String> stringPropertyNames()  -> 获取所有的key,保存到set集合中,相当于keySet方法
        Set<String> set = properties.stringPropertyNames();
        for (String key : set) {
            System.out.println(properties.getProperty(key));
        }
    }
}

第七章.集合嵌套

1.List嵌套List

需求:创建2List集合,每个集合中分别存储一些字符串,2个集合存储到第3List集合中
public class Demo01ListInList {
    public static void main(String[] args) {
        ArrayList<String> list1 = new ArrayList<>();
        list1.add("杨过");
        list1.add("小龙女");
        list1.add("尹志平");

        ArrayList<String> list2 = new ArrayList<>();
        list2.add("涛哥");
        list2.add("金莲");
        list2.add("三上");

        /*
           list中的元素是两个 ArrayList<String>
           所以泛型也应该是 ArrayList<String>
         */
        ArrayList<ArrayList<String>> list = new ArrayList<>();
        list.add(list1);
        list.add(list2);

        /*
          先遍历大集合,将两个小集合遍历出来
          再遍历两个小集合,将元素获取出来
         */
        for (ArrayList<String> arrayList : list) {
            for (String s : arrayList) {
                System.out.println(s);
            }
        }
    }
}

2.List嵌套Map

1班级有第三名同学,学号和姓名分别为:1=张三,2=李四,3=王五,2班有三名同学,学号和姓名分别为:1=黄晓明,2=杨颖,3=刘德华,请将同学的信息以键值对的形式存储到2Map集合中,在将2Map集合存储到List集合中。
public class Demo02ListInMap {
    public static void main(String[] args) {
        //1.创建两个map集合
        HashMap<Integer, String> map1 = new HashMap<>();
        map1.put(1,"张三");
        map1.put(2,"李四");
        map1.put(3,"王五");

        HashMap<Integer, String> map2 = new HashMap<>();
        map2.put(1,"黄晓明");
        map2.put(2,"杨颖");
        map2.put(3,"刘德华");

        //2.创建一个存储map集合的list集合
        ArrayList<HashMap<Integer, String>> list = new ArrayList<>();
        list.add(map1);
        list.add(map2);

        //3.先遍历list集合,再遍历map集合
        for (HashMap<Integer, String> map : list) {
            Set<Map.Entry<Integer, String>> set = map.entrySet();
            for (Map.Entry<Integer, String> entry : set) {
                System.out.println(entry.getKey()+"..."+entry.getValue());
            }
        }
    }
}

3.Map嵌套Map

- JavaSE  集合 存储的是 学号 键,值学生姓名
  - 1  张三
  - 2  李四
- JavaEE 集合 存储的是 学号 键,值学生姓名
  - 1  王五
  - 2  赵六
public class Demo03MapInMap {
    public static void main(String[] args) {
        //1.创建两个map集合
        HashMap<Integer, String> map1 = new HashMap<>();
        map1.put(1,"张三");
        map1.put(2,"李四");

        HashMap<Integer, String> map2 = new HashMap<>();
        map2.put(1,"王五");
        map2.put(2,"赵六");

        HashMap<String, HashMap<Integer, String>> map = new HashMap<>();
        map.put("javase",map1);
        map.put("javaee",map2);

        Set<Map.Entry<String, HashMap<Integer, String>>> set = map.entrySet();
        for (Map.Entry<String, HashMap<Integer, String>> entry : set) {
            HashMap<Integer, String> hashMap = entry.getValue();
            Set<Integer> set1 = hashMap.keySet();
            for (Integer key : set1) {
                System.out.println(key+"..."+hashMap.get(key));
            }
        }
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mybatisPlus和mybatis的版本冲突问题、若依换成MP、解决git无法推送、使用若依框架的swagger、以后再遇到团队项目应该怎么做。

20240716 一. mybatisPlus和mybatis的版本冲突问题1. 使用前的准备2. 我遇到了一个很严重的问题。3. 解决问题&#xff0c;好吧也没解决&#xff0c;发现问题&#xff01;&#xff01; 二、该死的git&#xff01;&#xff01;&#xff01;&#xff01;1. 解决无法在idea中使用g…

2024年公路水运工程施工企业安全生产管理人员证模拟考试题库及公路水运工程施工企业安全生产管理人员理论考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年公路水运工程施工企业安全生产管理人员证模拟考试题库及公路水运工程施工企业安全生产管理人员理论考试试题是由安全生产模拟考试一点通提供&#xff0c;公路水运工程施工企业安全生产管理人员证模拟考试题库是…

大数据平台之YARN

Hadoop YARN&#xff08;Yet Another Resource Negotiator&#xff09;是Hadoop 2.x引入的一个通用资源管理和作业调度框架&#xff0c;它将资源管理和作业调度/监控分离开来&#xff0c;从而提升了集群的资源利用率和可扩展性。YARN是Hadoop生态系统的核心组件之一&#xff0c…

Go 1.19.4 函数-Day 08

1. 函数概念和调用原理 1.1 基本介绍 函数是基本的代码块&#xff0c;用于执行一个任务。 Go 语言最少有个 main() 函数。 你可以通过函数来划分不同功能&#xff0c;逻辑上每个函数执行的是指定的任务。 函数声明告诉了编译器函数的名称&#xff0c;返回类型&#xff0c;和参…

持续集成06--Jenkins构建触发器

前言 在持续集成&#xff08;CI&#xff09;的实践中&#xff0c;构建触发器是自动化流程中不可或缺的一环。它决定了何时启动构建过程&#xff0c;从而确保代码变更能够及时地得到验证和反馈。Jenkins&#xff0c;作为业界领先的CI/CD工具&#xff0c;提供了多种构建触发器选项…

【C++编程】双端数组 deque 容器基本操作

&#x1f525; 特点&#xff1a;deque 头插、头删速度比 vector 快 deque 是一个双向队列&#xff08;double-ended queue&#xff09;&#xff0c;可以在队列的两端进行元素的插入和删除操作。 deque 涵盖了 queue&#xff08;队列&#xff09;、stack&#xff08;堆栈&#x…

一五六、Node+Vue 使用七牛上传图片,并配置个人域名

1. 七牛云ak/sk获取 点击注册&#x1f517;开通七牛开发者帐号如果已有账号&#xff0c;直接登录七牛开发者后台&#xff0c;点击这里&#x1f517;查看 Access Key 和 Secret Key 2. Node.js获取七牛token 安装qiniu npm install qiniu创建空间 Node获取token const qi…

在SpringCloud中如何轻松实现微服务间的通信

在Spring Cloud中&#xff0c;实现微服务间的通信非常简单。Spring Cloud提供了多种方式来进行微服务之间的通信&#xff0c;包括使用RestTemplate、Feign、Ribbon、Eureka等组件。下面我将详细介绍这些方式的使用方法。 使用RestTemplate进行通信&#xff1a; RestTemplate是S…

ECCV2024|GLAD:利用全局和局部自适应扩散模型实现更好的无监督异常检测重建

GLAD&#xff1a;利用全局和局部自适应扩散模型实现更好的无监督异常检测重建 论文标题&#xff1a;GLAD: Towards Better Reconstruction with Global and Local Adaptive Diffusion Models for Unsupervised Anomaly Detection 论文地址&#xff1a;https://arxiv.org/abs/2…

字符串类中的常用方法

1 string对象的创建 静态创建 String s1  "abc";  String s2  "abc";  动态创建 String s3  new String("abc"); String s4  new String("abc"); 2string对象的不可变性 任何一个String对象在创建之后都不能对它的…

blender中设置物体的中心到某个顶点

方式一&#xff1a; 1&#xff0c;首先选中物体&#xff0c; 2&#xff0c;选中编辑模式&#xff0c; 3&#xff0c;选中点模式&#xff0c;并选择物体的一个顶点 4&#xff0c;鼠标点击右键---- 吸附至像素点--游标->选中项&#xff0c;这样&#xff0c;游标移动到了选中…

PulsarClient源码解析

一、Pulsar客户端简析 pulsar服务是经典的C/S架构&#xff0c;由客户端和服务端构成。服务端提供处理读写请求服务&#xff0c;客户端负责发起读写请求。pulsar将客户端按照读写分成了生产者和消费者&#xff0c;但是无论怎么分&#xff0c;它们本质上都是Pulsar客户端并有很多…

QT--控件篇四

一、对话框 在软件开发中&#xff0c;对话框&#xff08;Dialog&#xff09;是一种常见的用户界面元素&#xff0c;用于与用户进行交互和获取信息。它通常以模态或非模态的形式出现&#xff0c;模态对话框会阻止用户与应用程序的其他部分交互&#xff0c;直到对话框关闭为止&a…

快速排序(quick sort)

欢迎来到一夜看尽长安花 博客&#xff0c;您的点赞和收藏是我持续发文的动力 对于文章中出现的任何错误请大家批评指出&#xff0c;一定及时修改。有任何想要讨论的问题可联系我&#xff1a;3329759426qq.com 。发布文章的风格因专栏而异&#xff0c;均自成体系&#xff0c;不足…

nftables(9)NAT、FLOWTABLES

NAT NAT简介 我们在iptables、firewalld中都介绍过有关NAT的相关部分。那么在nftables中&#xff0c;我们继续介绍nftables中NAT的功能实现方式&#xff0c;配置方法和与前两者的区别。 我们先简单回顾一下NAT的类型和其功能&#xff1a; 这些是不同的网络地址转换&#xf…

在 Windows 上开发.NET MAUI 应用_1.安装开发环境

开发跨平台的本机 .NET Multi-platform App UI (.NET MAUI) 应用需要 Visual Studio 2022 17.8 或更高版本&#xff0c;或者具有 .NET MAUI 扩展的最新 Visual Studio Code。要开始在 Windows 上开发本机跨平台 .NET MAUI 应用&#xff0c;请按照安装步骤安装 Visual Studio 20…

leetcode94. 二叉树的中序遍历,递归法+迭代法。附带前序遍历方法

leetcode94. 二叉树的中序遍历 给定一个二叉树的根节点 root &#xff0c;返回 它的 中序 遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,3,2] 示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[] 示例 3&#xff1a; …

高职综合布线实训室

一、高职综合布线实训室建设背景 随着《国民经济和社会发展第十四个五年规划和2035年远景目标纲要》的深入实施&#xff0c;数字化转型已成为国家发展的核心战略之一&#xff0c;计算机网络技术作为数字化建设的基石&#xff0c;其重要性日益凸显。然而&#xff0c;面对数字时代…

【手撕RLHF-DPO(1)】不是PPO训不起,而是DPO更有性价比!

Introduction Direct Preference Optimization: Your Language Model is Secretly a Reward Model 在LLM对齐问题上&#xff0c;OpenAI提出的RLHF训练范式最为人熟知&#xff0c;同时也是ChatGPT行之有效的对齐方案。 RLHF通常包含三个步骤&#xff1a;SFT, Reward Model, PPO…

【STM32】RTT-Studio中HAL库开发教程三:IIC通信--AHT20

文章目录 一、I2C总线通信协议二、AHT20传感器介绍三、STM32CubeMX配置硬件IIC四、RTT中初始化配置五、具体实现代码六、实验现象 一、I2C总线通信协议 使用奥松的AHT20温湿度传感器&#xff0c;对环境温湿度进行采集。AHT20采用的是IIC进行通信&#xff0c;可以使用硬件IIC或…