Day18-C:\Users\Lenovo\Desktop\note\code\JavaSE\Basic\src\com\CollectionandMap

Collection

  1. add

  2. node

  3. 增强for

  4. Lambda

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collection;
    import java.util.Iterator;
    import java.util.function.Consumer;
    
    public class CollectionDemo01 {
        public static void main(String[] args) {
            Collection<String> c = new ArrayList<>();
            c.add("张三");
            c.add("李四");
            c.add("王五");
            c.add("赵六");
            System.out.println(Arrays.toString(c.toArray()));
            //使用迭代器遍历集合
            Iterator<String> it = c.iterator();//默认站在第一个元素处
            System.out.println(it.next());//取数据并移动到下一处
            System.out.println(it.next());
            System.out.println(it.next());
            System.out.println(it.next());
            //System.out.println(it.next());//出现异常
            Iterator<String> its = c.iterator();
            while (its.hasNext()) {
                String s1 = its.next();
                System.out.println(s1);
            }
            //增强for遍历集合集合
            for(String s2: c){
                System.out.println("增强for遍历集合"+s2);//增强for遍历集合本质上是迭代器遍历集合的简化版
            }
            String[] name = {"张三","李四","王五","赵六"};
            for (String s3: name){
                System.out.println("增强for遍历数组"+s3);
            }
            //s.for 然后回车就可以调用增强for方法
            /*c.forEach(new Consumer<String>() {//创建Consumer接口的匿名内部类,重写方法
                @Override
                public void accept(String s) {
                    System.out.println(s);
                }
                /*
                源码
                default void forEach(Consumer<? super T> action) {
                  Objects.requireNonNull(action);
                  for (T t : this) {
                      action.accept(t);
                     }
                 }
                 类 '派生自 Consumer 的匿名类' 必须在 'Consumer' 中实现 abstract 方法 'accept(T)'
                 */
            c.forEach((String s) ->{
                    System.out.println(s);
            });
            //-> 是lambda 表达式的语法符号,用于简化函数式接口(只有一个抽象方法的接口)的实现。它将左侧的参数列表与右侧的方法体连接起来,相当于 “把参数传递给方法体执行”。
            c.forEach(ele->{// lambda 表达式 s -> System.out.println(s) 是 Consumer 接口的实现,其中的 s 就是对 accept 方法参数 t 的 “简写”(变量名可以自定义,比如写成 x 或 elem 都可以)。
                System.out.println(ele);
            });
            c.forEach(s-> System.out.println(s));//println是方法,out是对象
            c.forEach(System.out::println);
            //:: 是方法引用(Method Reference) 的语法符号
            //由于 forEach 在遍历过程中会依次把每个元素传入 accept 方法,因此 s 自然就代表了集合 c 在当前遍历步骤中的元素。
            //如果 forEach 要求 Consumer<String>,则只能传入 “专门处理 String 的消费逻辑”。
            //但用 Consumer<? super String> 后,不仅能传入 Consumer<String>,还能传入 Consumer<Object>(因为 Object 是 String 的父类)。比如 Consumer<Object> 中可能有一个通用的打印逻辑(如 System.out::println,它本质上是 Consumer<Object>),这样的逻辑同样能处理 String 元素。
        }
    }
    
import java.util.ArrayList;
import java.util.Collection;

public interface collectionDemo02 {
    public static void main(String[] args) {
        //1.创建一个集合容器负责储存多部电影对象
        Collection<Movie> movies = new ArrayList<>();
        movies.add(new Movie("《肖申克的救赎》",9.7,"罗宾斯"));
        movies.add(new Movie("《霸王别姬》",9.6,"张国荣"));
        movies.add(new Movie("《阿甘正传》",9.5,"汤姆·汉克斯"));
        //集合存储对象本质上是存储对象的地址信息,直接打印出来的是地址
        System.out.println(movies);
        movies.forEach(System.out::println);
        for (Movie movie : movies) {
            System.out.println("电影名:"+movie.getName());
            System.out.println("评分:"+movie.getScore());
            System.out.println("主演:"+movie.getActors());
            System.out.println();
        }

    }
}

List

  1. 特点:有序可重复有索引
  2. 方法:add,remove,set,get
  3. 遍历方式:支持for循环遍历
  4. ArrayList和LinkedList存贮和组织数据结构的方式不同

ArrayList

基于数组实现的,根据索引,查询速度较快,查询任意数据耗时相同;但是删除效率低,需要把后面数据前移;添加效率极低,先把后面数据后移,再添加元素

