JavaSE-13笔记【集合2(+2024新)】

news2024/12/25 10:49:41

文章目录

  • 3.Map
    • 3.1 Map继承结构
    • 3.2 Map接口的常用方法
    • 3.3 遍历Map
    • 3.4 HashMap集合
      • 3.4.1 HashMap集合key的特点
      • 3.4.2 HashMap集合的key存储自定义类型
      • 3.4.3 哈希表
        • 3.4.3.1 哈希表的介绍
        • 3.4.3.2 哈希表的存储原理
      • 3.4.4 存放在HashMap和HashSet集合key部分的元素必须同时重写hashCode+equals方法,若equals返回true时,hashCode必须相同。
      • 3.4.5 HashMap中的key可以存null
      • 3.4.6 手写HashMap的put和get方法
      • 3.4.7 HashMap在Java8后的改进(包含Java8)
      • 3.4.8 HashMap初始化容量永远都是2的次幂
      • 3.4.9 HashMap的初始化容量的设置
    • 3.5 LinkedHashMap
    • 3.6 Hashtable
    • 3.7 Properties
    • 3.8 一些树的介绍
      • 3.8.1 二叉树
      • 3.8.2 排序二叉树
      • 3.8.3 平衡二叉树(AVL)
      • 3.8.4 红黑二叉树
    • 3.9 TreeMap
      • 3.9.1 TreeMap集合的key为自定义类型
  • 4.Set
    • 4.1 HashSet
    • 4.2 LinkedHashSet
    • 4.3 TreeSet
    • 4.4 关于HashSet的面试题
  • 5.Collections工具类

接上一篇 JavaSE-12笔记【集合1】

3.Map

3.1 Map继承结构

在这里插入图片描述

  • Map集合以key和value的键值对形式存储。key和value存储的都是引用。
  • Map集合中key起主导作用。value是附属在key上的。
  • SequencedMap是Java21新增的。
  • LinkedHashMap和TreeMap都是有序集合。(key是有序的)
  • HashMap,Hashtable,Properties都是无序集合。(key是无序的)
  • Map集合的key都是不可重复的。key重复的话,value会覆盖。
  • HashSet集合底层是new了一个HashMap。往HashSet集合中存储元素实际上是将元素存储到HashMap集合的key部分。HashMap集合的key是无序不可重复的,因此HashSet集合就是无序不可重复的。HashMap集合底层是哈希表/散列表数据结构,因此HashSet底层也是哈希表/散列表。
  • TreeSet集合底层是new了一个TreeMap。往TreeSet集合中存储元素实际上是将元素存储到TreeMap集合的key部分。TreeMap集合的key是不可重复但可排序的,因此TreeSet集合就是不可重复但可排序的。TreeMap集合底层是红黑树,因此TreeSet底层也是红黑树。它们的排序通过java.lang.Comparable和java.util.Comparator均可实现。
  • LinkedHashSet集合底层是new了一个LinkedHashMap。LinkedHashMap集合只是为了保证元素的插入顺序,效率比HashSet低,底层采用的哈希表+双向链表实现。
  • 根据源码可以看到向Set集合中add时,底层会向Map中put。value只是一个固定不变的常量,只是起到一个占位符的作用。主要是key。

3.2 Map接口的常用方法

V put(K key, V value); 添加键值对
void putAll(Map<? extends K,? extends V> m); 添加多个键值对
V get(Object key); 通过key获取value
boolean containsKey(Object key); 是否包含某个key(底层会调用equals方法)
boolean containsValue(Object value); 是否包含某个value(底层会调用equals方法)
V remove(Object key); 通过key删除key-value
void clear(); 清空Map
int size(); 键值对个数
boolean isEmpty(); 判断是否为空Map
Collection values(); 获取所有的value
Set keySet(); 获取所有的key
Set<Map.Entry<K,V>> entrySet(); 获取所有键值对的Set视图。
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3); 静态方法,使用现有的key-value构造Map

示例代码:

package maptest;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

public class MapTest01 {
    public static void main(String[] args) {
        //创建一个Map集合
        Map<Integer, String> maps = new HashMap<>();

        //添加键值对
        maps.put(1,"赵倩");
        maps.put(2,"王雷");
        maps.put(3, "张芝");

        System.out.println("键值对个数:" + maps.size());

        //根据key获取value
        System.out.println(maps.get(2));

        //key对应的value不存在时返回null
        System.out.println(maps.get(4));

        //判断是否包含某个key
        System.out.println(maps.containsKey(3));

        //判断是否包含某个value
        System.out.println(maps.containsValue("王雷"));

        //通过key删除键值对
        maps.remove(3);
        System.out.println("键值对个数:" + maps.size());

        //添加多个键值对
        Map<Integer,String> newMap = new HashMap<>();
        newMap.put(4,"刘柳");

        newMap.putAll(maps);

        //获取所有的value
        Collection<String> values = newMap.values();
        for (String value:values) {
            System.out.println(value);
        }

        System.out.println("当前是否为空map:" + maps.isEmpty());

        //清空map
        maps.clear();

        System.out.println("当前是否为空map:" + maps.isEmpty());

    }
}

运行结果:
在这里插入图片描述

3.3 遍历Map

  • 方式一:通过keySet()方法获取所有key,之后再通过key获取对应value
package maptest;

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

public class MapTest02 {
    public static void main(String[] args) {
        //创建一个Map集合
        Map<Integer, String> maps = new HashMap<>();

        //添加键值对
        maps.put(1,"赵倩");
        maps.put(2,"王雷");
        maps.put(3, "张芝");
        maps.put(4, "刘柳");

        //方式一:通过keySet()方法获取所有key,之后再通过key获取对应value
        Set<Integer> keys = maps.keySet();
        Iterator<Integer> it = keys.iterator();
        while (it.hasNext()){
            Integer key = it.next();
            String value = maps.get(key);
            System.out.println(key + ":" + value);
        }

        System.out.println("========================");
        //使用for-each
        Set<Integer> keys1 = maps.keySet();
        for (Integer key: keys1) {
            System.out.println(key + ":" + maps.get(key));
        }
    }
}

运行结果: 在这里插入图片描述

  • 方式二:通过EntrySet()方法直接获取key和value(这种方式效率较高,推荐使用)
package maptest;

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

public class MapTest02 {
    public static void main(String[] args) {
        //创建一个Map集合
        Map<Integer, String> maps = new HashMap<>();

        //添加键值对
        maps.put(1,"赵倩");
        maps.put(2,"王雷");
        maps.put(3, "张芝");
        maps.put(4, "刘柳");

        //方式二:通过EntrySet()方法
        Set<Map.Entry<Integer, String>> entries = maps.entrySet();
        Iterator<Map.Entry<Integer, String>> it = entries.iterator();
        while (it.hasNext()){
            Map.Entry<Integer, String> entry = it.next();
            Integer key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key + ":" + value);
        }
        System.out.println("================================");
        //for-each
        Set<Map.Entry<Integer, String>> entries2 =  maps.entrySet();
        for (Map.Entry<Integer, String> entry: entries2) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }
    }
}

