如下,set中添加重复元素是不可以的,如下
php被添加了2次,但是输出的时候只有一次,那么其去重的原理是什么呢?

public class Test {public static void main(String[] args) {HashSet<String> set=new HashSet<>();set.add("hello");set.add("html");set.add("php");set.add("php");System.out.println(set);//[php, html, hello]}
}

查看源码,调用了map的put方法

 public boolean add(E e) {return map.put(e, PRESENT)==null;}

继续溯源

 public V put(K key, V value) {return putVal(hash(key), key, value, false, true);}

继续溯源.可以看出依据hash以及equals判断的.

/*** Implements Map.put and related methods** @param hash hash for key* @param key the key* @param value the value to put* @param onlyIfAbsent if true, don't change existing value* @param evict if false, the table is in creation mode.* @return previous value, or null if none*/final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);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 1sttreeifyBin(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 keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;if (++size > threshold)resize();afterNodeInsertion(evict);return null;}

如果是自己自定义的类,就不能利用set去重了,需要自己重写继承自Object类的hashCode方法以及equals方法,这样就可以自定义set添加时去重的逻辑了. 为什么用了hashCode方法还要再次重写equals方法呢?因为哈希值相同的未必是同一对象。所以还需要equals方法进一步确认.

如下实例,wangwu只能加进去一个,因为重写了hashCode方法和equals方法,如果不是这样,就会加进去2个wangwu哦.

public class Test {public static void main(String[] args) {HashSet<Student> set = new HashSet<>();set.add(new Student("bob", 23));set.add(new Student("lisi", 21));set.add(new Student("wangwu", 20));set.add(new Student("zhaoliu", 23));set.add(new Student("wangwu", 20));System.out.println(set);// [lisi, wangwu, zhaoliu, bob]}
}
class Student {private String name;private int age;Student(String name, int age) {this.name = name;this.age = age;}@Overridepublic int hashCode() {return name.hashCode()+age*23;}@Overridepublic boolean equals(Object obj) {if(!(obj instanceof Student)){throw new RuntimeException();}Student stu=(Student)obj;return this.name.equals(stu.name)&&this.age==stu.age;}@Overridepublic String toString() {return name;}
}

HashMap key唯一原理

HashMap的key是不能重复的,如果重复,会覆盖.如下. 有两个key为ss,但是输出只有最后一个键值对.

public class Test {public static void main(String[] args) {HashMap<String,String> map=new HashMap<>();map.put("ss","dd");map.put("ss","good");System.out.println(map);//{ss=good}}
}

HashMap保证key唯一的原理和HashSet是一样的.如果是自定义的类,也需要重写Object类的hashCode方法和equals,方法,才可以自定义自己的HashMap的key唯一的原理.
举例如下

public class Test {public static void main(String[] args) {// 保证键不重复的原理和HashSet一样 int hashCode、 boolean equals//key是可以为任意Object的HashMap<Student, String> map = new HashMap<>();map.put(new Student("lisi", 20), "beijing");map.put(new Student("zhangsan", 20), "beijing");map.put(new Student("xiaobai", 20), "beijing");map.put(new Student("liuqian", 27), "beijing");map.put(new Student("xiaobai", 20), "shenzhen");//key看出最后一个xiaobai只进去了一个//如果不定义hashcode和equals方法,结果是{liuqian27=beijing, zhangsan20=beijing, lisi20=beijing, xiaobai20=beijing, xiaobai20=shenzhen}//可以看到xiaobao有两个,因为两个key都是new的,肯定不是同一对象System.out.println(map);//{lisi20=beijing, zhangsan20=beijing, xiaobai20=shenzhen, liuqian27=beijing}//遍历输出map.forEach((t,u)-> System.out.println(t+":"+u));}
}
class Student {String name;int age;public Student(String name, int age) {super();this.name = name;this.age = age;}public int hashCode() {return name.hashCode() + age * 36;}// 姓名年龄相同的认为是相同的键public boolean equals(Object obj) {if (!(obj instanceof Student))throw new ClassCastException();Student stu = (Student) obj;return this.name.equals(stu.name) && this.age == stu.age;}public String toString() {return name + age;}
}

scala中用HashSet去重

在scala中应用HashSet,去重的原理也是一样的.

object Test extends App {private val set = new util.HashSet[Person]()set.add(new Person("wangwu", 23))set.add(new Person("wangwu", 23))//因为重写了hashCode以及equals方法,所以在向set中添加时就会自动判断,重复的内容不会被添加//如果不重写hashCode和equals方法,set中输出就会包含两个wangwu,因为都是new的,属于不同的对象println(set)//[wangwu--23]}class Person(var name: String, var age: Int) {override def toString: String = {s"$name--$age"}override def hashCode(): Int = {return 23 * age}override def equals(obj: Any): Boolean = {val person: Person = obj.asInstanceOf[Person]return person.name == this.name && person.age == this.age}
}

case class与HashSet去重

如果用case class,就可以不用重写,case class默认会重写hashCode和equals方法

object Test extends App {private val set = new util.HashSet[Person]()set.add(Person("wangwu", 23))set.add(Person("wangwu", 23))//case class 会自动写hashCode和equals方法,所以无需自己写,不会添加进重复的内容println(set)//[Person(wangwu,23)]}case class Person(name:String,age:Int)

scala 可变set与去重

scala mutable.Set如果不重写hashCode换equals方法,同样会输出两个,不会去重

object Test extends App {private val set = mutable.Set[Person]()set.add(new Person("wangwu", 23))set.add(new Person("wangwu", 23))//scala mutable.Set如果不重写hashCode换equals方法,同样会输出两个println(set)//Set(wangwu--23, wangwu--23)}class Person(var name: String, var age: Int) {override def toString: String = {s"$name--$age"}//  override def hashCode(): Int = {//    return 23 * age
//  }
//
//  override def equals(obj: Any): Boolean = {//    val person: Person = obj.asInstanceOf[Person]
//    return person.name == this.name && person.age == this.age
//  }
}

如果是case class就不用写了

object Test extends App {private val set = mutable.Set[Person]()set.add(new Person("wangwu", 23))set.add(new Person("wangwu", 23))//即使是自定义类,case class也可以去重println(set)//Set(Person(wangwu,23))
}case class Person(name:String,age:Int)

总结

