Map接口
HashMap是基于哈希表实现,实现Map接口,存储的是 (key,value)键值对
哈希表
-
哈希表又称散列表,是根据关键码key直接访问内存存储位置的数据结构,即通过Key的函数,映射到一个地址来访问数据。这样加快查找速度
- 数组 查找容易 删除插入不易
- 链表 查找不易 删除插入容易
- 哈希表 二者的中和 查找,删除,插入 都容易
产生哈希冲突的原因:例如手机通讯录
姓名康六的首字母为K --> key:K ----> f(Key) ---->address —>存放康六这一数据
通过key:K—> f(Key) ---->address —>vlaue ,如果f(key)哈希函数设计合理,O(1)的时间复杂度即可查找到找得到
哈希冲突是由于 康六的 key是K ,康七key是K ,他们首字母是一样的 f(key)—>address也是一样的,对不同的关键字得到同一散列地址
即就是关键字根据哈希函数得到相同的地址,就产生了哈希冲突
解决哈希冲突
链地址法
-
数组+链表 (JDK1.8之前的数据结构)
将数组查找速度快,链表的增删容易的优点结合形成链地址法
- HashMap本身处理海量数据,当位于同一位置的元素越来越多时,hash值相等的元素越来越多,使用链表查找的效率较低, 时间复杂度为 O(1)-----> O(n)
-
红黑树(JDK1.8 只使得时间复杂度为O(log2N))
- 链表长度 超过阈值8的时候,会将链表的结构转为红黑树 ,降低时间复杂度为O(log 2N)
-
二叉排序树 ----->AVL树 、红黑树
- 红黑树的特性
- 红黑树每个节点要么是红色要么是黑色
- 根节点是黑色
- 叶子节点是黑色
- 如果一个节点是红色,那么叶子节点必须是黑色
- 每个节点到叶子节点所经过的黑色节点数目是相同的
-
开放地址法
Map接口的简单使用
import java.util.*;
public class TestDome2 {
public static void main(String[] args) {
//HashMap的简单使用
Map<String ,Integer> map =new HashMap<>(); //键值对
map.put("张三",10);
map.put("李四"15);
map.put("王五",50);
System.out.println(map.remove("张三"));
System.out.println(map.size());
System.out.println(map.isEmpty());
System.out.println(map.get("李四"));
System.out.println(map.containsValue(50));
System.out.println(map.containsKey("李四"));
//HashMap当中所有的元素作为一个entry节点存在,所有节点封装为一个Set集合
//获取set集合的迭代器对象
//通过对象调用迭代器
Set<Map.Entry<String, Integer>> entries = map.entrySet(); //map里面存在节点,每一个Entey节点作为set集合返回
Iterator<Map.Entry<String,Integer>> iterator = entries.iterator(); //因为set 是继承与Collection
的子接口,实现了迭代器方法,就可以通过set对象的来获取迭代器
while (iterator.hasNext()){
Map.Entry<String,Integer> next = iterator.next();
System.out.println(next.getKey()+" :: " +next.getValue());
}
}
HashMap的实现
/*
*
*
* HashMap实现
*
* Hash函数 类比 JDK中的HashMap的hash函数,解决哈希冲突
* 基于散列表实现
* 解决哈希冲突需要用到(JDK1.8之前)链地址法即;链表加数组实现
* JDK1.8基于数组加链表加红黑树 ,查找速度快
* key--->f(key)-->index--->O(1)
* key--->f(key)-->index--->LinkedList--->O(n)
* --->红黑树----->o(log2 N)
* 以put方法引入
* 自定义put方法
* 1)key--->h=hash(key )散列表 --->table.length-1& h--->确定位置index
* 2)table[index]==null是否存在节点
* 3)不存在,直接将 key-value键值对封装成一个节点 放到index位置
* 4)存在 key不允许重复
* 5)存在 重复-------》新值覆盖旧值
* 6)存在 不重复-----》遍历链表-->(不重复)尾插(插入新节点)
重复----更新值
实现方法有 :put(k key , V value ) ,get (K key) remone(K key) resize();
HashMap迭代器实现
1)由于哈希表数据分布不连续,所以在迭代器初始化的过程中需要找到第一个非空的位置点
避免无效的迭代
2)当迭代器的游标到达某一个桶链表的末尾时,迭代器的游标需要跳到下一个非空的位置点
*/
class MyHashMap<K,V> {
private int size;//有效节点个数
private Node<K,V>[] table;// HashMap底层的桶;因为存放Node节点,故是Node类型
private static final int initalCapacity=16; //初始化桶的容量
public MyHashMap (int capacity){
table=new Node[capacity];
}
public MyHashMap(){
this(initalCapacity); //默认初始化
}
public int hash(Object key){
int h;
return (key==null)?0:(h=key.hashCode())^(h>>>16);
}
class Node<K,V> {
public K key;
public V value;
protected Node<K, V> next;
protected int hash; //存放hash地址
public Node(int hash, K key, V value) {
this.hash = hash;
this.key = key;
this.value = value;
}
}
public V get(K key){
//获取key对应的value
int hash=hash(key);
int index=table.length-1&hash;
//key--->index
//在index位置所有节点中找与key相等的key
Node<K,V> firstNode=table[index];
if(firstNode==null){
return null;
}
if(firstNode.key.equals(key)){
return firstNode.value;
}
else{
Node<K,V> temp=firstNode.next;
while (temp!=null&&!temp.key.equals(key)){
temp=temp.next;
}if(temp==null){
if(temp.key.equals(key)){
return temp.value;
}
return null;
}
else {
return temp.value;
}
}
}
public boolean remove(K key){
//找到key对应的index
int h = hash(key);
int index=table.length-1&h;
//找到index对应的Node
//删除这个节点
Node<K,V> temp=table[index];
if(table[index]==null){ //表示table桶中的该位置不存在节点
return false;
}
else {
//删除第一个节点
Node<K, V> p = table[index];
if(p.key.equals(key)){
table[index]=p.next;
size--;
return true;
}
while (p.next != null ) {
if (p.next.key.equals(key)) { //p.next时所有删除的节点
p.next = p.next.next;
size--;
return true;
}else{
p=p.next;
}
}
return false;
}
}
public void resize(){ //对数组进行扩容
//HashMap扩容
//table进行扩容 2倍
Node<K,V>[] newTable= new Node[table.length*2];
// index--->key%table.length;
//table进行扩容 2倍
//table.length改变所以index改变
//所以需要重哈希,即就是原来的数据需要重新放入新数组中
//要么在原位置,要么在原位置+扩容后的长度
//重哈希
for(int i=0;i<table.length;i++){
reHash(i,newTable);
}
this.table=newTable;
}
public void reHash(int index,Node<K,V>[] newtable){
//相当于对原先哈希表中的每一个有效节点 进行冲哈希的过程
//要么在原位置 ----》地位位置
//要么在原位置加上扩容后的长度 -----》高位位置
Node<K,V> currentNode=table[index];
if(currentNode==null){
return;
}
Node<K,V> lowHead=null; //低位的头尾
Node<K,V> lowTail=null;
Node<K,V> highHead=null;//高位的头尾
Node<K,V> highTail=null;
while (currentNode!=null){
//遍历index位置的所有节点
int newIndex=hash(currentNode.key)&(newtable.length-1); //确定新的索引位置
if(newIndex==index){ //新的索引位置 等于原先的位置,即低位位置,
//当前节点链接到lowTail后
if(lowTail==null){ //第一次链接
lowHead=currentNode;
lowTail=currentNode;
}else{
lowTail.next=currentNode;
lowTail=currentNode;
}
}else{ //高位链接
if(highHead==null){
highHead=currentNode;
highTail=currentNode;
}else {
highTail.next=currentNode;
highTail=currentNode;
}
}
currentNode=currentNode.next;
}
if(lowHead!=null&&lowTail!=null){ //将链接尾巴后的节点的指向置为空,作为新尾巴
lowTail.next=null;
newtable[index]=lowHead; //将低位位置的头部放置在新数组中的原位置
}
//要么在原来位置+扩容前的长度(高位位置)
if(highHead!=null&&highTail!=null) {
highTail.next=null;
newtable[index + table.length] = highHead;//将高位位置的头部放置在新数组中的原位置加上扩容后的长度的位置
}
}
public void put(K key, V value) {
//key--->h=hash(key)散列码 --->table.length-1& h--->确定位置index
int h = hash(key); //得到散列码
int index=table.length-1&h;
//判断index位置是否存在节点
if(table[index]==null){
// Node<K,V> node =new Node<>(h,key,value);
//
// table[index]= node;
table[index]=new Node<>(h,key,value);
size++;
return;//防止代码往下运行,出现空指针异常
}
//如果不存在,直接将当前key,value封装为一个节点Node,插入到该Index位置
//如果存在节点(保证HashMap中的key不重复)
//判断key是否重复
//如果key重复,考虑新值覆盖旧值
//如果没有重复,将当前key,value封装为一个节点Node,插入到该Index的位置
else{ //如果存在节点(保证HashMap中的key不重复)
//获取第一个位置的节点,key重复,则覆盖值
if(table[index].key.equals(key)){
table[index].value=value;
} else {
Node<K,V>temp=table[index];
while (temp.next!=null&&!temp.key.equals(key)){ //temp一直往后跑,要么跑到最后一个节点,要么找到一个与key相等的节点
temp=temp.next; //遍历链表
} //跳出循环条件,要么跑到最后一个节点,要么找到一个与key相等的节点,一个不满足就跳出
if(temp.next==null) { //如果跑到最后一个节点,则!temp.key.equals(key)判断条件未执行就退出,故需要重新判断是否重复
if (temp.key.equals(key)) { //如果最后一个节点的key值重复,考虑新值覆盖旧值
temp.value = value;
} else { //否则就将新节点采用尾插链接其后面
temp.next = new Node(h, key, value);
size++;
}
}
else { // 找到一个与key值相等的然后进行覆盖
temp.value=value;
}
}
}
}
public Iterator<Node<K,V>> iterator(){
return new Itr();
}
class Itr implements Iterator<Node<K,V>> {
// HashMap迭代器实现
// 1)由于哈希表数据分布不连续,所以在迭代器初始化的过程中需要找到第一个非空位置避免无效的迭代
// 2)当迭代器的游标到达某个桶链表的末尾时,迭代器的游标需要跳到下一个非空的位置点
private int cursor;//指向当前遍历到的元素
private Node<K,V> currentNode ;//需要返回的元素节点
private Node<K, V> nextNode;//下一个元素的节点
private int currentIndex;
public Itr( ) {
//1)由于哈希表数据分布不连续,所以在迭代器初始化的过程中需要找到第一个非空的位置点,避免无效的迭代
//currentIndex currentNode nestNode 初始化
// int hash=hash(key);
// currentIndex =hash&table.length;
// currentNode=table[currentIndex];
// nextNode=currentNode.next;
//
if(MyHashMap.this.size<=0){
return;
}
for(int i=0;i<table.length;i++){
if(table[i]!=null) {
cursor = i;
nextNode = table[i];
return;
}
}
@Override
public Node<K,V> next() {
currentNode=nextNode; //暂时保存需要返回的元素节点
nextNode=nextNode.next; //nextNode 用来遍历链表中的节点
if(nextNode==null){ //迭代器走到末尾时
// 2)当迭代器的游标到达某一个桶链表的末尾时,迭代器的游标需要跳到下一个非空的位置点
for(int j =cursor+1;j<table.length;j++){ // curson 用来遍历桶中的元素
if (table[j]!=null){
//table[j]表示该位置的第一个元素
cursor=j;
nextNode=table[j];
break;
}
}
}
return currentNode;
}
@Override
public boolean hasNext() {
//判断是否还有下一个可迭代的元素
return nextNode!=null;
}
}
}
public class HashMapTest {
public static void main(String[] args) {
MyHashMap<Integer,String> map=new MyHashMap<>(16);
map.put(15,"张三");
map.put(16,"李四");
map.put(17,"王五");
map.put(13,"程六");
System.out.println(map.get(15));
System.out.println(map.remove(13));
System.out.println(map.remove(19));
System.out.println(map.remove(16));
Iterator<MyHashMap<Integer,String>.Node<Integer,String>> ite= map.iterator();
while (ite.hasNext()){
MyHashMap<Integer, String>.Node<Integer,String> next = ite.next();
System.out.println(next.key+": :" +next.value);
}
}
}