JAVA集合V-Set接口实现类HashSet
1.HashSet简单说明
(1)说明
- HashSet实现了Set接口
- HashSet实际上是HashMap,看下图源码
- 可以存放null值,但是只能有一个
- HashSet不保证元素是有序的,取决于Hash后,再确定索引的结果(不保证存放元素的顺序和取出的顺序一致)。
- 不能有重复元素/对象,前面Seth中已说明。
2.案例说明
package Collection.hashSet;
import java.util.HashSet;
public class hashSetTest2 {
public static void main(String[] args) {
HashSet hashSet = new HashSet();
//1. 在执行 add 方法后,会返回一个 boolean 值
// 2. 如果添加成功,返回 true, 否则返回 false
// 3. 可以通过 remove 指定删除哪个对象
hashSet.add("lucy");//T
hashSet.add("lucy");//F
//创建两个Dog对象,名字和age均相同,可以添加成功
HashSet hashSet1=new HashSet();
hashSet1.add(new Dog("嘿嘿",3));//T
hashSet1.add(new Dog("嘿嘿",3));//T
for (Object o : hashSet1) {
System.out.println(o);
}
//面试题:利用String创建两个对象,内容相同,
//添加不了的原因:
//因为String类中重写了hashcode和equals方法
//而hashset在添加时,会利用hashcode和equals方法比较当前添加对象或元素是否相等
//当将Dog类中重写hashcode和equals方法后,发现,上面添加时new Dog("嘿嘿",3)
//也只能添加第一个。
HashSet hashSet2=new HashSet();
hashSet2.add(new String("小明"));//可以
hashSet2.add(new String("小明"));//添加不了
for (Object o : hashSet2) {
System.out.println(o);
}
}
}
class Dog{
private String name;
private int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Dog dog = (Dog) o;
if (age != dog.age) return false;
return name != null ? name.equals(dog.name) : dog.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
}
3.hashSet底层源码
HashSet底层是HashMap,HashMap底层是(数组+链表+红黑树)
(1)HashSet添加元素的方式
- 先获取元素的哈希值(hashcode方法以及一个右移操作">>>"),源码计算公式 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
- 对哈希值进行运算,得出一个索引值即为要存放哈希表中的位置号
- 如果该位置上没有其他元素,则直接存放;如果该位置上已经有元素,则需要进行equals判断,如果相等,则不再添加;如果不相等,则以链表的方式添加。
如下图解:
(2)HashSet底层解析
- HashSet底层是HashMap
- 添加一个元素时,先得到hash值-会转成索引值
- 找到存储数据表table,看这个索引位置是否已经存放有元素
- 如果没有,直接加入
- 如果有,调用equals比较,如果相同,就放弃添加;否则就添加到最后
- 在java8中,如果一条链表的元素个数到达TREEIFY_THRESHOLD(默认是8),并且table的大小>=MIN_TREEIFY_CAPACITY(默认64),就会进行树化(红黑树)。
涉及源码有些复杂,简单介绍:
package Collection.hashSet;
import java.util.HashSet;
public class hashSetTest {
public static void main(String[] args) {
HashSet set = new HashSet();
set.add("java");
set.add("php");
set.add("java");
System.out.println("set="+set);
}
/*
* HashSet 底层源码剖析
* 1,执行构造器hashset()
* public HashSet() {
map = new HashMap<>();
}
2,执行add方法
public boolean add(E e) {
return map.put(e, PRESENT)==null;//PRESENT是一个object类,仅仅
用于占位,为了使用hashMap
}
3,执行put()方法,会得到一个key的hash值, 算法:return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
4,执行putVal()方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//table就是hashMap的一个数组,类型为node[]
//if语句的作用是为table赋值,如果table为空,如进入resize(),为
//table赋值16个大小的空间
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//根据key的hash值定位table表的位置,判断改位置是否为空,
//如果为空执行if语句内容,则创建一个node<key="java",value=PRESENT>;
//否则,执行else语句,判断hash值和equals.
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
//当前索引对应的table中的位置的第一个元素的hash值与当前key的hash值比较p.hash == hash
//同时比较对应的key的是否相等,(key != null && key.equals(k))
//或者key的地址是否相等(可能传入的是对象)(k = p.key) == key。
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//在判断p是不是一颗红黑树
//如果是一颗红黑树,就调用putTreeVal来进行添加
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
//(1)依次和该链表的每一个元素比较后,都不相同,则加入该链表的最后
注意:在把元素添加到链表后,立即判断该链表是否已经到该链表的最后
,就调用treeifyBin()对当前这个链表进行树化(转成红黑树)
注意,在转成红黑树时,要进行判断数组长度是否大于64,如果没有
继续扩容,直到数组长度为64,且链表长度等于8时,转成红黑树。
//(2)依次和该链表的每一个元素比较过程中,如果有相同情况,就直接break.
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;
}
*
*
*
* */
}