JavaSE进阶——Day08

1-Set集合


TreeSet、HashSet、LinkedHashSet

TreeSet:排序操作

HashSet:保证元素唯一性

LinkedHashSet

1.1-HashSet

  • 存取有序?

  • 元素唯一,去重原因?

保证元素唯一的结论

  • 需要同时重写对象中的hashCode方法和equals方法

在Student类中重写hashCode方法返回值为1;

4个对象,equals被执行6次

HashSet<Student>hs = new HashSet<>();
hs.add(new Student(name:"张三",age:23));
hs.add(new Student(name:"李四",age:23));
hs.add(new Student(name:"王五",age:23))
hs.add(new Student(name:"王五",age:23));

注:HashSet底层的数据结构是哈希表结构

哈希表:

JDK8版本之前:数组+链表

JDK8版本之后:数组+链表|红黑树

1.1.1-hashCode方法和equals方法的配合流程

  • 当添加对象的时候,会先调用对象的hashCode方法计算出一个应该存入的索引位置,查看该位置上是否存在元素

  • 不存在:直接存

  • 存在:调用equals方法比较内容

false:存

true:不存

1.1.2-hashCode方法改进

@override
public int hashCode(){
    //复杂原因:如果对象的属性不相同,返回的哈希值,尽量不相同
    return Objects.hash(name,age);
}

注:重写hashCode方法的时候,应该让对象的属性,都参与到哈希值的计算

  • IDEA可用快捷键自动生成重写后的hashCode方法

1.1.3-测试

package com.lyl.hash;
​
import com.lyl.domain.Student;
​
import java.util.HashSet;
​
public class HashDemo1 {
    public static void main(String[] args) {
        HashSet<Student> hs = new HashSet<>();
        hs.add(new Student("赵六",26));
        hs.add(new Student("张三",23));
        hs.add(new Student("李四",24));
        hs.add(new Student("王五",25));
        hs.add(new Student("李四",24));
​
        System.out.println(hs);
    }
}
@Override
//Student类中重写的equals方法
public boolean equals(Object o) {
    System.out.println("equals方法被执行了...");
    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);
}

输出结果:

equals方法被执行了...

[Student{name = 张三, age = 23}, Student{name = 李四, age = 24}, Student{name = 王五, age = 25}, Student{name = 赵六, age = 26}]

1.1.4-哈希值

  • 是JDK根据某种规则算出来的int类型的整数

  • Object类的API

    @IntrinsicCandidate
    public native int hashCode();

public int hashCode():调用底层C++代码计算出的一个随机数(常被人称作地址值)

所以我们需要重写hashCode方法根据对象的属性值计算出哈希值

1.1.5-HsashSet原理解析

7版本原理解析:数组+链表+(结合哈希算法)

 

8版本之后:

  • 底层结构:哈希表(数组、链表、红黑树的结合体)

    ①:创建HashSet集合,内部会存在一个长度为16个大小的数组public

    public HashSet() {
     map = new HashMap<>();
    }

    ②:调用集合的添加方法,会拿着对象的hashCode方法计算出应存入的索引位置(哈希值%数组长度)

    • 源码:HashSet的add方法return map.put(e, PRESENT)==null;进到map的put方法return putVal(hash(key), key, value, false, true);

    static final int hash(Object key) {
        int h;
        //对哈希值扰动,进行二次哈希操作,可以一定程度的减少链表挂载的数量
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    } 
    • 为什么二次哈希?

      • 如果一次哈希就模以16计算该存的位置,重复的索引就会有很多,挂载的元素就很多 

      • 如果第一次哈希之后,拿着值向右移动16位,再对原有哈希做一个异或运算(二次哈希),最后模以16,得到的结果就更散,元素就更散

      但是底层不是直接%数组长度

    ③:判断索引位置元素是否是null 

    是:存入

    不是:说明有元素,调用equals方法比较内容

如何能够提高查询性能?

1.扩容数组

  • 扩容数组的条件:当数组中的元素个数到达了16*0.75(加载因子)=12

  • 扩容原数组2倍的大小

  • 链表挂载的元素超过了8(阈值)个且数组长度小于64

2.链表转红黑树

  • 链表挂载的元素超过了8(阈值)个且数组长度达到了64

1.2-LinkedHashSet 

少用

1.3-Collections集合工具类

addAll:批量添加

shuffle:将List集合中的元素乱序(洗牌)

sort:可以对传入的List集合进行排序(升序),如果集合中存储的类型,没有实现过Comparable接口,将编译出错

积累思路:

只要涉及集合的排序操作,有两个可选方案

1.TreeSet

2.Collections.sort()

  • 集合中存储的类型:

    Java己经写好类(String,Integer,Double...):具有自然排序规则

    如果自己想指定排序规则,就可以传入比较器

    自己编写的类(Student,Phone,User,Goods):

    • 方案1:让自己的类,实现Comparable接口,具有自然排序

    • 方案2:在sort方法的第二个参数,指定比较器

2-Map集合


HashMap、LinkedHashMap、TreeMap

实际上我们使用的单列集合,底层都是依赖于双列集合

HashSet --- HashMap