底层原理

  1. 利用无参构造器创建的集合,会在底层创建一个默认长度为0的数组

    List<String> list = new ArrayList<>();//(经典代码)这里右边不能再用List,要用他的实现类ArrayList<>
    list.add("蜘蛛精");
    
  2. 添加第一个元素时,底层会创建一个新的长度为10的数组,然后让size从第一个索引(0)指向第二个索引(1),size索引下标等于元素个数等于下次存入位置

  3. 当存储第11个数据时,会将原来的数组进行扩容,并将数组中的元素迁移到新数组

  4. 如果一次添加多个元素,1.5倍还放不下,则新创建数组长度以实际为准,例如将list2中的21个元素用addAll方法倒入只有长度为10的list1时,会直接创建到21个空间

适合场景

  1. 根据随机索引取数据或数据量不是很大时
  2. 不适合数据量大且需要频繁增删操作

LinkedList

通过双向链表存储

链表特点:

查询慢,按照索引查询也得从头查

链表增删相对较快

0680f19a689050a4f2a0665d9024864d

634f2aba2b21d2c7d2b0432604010332

327e834bcd1302606b75baffef4ab907

LinkedList设计:排队系统,栈

排队:

import java.util.LinkedList;
import java.util.List;

public class ListTest2 {
    public static void main(String[] args) {
        //1.创建一个队列
        LinkedList<String> queue = new LinkedList<>();//这里不要用多态,因为后面需要调用LinkedList的专用方法
        queue.addLast("第一号人");
        queue.addLast("第二号人");
        queue.addLast("第三号人");
        queue.addLast("第四号人");
        queue.addLast("第五号人");
        System.out.println(queue);
        //出队
        System.out.println(queue.removeFirst());
        System.out.println(queue.removeFirst());
        System.out.println(queue.removeFirst());
        System.out.println(queue);
    }
}

栈(先进后出,后进先出)类似于手枪弹夹,只在首部增删元素

数据进入栈模型的过程为:压/进栈(push)

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

//2.创建一个栈对象
    LinkedList<String> stack = new LinkedList<>();
    //压栈(push方法)
    stack.addFirst("第一颗子弹");
    stack.push("第二颗子弹");
    stack.push("第三颗子弹");
    stack.addFirst("第四颗子弹");
    System.out.println(stack);
    //出栈(pop方法)
    System.out.println(stack.removeFirst());
    System.out.println(stack.pop());
    System.out.println(stack);
}

Set

无序(指的是添加数据和获取数据的顺序不一致),不重复,无索引

package com.CollectionandMap;

import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;

public class SetTest1 {
    public static void main(String[] args) {
        //创建一个Set集合的对象
        //Set<Integer> set = new HashSet<>();//创建了一个HashSet的集合对象,一行经典代码,无序的顺序一旦定下来了就不会改变,多运行几次不能改变他的顺序
        //Set<Integer> set = new LinkedHashSet<>();//创建了一个LinkedHashSet的集合对象,一行经典代码,有序:指的是添加顺序和输出顺序的一致
        Set<Integer> set = new TreeSet<>();//创建了一个LinkedHashSet的集合对象,一行经典代码,有序:默认可排序,默认升序[555, 666, 777, 888]
        set.add(666);
        set.add(555);
        set.add(555);
        set.add(888);
        set.add(888);
        set.add(777);
        set.add(777);
        System.out.println(set);
    }
}
  1. HashSet:无序,不重复,无索引

  2. LinkedHashSet(HashSet的子类):有序,不重复,无索引

  3. TreeSet:排序,不重复,无索引

    package com.CollectionandMap;
    
    public class SetTest2 {
        public static void main(String[] args) {
            Student s1 = new Student("蜘蛛精",25,169.5);
            Student s2 = new Student("紫霞",22,166.5);
            System.out.println(s1.hashCode());
            System.out.println(s1.hashCode());
            System.out.println(s2.hashCode());
    
            String str1 = new String("abc");
            String str2 = new String("acD");//哈希碰撞
            System.out.println(str1.hashCode());
            System.out.println(str2.hashCode());
        }
    }
    

HashSet

哈希值就是一个int类型的数值,java中每一个对象都有哈希值

Java中的所有对象,都可以调用Object类提供的hashCode方法,返回该对象自己的哈希值

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

不同的对象,它们的哈希值一般不相同,但也有可能会相同(哈希碰撞)

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

哈希表

JDK8之前,哈希表 = 数组+链表

