【Java22】hashcode/哈希表原理,Map/内部接口


1.Object类的hashcode方法

package com.itheima03.hash;
/*
*   HashCode : 哈希码
*       1. Object类有一个方法:
*            int hashCode()  : 返回该对象的哈希码值。
*               1. 原理: 将对象的真正的内存地址(明文) 进行 哈希算法 加密之后产生的 哈希码值(密文)
*               2. 加密 :
*                       明文 : 大家都看的懂东西                          I love you
*                       密文 : 明文经过加密算法变成密文                   J mpwf zpv
*                    加密算法: 数学  (凯撒加密: 字母按字母表右移动一位)
*    破解: 频率分析法 (e i -> d h),截获大量数据进行大数据分析e,i出现频率最高,密文中出现最多的是d,h
*
*                    哈希算法: 公开
*                        基本保证  一个明文  -> 一个密文   不同明文不同的密文
*                        告诉你算法,告诉你密文, 算不出明文
*                       
*              3. 源码: public native int hashCode();  本地方法
*                       native(本地关键字) 修饰的方法没有java方法体 (方法实现在JVM底层, 用C语言写的)
*                       返回值 int (43亿不到的可能)
*
*  对象的真正的内存地址(无限种)->哈希码 (43亿不到的可能)。极端 : 多个明文 -> 同一密文  (哈希碰撞)
*/
public class HashcodeDemo {
    
    
    public static void main(String[] args) {
    
    
        Person p = new Person();
        System.out.println(p); //com.itheima03.hash.Person@14ae5a5
        // return getClass().getName() + "@" + Integer.toHexString(hashCode());
        System.out.println(p.toString());//com.itheima03.hash.Person@14ae5a5   
        
        // 上面两个打印结果都一样       
        // 内存地址: 16进制哈希码值14ae5a5 和下面10进制相等
        System.out.println(p.hashCode()); // 10进制: 21685669
    }
}
class Person{
    
    
}

2.String类的hashcode方法

String s1 = “abc”,如下h就是hash值(只看最后一个h),b是98,c是99。
在这里插入图片描述

package com.itheima03.hash;
// String类重写了Object的hashcode方法 (31算法)
public class StringHashCodeDemo {
    
    
    public static void main(String[] args) {
    
    
        String s1 = "abc";
        String s2 = "acD";
        String s3 = "重地";
        String s4 = "通话";
        System.out.println(s1.hashCode());//96354
        System.out.println(s2.hashCode());//96354 //和上面哈希碰撞
        System.out.println(s3.hashCode()); //1179395
        System.out.println(s4.hashCode()); //1179395
    }
}

3.哈希表(HashSet)原理

hashcode(密文)为HashSet(HashSet底层数据结构是hash表)做铺垫。如下三个abc字符串都为96354。问题:HashSet如何判定这个元素是否跟已存在的元素是重复即如下[重地,通话,abc,acD]?Set不存重复元素。S3因为是new出来的,和S1,S2明文即真正的内存地址不一样。

下面S1,S4,S5,S6都是不重复元素。竖:暗文或暗文%16同,equal不一样。横:明文和暗文和equal都不一样。HashSet是效率最高的set且元素不重复,同一链表(竖)hashcode一样,但是链表如果太长查询慢,所以假如同一hash值(hashcode)碰撞了8次,链表重构为红黑树

%16因为数组长度为16,同一链表上都hash碰撞,数组的第一个位置余数=0,第二个位置余数=1。。。16的容量为什么到16*0.75=12就扩容了?再哈希rehash(余数重新算)这段时间内,16没满,我还有的用。如果rehash非常快就不用提前。
在这里插入图片描述

4.HashSet元素不重复应用