运行结果:
在这里插入图片描述

3.4 HashMap集合

3.4.1 HashMap集合key的特点

key是无序不可重复的。
无序:存进去的顺序与取出的顺序不一定相同;
不可重复:具有唯一性(如果key重复的话,value会覆盖)。

示例代码:

package maptest;


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

public class HashMapTest01 {
    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<Integer, String>();
        map.put(111111115,"张三");
        map.put(111111112,"李四");
        map.put(111111113,"王五");
        map.put(111111114,"赵六");
        map.put(111111114,"赵六2"); //存储重复的key,value会覆盖

        Set<Map.Entry<Integer, String>> entries = map.entrySet();
        for (Map.Entry<Integer, String> entry:entries) { //取出的顺序不一定与存入的顺序相同
            Integer key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key + ":" + value);
        }
    }
}

运行结果:
在这里插入图片描述

3.4.2 HashMap集合的key存储自定义类型

自定义User类型(已覆写toString方法和equals方法):

package maptest;

import java.util.Objects;

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

    public User() {
    }

    public User(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 "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public boolean equals(Object o) {
       if(o == null){
           return false;
       }
       if(o == this){
           return true;
       }
       if(o instanceof User){
           User user = (User) o;
           return user.age == this.age && user.name.equals(this.name);
       }
       return false;
    }
}

创建相应的map并遍历:

package maptest;

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

public class HashMapTest02 {
    public static void main(String[] args) {
        //key中存储自定义类型User
        Map<User, String> map = new HashMap<User, String>();

        //创建User对象
        User user1 = new User("张三", 18);
        User user2 = new User("李四", 23);
        User user3 = new User("王五", 25);
        User user4 = new User("张三", 18);

        //向map中存入元素
        map.put(user1,"1111");
        map.put(user2,"2222");
        map.put(user3,"3333");
        map.put(user4,"4444");

        //遍历map
        Set<User> users = map.keySet();
        for (User user:users) {
            String value = map.get(user);
            System.out.println(user + "=" + value);
        }
    }
}

运行结果:
在这里插入图片描述
在重写了equals()方法的情况下,对于equals()为true的User作为key,其value并没有被覆盖,而是都存入了map,这与HashMap的存储结构【哈希表】有关。

3.4.3 哈希表

3.4.3.1 哈希表的介绍

HashMap的底层为散列表(又称哈希表)的存储结构【数组+单向链表】,每一个节点存储有四个值:key、hash(哈希值,或者叫哈希码,由key调用hashCode()方法获得)、value、next(下一个节点的地址)
①哈希表是一种查询和增删效率都很高的一种数据结构,非常重要,在很多场合使用,并且面试也很常见。必须掌握。
②哈希表的查询和增删效率都好,因为哈希表是“数组 + 链表”的结合体,同时结合了二者的优点。
③数组和链表的结合不是绝对的。哈希表可能是:数组 + 链表,数组 + 红黑树, 数组 + 链表 + 红黑树等。

在这里插入图片描述
在这里插入图片描述

3.4.3.2 哈希表的存储原理

①哈希函数:

  • 通过哈希函数(即hashCode())可以将一个Java对象映射为一个数字,这个数字就是哈希值。(就像现实世界中,每个人(对象)都会映射一个身份证号(哈希值)一样。)
  • 也就是说hashCode()方法的返回值就是哈希值。
  • 一个好的哈希函数,可以让散列分布均匀。
    ②哈希值:也叫做哈希码。是哈希函数执行的结果。
    ③哈希碰撞:也叫做哈希冲突。
  • 当两个对象“哈希值%数组长度”之后得到的下标相同时,就发生了哈希冲突。
  • 如何解决哈希冲突?将冲突的挂到同一个链表上或同一个红黑树上。
    ④以上描述凡是“哈希”都可以换为“散列”。
    在这里插入图片描述
    哈希表的put()方法的底层执行步骤:(如上图)
  1. 对应的key调用hashCode()方法,得到哈希值(即Node中的hash);
  2. 通过“哈希值%数组长度”得到该键值对的存入数组的索引值(这里可能产生哈希碰撞);
  3. 把键值对存入对应索引的位置,对应两种情况(假设计算的索引值为9):
    情况一:如果索引为9的位置没有存储元素,则将键值对封装为Node对象,然后存入到索引为9的位置中。
    情况二:如果索引为9的位置有存储元素(有首节点),那么就遍历整个单链表,如果遍历出来节点的key和添加键值对的key相同,则做覆盖操作;如果单链表遍历出来节点的key和添加键值对的key都不同,则把添加键值对封装为Node对象,最后插入到单链表的末尾。

哈希表的get()方法的底层执行步骤:

  1. 对应的key调用hashCode()方法,得到哈希值(即Node中的hash);
  2. 通过“哈希值%数组长度”得到该键值对的存入数组的索引值;
  3. 从对应数组索引值的位置的链表中逐一开始查找,直到找到key.equals返回true的节点,返回其value值。

3.4.4 存放在HashMap和HashSet集合key部分的元素必须同时重写hashCode+equals方法,若equals返回true时,hashCode必须相同。

再回去想想刚刚哪个例子,在重写了equals()方法的情况下,对于equals()为true的User作为key,其value并没有被覆盖,而是都存入了map,原因是:并没有调用equals()方法,由于并没有重写hashCode()方法,所以这里两个User的哈希值肯定不一样,而刚好“哈希值%数组长度”得到的索引值也不一样,导致没有产生哈希碰撞,直接就存进去了。

package maptest;

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

public class HashMapTest02 {
    public static void main(String[] args) {
        //key中存储自定义类型User
        Map<User, String> map = new HashMap<User, String>();

        //创建User对象
        User user1 = new User("张三", 18);
        User user2 = new User("李四", 23);
        User user3 = new User("王五", 25);
        User user4 = new User("张三", 18);

        //向map中存入元素
        map.put(user1,"1111");
        map.put(user2,"2222");
        map.put(user3,"3333");
        map.put(user4,"4444");

        System.out.println("user1的哈希值:" + user1.hashCode());
        System.out.println("user2的哈希值:" + user2.hashCode());

        //遍历map
        Set<User> users = map.keySet();
        for (User user:users) {
            String value = map.get(user);
            System.out.println(user + "=" + value);
        }
    }
}

运行结果:
在这里插入图片描述

所以,重要原理:
①存放在HashMap和HashSet 集合key部分的元素必须同时重写hashCode+equals方法。
②equals返回true时,hashCode的返回值必须相同。

为什么要同时重写hashCode+equals方法?

  • 若equals方法返回true,则表明两个对象为同一个对象,而HashMap(HashSet)中的key是不可重复的,所以两个对象只能放其中一个,如果此时hashCode方法不重写则以对象的内存地址为基础产生哈希值,得到的哈希值一定不相同,则可能不会产生哈希碰撞,使得两个对象都成功存入,如此,则违背了“HashMap(HashSet)中的key是不可重复的”这一原则。

