首先,先谈一下Set和Map的一些特性及体系结构:
1.1 Set和Map
Set代表一种无序的、元素不可重复的集合。Map则代表是一种key-value对组成的集合,Map集合类似于传统的关联数组。表面上看它们之间的关系的相似性很少,实则Map和Set之间有着莫大的关联,可以这样说Map集合实则是Set集合的扩展。
至于为什么是Set是一种无序的、元素不可重复的集合;Map集合类似传统的关系数组。理解这个非常重要,这个我在后面会进行补充说明。
1.2 Set和Map之间的关系
先看看Set集合的继承体系,再来看Map集合的类继承体系:
从上图灰色阴影部分可以看出Map和Set的集合实现类除了后面Set和Map部分不一样,前面的类或接口的名称以及其继承结构完全一样。是偶然吗?答案绝对不是。而且你若是用过Map的话,你会发现Map不允许再现相同的key,且Map在遍历的时候,key是无序的,正是对应Map中keySet()方法。这与上述Set集合的特征又是惊人的相似。下面我会用一段小程序来说明Set为何与Map这么相似了。
先说一下我的思路:
(1)在我的印象中:map的key与value存在的关系如下图:
(2)既然Map中的key与value存在一一对应关系,那么,现在我们换一种思路来思考,把key-value如下图:
由此可见,如上述图3.4表明的一样,如果将key-value绑定在一起,那么Map说白了就是个(key-value)的Set集合
由上述思路,下面是程序实现由Set实现Map:
(1)包含key-value的entry实现代码:
1 package com.ngu4k.test.collection; 2 3 import java.io.Serializable; 4 import java.util.Map.Entry; 5 6 public class SimpleEntry<K,V> implements Entry<K,V>,Serializable{ 7 private static final long serialVersionUID = -4352059623055803486L; 8 private final K key; 9 private V value; 10 //定义两个如下构造器 11 public SimpleEntry(K key,V value) 12 { 13 this.key = key; 14 this.value = value; 15 } 16 17 public SimpleEntry(Entry<? extends K,? extends V> entry) 18 { 19 this.key = entry.getKey(); 20 this.value = entry.getValue(); 21 } 22 //获取key 23 public K getKey() 24 { 25 return key; 26 } 27 //获取value 28 public V getValue() 29 { 30 return value; 31 } 32 //改变该key-value对的value值 33 public V setValue(V value) 34 { 35 V oldValue = this.value; 36 this.value = value; 37 return oldValue; 38 } 39 //根据key比较两个对象是否相等 40 public boolean equals(Object o) 41 { 42 if(o == this){ 43 return true; 44 } 45 if(o.getClass() == SimpleEntry.class){ 46 SimpleEntry se = (SimpleEntry)o; 47 return se.getKey().equals(getKey()); 48 } 49 return false; 50 } 51 //根据key获得hashCode的值 52 public int hashCode(){ 53 return key == null ? 0 : key.hashCode(); 54 } 55 56 public String toString(){ 57 return key + "=" + value; 58 } 59 }
(2)由Set到Map的简单实现代码:
1 package com.ngu4k.test.collection.map; 2 3 import java.util.HashSet; 4 import java.util.Iterator; 5 import java.util.Map; 6 7 import com.ngu4k.test.collection.SimpleEntry; 8 9 /** 10 * Set到Map的简单实现 11 * @author ikouer 12 * @param <K> 13 * @param <V> 14 */ 15 public class Set2Map<K,V> extends HashSet<SimpleEntry<K,V>>{ 16 17 private static final long serialVersionUID = 1596710712757978024L; 18 19 //清空Set2Map中key-value对 20 public void clear() 21 { 22 super.clear(); 23 } 24 //查看是否包含指定的key 25 public boolean containsKey(K key) 26 { 27 return super.contains(new SimpleEntry<K, V>(key,null)); 28 } 29 //查看是否包含指定的value 30 public boolean containsValue(V value) 31 { 32 for(SimpleEntry<K,V> se : this) 33 { 34 if(se.getValue().equals(value)) 35 { 36 return true; 37 } 38 } 39 return false; 40 } 41 //根据指定的key获取对应的value 42 public V get(K key) 43 { 44 for(SimpleEntry<K,V> se : this) 45 { 46 if(se.getKey().equals(key)){ 47 return se.getValue(); 48 } 49 } 50 return null; 51 } 52 //把指定的key-value添加到集合中 53 public V put(K key,V value) 54 { 55 this.add(new SimpleEntry<K,V>(key,value)); 56 return value; 57 } 58 //把一个Map集合添加到Set2Map集合中 59 public void putAll(Map<? extends K,? extends V> om) 60 { 61 for(K key : om.keySet()) 62 { 63 this.add(new SimpleEntry<K,V>(key,om.get(key))); 64 } 65 } 66 //根据指定的key移除对应的key-value 67 public V remove(K key) 68 { 69 for(Iterator<SimpleEntry<K,V>> it = this.iterator(); it.hasNext();){ 70 SimpleEntry<K,V> se = it.next(); 71 if(se.getKey().equals(key)) 72 { 73 V rv = se.getValue(); 74 it.remove(); 75 return rv; 76 } 77 } 78 return null; 79 } 80 //获取集合大小 81 public int size(){ 82 return super.size(); 83 } 84 85 public static void main(String[] args) { 86 Set2Map<String,Integer> smap = new Set2Map<String,Integer>(); 87 //将key-value放入集合 88 smap.put("语文", 88); 89 smap.put("数学", 99); 90 smap.put("英语", 102); 91 System.out.println(smap); 92 System.out.println(smap.size()); 93 //执行移除对称key的entry 94 smap.remove("数学"); 95 System.out.println("删除key为\"数学\"的Entry之后:"+smap); 96 //获取key为语文的value值 97 System.out.println("key为\"语文\"的value值:"+smap.get("语文")); 98 //判断是否包含key 99 System.out.println("是否包含key为\"英语\"的entry:"+smap.containsKey("英语")); 100 //判断是否包含value 101 System.out.println("是否包含value为102:"+smap.containsValue(102)); 102 //执行clear方法 103 smap.clear(); 104 System.out.println("执行完clear()方法之后的集合:"+smap); 105 106 } 107 }
此处的提供的Set2Map集合的功能甚至可以媲美Map集合的功能
而TreeSet底层也是采用TreeMap实现的。至于Tree(二叉树)是怎么实现,读者可参考疯狂java系列书籍或到网上自行搜索。
至此,我想大家应该知道Set中的元素为什么是无序且不可重复的了吧。(因为Set集合的底层全部采用Map集合来实现的)
下面再和大家分析一下java中的Map的实现--HashMap的原理:
1.3 HashMap的实现原理
要搞清hashmap是什么,首先要清楚它的数据结构,在java编程语言中,最基本的结构就是数组和模拟指针了,几乎所有的数据都可以用这两具基本的结构来构造的,hashmap也不例外。java中的hashmap实际上是一个数组和链表的结合体(在数据结构中,一般称之为“链表散列”)
java中的hashmap是以key-value的模式存取的,实际上它也是存放在数组当中的。过程是这样的:首先会得到key的hashcode,然后通过hashcode(key)%size,来得到数组的索引,把entry放入数组中。请看下图(横排表示数组,纵排表示链表):
由上图可知,hashMap其实是就是一个数组。看一下hashMap的源代码:
上面table中的定义为: transient Entry[] table;
由此可见hashmap在初始化时会创建一个Entry类型的数组。再看一下Entry的源代码:
可以看到entry中持有一个指向下一个元素的引用,这样就构成了链表。
当我们往hashmap中put元素中,首先根据key的hash值得到这个元素在数组中的位置(即下标),然后可这个元素放入到对应的位置就行了。如果这个位置已经有了其它元素,那么在同一个位置的元素将以链表的形式存储,新加入的元素放在链头,最先加入的元素放在链尾。从hashmap中get元素的时候,首先根据传入的key值的hash得到元素在数组中的存放位置,然后再通过key的equals方法来得到需要的元素。
java中的Set与Map并不是我们想像中的那么神秘,其实很简单。
本文参考:《疯狂java》、《java中的哈希表》
上述是Map与Set的关系及一些实现细节。若有不到之处,敬请各位指导并谅解,谢谢!
下篇文章我会把List与Map的关系及ArrayList,LinkList的实现细节做一些详细的记录。
欢迎大家阅读与转载。转载请注入本文原址,谢谢!
java集合之间的关系及实现细节——Set与Map