  • hashmap key唯一和hashset 去重的原理是一样的
  • 去重的保证是在add 或put元素时调用了重写的HashCode方法以及equals方法.
  • scala 中set去重原理是java一样
  • scala case class会自动完成hashCode以及equals方法,用起来会比较方便

java Hashset去重原理及HashMap key唯一原理相关推荐

  1. java源码系列:HashMap底层存储原理详解——4、技术本质-原理过程-算法-取模具体解决什么问题

    目录 简介 取模具体解决什么问题? 通过数组特性,推导ascii码计算出来的下标值,创建数组非常占用空间 取模,可保证下标,在HashMap默认创建下标之内 简介 上一篇文章,我们讲到 哈希算法.哈希 ...

  2. Java数据结构和算法:HashMap的实现原理

    HashMap源码理解 Java集合之HashMap HashMap原理及实现学习总结 HashMap源码分析 HashMap原理及实现学习总结 1. HashMap概述 HashMap是基于哈希表的 ...

  3. 深入Java集合学习系列:HashMap的实现原理

    2019独角兽企业重金招聘Python工程师标准>>> HashMap概述: HashMap是基于哈希表的Map接口的非同步实现.此实现提供所有可选的映射操作,并允许使用null值和 ...

  4. java源码系列:HashMap底层存储原理详解——5、技术本质-原理过程-算法-取模会带来一个什么问题?什么是哈希冲突?为什么要用链表?

    目录 取模会带来一个什么问题? 演示什么是哈希冲突(哈希碰撞)? 为什么要用链表? 其他--布隆过滤器 取模会带来一个什么问题? 好,那同学们这样他能达到一个目的,但是呢,它也会带来的一个问题,那它会 ...

  5. shiro漏洞原理以及检测key值原理

    一.shiro漏洞原理 Shiro 1.2.4及之前的版本中,AES加密的密钥默认硬编码在代码里(SHIRO-550),Shiro 1.2.4以上版本官方移除了代码中的默认密钥,要求开发者自己设置,如 ...

  6. java hashset 实现原理_深入Java集合学习系列:HashSet的实现原理

    Updated on 九月 8, 2016 深入Java集合学习系列:HashSet的实现原理 1.HashSet概述: HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持. ...

  7. Java HashMap的实现原理详解

    HashMap是Java Map类型的集合类中最常使用的,本文基于Java1.8,对于HashMap的实现原理做一下详细讲解. (Java1.8源码:http://docs.oracle.com/ja ...

  8. java HashMap的实现原理

    深入Java集合学习系列:HashMap的实现原理 原文:http://zhangshixi.iteye.com/blog/672697 1.    HashMap概述:

  9. 深入Java集合学习系列:LinkedHashSet的实现原理

    转载自  深入Java集合学习系列:LinkedHashSet的实现原理 1.    LinkedHashSet概述: LinkedHashSet是具有可预知迭代顺序的Set接口的哈希表和链接列表实现 ...

最新文章

  1. Swift 中 10 个震惊小伙伴的单行代码
  2. 【Python基础】14_Python中的TODO注释
  3. 都2021年了,不会还有人连深度学习都不了解吧(三)- 损失函数篇
  4. ssh ip登录缓慢问题解决
  5. Sql如何统计连续打卡天数
  6. 最短路常用的四种模板(poj1847)
  7. 展讯康一:2020年推出5G芯片 第一桶金含金量最高
  8. java url重写 session_Java Web学习之Cookie和Session的深入理解
  9. js获取textarea中的回车换行
  10. 西瓜书+实战+吴恩达机器学习(二一)概率图模型之贝叶斯网络
  11. 软件定制开发,程序外包就在这
  12. 企业信息化建设的总体规划
  13. 相见恨晚的5个资源网站 影视音乐资源随你看
  14. 上树建站教程:新手单页网站制作教程上集
  15. 判决文书网爬虫获取vjkl5失败原因
  16. MFC多线程 信号量CSemaphore 临界区与互斥 事件
  17. TrinityCore魔兽世界服务器-注册网站
  18. ORACLE存储过程RECORD数据类型的使用
  19. 『每周译Go』开启并发模式
  20. 豆瓣Top250电影爬虫

热门文章

  1. echart多个柱状图 设置y轴显示_Origin做多因子柱状图
  2. python json转xml_Python中xml和json格式相互转换操作示例
  3. python穷举法_python 穷举指定长度的密码例子
  4. vue大屏可视化布局
  5. java构造函数的执行顺序,java构造函数和初始化函数的执行顺序
  6. windows安装mysql修改密码_1、Windows下安装mysql-8.0.12及修改初始密码
  7. layer mvc json 中文乱码处理
  8. linux关闭端口进程命令,windows Linux 下命令行查看端口占用情况并且关闭进程
  9. php遍历指定目录下的文件,PHP遍历指定目录下所有文件和目录
  10. byte数组转double_java数组(不同类型的初始值)