ca2b2195846933704f8ccb44b110ca81

哈希表的核心是通过哈希函数将元素映射到数组(哈希桶)的索引位置。但随着元素不断插入:

  1. 哈希冲突概率增加:当元素数量接近或超过哈希表容量时,多个元素会被映射到同一个桶中(哈希冲突),导致桶中的链表 / 红黑树过长,查询、插入、删除的时间复杂度从理想的 O (1) 退化到 O (n) 或 O (log n)。

  2. 负载因子过高

    :哈希表用「负载因子(load factor)」衡量元素填充程度,公式为:负载因子 = 元素数量 / 哈希表容量负载因子越高,冲突概率越大。Java 中HashMap的默认负载因子是 0.75,即当元素数量达到容量的 75% 时,触发扩容。

    二、扩容的核心步骤

    扩容的本质是「扩大哈希表容量 + 重新分布元素」,以 Java HashMap 为例,步骤如下:

    1. 确定新容量:通常将容量扩大为原来的 2 倍(保证容量是 2 的幂,简化哈希计算)。例如,原容量为 16 时,扩容后为 32。

    2. 创建新的哈希桶数组:容量为新容量,用于存储重新分布后的元素。

    3. 重新哈希(rehash)并迁移元素

      遍历原哈希表中的所有元素(包括链表 / 红黑树中的节点),根据新容量重新计算每个元素的哈希值(即新的索引位置),然后将元素迁移到新数组的对应位置。

      • 为什么要重新计算哈希?因为索引计算依赖容量(如 index = hash & (容量 - 1)),容量变了,索引也会变化。
    4. 替换引用:将哈希表的底层数组引用指向新数组,释放原数组内存。

JDK8开始,哈希表 = 数组+链表+红黑树

当链表长度超过8,且数组长度>=64,自动将链表转成红黑树

aa802d49349890a02931c5c6fd356334

二叉树

23e85031025ff72c8d46972d2d2cce98

8647444df955df30549bb02b9166a4e2

8838059b08e5c1195252847f6231aa43

79bc4760b659168ad260a0635aaef405

如果希望Set集合认为两个内容一样的对象是重复的,必须重写对象的hashCode()方法和equals()}方法
package com.CollectionandMap;

import java.util.HashSet;
import java.util.Set;

public class SetTest3 {
    @Override
    public boolean equals(Object obj) {
        return super.equals(obj);
    }

    public static void main(String[] args) {
        Set<Student> students = new HashSet<>();
        Student s1 = new Student("至尊宝", 28, 169.6);
        Student s2 = new Student("蜘蛛精", 23, 169.6);
        Student s3 = new Student("蜘蛛精", 23, 169.6);
        Student s4 = new Student("牛魔王", 48, 169.6);
        System.out.println(s2.hashCode());
        System.out.println(s3.hashCode());
        students.add(s1);
        students.add(s2);
        students.add(s3);
        students.add(s4);
        System.out.println(students);//内容一样的两个Student类的对象存入到HashSet集合中去,HashSet集合是不能去重复的!
        //如果希望Set集合认为两个内容一样的对象是重复的,必须重写对象的hashCode()方法和equals()}方法,要在Student类里面重写
        //右键->生成

    }
}

重写如下

package com.CollectionandMap;

import java.util.Objects;

public class Student {
    private String name;
    private int age;
    private double weight;

    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;
    }
    public double getWeight() {
        return weight;
    }
    public void setWeight(double weight) {
        this.weight = weight;
    }

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

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

    @Override
    public boolean equals(Object o) {
        //if (this == o) return true;//检查地址是否相同,即是否为同一个对象
        if (o == null || getClass() != o.getClass()) return false;//非空判断,getClass() != o.getClass() 检查两个对象是否属于同一个类。
        Student student = (Student) o;
        return age == student.age && Double.compare(weight, student.weight) == 0 && Objects.equals(name, student.name);//简化版
        //&& 是逻辑 “与” 运算符,只有当所有条件都满足时,整个表达式的结果才为 true,此时 return 语句会返回 true;只要有一个条件不满足(结果为 false),整个表达式就为 false,return 会返回 false。
    }

    //只要两个对象内容要用,返回的哈希值就是一样的
    @Override
    public int hashCode() {
        return Objects.hash(name, age, weight);//Objects.hash(name, age, weight) 是 Java 提供的工具方法,用于根据传入的多个参数计算哈希值。
    }
}
posted @ 2025-10-19 20:50  David大胃  阅读(7)  评论(0)    收藏  举报