在IDEA中,直接“Alt+Insert”重写hashCode+equals方法即可,非常方便且高效,不需要自己手动重写!
User修改如下:

package maptest;

import java.util.Objects;

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

    public User() {
    }

    public User(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 "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

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

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

新运行结果:
在这里插入图片描述

3.4.5 HashMap中的key可以存null

  • HashMap中的key可以存null,但也只能存一个key为null的(key不重复);
  • 如果添加的键值对key为null,则程序默认将该键值对存储到table数组中索引为0的位置。
package maptest;

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

public class HashMapTest03 {
    public static void main(String[] args) {
        //key中存储自定义类型User
        Map<User, String> map = new HashMap<User, String>();

        //存入key为null的节点
        map.put(null, "11111");
        map.put(null, "22222");
        map.put(null, "33333");
        
        Set<Map.Entry<User, String>> entries = map.entrySet();
        for (Map.Entry<User, String> entry:entries) {
            User key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key + ":" + value);
        }
    }
}

运行结果:
在这里插入图片描述

3.4.6 手写HashMap的put和get方法

package maptest;

/**
 * 手写HashMap的put和get方法
 */
public class MyHashMap<K,V> {
    /**
     * 哈希表
     */
    private Node<K,V>[] table;

    /**
     * 集合中的键值对个数
     */
    private int size;

    public MyHashMap() {
        //注意:这里new数组的时候不能使用泛型,new Node<K,V>[16]这样写是错误的
        this.table = new Node[16];
    }

    static class Node<K,V>{
         /**
          * key
          */
         K key;
         /**
          * 哈希值
          */
         int hash;
         /**
          * value
          */
         V value;
         /**
          * 下一个节点的内存地址
          */
         Node<K,V> next;

        /**
         * 构造一个节点对象
         * @param key 键
         * @param hash 哈希值
         * @param value 值
         * @param next 下一个节点的内存地址
         */
        public Node(K key, int hash, V value, Node<K, V> next) {
            this.key = key;
            this.hash = hash;
            this.value = value;
            this.next = next;
        }

        @Override
        public String toString() {
            return "{" +
                    "key=" + key +
                    ", value=" + value +
                    "}\n";
        }
    }


    /**
     * 获取集合中键值对个数
     * @return 个数
     */
    public int size(){
        return size;
    }

    /**
     * 向MyHashMap中添加一个键值对
     * @param key 键
     * @param value 值
     * @return value,如果key重复,返回的是oldValue,否则返回newValue
     */
    public V put(K key, V value){
        /*
            【第一步】:处理key为null的情况
                如果添加键值对的key就是null,则将该键值对存储到table数组索引为0的位置。
            【第二步】:获得key对象的哈希值
                如果添加键值对的key不是null,则就调用key的hashcode()方法,获得key的哈希值。
            【第三步】:获得键值对的存储位置
                因为获得的哈希值在数组合法索引范围之外,因此我们就需要将获得的哈希值转化为[0,数组长度-1]范围的整数,
                那么可以通过取模法来实现,也就是通过“哈希值 % 数组长度”来获得索引位置(i)。
            【第四步】:将键值对添加到table数组中
                当table[i]返回结果为null时,则键键值对封装为Node对象并存入到table[i]的位置。
                当table[i]返回结果不为null时,则意味着table[i]存储的是单链表。我们首先遍历单链表,如果遍历出来节点的
                key和添加键值对的key相同,那么就执行覆盖操作;如果遍历出来节点的key和添加键值对的key都不同,则就将键键
                值对封装为Node对象并插入到单链表末尾。
         */
        //第一步:处理key为null的情况
        if(key == null){
            return putForKeyNull(key,value);
        }
        //第二步:获得key对象的哈希值
        int hash = key.hashCode();

        //第三步:获得键值对的存储位置
        int index = Math.abs(hash % table.length);

        //第四步:将键值对添加到table数组中
        Node<K,V> node = table[index]; //取出下标index位置的node
        if(node == null){
            //下标为index的位置上为空
            table[index] = new Node<>(key, hash, value, null);
            size++;
            return value;
        }
        //下标为index的位置上有单向链表
        Node<K,V> prev = null;
        while (node != null){
            //找到key等于当前传入key的节点
            if(node.key.equals(key)){
                V oldValue = node.value;
                node.value = value;
                return oldValue;
            }
            prev = node;
            node = node.next;
        }
        //没有找到key等于当前传入key的节点(插入,尾插法)
        prev.next = new Node<>(key,hash,value,null);
        size++;
        return value;
    }

    /**
     * 处理添加key为null的键值对
     * @param key
     * @param value
     * @return
     */
    private V putForKeyNull(K key, V value) {
        Node<K,V> node = table[0];
        if(node == null){
            //下标为0的位置上为空
            table[0] = new Node<>(key,0,value,null);
            size++;
            return value;
        }
        //下标为0的位置上有单向链表
        Node<K,V> prev = null;
        while (node != null){
            if(node.key == null){
                //找到key为null的节点
                V oldValue = node.value;
                node.value = value;
                return oldValue;
            }
            prev = node;
            node = node.next;
        }
        //没有找到key为null的节点(插入,尾插法)
        prev.next = new Node<>(key,0,value,null);
        size++;
        return value;
    }

    /**
     * 根据key获取对应value
     * @param key 键
     * @return 值
     */
    public V get(K key){
        /*
        【第一步】:处理key为null的情况
            如果查询的key就是null,则就在table数组索引为0的位置去查询。
        【第二步】:获得key对象的哈希值
            如果查询的key不是null,则就调用key的hashcode()方法,获得key的哈希值。
        【第三步】:获得键值对的存储位置
            因为获得的哈希值在数组合法索引范围之外,因此我们就需要将获得的哈希值转化为[0,数组长度-1]范围的整数,
            那么可以通过取模法来实现,也就是通过“哈希值 % 数组长度”来获得索引位置(i)。
        【第四步】:遍历单链表,根据key获得value值
            如果table[i]返回的结果为null,则证明单链表不存在,那么返回null即可
            如果table[i]返回的结果不为null时,则证明单链表存在,那么就遍历整个单链表。如果遍历出来节点的key和查询
            的key相同,那么就返回遍历出来节点的value值;如果整个单链表遍历完毕,则遍历出来节点的key和查询的key都不
            相等,那么就证明查询key在链表中不存在,则直接返回null即可。
         */
        //第一步:处理key为null的情况
        if(key == null){
            Node<K,V> node = table[0];
            //索引为0的位置没有存储任何元素
            if(node == null){
                return null;
            }
            //索引为0的位置存储了元素,有单向链表
            while (node!= null){
                if(node.key == null){
                    return node.value;
                }
                node = node.next;
            }
        }
        //key不是null
        //第二步:获得key对象的哈希值
        int hash = key.hashCode();

        //第三步:获得键值对的存储位置
        int index = Math.abs(hash % table.length);

        //第四步:遍历单链表,根据key获得value值
        Node<K,V> node = table[index];
        if(node == null){
            //下标为index的位置上为空
            return null;
        }
        //下标为index的位置上有单向链表
        while (node != null){
            if(node.key.equals(key)){
                return node.value;
            }
            node = node.next;
        }
        return null;
    }

