一、Set
Set注重独一无二的性质,该体系集合可以知道某物是否已经存在于集合中,不会存储重复的元素,用于存储无序(存入和取出的顺序不一定相同)元素,值不能重复
对象的相等性:
引用到堆上同一个对象的两个引用是相等的。如果对两个引用调用hashcode方法,会得到相同的结果,如果对象所属的类没有覆盖object的hashcode方法的话,hashcode会返回每个对象特有的序号(java是依据对象的内存地址计算出的此序号),所以两个不同的对象的hashcode值是不可能相等的。
如果想要让两个不同的Person对象视为相等的,就必须覆盖Object继承下来的hashcode方法和equals方法,因为Object hashcode返回的是该对象的内存地址,所以必须重写hashcode方法,才能保证两个不同的对象具有相同的hashcode,同时也需要两个不同对象比较equals方法返回true。
该集合中没有特有的方法,直接继承自Collection:
/**
* Collection
* \--List
* 有序(存储顺序和取出顺序一致),可重复
* \--Set
* 无序(存储顺序和取出顺序不一致),唯一
* HashSet:它不保证set的迭代顺序;特别是它不保证该顺序恒久不变
* 注意:虽然set集合的元素无序,但是,作为集合来说,它肯定有它自己的存储顺序,
* 而你的顺序恰巧和它的存储顺序一致,这代表不了有序,你可以多存储一些数据就能看到效果
**/
案例:set集合添加元素并使用增强for循环遍历
public static void main(String[] args) {
//创建集合对象
Set<String> set=new HashSet<String>();
//创建并添加元素
set.add("hello");
set.add("world");
set.add("java");
set.add("java");
set.add("apple");
//增强for循环 [world,java,hello,apple]
for (String s:set){
System.out.println(s);
}
}
二、HashSet
HashSet是一个没有重复元素的集合,它其实是由HashMap实现的,HashMap保存的是建值对,然而我们只能向HashSet中添加Key,原因在于HashSet的Value其实都是同一个对象,这是HashSet添加元素的方法,可以看到辅助实现HashSet的map中的value其实都是Object类的同一个对象。
案例:HashSet存储字符串并遍历
public static void main(String[] args) {
//创建集合对象
HashSet<String> hashSet=new HashSet<String>();
//创建并添加元素
hashSet.add("hello");
hashSet.add("world");
hashSet.add("java");
hashSet.add("java");
//遍历集合 [java,hello,world]
for(String s:hashSet){
System.out.println(s);
}
}
结果输出的是:java,hello,world,为什么存储字符串的时候,字符串内容相同的只存储了一个呢?思考一下,为什么第二个java没有存进去呢,我们来分析一下add()的源码,了解一下底层是怎么工作的,首先把他们的继承关系理出来,会有一个类HashSet实现Set接口,还会有一个Collection的接口,如下代码:
interface Collection{
...
}
interface Set extends Collection{
...
}
class HashSet implements Set{
}
紧接着点进去看add()方法的源码,它返回的是map.put(e, PRESENT)==null,传递一个e,传递一个PRESENT变量,PRESENT是一个静态的常量,对我们看代码来说意义不大,我们只需要关注e的值就行,HashSet在它的无参构造里面有一个HashMap,创建了一个HashMap对象,到这里我们就可以知道HashSet的底层其实是HashMap,紧接着我们就继续找HashMap里面的put方法,看它的源码:
class HashSet implements Set{
private static final Object PRESENT = new Object();
private transient HashMap<E,Object> map;
public HashSet() {
map = new HashMap<>();
}
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,
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 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;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
}
HashSet:哈希表里面存放的是哈希值,HashSet存储元素的顺序并不是按照存入时的顺序(和List显然不同) 是按照哈希值来存的所以取数据也是按照哈希值取得。HashSet不存入重复元素的规则.使用hashcode和equals,由于Set集合是不能存入重复元素的集合。那么HashSet也是具备这一特性的。HashSet如何检查重复?HashSet会通过元素的hashcode()和equals()方法进行判断元素师否重复,当你试图把对象加入HashSet时,HashSet会使用对象的hashCode()方法来判断对象加入的位置。同时也会与其他已经加入的对象的hashCode进行比较,如果没有相等的hashCode,HashSet就会假设对象没有重复出现,简单一句话,如果对象的hashCode值是不同的,那么HashSet会认为对象是不可能相等的,因此我们自定义类的时候需要重写hashCode,来确保对象具有相同的hashCode值。
如果元素(对象)的hashCode值相同,是不是就无法存入HashSet中了? 当然不是,会继续使用equals 进行比较.如果 equals为true 那么HashSet认为新加入的对象重复了,所以加入失败。如果equals 为false那么HashSet 认为新加入的对象没有重复.新元素可以存入。
三、总结
元素的哈希值是通过元素的hashcode()方法来获取的, HashSet首先判断两个元素的哈希值,如果哈希值一样,接着会比较equals方法 如果 equlas结果为true ,HashSet就视为同一个元素。如果equals 为false就不是同一个元素。
哈希值相同equals为false的元素是怎么存储呢,就是在同样的哈希值下顺延(可以认为哈希值相同的元素放在一个哈希桶中)。也就是哈希一样的存一列。
HashTable
图1:hashCode值不相同的情况
图2:hashCode值相同,但equals不相同的情况。
HashSet:通过hashCode值来确定元素在内存中的位置。一个hashCode位置上可以存放多个元素。
当hashcode() 值相同equals() 返回为true 时,hashset 集合认为这两个元素是相同的元素.只存储一个(重复元素无法放入)。调用原理:先判断hashcode 方法的值,如果相同才会去判断equals 如果不相同,是不会调用equals方法的。
四、HashSet到底是如何判断两个元素重复
通过hashCode方法和equals方法来保证元素的唯一性,add()返回的是boolean类型,判断两个元素是否相同,先要判断元素的hashCode值是否一致,只有在该值一致的情况下,才会判断equals方法,如果存储在HashSet中的两个对象hashCode方法的值相同equals方法返回的结果是true,那么HashSet认为这两个元素是相同元素,只存储一个(重复元素无法存入)。
注意:HashSet集合在判断元素是否相同先判断hashCode方法,如果相同才会判断equals。如果不相同,是不会调用equals方法的。
HashSet 和ArrayList集合都有判断元素是否相同的方法,
boolean contains(Object o)
HashSet使用hashCode和equals方法,ArrayList使用了equals方法
五、HashSet存储自定义对象
问题:现在有一批数据,要求不能重复存储元素,而且要排序。ArrayList,LinkedList不能去除重复数据。HashSet可以去除重复,但是无序。