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