    /**
     * 重写toString方法,输出所有节点
     */
    @Override
    public String toString() {
        StringBuilder stb = new StringBuilder();

        for (int i = 0; i < table.length; i++) {
            Node<K,V> node = table[i];
            if(node != null){
                //如果node不为空,遍历整个单向链表
                while (node != null){
                    stb.append(node);
                    node = node.next;
                }
            }
        }
        return stb.toString();
    }
}

测试程序(User与上面最终的User一致):

package maptest;

public class HashMapTest04 {
    public static void main(String[] args) {
        MyHashMap<Integer,String> map = new MyHashMap<>();
        map.put(1111, "张三");
        map.put(2222, "李四");
        map.put(3333, "王五");
        map.put(4444, "赵六");
        map.put(1111, "张三2");
        map.put(null, "钱七");
        map.put(null, "钱七2");
        map.put(null, "钱七3");
        System.out.println(map);
        System.out.println(map.get(1111));

        //key为自定义类型User
        MyHashMap<User,String> userMap = new MyHashMap<>();
        User user1 = new User("张三", 18);
        User user2 = new User("李四", 23);
        User user3 = new User("王五", 25);
        User user4 = new User("张三", 18);

        userMap.put(user1, "111");
        userMap.put(user2, "222");
        userMap.put(user3, "333");
        userMap.put(user4, "444");
        userMap.put(user1, "aaa");
        System.out.println(userMap);
        System.out.println(userMap.get(user1));
    }
}

运行结果:
在这里插入图片描述

3.4.7 HashMap在Java8后的改进(包含Java8)

①初始化时机:

  • Java8之前,构造方法执行时初始化table数组。
  • Java8之后,第一次调用put方法时初始化table数组。
    ②插入法:
  • Java8之前,头插法
  • Java8之后,尾插法
    ③数据结构:
  • Java8之前:数组 + 单向链表
  • Java8之后:数组 + 单向链表 + 红黑树。
  • 最开始使用单向链表解决哈希冲突。如果结点数量 >= 8,并且table的长度 >= 64。单向链表转换为红黑树。
  • 当删除红黑树上的结点时,结点数量 <= 6 时。红黑树转换为单向链表。
    -在这里插入图片描述

3.4.8 HashMap初始化容量永远都是2的次幂

在这里插入图片描述
①HashMap集合初始化容量16(第一次调用put方法时初始化)
②HashMap集合的容量永远都是2的次幂,假如给定初始化容量为31,它底层也会变成32的容量。
③将容量设置为2的次幂作用是:1)加快哈希计算;2)减少哈希冲突。

  • 为什么会加快哈希计算?
    首先,使用二进制运算是最快的。当一个数字是2的次幂时,例如数组的长度是2的次幂时,hash & (length-1) 的结果和 hash % length的结果相同。
    注意: 只有length是2的次幂时,以上等式才会成立。因为使用 & 运算符,让效率提升,因此建议容量一直是2的次幂。
  • 为什么会减少哈希冲突?
    计算索引的底层运算是:hash & length - 1。
    如果length是偶数:length-1后一定是奇数,奇数二进制位最后一位一定是1,1和其他二进制位进行与运算,结果可能是1,也可能是0,这样可以减少哈希冲突,让散列分布更加均匀。
    如果length是奇数:length-1后一定是偶数,偶数二进制位最后一位一定是0,0和任何数进行与运算,结果一定是0,则只能利用数组中偶数位的索引位置进行存储,这样就会导致发生大量的哈希冲突,白白浪费了一半的空间。

3.4.9 HashMap的初始化容量的设置

  1. 当哈希表中的元素越来越多的时候,散列碰撞的几率也就越来越高(因为数组的长度是固定的),从而导致单链表过长,降低了哈希表的性能,此时我们就需要对哈希表进行扩容操作。
  2. 那么HashMap什么时候进行扩容呢?当执行put()操作的时候,如果HashMap中存储元素的个数超过数组长度*loadFactor的结果(loadFactor指的是负载因子,loadFactor的默认值一般为0.75),那么就需要执行数组扩容操作。
  3. 所谓的扩容操作,就是把数组的空间大小扩大一倍,然后遍历哈希表中元素,把这些元素重新均匀分散到扩容后的哈希表中。例如,默认情况下,数组大小为16,那么当HashMap中元素个数超过160.75=12的时候,就需要执行扩容操作,把数组的大小扩展为216=32,然后重新计算每个元素在数组中的位置,这是一个非常消耗性能的操作。
  4. 为了避免扩容带来的性能损坏,建议使用哈希表之前,先预测哈希表需要存储元素的个数,提前为哈希表中的数组设置合适的存储空间大小,避免去执行扩容的操作,进一步提升哈希表的性能。例如:我们需要存储1000个元素,按照哈希表的容量设置为2的整数次幂的思想,我们设置哈希表的容量为1024更合适。但是0.75*1024 < 1024,需要执行消耗性能的扩容操作,因此我们设置哈希表的容量为2048更加合适,这样既考虑了&的问题,也避免了扩容的问题。
  5. 思考:当我们创建一个HashMap对象,设置哈希表的容量为15,请问HashMap对象创建成功后,哈希表的实际容量为多少呢???==>实际容量为12,因为即使设置哈希表的容量为15,底层也会变为16,而由于HashMap中存储元素的个数超过数组长度*loadFactor就会扩容,所以在扩容前的实际能存储的容量为16*0.75=12。

3.5 LinkedHashMap

  • LinkedHashMap集合和HashMap集合的用法完全相同,不过LinkedHashMap可以保证插入顺序。
  • LinkedHashMap集合因为可以保证插入顺序,因此效率比HashMap低一些。
  • LinkedHashMap是如何保证插入顺序的?底层采用了双向链表来记录顺序。
  • LinkedHashMap集合底层采用的数据结构是:哈希表 + 双向链表。
  • LinkedHashMap集合的key是:有序不可重复。key部分也需要同时重写hashCode + equals。
  • key的取值可以为null,key如果相同,value也是覆盖。

存储结构图:
在这里插入图片描述

示例代码:

package maptest;

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

