目录
总结
前言
散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。我们学习的HashSet、LinkedHashSet、HashMap、LinkedHashMap底层用的都是哈希表,那我们就来看一下哈希表的底层实现过程。
一、HashSet特点
Set 的实现类都有一套自己的排序算法,每添加一个元素,都会按照其内部算法将元素添加到合适的位置,所以Set集合不能有重复的元素,也不能保证内部存储是按元素添加的顺序而存储的,因为它的父类Set接口没有索引值,所以他也没有相应的根据索引值进行的取值、添加等操作,特点总结如下:
1、没有索引值
2、不能重复
3、无序
public class Test06 {
public static void main(String[] args) {
Set<Integer> s=new HashSet();
s.add(10);
s.add(6);
s.add(6);
System.out.println(s); //输出结果为[6,10]
}
}
二、如何实现的新增过程
1.底层数据结构
底层数据结构,是哈希表(数组+链表/红黑树)
数组类型:java.util.HashMap$Node (表示一个单链表)
数组的初始长度:16
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash; //为Node的哈希值,也就是存放table数组的下标
this.key = key; //写入的健值
this.value = value; //写入的key对应的value值
this.next = next; //next为存放的当前Node的next(实现链表结构)
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
//equals 方法的重写
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
//key和value都相等,两个Map.Entry才相等
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
2.新增过程
简单总结来说:
1.计算新增元素的哈希值(通过hashCode计算)
2.通过哈希值%数组长度,确定新增元素的在数组中的索引值位置
3.如果该位置为空,则直接新增
如果不为空,判断是否重复:
如果不重复:直接新增,挂到该索引值链表末尾处
如果重复:就不新增
//判断元素是否在范围内
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
3.扩容
什么时候会扩容:
a.同一索引值下元素个数>8,并且数组长度<64
b.数组的索引值占有率>0.75
扩容规则:
新数组容量 = 就数组容量<<1
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//扩容:新容量=旧容量<<1
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
4.红黑树
什么情况下会转红黑树:
同一索引值下元素个数>8,并且数组长度>=64,会把"该索引值"下的元素转换为红黑树。
同一索引值下元素不能超过8,如果超过8,会进行以下操作:
1、扩容,把元素分开,让其小于8个
2、把链表结构转为红黑树
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent;
TreeNode<K,v> left;
TreeNode<K,V> right;
TreeNode<K,V> prev;
boolean red;
TreeNode(int hash,K key,V val,Node<K,V> next){
super(hash,key,val,next);
}
final TreeNode<K,V) root(){
for(TreeNode<K,V> r=this,p;;){
if((p=r.parent)==null){
return r;
r=p;
}
}
}
}
5.加载因子
1、可以通过构造方法指定加载因子
2、可以通过构造方法指定哈希数组的长度
//默认的加载因子为0.75,为时间和空间复杂度的平衡值
static final float DEFAULT_LOAD_FACTOR = 0.75f
/第一个是初始容量,第二个是负载因子
public HashMap(int initialCapacity, float loadFactor) {
//初始容量小于0,抛出异常
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
//如果设置的初始容量大于Node[] table 数组的长度的最大值,设置initialCapacity为最大值
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
//把传入的负载因子赋值给成员的负载因子
this.loadFactor = loadFactor;
//tableSizeFor()方法,会传入最近2的次方的值
this.threshold = tableSizeFor(
总结
以上就是我对哈希表的认识,主要从HashSet入手,简单的根据源码阐述了对底层的新增过程以及特点的说明,还有很多不足,希望大家可以指出。