Java基础学习笔记二十六 容器(Map接口)

Map接口

Map就是用来存储"键(key)-值(value)对"的。类似python中的字典。Map类中存储的键值对通过键来标识,键对象不能重复。
Map接口的实现类有HashMap、TreeMap、HashTable、Properties等。
小例子开篇:

import java.util.HashMap;
import java.util.Map;
/**
 *测试HashMap的使用
 */
public class TestMap {
	public static void main(String[] args){
		Map<Integer,String> m1 = new HashMap<>();
		m1.put(1,"one");
		m1.put(2,"two");
		m1.put(3,"three");		
		System.out.println(m1.get(1));
		System.out.println(m1.size());//3	
		System.out.println(m1.isEmpty());//false
		System.out.println(m1.containsKey(2));//true
		System.out.println(m1.containsKey("four"));//false
		
		Map<Integer,String> m2 = new HashMap<>();
		m2.put(4,"四");
		m2.put(5,"五");
		m1.putAll(m2);//putAll方法
		System.out.println(m1);
		
		//map中键不能重复,如果重复(是否重复根据equals方法来判断),则新的覆盖旧的
		m1.put(3,"三");
		System.out.println(m1);
	}
}
Map接口的常用方法

在这里插入图片描述

/**
 * 测试Map的常用方法
 */
import java.util.HashMap;
import java.util.Map;

public class TestMap2 {
	public static void main(String[] args){
		Employee e1 = new Employee(1001,"Jsp1",50000);
		Employee e2 = new Employee(1002,"Jsp2",5000);
		Employee e3 = new Employee(1003,"Jsp3",6000);
		Employee e4 = new Employee(1001,"Jsp4",8000);
		
		Map<Integer,Employee> map = new HashMap<>();
		map.put(1001,e1);
		map.put(1002,e2);
		map.put(1003,e3);
		map.put(1001,e4);

		Employee emp = map.get(1001);
		System.out.println(emp.getEname());
		
		System.out.println(map);//键重复会覆盖旧的
	}
}
//雇员信息
class Employee {
	private int id;
	private String ename;
	private double salary;
	
	public Employee(int id,String ename,double salary){
		super();
		this.id = id;
		this.ename = ename;
		this.salary = salary;
	}
	
	@Override
	public String toString() {
	    return "id:"+id+"name:"+ename+"薪水:"+salary;
	}
	
	
	public int getId(){
		return id;
	}
	public void setId(int id){
		this.id = id;
	}
	public String getEname(){
		return ename;
	}
	public void setEname(String ename){
		this.ename = ename;
	}
	public double getSalary(){
		return salary;
	}
	public void setSalary(double salary){
		this.salary = salary;
	}
}

HashMap

HashMap底层实现采用了哈希表,这是一种非常重要的数据结构。

HashMap底层实现详解

数据结构中由数组和链表来实现对数据的存储:

  • ①、数组:占用空间连续。寻址容易,查询速度快。但是,增加和删除效率非常低。
  • ②、链表:占用空间不连续。寻址困难,查询速度慢。但是,增加和删除效率非常高。

哈希表的基本结构就是:数组+链表(结合了数组和链表的优点,即查询快,增删效率也高)

HashMap基本结构详解

源码:

在这里插入图片描述
位桶数组(单向链表):
Node[] table是HashMap的核心数组结构。称之为位桶数组
在这里插入图片描述
一个Node对象存储了:

  • 1,key:键对象 value:值对象
  • 2,next:下一个节点
  • 3,hash:键对象的hash值

可以看出每一个Node对象就是一个单向链表结构。Node对象示意图:
在这里插入图片描述
数组的结构(HashMap的结构):
在这里插入图片描述

存储数据的过程put(key,value)

如何产生hash值--------存储示意图
在这里插入图片描述

目的是将key-value两个对象成对存放在HashMap的Node[]数组中。

  • 1,获得key对象的hashcode
    • 首先调用key对象的hashcode()方法,获得hashcode
  • 2,根据hashcode计算出hash值(要求在[0,数组长度-1]区间)
    • hashcode是一个整数,需要将它转换成[0,数组长度-1]的范围。要求转换后的hash值尽量均匀的分布在[0,数组长度-1]这个区间,减少hash冲突
    • i、一种极端简单和低下的算法是:hash值 = hashcode/hashcode。意味着键值对对象都会存储到数组索引为1位置,形成一个长的链表。相当于每存储一个对象都会发生hash冲突,HashMap也退化成一个链表
    • ii、一种简单和常用的算法是(相除取余算法):hash值 = hashcode%数组长度(JDK后来改进了算法,约定数组长度必须为2的整数幂,采用位运算实现取余效果:hash值 = hashcode&(数组长度-1))
    • iii、JDK8以后,链表就转换为红黑树。