LinkedHashSet --- LinkedHashMap

TreeSet --- TreeMap

底层的数据结构都是一样,只不过这些数据结构,只针对于双列集合的键有效,跟值没有关系的

 

2.1-Map—API

V put(K key,V value):添加元素

put方法的细节:向集合中添加元素,如果添加的元素(键,已经存在了

  • 此时,就不是添加了,而是修改

  • 返回值:被覆盖掉的元素

V remove(object key):根据键删除键值对元素,返回值为被删除的元素

void clear():移除所有的键值对元素

boolean isEmpty():判断集合是否为空

int size():集合的长度,也就是集合中键值对的个数

boolean containsKey(object key):判断集合是否包含指定的

boolean containsValue(object value):判断集合是否包含指定的

2.2-Map集合的三种遍历

2.2.1-键找值

package com.lyl.map;
​
import java.util.HashMap;
import java.util.Set;
​
public class MapDemo1 {
    public static void main(String[] args) {
        HashMap<String, String> hm = new HashMap<>();
        hm.put("张三", "北京");
        hm.put("李四", "上海");
        hm.put("王五", "广州");
        //1.调用map集合的keySet方法获取所有键
        Set<String> keySet = hm.keySet();
        //2.遍历keySet集合,获取每一个键
        for (String key : keySet) {
            //3.通过map的get方法获取对应的value
            String value = hm.get(key);
            //4.打印在控制台
            System.out.println("通过keySet方法:" + key + "---" + value);
        }
    }
}

输出结果:

通过keySet方法:李四---上海

通过keySet方法:张三---北京

通过keySet方法:王五---广州

2.2.2-根据键值对对象,获取键和值

public Set<Map.Entry<K,V>>entrySet():返回集合中所有的键值对对象

Entry:键值对对象,是Map里面的一个内部类

package com.lyl.map;
​
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
​
public class MapDemo2 {
    public static void main(String[] args) {
        HashMap<String, String> hm = new HashMap<>();
        hm.put("张三", "北京");
        hm.put("李四", "上海");
        hm.put("王五", "广州");
        //1.通过entrySet方法获取键值对对象
        Set<Map.Entry<String, String>> entrySet = hm.entrySet();
        //2.遍历entrySet集合获取每一个对象
        for (Map.Entry<String, String> entry : entrySet) {
            //3.获取每一个对象的键
            String key = entry.getKey();
            //4.获取每一个对象的值
            String value = entry.getValue();
            //5.打印在控制台
            System.out.println("通过entrySet方法:" + key + "---" + value);
        }
    }
}

输出结果:

通过entrySet方法:李四---上海

通过entrySet方法:张三---北京

通过entrySet方法:王五---广州

2.2.3-forEach方法

package com.lyl.map;
​
import java.util.HashMap;
import java.util.function.BiConsumer;
​
public class MapDemo3 {
    public static void main(String[] args) {
        HashMap<String, String> hm = new HashMap<>();
        hm.put("张三", "北京");
        hm.put("李四", "上海");
        hm.put("王五", "广州");
        //用forEach方法遍历,传入BiConsumer接口,用匿名内部类实现(也可转换为Lambda表达式)
        hm.forEach(new BiConsumer<String, String>() {
            @Override
            public void accept(String key, String value) {
                System.out.println(key + "---" + value);
            }
        });
    }
}

输出结果:

李四---上海

张三---北京

王五---广州

2.3-练习

需求:键盘录入一个字符串,统计出每一个字符出现的次数(键的位置要有顺序)格式为:a(3)b(4)c(6)

package com.lyl.map;
​
import java.util.Scanner;
import java.util.TreeMap;
​
public class MapTest1 {
    /*
    需求:键盘录入一个字符串,统计出每一个字符出现的次数(键的位置要有顺序)
    格式为a(3)b(4)c(2)d(1)e(2)
     */
    public static void main(String[] args) {
        TreeMap<Character, Integer> tm = new TreeMap<>();
        //1.键盘录入字符串
        System.out.println("请输入:");
        String content = new Scanner(System.in).nextLine();
        //2.拆分为字符
        char[] chars = content.toCharArray();
        //3.遍历字符数组
        for (char c : chars) {
            //4.判断map集合中是否存在
            //5.不存在,值为1
            if (!tm.containsKey(c)) {
                tm.put(c, 1);
            } else {
                //6.存在,值+1
                tm.put(c, tm.get(c) + 1);
            }
        }
        //7.创建一个字符串缓冲区(容器)
        StringBuilder sb = new StringBuilder();
        //8.遍历集合获取键和值
        tm.forEach((key, value) -> {
            //9.拼接为指定格式
            sb.append(key).append("(").append(value).append(")");
        });
        System.out.println(sb);
    }
}

输出结果:

请输入:

aaabbbdddccccww

a(3)b(3)c(4)d(3)w(2)

2.4-HashMap

键的位置底层是哈希表 :

别忘了重写hashcodeequals

2.5-LinkedHashMap

键的位置底层是哈希表 + 双向链表 : 能够保证存取顺序

2.6-TreeMap

键的位置底层是红黑树 : 可排序

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值