public class LinkedHashMapTest01 {
    public static void main(String[] args) {
        Map<Integer, String> map = new LinkedHashMap<>();
        map.put(100, "张三");
        map.put(5, "李四");
        map.put(3000, "王五");
        map.put(20, "赵六");
        map.put(100, "张三2");
        map.put(null,null);

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

运行结果(有序:插入顺序与取出顺序一致):
在这里插入图片描述

3.6 Hashtable

  • Hashtable和HashMap一样,底层也是哈希表。
  • Hashtable的key和value都不能为null。
  • Hashtable是线程安全的,方法上都有synchronized关键字。使用较少,因为保证线程安全有其他方式。
  • Hashtable的初始化容量:11。默认加载因子:0.75
  • Hashtable的扩容策略:2倍。
  • HashMap中能用的方法在Hashtable中也能用,不过Hashtable中有一些传统方法,这些方法不属于集合框架(需要用Hashtable类型的对象调用):
    ①Enumeration keys(); 获取所有key的迭代器
    ②Enumeration elements(); 获取所有value的迭代器
  • 迭代器Enumeration的相关方法
    ①boolean hasMoreElements(); 是否含有元素
    ②E nextElement(); 获取元素
  • Hashtable和HashMap集合的区别:
    ①HashMap集合线程不安全,效率高,key和value允许null。
    ②Hashtable集合线程安全,效率低,key和value不允许null。

示例代码:

package maptest;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;

public class HashtableTest01 {
    public static void main(String[] args) {
        Map<Integer,String> map = new Hashtable<>();
        map.put(100, "张三");
        map.put(5, "李四");
        map.put(3000, "王五");
        map.put(20, "赵六");
        map.put(100, "张三2");

        //java.lang.NullPointerException
        //map.put(123,null);

        //java.lang.NullPointerException
        //map.put(null,"钱七");

        //java.lang.NullPointerException
        //map.put(null,null);

        //继承的迭代方法
        Set<Map.Entry<Integer, String>> entries = map.entrySet();
        for (Map.Entry<Integer, String> entry:entries) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }

        System.out.println("=============================");
        Hashtable<Integer,String> hashtable = (Hashtable<Integer, String>) map;
        //Hashtable独有的迭代方式
        Enumeration<Integer> keys = hashtable.keys(); //获取所有key
        while (keys.hasMoreElements()){
            Integer key = keys.nextElement();
            System.out.println(key);
        }

        Enumeration<String> values = hashtable.elements(); //获取所有value
        while (values.hasMoreElements()){
            String value = values.nextElement();
            System.out.println(value);
        }
    }
}

运行结果:
在这里插入图片描述

3.7 Properties

  • Properties被称为属性类。通常和xxx.properties属性文件一起使用。
  • Properties的父类是Hashtable。因此Properties也是线程安全的。
  • Properties不支持泛型,key和value只能是String类型,且均不能为null。
  • Properties相关方法:
    ①Object setProperty(String key, String value); 和put方法一样。
    ②String getProperty(String key); 通过key获取value
    ③Set<String> propertyNames(); 获取所有的key

示例代码:

package maptest;

import java.util.Enumeration;
import java.util.Properties;

public class PropertiesTest01 {
    public static void main(String[] args) {
        //创建Properties对象
        Properties properties = new Properties();

        //向对象中存储key和value
        properties.setProperty("jdbc.driver", "com.mysql.jdbc.Driver");
        properties.setProperty("jdbc.user", "root");
        properties.setProperty("jdbc.password","123456");
        properties.setProperty("jdbc.url", "jdbc:mysql://localhost:3306/sunny");

        //通过key获取value
        String driver = properties.getProperty("jdbc.driver");
        String user = properties.getProperty("jdbc.user");
        String password = properties.getProperty("jdbc.password");
        String url = properties.getProperty("jdbc.url");

        System.out.println(driver);
        System.out.println(user);
        System.out.println(password);
        System.out.println(url);

        System.out.println();
        //获取所有的key
        Enumeration<?> names = properties.propertyNames();
        while (names.hasMoreElements()){
            String name = (String) names.nextElement();
            System.out.println(name + "=" + properties.getProperty(name));
        }
    }
}

运行结果:
在这里插入图片描述

3.8 一些树的介绍

3.8.1 二叉树

叉树(BinaryTree)由一个结点及两棵互不相交的、分别称作这个根的左子树和右子树的二叉树组成。下图中展现了五种不同基本形态的二叉树。
在这里插入图片描述
(a) 为空树。
(b) 为仅有一个结点的二叉树。
© 是仅有左子树而右子树为空的二叉树。
(d) 是仅有右子树而左子树为空的二叉树。
(e) 是左、右子树均非空的二叉树。

3.8.2 排序二叉树

排序二叉树采用左小右大原则存储,按照中序遍历方式,自动就是排好序的。

  • 中序遍历:左根右
  • 前序遍历:根左右
  • 后序遍历:左右根

比如:我们要将数据【14, 12, 23, 4, 16, 13, 8, 3】存储到排序二叉树中,结果如下图所示:
在这里插入图片描述

排序二叉树的问题: 排序二叉树本身实现了排序功能,可以快速检索。但如果插入的节点集本身就是有序的,要么是由小到大排列,要么是由大到小排列,那么最后得到的排序二叉树将变成普通的链表,其检索效率就会很差。

同上数据,先进行排序变成:【3, 4, 8, 12, 13, 14, 16, 23】,然后存储到排序二叉树中,显然就变成了链表,如下图所示:
在这里插入图片描述

3.8.3 平衡二叉树(AVL)

为了避免出现上述一边倒的存储,提出了“平衡二叉树”。