测试hash算法

public class Test {
	public static void main(String[] args) {
		int h = 25860399;
		int length = 16;//length为2的整数次幂,则h&(length-1)相当于对length取模
		myHash(h,length);
	}
	public static int myHash(int h,int length){
		System.out.println(h&(length-1));
		//length为2的整数幂情况下,和取余的值一样
		System.out.println(h%length);//取余数
		return h&(length-1);
	}
}

结果:

15
15

取数据过程get(key)

通过key对象获得键值对象,进而返回value对象。

  • 1,获得key的hashcode,通过hash()散列算法得到hash值,进而定位到数组的位置。
  • 2,在链表上挨个比较key对象。调用equals()方法,将key对象和链表上所有节点的key对象进行比较,直到碰到返回true的节点对象为止。
  • 3,返回equals()为true的节点对象的value对象。

Java中规定,两个内容相同(equals()为true)的对象必须具有相等的hashcode。

自定义实现HashMap

定义Node用于JspHashMap

public class Node {
    int hash;
    Object key;
    Object value;
    Node next;
}

下面的代码我把Node类直接放进去了~~~

/**
 * 
 * 自定义实现HashMap
 * @author:Jsp
 *
 */
public class JspHashMap <K,V> {
    public class Node <K,V> {
        int hash;
        K key;
        V value;
        Node next;
    }
    
    Node[] table;//位桶数组。bucket array
    int size;
    
    public JspHashMap(){
        table = new Node[16];//长度一般定义成2的整数幂
    }
    
    //get方法
    public V get(K key){
        
        int hash = myHash(key.hashCode(),table.length);
        V value = null;
        
        if (table[hash]!=null){
            Node temp = table[hash];
            
            while(temp!=null){
                if (temp.key.equals(key)){ //如果相等,则说明找到了键值对,返回相应的value
                    value = (V)temp.value;
                    break;
                }else{
                    temp = temp.next;
                }
            }
        }
        return value;
    }
    
    //put方法
    public void put(K key,V value){
        //定义了新的节点对象
        Node newNode = new Node();
        newNode.hash = myHash(key.hashCode(),table.length);
        newNode.key = key;
        newNode.value = value;
        newNode.next = null;
        
        Node temp = table[newNode.hash];
        Node iterLast = null;//正在遍历的最后一个元素
        boolean keyRepeat = false;
        
        if(temp==null){
            //此处数组元素为空,则直接将新节点放进去
            table[newNode.hash] = newNode;
            size++;
        }else{
            //此处数组元素不为空。则遍历对应链表
            while(temp!=null){
                //判断key如果重复,则覆盖
                if(temp.key.equals(key)){
                    System.out.println("key重复了");
                    
                    temp.value = value;//只是覆盖value即可。其他的值不变
                    break;
                    
                }else{
                    //key不重复
                    iterLast = temp;
                    temp = temp.next;
                }
                
            }
            if(!keyRepeat){ //没有发生key重复的情况,则添加到链表最后
                iterLast.next = newNode;
                size++;
            }
           
        }
    }
    
    @Override
    public String toString(){
        StringBuilder sb = new StringBuilder("{");
        //遍历bucket数组
        for (int i=0;i<table.length;i++){
            Node temp = table[i];
            //遍历链表
            while(temp!=null){
                sb.append(temp.key+":"+temp.value+",");
                temp = temp.next;
            }
        }
        sb.setCharAt(sb.length()-1,'}');
        return sb.toString();
    }
    
    public int myHash(int v,int length){
        System.out.println("hash:"+(v&(length-1)));
        return v&(length-1);
    }
    
    public static void main(String[] args){
        JspHashMap<Integer,String> m = new JspHashMap<>();
        m.put(10,"aa");
        m.put(20,"bb");
        m.put(30,"cc");
        
        m.put(53,"123");
        m.put(69,"1234");
        m.put(85,"1230");
        
        //m.put(20,"ssss");
        System.out.println(m);
        
        System.out.println(m.get(53));
    }
      
}

运行结果:

hash:10
hash:4
hash:14
hash:5
hash:5
hash:5
{20:bb,53:123,69:1234,85:1230,10:aa,30:cc}
hash:5
123
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值