HashSet基本介绍
HashSet集合底层采取哈希表存储数据,对于增删改查的操作数据性能都较好。
JDK8版本后的的哈希表是数组、链表、红黑树的结合体。
底层原理
创建集合
创建HashSet集合,内部会存在一个长度为16个大小的数组。
public HashSet() {
map = new HashMap<>();
//使用默认初始容量16和默认加载因子0.75构造一个空HashMap
}
添加元素
调用集合的添加方法,通过对象的hashCode方法计算出哈希值,进而得到应存入的索引位置(哈希值 mod 数组长度)。
//HashMap中的hash方法源码
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
//对哈希值扰动,进行二次哈希操作,可以一定程度地减少链表挂载的数量
}
//HashMap中的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;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)ll)
//此计算方法与(哈希值 mod 数组长度)值相同,但运算效率更高
……
}
应存入索引位置计算方法:
- 哈希值的计算:为了打散哈希表的元素,一定程度地减少链表挂载的数量,在具体源码中是先使用对象的hashCode方法得到原始哈希值,再将原址哈希值向右移动16位进行一次哈希扰动,最后拿扰动后的哈希值与原始哈希值做异或操作即二次哈希操作,获得哈希值。
- 为了提高运算效率,不直接将哈希值模于数组长度,而是将对数组长度减一后的值和计算出的哈希值进行与运算,得到应存入的索引位置。
判断计算出来的索引位置是否为null,如果是null则直接存入;如果不是null表示该位置有元素,则调用equals方法比较属性值,如果相同则不存,如果不同则存储数组。
提高查询性能
存入元素后,为提高查询性能,判断哈希表结构是否需要进行调整,如果重新调整则需重新计算应存入的索引位置。
(1)当数组中的元素个数超过了 ( 数组长度 * 加载因子0.75 ) 时,扩容原数组2倍的大小。(注意是元素个数而不是有元素的索引位置)
(2)当链表挂载的元素超过阈值8个时,如果数组长度没有超过64则扩容原数组2倍的大小,如果数组长度超过了64则将链表转为红黑树。
基本操作
集合元素的遍历
采取集合元素的通用遍历方式,即迭代器、增强for循环、forEach三种方法(参考 Java集合的遍历方式:Iterator和ListIterator - 集合元素通用遍历方式)
常见API
public int size() //返回存储的元素个数
public boolean isEmpty() //返回集合是否为空
public boolean contains(Object o) //返回集合是否包含o元素
public boolean add(E e) //添加元素e
public boolean remove(Object o) //删除元素o
public void clear() //清空集合中的元素
存储自定义类对象
public class City {
private String name;
private char carID;
private int adminID;
public City(String name, char carID, int adminID){
this.name=name;
this.carID=carID;
this.adminID=adminID;
}
}
保证元素唯一性
为了保证元素的唯一性,需要同时重写自定义类中的hashCode方法和equals方法。
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
City minCity = (City) o;
return carID == minCity.carID && adminID == minCity.adminID && Objects.equals(name, minCity.name);
}
@Override
public int hashCode() {
return Objects.hash(name, carID, adminID);
}
以文本方式表示对象
为了在输出HashSet集合时显示对象属性值,需要重写自定类中的toString方法。
@Override
public String toString() {
return "City{" +
"name='" + name + '\'' +
", carID=" + carID +
", adminID=" + adminID +
'}';
}