hashset方法的底层调用解读

1,创建HashSet对象,调用了该类中无参构造方法——>执行了该构造方法中map = new HashMap<>();——>map为HashSet全局变量。
调用HashSet add方法——>map.put(e, PRESENT)——>map为全局变量——>map指向的是创建HashSet对象时创建的HashMap对象
在HashSet()中显示出在无参构造方法中调用了HashMap类的并创建了对象map。
这里我们首先要搞明白的是,HashMap存储时是根据key的hashCode值通过hash算法得出其存储空间,所以不管你对象是否相同,如果你的hashCode数值一样,那你的存储空间位置就是相等的。
在这里插入图片描述

import java.util.HashSet;

public class Text {
	public static void main(String[] args) {
		HashSet<String> set = new HashSet<>();//创建HashSet对象,调用了该类中无参构造方法——>执行了该构造方法中map = new HashMap<>();——>map为HashSet全局变量
		set.add("Tom");//调用HashSet add方法——>map.put(e, PRESENT)——>map为全局变量——>map指向的是创建HashSet对象时创建的HashMap对象
		Text text=new Text();
		System.out.println(text.hashCode());//hashCode();对象的地址            **hashCode第一种情况**。
		System.out.println(text.toString());
		System.out.println(Integer.toHexString(text.hashCode()));
		System.out.println(text);//默认调用toString方法	
		//:不同对象的hashCode一定不相同;相同对象的HashCode的值一定相同——>所以HashMap中hash方法:传入相同的对象,得到相同的结果
		text=new Text();//再次创建对象。
		System.out.println(text.hashCode());
		String name1="Tom";
		System.out.println(name1.hashCode());//第二种的hashCode类
		String name2=new String("Tom");
		System.out.println(name2.hashCode());//第二种的hashCode类		
		System.out.println(name1==name2);
	}
}	
	}
}

在这里插入图片描述
这是方法toString执行的方法,toHexString的作用把hashCode所代表的地址转换成16进制。
在这里插入图片描述
直接如上写的第一种情况下text.hashCode(),是调用Object类中的hashCode的方法,虽然没有方法体其中的native会自动调用系统会算出所要的地址。
在这里插入图片描述
对于第二中的hashCode中点击会出现String类重写的hashCode的方法,其中创建的对象为一个在常量池中创建的String name1=“Tom”;一个是在堆中直接建立的String name2=new String(“Tom”);当然地址不会相同但内容是一样的,但结果经hashCode运算后结果相同,查看hashCode的此时方法为String重写后的方法:方法如下:

        public int hashCode(){
                 int h = hash;          
                 int len  = count;
                 if (h == 0 && len > 0){
                         int off = offset;       
                         char val[] = value;            
                         for (int i = 0; i < len; i++){
                                h = 31*h + val[off++];               
                         }            
                         hash = h;        
                 }        
                return h;    
        }

则可以得到:hashcode是不同对象的标识,普通类是直接取对象的ID作为hashcode,String比较特殊,他重写的hashcode是将对象的字符内容转为char作为hashcode,其中的char可以转换为整形,如”a“代表者97。
1、如果两个对象相同,那么它们的hashCode值一定要相同。
2,如果两个对象的hashCode相同。他们并不一定相同,因为只是对内容计算的。如在两个对象中都有String类型的相同内容的变量,此时在两个对象中他们存储的地址是相同的,这个String类型存储的信息地方功能有关,但这两个对象的首地址是不相同的,但Hashcode计算是一样的。
在String中也已经对equals方法进行了重写,是对内容(字符串中的每个字符进行对比)和地址的对比,地址不同但内容相同也会返回true,因为有两种途径进行判断的。

/*
		 public boolean equals(Object anObject) {//name2传入equals方法——>anObject指向name2对象——>name2为上转型对象
	        if (this == anObject) {//因为name1与name2都是采用直接赋值的方式创建的String类型的对象——>==true
	            return true;
	        }
	        if (anObject instanceof String) {
	            String anotherString = (String)anObject;
	            int n = value.length;
	            if (n == anotherString.value.length) {
	                char v1[] = value;
	                char v2[] = anotherString.value;
	                int i = 0;
	                while (n-- != 0) {
	                    if (v1[i] != v2[i])
	                        return false;
	                    i++;
	                }
	                return true;
	            }
	        }
	        return false;
	     }
		 */

2,在hashSet中add怎样判别不同增加的相同的对象。

package add;

import java.util.HashSet;

public class Text {
	public static void main(String[] args) {
		HashSet<String> set = new HashSet<>();//创建HashSet对象,调用了该类中无参构造方法——>执行了该构造方法中map = new HashMap<>();——>map为HashSet全局变量
		set.add("Tom");//调用HashSet add方法——>map.put(e, PRESENT)——>map为全局变量——>map指向的是创建HashSet对象时创建的HashMap对象
		set.add("Tom");//思考:怎么判断不允许重复?
	}
}