package com.itheima02.set;
import java.util.HashSet;
import java.util.Objects;
/*
*  HashSet: 判定重复元素:(明文地址【内】,hash值【外】,equals【名字】)。
*   内同-》重复不插,     内不同 外不同 默认e不同-》插(横),       内不同 外同e不同-》插(竖)
*
*  person类父类的Object: 1. hashcode
*               内存地址加密得到密文hash值(不同明文产生不同密文,刘亦菲两个内存地址不一样,密文hash值基本不会相同,万一碰撞了,还有下面2进行保障)
*           2. equals   == 比较真正内存地址
*
*      需求: 两个对象就算地址不同, 但是所有属性一一相同, 就认为是同一元素
*      解决: 重写hashcode和equals方法 -> 类中的所有属性
*/
public class HashSetDemo02 {
    
    
    public static void main(String[] args) {
    
    
        HashSet<Person> set = new HashSet<>();
        set.add(new Person("高圆圆",18));
        set.add(new Person("刘亦菲",19));
        set.add(new Person("刘亦菲",19));//Person类继承Object类,new新地址
        System.out.println(set); 
    }
}
class Person{
    
    
    String name;
    int age;
    public Person(String name, int age) {
    
    
        this.name = name;
        this.age = age;
    }
    @Override
    public boolean equals(Object o) {
    
     //alt+insert选equals()and hashCode()
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false; //每个属性一一比对
        Person person = (Person) o;
        return age == person.age &&
                Objects.equals(name, person.name);
    }
    @Override
    public int hashCode() {
    
    
        return Objects.hash(name, age); //工具类Objects.java中hash方法中hashCode方法就是31算法,也是逐一遍历
    }
    @Override
    public String toString() {
    
    
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

没有重写Person类的hashcode和equals方法
在这里插入图片描述
如下重写…如下就是hash表的应用:元素不重复,效率高
在这里插入图片描述

5.linkedHashset和Hashset区别

package com.itheima00.question;
import java.util.HashSet;
import java.util.LinkedHashSet;
/*
*   Set: 不保证存入和取出顺序一致
*       HashSet : 无序
*       LinkedHashSet : 多个另一个链表, 来记录存入的顺序,有序即取出有序,所以效率变低(少用)
*/
public class Demo01 {
    
    
    public static void main(String[] args) {
    
    
        HashSet<String> set = new LinkedHashSet<>(); //向上转型
        set.add("张三");
        set.add("李四");
        set.add("王五");
        set.add("马六");
        System.out.println(set); //打印出有序的,LinkedHashSet不同于HashSet 
    }
}
package com.itheima00.question;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class Demo02 {
    
    
    public static void main(String[] args) {
    
    
        Collection<String> coll = new ArrayList<String>(); //Collection是接口
        coll.add("张三");
        coll.add("张三2");
        coll.add("张三3");                
       // Iterator接口类型 变量 = 其实现类对象 (多态的向上转型)
       /* Iterator<String> it = coll.iterator();//Collection即coll是接口,接口调用方法执行子类ArrayList重写的iterator()
        while(it.hasNext()){
            String name = it.next();
            System.out.println(name); //张三 张三2 张三3
        }*/

//        while(coll.iterator().hasNext()){ //不能这样把it换了链式编程,原因如下图
//            String name = coll.iterator().next();
//            System.out.println(name);
//        }
    }

//11111111111111111111111111111111111111111111111111111111111111111111111111111111111111
    public static void method01(){
    
    
        MyClass mc = new MyClass();
        A a = mc.test(); //右边返回必然是A接口实现类对象即向上转型,不需要new        
        //上行等同于Iterator<String> it = coll.iterator(); 不一定需要看到new  
             
        A a2 = new A() {
    
     //java中对象不一定看到new才放心
           //new一个实现接口的匿名内部类A,使用{}具体实现接口
            @Override
            public void show() {
    
    
            }
        } ;
    }
}
interface A{
    
    
    void show();
}
class MyClass{
    
    
    public A test(){
    
     //返回A接口,不写void
//        A a = new A(){}; //匿名内部类
//        return a;

        return new A() {
    
     //下面等同于上面两行,return A接口的子类对象
            @Override
            public void show() {
    
    
            }
        };
    }
}

在这里插入图片描述

6.Map

Map和Collection是并列关系
在这里插入图片描述

package com.itheima01.map;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/*
    Map中的方法
	1. Map<K,V> <泛型>: K 表示作为键的类型,V表示值的类型
		2. put: 存储键值对
			1. 键值对存储到集合中 V put (K,V)
			2. 如果存储了相同的键,覆盖原有的值
			3. 返回值:一般返回null,如果存储了重复的键,返回被覆盖之前的值
		3. get:通过键,取出键对应的值
			1. V get(K),传递键,返回对应的值
		 	2. 如果集合中没有这个键,返回null
		4. remove:移除键值对
		 	1. V remove(K),传递键,移除这个键值对
		 	2. 返回值:移除之前的值(无此键,则返回null)
		5. keySet: 将集合中所有的键,存储到Set集合中
		6. entrySet:获取到Map集合中所有的键值对存入Set接中
		7. size:获取map集合的大小
*   Map:
*       1. key不可以重复
*       2. value可以重复
*       如果key存在,那么新value覆盖旧value
*/
public class MapDemo {
    
    
    public static void main(String[] args) {
    
    
        Map<Integer,String> map = new HashMap<>();
        map.put(1,"张三");
        map.put(2,"李四");
        map.put(3,"王五");
        map.put(4,"王五");
        map.put(3,"马六"); //覆盖王五
        System.out.println(map);//{1=张三,2=李四,3=马六,4=王五}
        String name = map.get(5); //null,不是越界异常,无索引
        String name = map.get(3); //从key获取value
        System.out.println(name);//马六
        //根据key删除key-value
        map.remove(3);
        System.out.println(map);
        System.out.println(map.size()); //3,几个k
    }
}

如下key不可重复,所以放到set集合(单例)。如下两种遍历方式都涉及set:
在这里插入图片描述

package com.itheima01.map;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
 
public class MapLoopDemo01 {
    
     //loop循环
    public static void main(String[] args) {
    
    
        Map<Integer,String> map = new HashMap<>();
        map.put(1,"张三");
        map.put(2,"李四");
        map.put(3,"王五");        
        Set<Integer> keySet = map.keySet();  //1.把key这一列取出来放到set集合中        
        for (Integer key : keySet) {
    
      //2.遍历这个set集合,取出每个key。keySet.for回车            
            String value = map.get(key);  //3. 根据key获取value
            System.out.println(key + "->" + value);
        }
    }
}

在这里插入图片描述

package com.itheima01.map;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class MapLoopDemo02 {
    
    
    public static void main(String[] args) {
    
    
        Map<Integer,String> map = new HashMap<>();
        map.put(1,"张三");
        map.put(2,"李四");
        map.put(3,"王五");
        Set<Map.Entry<Integer,String>> entrySet = map.entrySet(); // 1. 把map转化成 Set<Entry> set
        for(Map.Entry<Integer,String> entry : entrySet){
    
     // 2. 遍历这样的set, 取出每个entry
   //Entry是Map的内部接口,Map有很多Entry,Entry相当于Map属性一样。Map.是接口名直接调用
   //如果import java.util.Map.Entry,则Map.Entry可换成Entry            
            Integer key = entry.getKey(); //3. 从这个键值对中,取键,再取值
            String value = entry.getValue();
            System.out.println(key + "--" + value);
        }
    }
}

在这里插入图片描述

7.内部接口

package com.itheima02.inner;
import java.lang.reflect.Field;
import java.util.Date;
import com.itheima02.inner.Outer.Inner;
/*
* inner class : 访问受限, 受限于外部类
* 有两个包是不用导入:1. java.lang (String,Object)
*                     2. 当前类所在的包
* 1和2的子包都要导(如内部接口就在当前包的子包下)
*/
public class InnerDemo {
    
    
    public static void main(String[] args) {
    
    
//        new Date()
        new String("");
        new Object();
//        new Field(); //java.lang包的子包,要导包
    }
}
interface Outer{
    
    
    //public static final //因为Outer是接口,所以不能实例化,只能接口.,所以final让I变为常量,static让I可用接口名.直接调用
    int I = 1;
    //public abstract
    void outerMethod();
    
    //public static //外部接口不能创建实例来调用,所以接口名调用,所以静态。上面Map.Entry即外接口.内接口(Entry是Map的内接口)
    interface Inner{
    
     
    //除了Inner访问受限于Outer,这两个各自独立,new外部类对象不需要new内部类对象,同理实现Outer接口不需要实现Inner接口
        void innerMethod();
    }
}

//1111111111111111111111111111111111111111111111111111111111111111111111111111111111111
class A implements Outer{
    
     //不需要实现Inner
    @Override
    public void outerMethod() {
    
    
    }
}
class B implements Outer.Inner{
    
    
    @Override
    public void innerMethod() {
    
    
    }
}
class C implements Outer,Inner{
    
    //上面导过包了,Inner不用写成Outer.Inner
    @Override
    public void outerMethod() {
    
    
    }
    @Override
    public void innerMethod() {
    
    
    }
}

8.HashMap原理分析

在这里插入图片描述

package com.itheima03.impl;
import java.util.*;
/*
*   HashMap是最常用的map实现类,因为快
*       1. key不可以重复,但是value可以重复
*       2. key如何判定重复? 先判断hashcode ,再判断equals
*           Object: hashcode 和 equals 跟对象真正地址有关
*           重写了hashcode 和 equals,张三山东 覆盖 张三山西  新覆盖旧
*/
public class HashMapDemo {
    
    
    public static void main(String[] args) {
    
    
//        method01();
//        method02(); //较method01交换了k和v

        new LinkedHashSet<>(); //点进源码,底层是LinkedHashMap
        new TreeSet<>(); //TreeMap
        new HashSet<>(); //HashMap
        
        //如下有序存取
        LinkedHashMap<Person,String> map = new LinkedHashMap<>();
        map.put(new Person("张三",18),"山西");
        map.put(new Person("吴彦祖",20),"福州");
        map.put(new Person("李四",19),"广东");
//        map.put(new Person("张三",18),"山东");
        Set<Person> keySet = map.keySet();
        for (Person key : keySet) {
    
    
            String value = map.get(key);
            System.out.println(key + "---" + value);
        }
    }

    private static void method02() {
    
    
        HashMap<Person,String> map = new HashMap<>();
        map.put(new Person("张三",18),"山西");  //key=Person 自定义类型
        map.put(new Person("吴彦祖",20),"福州");
        map.put(new Person("李四",19),"广东");
        map.put(new Person("张三",18),"山东"); //new出来地址不同
        Set<Person> keySet = map.keySet();
        for (Person key : keySet) {
    
    
            String value = map.get(key);
            System.out.println(key + "---" + value);
        }
       // HashSet<Object> set = new HashSet<>(); //点进HashSet看源码
//HashSet【collection接口】的底层是HashMap【Map接口】 ,只不过hashset只使用了HashMap key这一列,value这一列不用
    }

    private static void method01() {
    
            
        HashMap<String, Person> map = new HashMap<>();
        map.put("1号",new Person("张三",18)); //value=Person 自定义类型
        map.put("2号",new Person("李四",19));
        map.put("3号",new Person("李四",19));
        map.put("1号",new Person("王五",20)); 
        //System.out.println(map);
        Set<Map.Entry<String,Person>> entrySet =  map.entrySet();
        for (Map.Entry<String, Person> entry : entrySet) {
    
    
            String key = entry.getKey();
            Person value = entry.getValue();
            System.out.println(key + "-" + value);
        }
    }
}

class Person{
    
    
    String name;
    int age;
    public Person(String name, int age) {
    
    
        this.name = name;
        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 age == person.age &&
                Objects.equals(name, person.name);
    }
    @Override
    public int hashCode() {
    
    
        return Objects.hash(name, age);
    }
}

在这里插入图片描述
在这里插入图片描述
如下吴彦祖应该在第二个,存取无序,解决:HashMap换成LinkedHashMap
在这里插入图片描述
B站/知乎/微信公众号:码农编程录
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43435675/article/details/107406897