[19/03/26-星期二] 容器_Map(图、键值对、映射)接口之HashMap(散列映射)&TreeMap(树映射)

一、概念&方法

      现实生活中,我们经常需要成对存储某些信息。比如,我们使用的微信,一个手机号只能对应一个微信账户,这就是一种成对存储的关系。

      Map就是用来存储“键(key)-值(value) 对”的。 Map类中存储的“键值对”通过键来标识,所以“键对象”不能重复。

      Map 接口的实现类有HashMap(哈希对)、TreeMap、HashTable、Properties等。

【常用方法】

【代码】

 1 /*
 2 *测试键值对
 3  * 
 4  */
 5 package cn.sxt.collection;
 6 
 7 import java.util.HashMap;
 8 import java.util.Map;
 9 
10 public class Test_0326_Map {
11     public static void main(String[] args) {
12         Map<Integer,String> map=new HashMap<Integer,String>(); //Map是个接口  HashMap(哈希)是具体实现类
13         map.put(101, "赵");//put() 存放键值对
14         map.put(102, "钱");
15         map.put(103, "孙");
16         
17         Map<Integer,String> map2=new HashMap<Integer,String>();
18         map2.put(104, "li");//put() 存放键值对
19         map2.put(105, "zhou");
20         
21         
22         System.out.println(map);
23         System.out.println(map.get(101));//get() 通过键对象查找值对象 返回 "赵"
24         map.remove(102);//remove() 通过键对象删除值对象 
25         System.out.println(map);
26     
27         System.out.println(map.size());// map对象键值对的数量
28         System.out.println(map.isEmpty());//map对象中键值对是否为空
29         System.out.println(map.containsKey(103));//有没有包含103这个键?返回true或false
30         System.out.println(map.containsValue("老白"));//有没有包含"老白"这个键?返回true或false
31         
32         map.put(103, "sun");//键值不能重复 如果重复后边的会覆盖前边的值 如(103 "sun") 会输出后边的
33         System.out.println(map);
34         map.putAll(map2);//把map2对象中的全部加到map对象中去
35         System.out.println(map);
36         map2.clear();//清空所以键值对
37     
38     }
39 
40 }

 

1、HashMap  

      HashMap采用哈希算法实现,是Map接口最常用的实现类。 由于底层采用了哈希表存储数据,要求键不能重复,

如果发生重复,新的键值对会替换旧的键值对。 HashMap在查找、删除、修改方面都有非常高的效率。

  HashTable类和HashMap用法几乎一样,底层实现几乎一样,只不过HashTable的方法添加了synchronized关键字确保线程同步检查,效率较低。

HashMap与HashTable的区别

      1. HashMap: 线程不安全,效率高。允许key或value为null。

      2. HashTable: 线程安全,效率低。不允许key或value为null。

HashMap详解

 HashMap底层实现采用了哈希表,这是一种非常重要的数据结构。数据结构中由数组和链表来实现对数据的存储,他们各有特点。

      (1) 数组:占用空间连续。 寻址容易,查询速度快。但是,增加和删除效率非常低。

      (2) 链表:占用空间不连续。 寻址困难,查询速度慢。但是,增加和删除效率非常高。

  那么,我们能不能结合数组和链表的优点(即查询快,增删效率也高)呢? 答案就是“哈希表”。 哈希表的本质就是“数组+链表”。

过程:哈希地址模16取余(假设数组大小为16),假设当第1个键值地址取余为6,则存在数组a[6]中,假设第2个取余后为4则存在数组a[4]中

假设第3个取余后为也为6,则还存在数组a[6]中,不过是在存储第1个键值的后继区域存第3个的地址,组成链表(地址不连续)。以此类推,

也就是说,余数相同的键值地址组织成一个链表,但总体是在一个数组中(数组是空间连续的)。类似数据结构中图的邻接表的存储方式。

即散列存储。 (具体是用 获得key对象的hashcode的代替哈希地址模16取余的)

算法中直接取余(h%length)和位运算(h&(length-1))结果是一致的

最差的情况:所有键值的余数相同,这样都存在一个链表中了,查询非常慢。体现不了"数组+链表"的优点。

链接:   http://www.sxt.cn/Java_jQuery_in_action/nine-hashmap-bottom.html

 

 

 

如何取数据?

      (1) 获得key的hashcode,通过hash()散列算法得到hash值,进而定位到数组的位置。

      (2) 在链表上挨个比较key对象。 调用equals()方法,将key对象和链表上所有节点的key对象进行比较,直到碰到返回true的节点对象为止。

      (3) 返回equals()为true的节点对象的value对象。

【扩展】明白了存取数据的过程,我们再来看一下hashcode()和equals方法的关系:

          Java中规定,两个内容相同(equals()为true)的对象必须具有相等的hashCode。因为如果equals()为true而两个对象的hashcode不同;

那在整个存储过程中就发生了悖论。

 

