JavaSE进阶535-547HashSet Map HashTable HashCode

开始时间2020-11-18

HashSet

无序

package BUPT20201118;

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

public class HashSetTest {
    public static void main(String[] args) {
        Set<String> stringSet = new HashSet<>();
        stringSet.add("hellow");
        stringSet.add("world");
        stringSet.add("Java");
        stringSet.add("JDK15");
        for (String s : stringSet) {
            System.out.println(s);
        }
    }
}

TreeSet

package BUPT20201118;

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

/*
无序不可重复,但给进去的数据会根据大小排序
 */
public class TreeSetTest01 {
    public static void main(String[] args) {
        Set<String> stringSet = new TreeSet<>();
        stringSet.add("abc");
        stringSet.add("ABC");
        stringSet.add("EFG");
        stringSet.add("efg");
        for (String s : stringSet) {
            System.out.println(s);
        }
    }
}

Map

类似于字典
所有的Key是一个Set集合
先复习一下静态内部类

package BUPT20201118;

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

public class MyClass {
    private static class InnerClass {
        public static void m1() {
            System.out.println("静态内部类的m1方法执行");
        }

        public void m2() {
            System.out.println("静态方法的内部实例类方法执行");
        }
    }

    public static void main(String[] args) {
        //类名是 MyClass.InnerClass
        MyClass.InnerClass.m1();
        //外部类.内部类.方法
        MyClass.InnerClass myClass = new MyClass.InnerClass();
        myClass.m2();
        Set<MyClass.InnerClass> set = new HashSet<>();
        //上面和下面都是类似的,泛型装的都是类,没有本质不同
        //上面的MyClass可以省略
        Set<String> stringSet = new HashSet<>();
        Set<MyMap.MyEntry<Integer, String>> set1 = new HashSet<>();
    }
}

class MyMap {
    public static class MyEntry<K, V> {

    }
}
package BUPT20201118;

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

/*
Map以Key和Value存东西,有点像字典
Map和Collection没有继承关系
Map中常用方法:
V put(K key,V value) 向Map增加键值对
void clear() 清空Map
boolean containsKey(Object key)判断是否包含某个key

 */
public class MapTest01 {
    public static void main(String[] args) {
/*
Map集合通过entryset方法转换成的这个Set集合,Set集合中元素的类型是Map.Entry<K,V>
Map.Entry是静态内部类,都是一种类型的名字
 */
//创建Map集合对象
        Map<Integer, String> map = new HashMap<>();
        map.put(1, "锦到黑");
        map.put(2, "jindaohei");
        map.put(3, "JDH");
        map.put(4, "HDJ");
        //通过Key来获得Value
        System.out.println(map.get(2));
        //获取键值对数量
        System.out.println("键值对数量为" + map.size());
        //通过Key来删除键值对
        map.remove(3);
        System.out.println("键值对数量为" + map.size());
        //判断是否包括某个Key
        System.out.println(map.containsKey(4));
        //判断是否包括某个Value
        System.out.println(map.containsValue("锦到黑"));
        //底层都重写了equals的,放心比较值
        //之前给定了value的泛型为String,所以这里自动识别出来
        Collection<String> values = map.values();
        for (String s : values) {
            System.out.println(s);
        }
        //清空map
        map.clear();
        //判断是否为空
        System.out.println(map.isEmpty());
    }
}
package BUPT20201118;

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) {
        //第一种方式,获取所有的Key,通过遍历Key,来遍历Value
        Map<Integer, String> stringMap = new HashMap<>();
        stringMap.put(1, "锦到黑");
        stringMap.put(2, "jindaohei");
        stringMap.put(3, "JDH");
        stringMap.put(4, "HDJ");
        Set<Integer> keys = stringMap.keySet();
        Iterator<Integer> integerIterator = keys.iterator();
        while (integerIterator.hasNext()) {
            Integer key = integerIterator.next();
            String value = stringMap.get(key);
            System.out.println("键值对为" + key + ":" + value);
        }
        //不用迭代器
        for (Integer key : keys) {
            System.out.println(key + "=" + stringMap.get(key));
        }
        //第二种方法:Set<Map,Entry<K,V>> entrySet()
        //以上把Map集合全部转为Set集合
        //Set里面装的是Map.Entry,相当于装了一个键值对,这种效率较高
        Set<Map.Entry<Integer, String>> set = stringMap.entrySet();//逼历set集合,每一次取出一个Node
        // 迭代器
        Iterator<Map.Entry<Integer, String>> it2 = set.iterator();
        while (it2.hasNext()) {
            Map.Entry<Integer, String> node = it2.next();
            Integer key = node.getKey();
            String value = node.getValue();
            System.out.println(key + "=" + value);
            System.out.println(node);
            //直接输出也行:System.out.println(node);
        }
    }
}

散列表/哈希表

