1、再哈希
实现resize()和rehash()方法
2、迭代器
实现迭代器的三个方法(hasNext()、next()、remove())
1、再哈希->为什么?
在简单的哈希表的实现中,用的是数组+链表的方式去减少哈希碰撞,既然牵扯到数组,就不可避免地产生扩容操作。
我们的哈希函数是基于数组的长度所做的运算,既然数组的长度有所改变,那么就应该对原来数组+链表中的每个结点重新计算哈希值,以确保重置每个键通过哈希算法得到的 index 。
1)resize,扩容
逻辑:
简单,对数组二倍扩容,再扩容后,对每一个结点再哈希
代码:
public void resize(){
Node<K, V>[] newTable = new Node[this.table.length * 2];
for (int i = 0; i < this.table.length; i++) {
//对oldTable中每一个结点rehash()
rehash(i, newTable);
}
this.table = newTable;
}
2)rehash,对每一个结点再哈希,以确保将其放到正确的位置。
逻辑:
对于旧、新两个哈希表来说,将值重新哈希到新的表,对于数组下标上的结点,仍然映射到新表对应的下标上;对于链表上除去第一个结点的其它结点来说,是哈希到通过哈希算法得到的index + oldTable.length。
既然有了这样的规律,这里采用的是将本在 oldTable 下标的结点链成一个链表(LowList),将不在 oldTable 下标上的结点也链成一个链表(HighList),按照规矩办事,即可完成 rehash()。
代码:
public void rehash(int index, Node<K, V>[] newTable) {
//获取当前位置下的节点
Node<K, V> curNode = table[index];
if (curNode == null) {
return;
}
Node<K, V> lowListHead = null;//低位的头
Node<K, V> lowListTail = null;//低位的尾
Node<K, V> highListHead = null;//高位的头
Node<K, V> highListTail = null;//高位的尾
//currentNode是oldtable下index位置的第一个节点
while (curNode != null) {
//得到当前节点在新table中的位置
int newIndex = hash(curNode.key) & (newTable.length - 1);
//int newIndex = getIndex(currentNode.key, newTable);
//如果和index相等,则直接放
if (newIndex == index) {
//把该位置所有节点链成一个链表,最后统一放
if (lowListHead == null) {
lowListHead = curNode;
lowListTail = curNode;
} else {
lowListTail.next = curNode;
lowListTail = lowListTail.next;
}
} else {
//newIndex和index不等
if (highListHead == null) {
highListHead = curNode;
highListTail = curNode;
} else {
highListTail.next = curNode;
highListTail = highListTail.next;
}
}
curNode = curNode.next;
}
//将index位置连上当前lowList
if (lowListTail != null) {
lowListHead.next = null;
newTable[index] = lowListHead;
}
//将index+table.length位置连上当前highList
if (highListTail != null) {
highListTail.next = null;
newTable[index + this.table.length] = highListHead;
}
}
这个也是JDK源码中rehash的方法。
2、迭代器
迭代器,是一种遍历集合的方式。这种方式比for循环和其它循环更加的安全。
在Java,推荐使用的foreach也是基于迭代器实现。
比如:
ArrayList<Integer> list = new ArrayList<>();
list.add(2);
list.add(3);
list.add(1);
System.out.println(list);//list.toString()
for (Integer e : list) {
System.out.print(e + " ");
}
在某个自实现的集合中,要实现它的迭代器,就要实现Iterator接口,并重写hasNext()、next(),remove()不是必须要重写。
在这个自实现的类中,迭代器应该作为一个内部类或者一个匿名内部类实现,这样比较好操作。
1)构造器
首先,我们需要得到第一个不为空的结点,我们需要一个方法去get,不如使用构造器。
代码:
public Itr() {
//哈希表中没得结点
if (HashMap.this.size == 0) {
return;
}
//得到第一个不为空的结点
for (int i = 0; i < HashMap.this.table.length; i++) {
if (table[i] != null) {
this.nextNode = table[i];
this.curIndex = i;
return;
}
}
}
2) hasNext(), 被迭代的集合元素是否已被遍历完
代码:
public boolean hasNext() {
return nextNode != null;
}
3)next(),返回集合中的下一个元素
public Node<K, V> next() {
this.curNode = nextNode;
//遍历到当前链表的下一个
this.nextNode = nextNode.next;
//如果不为空,相等于一直遍历的是当前链表
//下一次调用next()方法返回下一个结点
//为空,当前链表最后一个一个结点已经遍历完
if (this.nextNode == null) {
//在数组上寻找下一个不为空的结点
for (int i = curIndex + 1; i < HashMap.this.table.length; i++) {
if (table[i] != null) {
this.nextNode = table[i];
this.curIndex = i;
break;
}
}
}
return curNode;
}
4)remove(),删除集合上一次调用next方法返回的元素
代码:
public void remove() {
if(this.curNode == null){
return;
}
K currentKey = this.curNode.key;
HashMap.this.remove(currentKey);
this.curNode = null;
}
注:
完整的哈希表代码