【代码】

 1 /*
 2 *哈希表结构节点
 3  * 
 4  */
 5 package cn.sxt.collection;
 6 
 7 public class Node2<K,V> {
 8     int hash;
 9     Object key;
10     Object value;
11     Node2<K,V> next;
12 }

 

 1 /*
 2  *HashMap
 3  * 
 4  */
 5 package cn.sxt.collection;
 6 
 7 public class Test_0326_HashMap<K,V> {
 8     Node2 table[];//位桶数组
 9     int size;
10 
11     public static void main(String[] args) {
12         Test_0326_HashMap<Integer,String> map=new Test_0326_HashMap<Integer,String>();
13         map.put(10, "A");
14         map.put(20, "B");
15         map.put(30, "C");
16         System.out.println(map);
17         System.out.println(map.get(10));
18     }
19 
20     public Test_0326_HashMap () {
21         table=new Node2[16];//构造方法 数组长度一般是2的n次方
22     }
23      //往里面添加元素
24     public void put(K key,V value) {
25 
26         Node2 newNode=new Node2();//hashCode()是Object类自带的方法
27         newNode.hash=myHash(key.hashCode(), table.length); //hash是int变量,来源于Node2类的属性 table.length数组的大小
28         newNode.key=key;
29         newNode.value=value;
30         newNode.next=null;
31 
32         Node2 temp=table[newNode.hash];
33         Node2 lastNode=null;//正在遍历最后一个元素
34 
35         boolean flag=false;
36         if (temp==null) {//如果数组元素为空,则直接将元素放进去
37             table[newNode.hash]=newNode;
38             size++;
39         }else {//如果数组不为空,则遍历数组
40             while (temp!=null) {
41                 //判断key值是否重复,若重复则覆盖,不重复在附加在后边
42                 if (temp.key.equals(key)) {
43                     flag=true;    
44                     temp.value=value;//只覆盖value,其它不变
45                     break;
46 
47                 } else {
48                     lastNode=temp;
49                     temp=temp.next;
50                 }
51             }
52             if (flag==false) {//没有反生key重复的情况(flag为假的情况) 则把指针指向下一个节点
53                 lastNode.next=newNode;    
54                 size++;
55             }
56         }    
57     }
58     //根据键值取元素
59     public V get(K key) {
60         int hash=myHash(key.hashCode(), table.length);
61         V value=null;
62         if (table[hash]!=null) {
63             Node2 temp=table[hash];
64             while (temp!=null) {
65                 if (temp.key.equals(key)) {
66                     value=(V)temp.value;
67                     break;
68                 }else {
69                     temp=temp.next;
70                 }            
71             }    
72         }
73         return value;
74         
75     }
76     
77 
78     public int myHash(int v,int length) {
79         System.out.println("取余后的结果为:"+ (v&(length-1)) );
80         return v&(length-1);//这里的取余相当于v%length  结果是一样的 ,取余后返回
81 
82     }
83     public String toString() {
84         StringBuilder sb=new StringBuilder("{");
85         for (int i = 0; i < table.length; i++) {
86             Node2 temp = table[i];
87             while (temp!=null) {
88                 sb.append(temp.key+":"+temp.value+",");
89                 temp=temp.next;    
90             }
91         }
92         sb.setCharAt(sb.length()-1, '}');
93         return sb.toString();
94     
95     }
96 
97 
98 }

2、TreeMap

    TreeMap是红黑二叉树的典型实现 。

private transient Entry<K,V> root = null;

root用来存储整个树的根节点。我们继续跟踪Entry(是TreeMap的内部类)的代码:

图9-23 Entry底层源码.png

      可以看到里面存储了本身数据、左节点、右节点、父节点、以及节点颜色。

TreeMap的put()/remove()方法大量使用了红黑树的理论。

TreeMap和HashMap实现了同样的接口Map,因此,用法对于调用者来说没有区别。

HashMap效率高于TreeMap;在需要排序的Map时才选用TreeMap。

 1 /*
 2  *测试TreeMap
 3  * 
 4  */
 5 package cn.sxt.collection;
 6 
 7 import java.awt.RenderingHints.Key;
 8 import java.util.Map;
 9 import java.util.TreeMap;
10 
11 public class Test_0326_TreeMap {
12     public static void main(String[] args) {
13         Map<Integer, String> tMap=new TreeMap<Integer, String>();
14         tMap.put(28, "B");
15         tMap.put(17, "A");
16         tMap.put(39, "C");
17         //首先,set是一个集合,keyset()返回的就是一个set集合,比如map里面的键值对是这样的<1,one>,<2,two><3,three><4,four>
18         //<5,five><6,six>那么keyset()函数就是把1,2,3,4,5,6放到一个set集合里面,然后返回给调用处。
19 
20         for (Integer key : tMap.keySet()) {//for-each循环输出 按照key递增的方式排序 输出
21             System.out.println(key+"--"+tMap.get(key));
22         }
23         
24         Map<Emp, String> tMap1=new TreeMap<Emp, String>();
25         tMap1.put(new Emp(102,"小李",26000),"工作努力");
26         tMap1.put(new Emp(101,"老王",6000),"混吃等死");
27         tMap1.put(new Emp(104,"张哥",7000),"工作不积极");
28         tMap1.put(new Emp(103,"老白",7000),"工作还行");
29         
30         for (Emp key : tMap1.keySet()) {//for-each循环输出 输出结果按工资从小到大排序,若工资相同按id从小到大递增排序
31             System.out.println(key+"--"+tMap1.get(key));
32         }
33 
34     }
35 }
36 class Emp implements Comparable<Emp>{//雇员类,自定义按工资排序 Comparable:比较接口
37     int id;
38     String  name;
39     double salary;
40     
41     public Emp(int id, String name, double salary) {
42         super();
43         this.id = id;
44         this.name = name;
45         this.salary = salary;
46     }
47     
48     public int compareTo(Emp o) { //负数:小于 ;0:等于;正数:大于
49         if (this.salary>o.salary) {
50             return 1;    
51         }else if (this.salary<o.salary) {
52             return -1;
53         }else { //工资相同比较id 
54             if (this.id>o.id) {
55                 return 1;
56             } else if(this.id<o.id) {
57                 return -1;
58             }else {
59                 return 0;
60             }
61         }
62     }
63     
64     public String toString() {
65         return "id:"+id+" name:"+name+" salary:"+salary;
66     }
67 
68 
69 }

 

posted @ 2019-03-24 15:02  ID长安忆  阅读(278)  评论(0)    收藏  举报