哈希表/散列表:―维数组,这个数组中每一个元素是一个单向链表。(数组和链表的结合体。
这一块是数据结构的概念,贴出之前大二学的时候的课堂笔记
在这里插入图片描述
在这里插入图片描述
数组:查询效率高,随机增删效率低
单向链表:随机增删效率低,查询效率高
哈希表结合二者
alt+ctrl+鼠标 可以跳转源码
HashMap底层是一个一维数组,存的是Node
Node类型存了hash值,key值,value值,下一个Node地址
Hash值是HashCode的执行结果,
Hash值通过哈希函数/算法可以转为数组下标

往里放:
map.put(k,v)实现原理:
第一步:先将k,v封装到Node对象当中。
第二步:底层会调用k的hashCode0方法得出hash值,
然后通过哈希函数/哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把Node添加到这个位置上了。如果说下标对应的位置上有链表,此时会拿着k和链表上每一个节点中的k进行equals ,如果所有的equals方法返回都是false,那么这个新节点将会被添加到链表的末尾。如果其中有一个equals返回了true,那么这个节点的value将会被覆盖。
在这里插入图片描述
往外取:
v = map.get(k)实现原理:
先调用k的hashCode()方法得出哈希值,通过哈希算法转换成数组下标,通过数组下标快速定位到某个位置上,如果这个位置上什么也没有,返回null。如果这个位置上有单向链表,那么会拿着参数k和单向链表上的每个节点中的k进行equals ,如果所有equals方法返回false,那么get方法返回null,只要其中有一个节点的k和参数k equals的时候返回true,那么此时这个节点的value就是我们要找的value , get方法最终返回这个要找的value.
通过这个匹配机制,能缩小查找的次数和范围

Hash表随机增删和查询效率都高,因为增删在链表上,查询是部分扫描。
但是效率都比不上纯链表/数组
要得到HashMap集合中的key,那么HashCode和equals都要重写

Hash集合中的key无序不可重复,加入的顺序和输出的不一定一样
equals保证了HashMap集合的Key不可重复,如果重复了,value会覆盖
而HashMap的key放在了HashSet里面,所以HashSet里面的equals和HashCode也需要重写

同一个单向链表上所有节点的Hash相同,因为数组下标相同
但同一链表上k和k的equals方法不同,
如果所有HashCode返回值都固定为某个值或者不一样的值,那么会导致底层哈希表变成了纯单向链表或纯数组,这种情况称为散列分布不均匀
100个元素,10个单向链表,10个节点,那么分布很均匀

package BUPT20201121;

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<>();
        map.put(111, "张三");
        map.put(12, "wangwu");
        map.put(21, "李四");
        map.put(21, "lisi");
        //key重复,value覆盖,所以输出为3
        System.out.println(map.size());
        Set<Map.Entry<Integer, String>> set = map.entrySet();
        for (Map.Entry<Integer, String> entry : set) {
            System.out.println(entry.getKey() + "====" + entry.getValue());
            //取出来顺序不固定,李四被lisi覆盖掉了
            //HashMap的Key部分,无序不可重复
        }
    }
}
3
21====lisi
12====wangwu
111====张三

HashMap默认初始化容量为16,初始化容量必须是2的倍数,可以提高存取效率。默认加载因子为0.75
意思是当容量用掉75%时,数组开始扩容

同时重写equals和HashCode

只重写了equals

package BUPT20201121;

import java.util.Objects;

public class Student {
    private String name;

    public Student() {
    }

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

    public String getName() {
        return name;
    }

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

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

    /*
    @Override

     public int hashCode() {
        return Objects.hash(name);
    }
     */

}

package BUPT20201121;

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

public class HashMapTest02 {
    public static void main(String[] args) {
        Student student = new Student("jindaohei");
        Student student1 = new Student("jindaohei");
        //这里重写了equals,所以返回了true
        System.out.println(student.equals(student1));
        //按理说,这两个都视为相同的了,但是HashCode又不同
        System.out.println(student.hashCode());
        System.out.println(student1.hashCode());
        //本来相同的会覆盖掉,现在又不覆盖了,就不太科学
        Set<Student> students = new HashSet<>();
        students.add(student);
        students.add(student1);
        System.out.println(students.size());
    }
}

输出

true
2129789493
668386784
2

这样不科学,把上面Student放开重写的HashCode

true
1791910440
1791910440
1

equals重写了,HashCode也必须重写,且两者的返回值一定要一致、

放在HashMap集合key部分的,以及放在HashSet 集合中的元素,需要同时重写hashCode方法和equals方法。

在JDK8之后,如果Hash表,单向链表的元素>=8,那么该单向链表会变成红黑树,当红黑树上的节点<6时,红黑树会变回单向链表。为了提高效率,二叉树检索会再次缩小检索范围,初始化容量16,默认加载因子0.75,扩容后容量为原容量2倍

对于哈希表数据结构来说:
如果o1和o2的hash值相同,一定是放到同一个单向链表上。
当然如果o1和o2的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”。
这个概念可以通过看我之前贴的数据结构笔记得出

结束时间2020-11-21

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值