散列是什么?
散列是使用一个散列函数,将一个键映射到一个索引;
可以这么理解,ArrayList使用0-N为索引来保存N-1个数据,而散列实现的集合使用经过散列函数处理过的索引来保存数据,如此一来,ArrayList中的数据在内存中是连续的,而散列实现的集合就不一定了。
散列的缺点与缺点:
优点:插入、更新、删除、查找单个数据速度快,均是O(1)的算法复杂度;
缺点:不能有重复数据,且不能保证数据的有序性;
如何实现散列集合?
说到这里,我想散列的重点已经很明确了,那就是散列函数的设计,散列函数的目的在于将一个键值映射到一个索引,这个步骤分为两步,首先将键值转换成一个称为散列码的整型值,然后将散列码压缩为散列表中的一个索引;
散列表的概念:一般来说,散列表是一个存储数据的数组(也可以使用链表,不过使用数组居多),可以认为这个散列表就是我们所需要的散列集合(基于数组实现的散列集合);
对于查找一个数据而言,首先使用散列函数将数据转换为整型索引,再根据这个索引去散列表取出数据;
在实现散列集合之前还需要以下基本知识:
1.获取一个数据的散列码:
首先你得明确散列码是一个32位的整型数值,获取散列码的过程也就是将一个数据转换成int类型,Object类提供的hashCode方法返回的就是普通java对象的散列码,该散列码代表了这个对象的内存地址。
2.压缩散列码:
通过第一步获得的散列码可能是一个很大的数值,这样一来散列表就需要足够的长度来支持,实际上存储的数据数量远远小于散列表的长度,过分浪费了内存。
常用的压缩方法:h=hashCode&(N-1)
h是压缩后的散列码,hashCode是原散列码,N是设定的散列表长度,这样一来h的取值范围只会在0或者N-1;
3.处理散列码冲突:
压缩散列码会出现散列码冲突的问题,通常的处理方式有开放地址法和链地址法。
这二者的最大区别在于开放地址法会寻找新的地址存放数据,而链地址则会将这些冲突的数据存在同一个地址,本质上就是散列表里存放的是一个集合;
常用的开放地址法有线性探测和二次探测,当一个数据出现散列码冲突时,线性探测的处理方法是将散列码自增后递归调用压缩函数,直到找到不冲突的位置,如果达到散列表末尾则返回散列表起点;二次探测的增量不同,假设探测次数是T,线性探测的散列码是h=(hashCode+T)&(N-1),而二次探测的散列码是h=(hashCode+T^2)&(N-1);
实现自定义HashSet的基本程序如下:
1.定义MySet接口:
package A03.Hash;
public interface MySet<E> extends Iterable<E>
{
// 查找元素
boolean contains(E e);
// 删除元素
boolean remove(E e);
// 是否为空
boolean isEmpty();
// 获取大小
int size();
// 插入元素
boolean add(E e);
}
2.定义MyHashSet实现类
package A03.Hash;
import A01.LSQ.MyArrayList;
import A01.LSQ.MyDLinkedList;
import java.util.Iterator;
public class MyHashSet<E> implements MySet<E>
{
// 默认的起始容量
private static int DEFAULT_INITIAL_CAPACITY = 10;
// 最大容量
private static int MAXIMUM_CAPACITY = 1 << 30;
// 当前容量
private int capacity;
// 默认最大负载因子
private static float DEFAULT_MAX_LOAD_FACTOR = 0.75f;
// 负载因子阈值
private float loadFactorThreshold;
// 集合大小
private int size = 0;
// 实现数据存储的链表(这个链表是作者自定义的,源代码见《数据结构03-双向链表》)
private MyDLinkedList<E>[] table;
public MyHashSet()
{
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_MAX_LOAD_FACTOR);
}
public MyHashSet(int initilCapacity)
{
this(initilCapacity, DEFAULT_MAX_LOAD_FACTOR);
}
public MyHashSet(int initilCapacity, float loadFactorThreshold)
{
if (initilCapacity > MAXIMUM_CAPACITY)
this.capacity = MAXIMUM_CAPACITY;
else
this.capacity = trimToPowerOf2(initilCapacity);
this.loadFactorThreshold = loadFactorThreshold;
table = new MyDLinkedList[capacity];
}
// 根据传入的自定义容量返回最大容量,扩容规则是倍增
private int trimToPowerOf2(int initilCapacity)
{
int capacity = 1;
while (capacity < initilCapacity)
capacity <<= 1;
return capacity;
}
// 散列算法
private int hash(int hashCode)
{
return supplementalHash(hashCode) & (capacity - 1);
}
// 补充散列,目的是使散列码分布更加均匀,javaAPI中也是使用的此种方法
private static int supplementalHash(int h)
{
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
// 再散列,目的是扩容,与ArrayList的扩容同理
private void rehash()
{
MyArrayList<E> list = setToList();
capacity <<= 1;
table = new MyDLinkedList[capacity];
size = 0;
for (E element : list)
add(element);
}
// 将HashSet以List方式返回(MyArrayList是作者自定义的ArrayList,源代码见《数据结构01-自定义ArrayList》)
private MyArrayList<E> setToList()
{
MyArrayList<E> list = new MyArrayList<>();
for (int i = 0; i < capacity; i++)
{
if (table[i] != null)
for (E e : table[i])
{
list.add(e);
}
}
return list;
}
@Override
public boolean contains(E e)
{
int bucketIndex = hash(e.hashCode());
if (table[bucketIndex] != null)
{
MyDLinkedList<E> bucket = table[bucketIndex];
for (E element : bucket)
if (element.equals(e))
return true;
}
return false;
}
@Override
public boolean remove(E e)
{
if (!contains(e))
return false;
int bucketIndex = hash(e.hashCode());
if (table[bucketIndex] != null)
{
MyDLinkedList<E> bucket = table[bucketIndex];
for (E element : bucket)
if (element.equals(e))
{
bucket.remove(e);
break;
}
}
size--;
return true;
}
@Override
public boolean isEmpty()
{
return size == 0;
}
@Override
public int size()
{
return size;
}
@Override
public boolean add(E e)
{
if (contains(e))
return false;
if (size + 1 > capacity * loadFactorThreshold)
{
if (capacity == MAXIMUM_CAPACITY)
throw new RuntimeException("");
rehash();
}
int bucketIndex = hash(e.hashCode());
if (table[bucketIndex] == null)
table[bucketIndex] = new MyDLinkedList<>();
table[bucketIndex].add(e);
size++;
return true;
}
@Override
public String toString()
{
MyArrayList<E> list = setToList();
StringBuffer sb = new StringBuffer("[");
for (E element : list)
sb.append(element + ",");
if (list.size() != 0)
sb.delete(sb.length() - 1, sb.length());
sb.append("]");
return sb.toString();
}
@Override
public Iterator<E> iterator()
{
return new MyHashSetIterator(this);
}
private class MyHashSetIterator implements Iterator<E>
{
private MyArrayList<E> list;
private int current = 0;
private MyHashSet<E> set;
public MyHashSetIterator(MyHashSet<E> set)
{
this.set = set;
list = setToList();
}
@Override
public boolean hasNext()
{
return current < list.size();
}
@Override
public E next()
{
return list.get(current++);
}
@Override
public void remove()
{
set.remove(list.get(current));
list.remove(current);
}
}
}