点击add会出现HashSet.class的add方法如下
在这里插入图片描述
在此点击put方法会显示出hashMap的put方法如下:
在这里插入图片描述
其中的hashMap中的hash()方法为计算对象的hash值方便运行:
在这里插入图片描述
在这里
Set的实现类的集合对象中不能够有重复元素,HashSet也一样他是使用了一种标识来确定元素的不重复,HashSet用一种算法来保证HashSet中的元素是不重复的, HashSet采用哈希算法,底层用数组存储数据。默认初始化容量16,加载因子0.75
Object类中的hashCode()的方法是所有子类都会继承这个方法,这个方法会用Hash算法算出一个Hash(哈希)码值返回,HashSet会用Hash码值去和数组长度取模, 模(这个模就是对象要存放在数组中的位置)相同时才会判断数组中的元素和要加入的对象的内容是否相同,如果不同才会添加进去。
相关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成,我们应该为保存到HashSet中的对象覆盖hashCode()和equals(),因为再将对象加入到HashSet中时,会首先调用hashCode方法计算出对象的hash值,接着根据此hash值调用HashMap中的hash方法,得到的值& (length-1)得到该对象在hashMap的transient Entry[] table中的保存位置的索引,接着找到数组中该索引位置保存的对象,并调用equals方法比较这两个对象是否相等,如果相等则不添加,注意:所以要存入HashSet的集合对象中的自定义类必须覆盖hashCode(),equals()两个方法,才能保证集合中元素不重复。在覆盖equals()和hashCode()方法时, 要使相同对象的hashCode()方法返回相同值,覆盖equals()方法再判断其内容。为了保证效率,所以在覆盖hashCode()方法时, 也要尽量使不同对象尽量返回不同的Hash码值。
在Put方法中有个putVal方法的作用

/*
		 public V put(K key, V value) {
	        return putVal(hash(key), key, value, false, true);
	     }
	
	     final V putVal(int hash, K key, V value, boolean onlyIfAbsent,//hash由hash(key)由对象计算出的哈希码值。
	                   boolean evict) {
	        Node<K,V>[] tab; Node<K,V> p; int n, i;
	        if ((tab = table) == null || (n = tab.length) == 0)//刚初次创建HashSet对象时——>new HashMap ,调用的是HashMap的无参构造方法没有对table赋值——>则table=null,再次用add调用时table就有了值了
	            n = (tab = resize()).length;//resize()的作用为:把NULL的table,给table = newTab,table为一个数组;  返回值newTab与table指向同一个地址,则在同一个地方
	        if ((p = tab[i = (n - 1) & hash]) == null)//n=16
	            tab[i] = newNode(hash, key, value, null);//添加第一个Tom tab[i]——>tab与table指向同一个地址,所以table[i]也是有值的,且相同当tab中地址有值的时候,table也就是有内容的变量了,因为原来就指的同一个地方。
	        else {
	            Node<K,V> e; K k;
	            if (p.hash == hash &&
	                ((k = p.key) == key || (key != null && key.equals(k))))
	                e = p;
	            else if (p instanceof TreeNode)
	                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
	            else {
	                for (int binCount = 0; ; ++binCount) {
	                    if ((e = p.next) == null) {
	                        p.next = newNode(hash, key, value, null);
	                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
	                            treeifyBin(tab, hash);
	                        break;
	                    }
	                    if (e.hash == hash &&
	                        ((k = e.key) == key || (key != null && key.equals(k))))
	                        break;
	                    p = e;
	                }
	            }
	            if (e != null) { // existing mapping for key
	                V oldValue = e.value;
	                if (!onlyIfAbsent || oldValue == null)
	                    e.value = value;//第二次添加Tom确实覆盖了第一次添加的Object常量,但是还是原来的NOde对象,只是其value属性发生了变化——被第二个Object常量覆盖了
	                afterNodeAccess(e);
	                return oldValue;
	            }
	        }
	        ++modCount;
	        if (++size > threshold)
	            resize();
	        afterNodeInsertion(evict);
	        return null;
	     }
		 */

在newNode中:
在这里插入图片描述
在Node中:
在这里插入图片描述
则newNode代指的就是对象。

在hashset中的add的底层代码添加的过程。

package add;

import java.util.HashSet;

public class Xitong {

	public static void main(String[] args) {
		HashSet<String> set = new HashSet<>();
		set.add("Tom");
		set.add("Tom");// 调用HashSet add方法——>map.put(e, //
						// PRESENT)——>map为全局变量——>map指向的是创建HashSet对象时创建的HashMap对象
		set.add(new String("Tom"));// 思考:怎么判断不允许重复?//这个时候是走(key != null && key.equals(k))这个判别式的k前面已经赋值了(k = p.key)
	}

}