  • 在平衡二叉树中任何结点的两个子树的高度最大差别为1,所以它也被称为高度平衡树。 增加和删除结点可能需要通过一次或多次树旋转来重新平衡这个树。
  • 结点的平衡因子是它的左子树的高度减去它的右子树的高度(有时相反)。带有平衡因子1、0或 -1的节点被认为是平衡的。带有平衡因子-2或2的节点被认为是不平衡的,并需要重新平衡这个树。
  • 比如,我们存储排好序的数据【3, 4, 8, 12, 13, 14, 16, 23】,增加结点如果出现不平衡,则通过节点的左旋或右旋,重新平衡树结构,最终平衡二叉树如下图所示(另参见:https://www.cs.usfca.edu/~galles/visualization/Algorithms.html)
    在这里插入图片描述

3.8.4 红黑二叉树

红黑二叉树(简称:红黑树),它首先是一棵二叉树,同时也是一棵自平衡的排序二叉树。
红黑树在原有的排序二叉树增加了如下几个要求:

  1. 每个结点要么红色,要么黑色。
  2. 根结点永远是黑色。
  3. 所有的叶子结点都是空结点(即null),并且是黑色的。
  4. 每个红色结点的两个子结点都是黑色 (从每个叶子结点到根结点的路径上不会有两个连续的红色结点) 。
  5. 从任一结点到其子树中每个叶子结点的路径都包含相同数量的黑色结点。
  6. 每次新结点在插入时,颜色是红色的。插入后,会根据红黑树的约束条件进行:树的旋转和颜色的调整。

自平衡的含义:以上这些约束强化了红黑树的关键性质:从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。这样就让树大致上是平衡的。

红黑树是一个更高效的检索二叉树,JDK 提供的集合类 TreeMap、TreeSet 本身就是一个红黑树的实现。红黑树的基本操作:插入、删除、左旋、右旋、着色。每插入或者删除一个节点,可能会导致树不在符合红黑树的特征,需要进行修复,进行 “左旋、右旋、着色” 操作,使树继续保持红黑树的特性。
在这里插入图片描述

3.9 TreeMap

①TreeMap底层就是红黑树。
②TreeMap和HashMap用法一样,只不过需要key排序的时候,就可以使用TreeMap。
③TreeMap的key可排序不可重复,且不能是null,不过value可以为null。
④TreeMap的key也需要重写hashCode和equals方法。

key为内置类型时可排序示例代码:

package maptest;

import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

public class TreeMapTest01 {
    public static void main(String[] args) {
        //创建HashMap集合
        Map<Integer, String> map = new TreeMap<>();

        //存入
        map.put(100, "张三");
        map.put(5, "李四");
        map.put(13, "王五");
        map.put(20, "赵六");
        map.put(100, "张三2");

        //java.lang.NullPointerException
//        map.put(null, "zhangzhang");
        map.put(1, "钱七");

        //遍历
        Set<Map.Entry<Integer, String>> entries = map.entrySet();
        for (Map.Entry<Integer, String> entry: entries) {
            System.out.println(entry.getKey() + "=" + entry.getValue());
        }

    }
}

运行结果(由于Integer实现了Comparable接口,实现了compareTo方法,是可排序的,所以这里按key部分排序输出了):
在这里插入图片描述

3.9.1 TreeMap集合的key为自定义类型

当TreeMap集合的key为自定义类型,要使TreeMap集合的key可排序,有两种方式:

  • 第一种方式:key实现Comparable接口,并且提供compareTo方法,在该方法中添加了比较规则。(比较规则不变的话建议这种。)
  • 第二种方式:创建TreeMap集合时,传一个比较器,比较器实现Comparator接口,在compare方法中添加比较规则。

自定义类型User(当前为不可排序):

package maptest;

import java.util.Objects;

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

    public User() {
    }

    public User(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 "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

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

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

测试代码:

package maptest;

import java.util.Map;
import java.util.TreeMap;

public class TreeMapTest02 {
    public static void main(String[] args) {
        //key中存储自定义类型User
        Map<User, String> map = new TreeMap<>();

        //创建User对象
        User user1 = new User("ccc", 18);
        User user2 = new User("ddd", 33);
        User user3 = new User("bbb", 15);
        User user4 = new User("aaa", 18);

        //向map中存入元素
        map.put(user1,"1111");
        map.put(user2,"2222");
        map.put(user3,"3333");
        map.put(user4,"4444");

        System.out.println(map);
    }
}

运行结果(由于TreeMap在根据key排序时试图将User转换为Comparable接口,但这里没有实现这个接口,导致出现类型转换异常):
在这里插入图片描述

第一种方式示例代码:

User实现Comparable接口:

package maptest;

import java.util.Objects;

public class User implements Comparable<User>{
    private String name;
    private int age;

    public User() {
    }

    public User(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 "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

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

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


    @Override
    public int compareTo(User o) {
        //年龄一致的话,比较名字
        if(this.age == o.age){
            return this.name.compareTo(o.name);
        }
        return this.age - o.age; //年龄不一致,直接按年龄比较
    }
}

测试代码如上不变,运行结果:
在这里插入图片描述

第二种方式示例代码:
User还是原来的User,不实现Comparable接口:

package maptest;

import java.util.Objects;

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

    public User() {
    }

    public User(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 "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

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

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

再定义一个比较器(这里使用匿名内部类):

package maptest;

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

public class TreeMapTest03 {
    public static void main(String[] args) {
        //创建TreeMap时,给构造方法传递一个比较器,这里直接使用匿名内部类的方式传入比较器
        Map<User, String> map = new TreeMap<>(new Comparator<User>() {
            @Override
            public int compare(User o1, User o2) {
                return o1.getAge() - o2.getAge(); //按年龄比较
            }
        });

        //创建User对象
        User user1 = new User("zhangsan", 18);
        User user2 = new User("lisi", 23);
        User user3 = new User("wangwu", 25);
        User user4 = new User("zhaoliu", 16);

        //向map中存入元素
        map.put(user1,"1");
        map.put(user2,"2");
        map.put(user3,"3");
        map.put(user4,"4");

        System.out.println(map);
    }
}

运行结果:
在这里插入图片描述

4.Set

Set也是Collection集合家族的,只不过Set底层都是Map,所以上面先把Map学习了。

  • Set接口继承Collection,没有任何新增任何方法。
  • Set接口常用实现类包括:HashSet、LinkedHashSet、TreeSet。

4.1 HashSet

  • HashSet底层就是HashMap,往HashSet集合中存储元素,实际上是放到了HashMap集合的key部分。因此放在HashSet集合中的元素,要同时重写hashCode+equals。
  • HashSet底层当然也是哈希表。
  • HashSet集合存储元素特点:无序不可重复。无序指的是存进去的顺序和取出的顺序不一定一样。

1)示例代码(无序不可重复):

package settest;

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

public class HashSetTest01 {
    public static void main(String[] args) {
        Set<Integer> set = new HashSet<>();

        //无序不可重复
        set.add(200);
        set.add(200);
        set.add(200);
        set.add(10);
        set.add(2);
        set.add(3000);

        System.out.println(set);

        Set<String> set2 = new HashSet<>();

        //无序不可重复
        set2.add("aaa");
        set2.add("aaa");
        set2.add("bbb");
        set2.add("ccc");
        set2.add("ddd");
        set2.add("eee");

        System.out.println(set2);

    }
}

运行结果:
在这里插入图片描述

2)示例代码(自定义类型需要重写hashCode和equals方法):

自定义Vip类(未重写hashCode和equals方法):

package settest;

public class Vip {
    private int id;
    private int age;
    private String name;

    public Vip() {
    }

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

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

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

测试代码:

package settest;

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

public class HashSetTest02 {
    public static void main(String[] args) {
        //创建HashSet
        Set<Vip> vips = new HashSet<>();

        //创建Vip对象
        Vip vip1 = new Vip(111,20,"zhangsan");
        Vip vip2 = new Vip(111,20,"zhangsan");
        Vip vip3 = new Vip(111,20,"zhangsan");

        //存入
        vips.add(vip1);
        vips.add(vip2);
        vips.add(vip3);

        System.out.println(vips);
    }
}

运行结果(三个key内容相同的都存进去了):
在这里插入图片描述
自定义Vip类(增加重写hashCode和equals方法):

package settest;

import java.util.Objects;

public class Vip {
    private int id;
    private int age;
    private String name;

    public Vip() {
    }

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

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

    public String getName() {
        return name;
    }

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

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

    @Override
    public boolean equals(Object o) { 
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Vip vip = (Vip) o;
        return id == vip.id; //id一致则为返回为true
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}

测试代码运行结果(只存进去一个):
在这里插入图片描述

4.2 LinkedHashSet

  • LinkedHashSet底层就是LinkedHashMap。所以底层是“哈希表+双向链表”。
  • LinkedHashSet集合存储元素特点:有序不可重复。有序指的是存进去的顺序和取出的顺序一样。放进去的元素也需要重写hashCode+equals。

示例代码:

package settest;

import java.util.LinkedHashSet;
import java.util.Set;

public class LinkedHashSetTest {
    public static void main(String[] args) {
        Set<Integer> set = new LinkedHashSet<>();

        //有序不可重复
        set.add(200);
        set.add(200);
        set.add(200);
        set.add(10);
        set.add(2);
        set.add(3000);

        System.out.println(set);

        Set<String> set2 = new LinkedHashSet<>();

        //有序不可重复
        set2.add("aaa");
        set2.add("aaa");
        set2.add("bbb");
        set2.add("ccc");
        set2.add("ddd");
        set2.add("eee");

        System.out.println(set2);
    }
}

运行结果(存入顺序和取出顺序一致):
在这里插入图片描述
key为自定义类型重写hashCode和equals方法就不写了…

4.3 TreeSet

  • TreeSet底层就是TreeMap。所以底层也是红黑树。
  • TreeSet集合存储元素特点:有序不可重复。有序表示可排序。放在TreeSet集合中元素要想排序,要么存储的元素实现Comparable接口,要么在构造TreeSet集合的时候传一个Comparator比较器。不可重复:放进去的元素也需要重写hashCode+equals。
  • TreeSet中不能存放null。
    示例代码(Vip类如上):
package settest;

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

public class TreeSetTest01 {
    public static void main(String[] args) {
        //创建TreeSet对象
        Set<Integer> set = new TreeSet<>();

        //存入
        set.add(100);
        set.add(23);
        set.add(5);
        set.add(18);
        set.add(1);

        System.out.println(set);

        //创建TreeSet对象
        Set<String> set2 = new TreeSet<>();

        //存入
        set2.add("abb");
        set2.add("bbb");
        set2.add("abc");
        set2.add("bba");
        set2.add("acb");


        System.out.println(set2);

        //创建存放自定义类型的TreeSet对象,并传入比较器
        Set<Vip> vips = new TreeSet<>(new Comparator<Vip>() {
            @Override
            public int compare(Vip o1, Vip o2) {
                return o1.getId() - o2.getId(); //按id排序
            }
        });

        //创建Vip对象
        Vip vip1 = new Vip(113,20,"zhangsan");
        Vip vip2 = new Vip(112,20,"lisi");
        Vip vip3 = new Vip(111,20,"wangwu");

        //存入
        vips.add(vip1);
        vips.add(vip2);
        vips.add(vip3);

        System.out.println(vips);
    }
}

运行结果(有序输出):
在这里插入图片描述

4.4 关于HashSet的面试题

有一个Student类:

package settest;

import java.util.Objects;

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;
        return age == student.age &&
                Objects.equals(name, student.name);
    }

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

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

阅读以下代码,回答其中的问题:

package settest;

import java.util.HashSet;

public class HashSetTest03 {
    public static void main(String[] args) {
        HashSet<Student> set = new HashSet<>();
        Student stu = new Student("张三", 18);
        set.add(stu);
        set.add(new Student("李四", 21));
        stu.setName("王五");
        // 问题1:请问是否删除了HashSet集合中的stu对象呢???
        set.remove(stu);
        // 问题2:添加以下Student对象是否成功???
        set.add(new Student("王五", 18));
        // 问题3:添加以下Student对象是否成功???
        set.add(new Student("张三", 18));
    }
}

分析与运行结果:

package settest;

import java.util.HashSet;

public class HashSetTest03 {
    public static void main(String[] args) {
    	//这种题目需要头脑清醒,一步一步分析
        //创建HashSet对象
        HashSet<Student> set = new HashSet<>();
        //创建Student对象stu
        Student stu = new Student("张三", 18);

        //存入
        set.add(stu);

        //存入新的对象
        set.add(new Student("李四", 21));
        //修改stu对象的name,由于set中存放的是stu的地址,所以set中对应的stu的name也修改了,但是set中stu对应节点还是采用的之前 张三和年龄18  生成的哈希值
        stu.setName("王五");
        System.out.println(set);

        // 问题1:请问是否删除了HashSet集合中的stu对象呢???
        //不能删除,remove寻找时,使用的是修改后的 王五 和年龄18 的哈希值 进行寻找,而stu节点还是采用的之前 张三和年龄18  生成的哈希值,并不一致,所以并不能找到
        set.remove(stu);
        System.out.println(set);

        // 问题2:添加以下Student对象是否成功???
        //添加成功,同上,存入时是按 王五 和年龄18 生成的哈希值,不会与当前哈希值产生冲突,可以直接存入
        set.add(new Student("王五", 18));
        System.out.println(set);

        // 问题3:添加以下Student对象是否成功???
        //添加成功,存入时会与stu节点产生哈希冲突,但是由于name不一样,equals返回为false,所以判定并不是相同节点,可以尾插法存入
        set.add(new Student("张三", 18));
        System.out.println(set);
    }
}

在这里插入图片描述

5.Collections工具类

测试几个针对List集合的常用方法:
①排序方法:sort
②混排,打乱顺序:shuffle
③反转:reverse
④替换所有元素:fill

package collectionstest;

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

/**
 * 测试集合工具类java.util.Collections
 */
public class CollectionsTest {
    public static void main(String[] args) {
        //1.sort方法,专门针对List集合提供的一个sort方法
        List<Integer> list = new ArrayList<>();
        list.add(100);
        list.add(12);
        list.add(8);
        list.add(7);
        list.add(10);
        System.out.println(list);
        Collections.sort(list);
        System.out.println(list);

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

        //如果List集合中的元素是自定义类型的,若需要使用sort排序,
        // 则该自定义类型需要实现Comparable接口,提供比较规则,
        // 或者排序时传入一个Comparator比较器
        List<Person> personList = new ArrayList<>();

        Person p1 = new Person(21);
        Person p2 = new Person(11);
        Person p3 = new Person(18);
        Person p4 = new Person(20);

        personList.add(p1);
        personList.add(p2);
        personList.add(p3);
        personList.add(p4);

        System.out.println(personList);
        Collections.sort(personList);
        System.out.println(personList);

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

        //2.shuffle方法打乱顺序
        System.out.println(list);
        Collections.shuffle(list);
        System.out.println(list);

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

        //3.reverse方法 反转List集合中的元素
        System.out.println(personList);
        Collections.reverse(personList);
        System.out.println(personList);

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

        //4.fill替换所有元素
        System.out.println(list);
        Collections.fill(list,null);
        System.out.println(list);
    }
}

class Person implements Comparable<Person>{
    private int age;

    public Person() {
    }

    public Person(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }

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

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

    @Override
    public int compareTo(Person o) {
        return this.age - o.age;
    }
}

运行结果:在这里插入图片描述

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

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

相关文章

postman汉化

一、postman历史版本下载&#xff1a;Postman 10.24.16 Download for Windows / Old Versions / FileHorse.comhttps://www.filehorse.com/download-postman/old-versions/ 二、汉化包下载&#xff1a; Releases hlmd/Postman-cn GitHubPostman汉化中文版. Contribute to h…

LeetCode———144—— 二叉树的前序遍历

目录 ​编辑 1.题目 2.解答 1.首先计算二叉树的节点个数&#xff1a; 2.以先序遍历&#xff08;Preorder Traversal&#xff09;的方式遍历一个二叉树&#xff0c;并将遍历到的节点的值存储在一个整数数组中 3.最终代码 1.题目 . - 力扣&#xff08;LeetCode&#xff09; 给…

浮点数的存储方式、bf16和fp16的区别

目录 1. 小数的二进制转换2. 浮点数的二进制转换3. 浮点数的存储3.1 以fp32为例3.2 规约形式与非规约形式 4. 各种类型的浮点数5. BF16和FP16的区别Ref 1. 小数的二进制转换 十进制小数转换成二进制小数采用「乘2取整&#xff0c;顺序排列」法。具体做法是&#xff1a;用 2 2…

如何协调数据集成和数据质量?

想象一下用腐烂的木头制作的一件漂亮的家具或用劣质面料制成的高级时尚衬衫。材料的质量影响最终产品。那么&#xff0c;为什么数据洞察&#xff08;贵公司庞大的数据管理工作的主要产品&#xff09;会有所不同呢&#xff1f; 无论您的数据管理生态系统有多强大&#xff0c;或…

《自动机理论、语言和计算导论》阅读笔记:p139-p171

《自动机理论、语言和计算导论》学习第 7 天&#xff0c;p139-p171总结&#xff0c;总计 33 页。 一、技术总结 1.reversal p139, The reversal of a string a1a2…an is the string written backwards, that is anan-1…a1. 2.homomorphism A string homomorphism is a f…

点云语义分割:使用Cylinder3D训练SemanticKITTI数据集

点云语义分割:使用Cylinder3D训练SemanticKITTI数据集 一、环境二、数据准备3、训练4、测试5、可视化 一、环境 系统&#xff1a;Ubuntu18 Pytorch&#xff1a;1.5.0 GPU&#xff1a;Tesla V100 cuda&#xff1a;10.2 代码: Cylinder3D 二、数据准备 下载semanticKITTI数据集…

32.5k star!发现一个新的 API 调试工具!postman 要被替换了【文末有项目源码】

在软件开发过程中&#xff0c;API&#xff08;应用程序接口&#xff09;扮演着至关重要的角色。为了确保 API 的可靠性和性能&#xff0c;开发人员需要一种高效的方式来测试和调试它们。这方面的工具&#xff0c;大家经常用到的应该就是 postman 了。不过&#xff0c;今天想要给…

钡铼IOy系列模块深挖工业场景需求提供丰富多样的I/O解决方案

钡铼IOy系列模块以其灵活性和多样性&#xff0c;在工业场景中提供了丰富多样的I/O解决方案&#xff0c;满足了不同行业、不同应用场景的需求。以下是一些常见的工业场景需求及钡铼IOy系列模块提供的解决方案&#xff1a; 1. 工厂自动化 需求&#xff1a;工厂自动化需要对生产线…

03-JAVA设计模式-迭代器模式

迭代器模式 什么是迭代器模式 迭代器模式&#xff08;demo1.Iterator Pattern&#xff09;是Java中一种常用的设计模式&#xff0c;它提供了一种顺序访问一个聚合对象中各个元素&#xff0c;而又不需要暴露该对象的内部表示的方法。迭代器模式将遍历逻辑从聚合对象中分离出来…

斯坦福大学2024年人工智能发展和前景全面分析报告

2024 年指数是斯坦福大学迄今为止最全面的指数&#xff0c;恰逢人工智能对社会的影响力达到前所未有的重要时刻。今年&#xff0c;斯坦福大学扩大了研究范围&#xff0c;更广泛地涵盖人工智能的技术进步、公众对该技术的看法以及围绕其发展的地缘政治动态等基本趋势。 完整详细…

C++基本输入输出

C 中的输入和输出&#xff08; I/O &#xff09;主要是通过标准库中的输入输出流来实现的。最常用的是 iostream 1. 库&#xff0c;它提供了用于输入和输出的基本流类&#xff0c;包括 cin 、 cout 、 cerr 和 clog 。 1.标准输出流 ( cout ) cout 代表标准输出流&a…

Java 基于微信小程序的医院预约挂号小程序(V3)

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

短信防刷之滑动验证码

前言&#xff1a;最近想写一个滑动验证码&#xff0c;前台的样式虽然很好看&#xff0c;但是并不安全&#xff0c;网上也都是一些demo&#xff0c;不是前后台分离的&#xff0c;然后就自己查资料&#xff0c;自己来完成了 滑动验证码 一、为什么要使用滑动验证码 首先&#x…

C++ 秋招必知必会(数据结构与算法:下)

20. 二叉树的定义与操作 二叉树&#xff08;binary tree&#xff09;是一种非线性数据结构&#xff0c;代表着祖先与后代之间的派生关系&#xff0c;体现着“一分为二”的分治逻辑 与链表类似&#xff0c;二叉树的基本单元是节点&#xff0c;每个节点包含&#xff1a;值、左子…

吐血整理102个Python项目,从基础到高级,练完你就牛了!

前言 Python 初学者在迈过安装编程环境和基本语法的门槛&#xff0c;准备大展身手的时候&#xff0c;可能突然就会进入迷茫期&#xff1a; 不知道做些什么、再学些什么。。。 然后对编程的兴趣就会慢慢消退&#xff0c;找不到坚持下去的理由&#xff0c;从而慢慢淡忘之前学会…

JookDB下载安装使用

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

【Arduino IDE 环境配置】

目录 Arduino IDE 环境配置 1. 安装方式2. 操作方法&#xff08;Arduino中文社区&#xff09; 2.1. 安装Arduino IDE2.2. 下载固件2.3. 修改Arduino IDE语言2.4. 添加开发板管理网址2.5. 运行离线包2.6. 检查安装是否成功 下载Arduino IDE&#xff1a; 如果你还没有安装Arduin…

文件包含漏洞利用技术总结

开发人员一般会把重复使用的函数写到单个文件中&#xff0c;需要使用某个函数时直接调用此文件&#xff0c;而无需再次编写&#xff0c;这中文件调用的过程一般被称为文件包含。 allow_url_fopen On&#xff08;是否允许打开远程文件&#xff09; allow_url_include On&…

claude3国内能用吗

claude3国内能用吗 如果您在国内无法直接使用Claude模型&#xff0c;可以考虑以下几种解决办法&#xff1a; 镜像站点&#xff1a;和GPT模型相似&#xff0c;使用为国内用户设置的镜像网站可以是一个解决方案。这些镜像站点可能会提供Claude模型的本地化服务&#xff0c;确保…

CAPL 定时器数组 实现同时注入多条CAN报文

&#x1f345; 我是蚂蚁小兵&#xff0c;专注于车载诊断领域&#xff0c;尤其擅长于对CANoe工具的使用&#x1f345; 寻找组织 &#xff0c;答疑解惑&#xff0c;摸鱼聊天&#xff0c;博客源码&#xff0c;点击加入&#x1f449;【相亲相爱一家人】&#x1f345; 玩转CANoe&…