1在这个过程中先说第一次添加的过程点击add获取他的底层信息:
在这里插入图片描述
在这里插入图片描述
这个时哈希算法的结果:之和hashcode有关,为一个Int类型的值。
在这里插入图片描述
putVal方法在上面有,当我们添加第一个Tom的时候,table为一个类型常量则为null,在resive作用上面也说了,
由map中设置的常量值使得n为16;p变量指向了tab所指的空间,下面一样tab[i] = newNode(hash, key, value, null);给tab刚创建的”Tom“,则直接就返回null,则putVal的结果就为null,则运行的put的结果就为null,则到add中就为null添加成功,2当在第二次添加的时候此时的table也就不为null了,因为在String 中已经重写了hasocode的方法了,只要内容相同则hash值就相同了,则这次是的p = tab[i = (n - 1) & hash]也不为null,往下走
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))到这个里面,在这里p.hash和hash上面说了是一样的,此时的地址也都相同,后面的也就不进行了,直接e=p;则此时的e != null则由上面可知onlyIfAbsent为false则
!onlyIfAbsent为true的进行下面代码,最后返回oldValue;则到add那最后返回的不是null则就添加失败。
3当添加的是直接是new String()的,此时的hash值也是和原来一样的,当到
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))时候,因为在创建String 类中的对象,两个的储存地址不相同,则(k = p.key) == key也就为false,(key != null && key.equals(k)在这里key.eauals(k)其中调用的是String 中重写的方法,他也是比对字符串中的每个字符是否相同,则现在他为true,在也直接走到e=p;和第二次相同了,也没添加在里面去。

另一种情况直接是对象的时候

package add;

import java.util.HashSet;

public class Xitong {

	public static void main(String[] args) {
		HashSet<Student> set = new HashSet<>();
		Student tom  = new Student("110");
		System.out.println(tom.hashCode());
		Student jim  = new Student("110");
		System.out.println(jim.hashCode());
		System.out.println(tom==jim);//false
		set.add(tom);
		set.add(jim);//思考:怎么判断不允许重复?重写以后走HashMap 638 else
		System.out.println(set.size());	
	}

}

在这里插入图片描述
在这个两个TOM 和 jim对象都是调用的是父类的hashcode所以他们的值是不想同的,此时显示set中的结果显示为2。

1第一种情况为,当添加tom对象时第一遍和原来的一样找个空间tab存放他,当Student中没有重写的情况下,在第二次的时候执行的是:
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);此时的hash和原来的是不一样的,则i值也就和原来的不一样了。就会直接给第二个对象进行储存。
第二种情况在Student中重写的时候:

public int hashCode() {
		return id.hashCode();
	}

	@Override
	public boolean equals(Object obj) {
		Student student = (Student) obj;
		return this.id.equals(student.id);

	}

当添加第二个对象的时候这时,hash值就会一样,因为在这最后也是调用String中的hashcode,则因为内容相等则hashcode的值也就相等,下面到的式中算到的
(p = tab[i = (n - 1) & hash]) 则也就是原来的那个,则现在就不为null了,则会运行到
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k)))),
因为中两个的hash相等,此时的两个key对象表面是调用父类(Object)的,实则是调用子类重写的equals方法,则就key.equals(k)为true,则进行赋值e=p;就直接走下面的if语句:
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
此时的e与p同指向一块地方,则不为null,!onlyIfAbsent为true,从方面主方法传入的参数可知他为false,则最后返回的为oldValue则此时putValue返回值不为null则就会使put方法的返回值不为null则就会使add的返回值也不为null,则说明此时添加失败。
在这里插入图片描述

在hashsetzhong 添加相同类型对象的信息在特有的相同信息下,对 equals 和hashcode的重写的认识。

可能在日常生活中我们,可能每个人的身份证号是特定的,所以当我们输入的id时就是在说输入的是同一个人,名字不等而已,可为大名和小名,则这就我们在判别上要有所区分了,对于创建的一个类都是继承Objest中的equal,hashcode方法在没有重写的情况下,他的hashcode 为native调用最底层c语言实现的是对其首地址进行十六进制转换,equals也是对首地址的对比,在我们可以但从我们的id进行选择是否添加的信息有重复,查看的底层代码可知,在add,contains和remove中都是靠有equals功能来部分实现的,但要经过这个条件语句时都需要hash算法出值(它是靠hashcode值)相等才能进行到判断,则就要改hash计算属性的针对性计算,只针对我们需要的属性进行计算匹配,也就是在不同地址存入的时候但还是同一个人信息,我们可以从需要的信息去匹配是否有相同的,避免存入时有误,这样我们就可以对单项进行判断查询,删除,添加比较有精准性添加,删除,查询:

//	@Override
//	public boolean equals(Object obj) {
//		Student student = (Student) obj;// 这里为什么要下转型呢,因为这时当在调用原来的数据对象时候就上转型了Object obj,要下转型才能调用原来的student中的值。
//		return this.id.equals(student.id);// 这个就会调用string的equals来进行判断。
//	}

//	public int hashCode() {
//		return this.id.hashCode();
//	}

在这个重写后在删除上有很大的简化,信息的匹配上有很大的方便:

public void addStudent() {//添加
		System.out.println("请输入姓名:");
		String name = sc.next();
		System.out.println("请输入id:");
		String id = sc.next();
		Student student = new Student(name, id);
		if (set.contains(student)) {
			System.out.println("学号不允许重复");
			return;
		}
		set.add(student);
		System.out.println(set.size());
	}

	public void shanchu() {//删除
		System.out.println("请输入id");
		String id = sc.next();
		Student student = new Student(id);
		if (set.remove(student)) {
			System.out.println("删除成功");
		} else {
			System.out.println("学生信息不存在");
		}
	}

	public void xiugai() {//修改
		System.out.println("请输入;id");
		String id = sc.next();
		for (Student student : set) {
			if (student.id.equals(id)) {
				System.out.println("请输入你们的姓名:");
				String name = sc.next();
				student.name = name;
				System.out.println("修改成功");
			}
		}
	}

	public void chaxun() {//查询
		System.out.println("请输入学号");
		String id = sc.next();
		for (Student student : set) {
			if (student.id.equals(id)) {
				System.out.println("此人为"+student.name);
			}else {
				System.out.println("无此人");
			}
		}

两个重写的方式,上面有情况说明。

添加hashset中不同类型对象的时候,对相同特有信息相同下对 equals 和hashcode的重写的认识。

第一个类型对象为dog:

package com.jd.cs;

public class Dog {

	private String id;

	public Dog(String id) {
		this.id = id;
	}

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

	public boolean equals(Object obj) {// 在这种重写的情况下,在下转型中必须还得是同以个对象,得和上转型中的保持一致,当直接不加if(obj instanceof Dog) 来判断的时候就要
		// 当第二次添加人的时候会下转型成了狗的对象了,这个时候是人的对象不能转换成狗的,下转型是不允许的,会出现错误运行不了,当在狗中没有重写的情况下,这样在调用String中的equals方法,则就不会保存了,在深层次代码中有解释。
		// 当在狗中也重写了方法,并在重写中加入了判别语句这时就会判别是否是和原来的是否是同一对象或者是子类,这样如果不是就会返回false,则key != null
		// && key.equals(k)就为false就往下面执行。
		// 在putVal中就会执行下面语句
		// if ((e = p.next) == null) {
		// p.next = newNode(hash, key, value, null);
		// 就会添加人的信息。当然在两个对象都不重写hashcode和equals时候也会存入进去,但我们在专项查找删除时就很不方便,对于同等id信息的人还会再一次存入无法识别,出现信息的多次存储,两个都重写区分这样就是在同一个hashset中添加不同的类的时候能区分开不同的类存的信息,
		// 使得这两个不同的类型对象中有明显的区分了,在运用if语句的时候就能区别是否是同一类型对象(包含继承)与前面一寸的类型信息对象对比,不是就直接储存,并区别dog中id与Student中的id是代表不同的事物,并能在添加相同类型对象中时是否来判断与原来的信息对象添加相同的id,是就不保存。
		if (obj instanceof Dog) {// 避免:第二次添加元素,但是HashMap中已经有了一个元素,该元素hashCode与待添加的元素的hashCOde相同,
			Dog dog = (Dog) obj;
			return this.id.equals(dog.id);
		}
		return false;
	}

}

第二个类型为Student:

package com.jd.cs;

public class Student {

	private String id;

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

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

	@Override
	public boolean equals(Object obj) {
		if (obj instanceof Student) {// 避免:第二次添加元素,但是HashMap中已经有了一个元素,该元素hashCode与待添加的元素的hashCOde相同,
			Student student = (Student) obj;
			return this.id.equals(student.id);
		}
		return false;
	}
}

运行的测试:

package com.jd.cs;

import java.util.HashSet;

public class Text {

	public static void main(String[] args) {
		Student tom = new Student("110");
		Dog dog = new Dog("110");
		HashSet<Object> set = new HashSet<>();
		set.add(tom);
		set.add(dog);// 思考:怎么判断不允许重复
		System.out.println(set.size());
	}
}

在这里插入图片描述
在Dog代码中写了不同的对象中添加在hashset中方法的区别。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值