在我们生活中,集合的概念就已经不陌生,比如身份证号对应的就一个人、电脑IP地址对应的就是主机名、学号对应就是一个学生等,其实就是一种一一对应的关系,我们可以认为这就是映射.在Java中就提供了专门的集合类用来存放这种对象(映射)关系的对象,即java.util.Map<K,V>接口>。面向对象大多的思想其实很生活化,只是转成计算机的语言、思想,这需要我们细细琢磨、研究!
1.Collection与Map接口的区别
①Collection的集合中的元素是独立的>,如同单身狗,向集合中存储数据的时候是一个个元素进行存储。
②Map的集合中的元素是成对的>,如同情侣,向集合中存储数据的时候是由键和值组成的元素的进行存储,其中通过键可以查找到对应的值;Map中的集合不能包含重复的键,值可以是重复的;每一个键只能对应一个值(可以是单个值、集合、数组值)。
总结:Collection的集合可以成为单列集合,Map的集合可以成为双列集合。
2.Map的常用方法
①添加
(1)V put(K key,V value): 将指定的值与此映射中的指定键关联(可选操作)。
演示代码1:
/*存储一个小组的选手的信息,key是编号,value是姓名*/ public class MapTest01 { @Test public void Test01() { Map<Integer, String> map1 = new HashMap<>(); map1.put(1, "大侠"); map1.put(2, "小侠"); Map<Integer,String> map2 = new HashMap<>(); map2.put(3, "大白"); map2.put(4, "小白"); System.out.println("map1的映射关系对数:"+map1.size());//map1的映射关系对数:2 System.out.println("map2的映射关系对数:"+map2.size());//map2的映射关系对数:2
<span class="token punctuation">}</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
演示代码2:
@Test
/*注意:在原有的映射关系上,根据key值进行put操作value值,实质上就是替换操作*/
public void Test02() {
Map<Integer, String> map1 = new HashMap<>();
map1.put(1, "大侠");
map1.put(2, "小侠");
System.out.println("map1的映射关系对数:"+map1.size());//测试---map1的映射关系对数:2
System.out.println(map1);//{1=大侠, 2=小侠}
//把key为1的value-"大侠"修改为“加油”
map1.put(1, "加油");
System.out.println("map1的映射关系对数:"+map1.size());//测试---map1的映射关系对数:2
System.out.println(map1);//{1=加油, 2=小侠}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
演示代码3:
/*存储一个学生的信息,key是学号,value是一个集合,存储的是个人信息*/ public class MapTest03 { @Test public void Test04() { Map<Integer, ArrayList<String>> map1 = new HashMap<>(); ArrayList<String> list = new ArrayList<>(); list.add("姓名"); list.add("学校"); list.add("学院"); list.add("专业"); map1.put(20190725, list); System.out.println("map1的映射关系对数:"+map1);//map1的映射关系对数:{20190725=[姓名, 学校, 学院, 专业]}
<span class="token punctuation">}</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
(2)void putAll(Map<?extends K,?extends V> m): 从指定映射中将所有映射关系复制到此映射中(可选操作)。
演示代码4:
/*存储一个小组的选手的信息,key是编号,value是姓名*/
public class MapTest04 {
@Test
public void Test2() {
Map<Integer, String> map1 = new HashMap<>();
map1.put(1, "大侠");
map1.put(2, "小侠");
Map<Integer,String> map2 = new HashMap<>();
map2.put(3, "大白");
map2.put(4, "小白");
map1.putAll(map2);
System.out.println("map1的映射关系对数:"+map1.size());//map1的映射关系对数:4
System.out.println("map2的映射关系对数:"+map2.size());//map2的映射关系对数:2
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
②删除
(3)void clear(): 从此映射中移除所有映射关系(可选操作)。
(4)V remove(Object key): 如果存在一个键的映射关系,则将其从此映射中移除(可选操作)。
演示代码5:
/*存储一个选手的信息,key是编号,value是姓名*/ public class MapTest05 { @Test public void Test3() { Map<Integer, String> map1 = new HashMap<>(); map1.put(1, "大侠"); map1.put(2, "小侠"); String value=map1.remove(1); System.out.println("map1根据key删除的value:"+value);//map1根据key删除的value:大侠 System.out.println("map1删除后映射关系对数:"+map1.size());//map1删除后映射关系对数:1
<span class="token punctuation">}</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
③查询
(5)V get(Object key):返回指定键所映射的值;如果此映射不包含该键的映射关系,则返回 null
。
(6)boolean containsKey(Object key): 如果此映射包含指定键的映射关系,则返回 true
。
(7)booleab caontainsValue(Object key): 如果此映射将一个或多个键映射到指定值,则返回 true
。
演示代码6:
/*存储一个选手的信息,key是编号,value是姓名*/ public class MapTest06 { @Test public void Test06() { Map<Integer, String> map1 = new HashMap<>(); map1.put(1, "大侠"); map1.put(2, "小侠"); System.out.println(map1.containsKey(1));//true System.out.println(map1.containsValue("大侠"));//true
<span class="token punctuation">}</span>
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
(8)boolean isEmpty(): 如果此映射未包含键-值映射关系,则返回 true
。
④元视图操作(遍历)>
(9)Set KeySet() :这种方式遍历所有的key,是因为Set中所有的key不能重复,所以Set系列可以根据key获取value;
代码演示7:
/*Set<K> KeySet(),遍历所有的key*/
public class MapTest07 {
@Test
public void Test() {
Map<Integer, String> map1 = new HashMap<>();
map1.put(1, "大侠");
map1.put(2, "小侠");
Set<Integer> keySet = map1.keySet();
System.out.println("遍历映射中的key值:"+keySet);//遍历映射中的key值:[1, 2]
for (Integer key : keySet) {
System.out.println("遍历映射中的key值:"+key);
/*
遍历映射中的key值:1
遍历映射中的key值:2
*/
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
(10)Collection values():这种方式遍历所有的Value,因为Set系列的value可能重复,所以不能用Set系列;
代码演示8:
/*Set<K> KeySet(),遍历所有的Value*/
public class MapTest08 {
@Test
public void Test() {
Map<Integer, String> map1 = new HashMap<>();
map1.put(1, "大侠");
map1.put(2, "小侠");
Collection<String> values = map1.values();
System.out.println("遍历映射中的value值:"+values);//遍历映射中的value值:[大侠, 小侠]
for (String value : values) {
System.out.println("遍历映射中的value值:"+value);
/*
遍历映射中的value值:大侠
遍历映射中的value值:小侠
*/
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
(11)Set<Entry<K,V>> entrySet():这种方式遍历所有的映射关系,因为所有的key不可能重复,那么映射关系的组合也不会重复,因为添加到map中(key,value)最后会封装成一个Entry的结点对象放到map中。
说明:Entry是Map接口的内部接口。
代码演示9:
/*Set<Entry<K,V>> entrySet() 遍历所有的映射关系*/
public class MapTest09 {
@Test
public void Test() {
Map<Integer, String> map1 = new HashMap<>();
map1.put(1, "大侠");
map1.put(2, "小侠");
Set<Entry<Integer, String>> entrySet = map1.entrySet();
System.out.println("遍历所有映射关系:"+entrySet);//遍历所有映射关系:[1=大侠, 2=小侠]
for (Entry<Integer, String> entry : entrySet) {
System.out.println(entry);
/*
1=大侠
2=小侠
*/
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
⑤数量
(12)int size(): 返回此映射中的键-值映射关系数。
3.Map接口的实现类
①HashMap<K,V>(哈希表)
②Hashtable<K,V>(哈希表)
③TreeMap<K,V>
④LinkedHashMap:是HashMap的子类
⑤Properties:是Hashtable的子类
4.HashMap与Hashtable的区别(面试常问)
①相同点:它们都是底层原理都是哈希表。
②不同点:
(1)Hashtable:旧版的–JDK1.0,线程是安全的(同步的),不允许key和value为null值;
(2)HashMap:新版的–JDK1.2,线程不安全的(不同步的),在多线程场景下要手动同步,允许key和value为null值。
(3)HashTable使用Enumeration进行遍历,HashMap使用Iterator进行遍历。
(4)HashMap中hash数组的默认大小是16,而且一定是2的指数;HashTable中hash数组默认大小是11,增加的方式是 old*2+1。
(5)哈希值的使用存在差异,HashTable直接使用对象的hashCode,而HashMap重新计算hash值,而且用“&”代替求模。
5.哈希表、TreeMap与LinkHashMap的区别(面试常问)
①哈希表:遍历顺序与添加的顺序无关,即不能保证添加的顺序。
②TreeMap:按照key的大小顺序进行遍历,即TreeMap要么实现java.lang.Comparable接口,要么实现在创建TreeMap是指定Comparator的的对象(实质就是内部类的应用)。
③LinkHashMap:遍历的顺序是可以保证添加的顺序。
6.Properties(注意)
Properties 类是 Hashtable 的子类,Properties 可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。在存取数据时,建议使用setProperty(String key,String value)方法和getProperty(String key)方法。
Properties比较特殊,它的key和value必须是String的类型,这里需要注意。
public class MapTest {
@Test
public void Test() {
Properties properties = System.getProperties();// 系统类System
Set<Entry<Object, Object>> entrySet = properties.entrySet();// 遍历Map
for (Entry<Object, Object> entry : entrySet) {
System.out.println(entry);//打印本地系统属性
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
7.Set的底层都是Map来实现
☛思考:我们知道存入Set的是一个对象,但是Map是一对,那另一个怎么来呢?
例如1: TreeSet<String> set = new TreeSet<>();
set.add("林大侠");
例如2: Map<String,String> map =new Map<>();
map.put("林大侠","你好");
- 1
- 2
- 3
- 4
有这样的可以解释:添加到Set中的元素(元素不可重复的特点)作为了底层Map的key值,Map的value是一个Object类型的PRESENT常量对象。
查看(JDK1.6)源码1:
private transient HashMap<E,Object> map;
<span class="token comment">// Dummy value to associate with an Object in the backing Map</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> Object PRESENT <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Object</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/** * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has * default initial capacity (16) and load factor (0.75). */</span> <span class="token keyword">public</span> <span class="token function">HashSet</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{<!-- --></span> map <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token operator"><</span><span class="token operator">></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
▲可以看到,我们实例化HashSet的时候,返回的是HashMap的对象map。
查看(JDK1.6)源码2:
/**
* Adds the specified element to this set if it is not already present.
* More formally, adds the specified element <tt>e</tt> to this set if
* this set contains no element <tt>e2</tt> such that
* <tt>(e==null ? e2==null : e.equals(e2))</tt>.
* If this set already contains the element, the call leaves the set
* unchanged and returns <tt>false</tt>.
*
* @param e element to be added to this set
* @return <tt>true</tt> if this set did not already contain the specified
* element
*/
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
▲可以看到,在实现add()方法中,实际上调用的是map的put方法,我们发现添加的元素作为map的key,而value则是一个静态的Object类型的常量,而这个常量我们用不到。
①问题一:为什么Set要使用到底层Map?
因为设计出一种新的数据结构比较难,又因为Set与Map的key有很大的相同点,都是不可重复。
②问题二:为什么要用一个PERSENT常量对象?
主要目的就是为了减少value对象对内存资源的浪费,共享同一个静态的常量对象。
☛推荐阅读往期博文:
•JavaSE集合篇#List之实现类ArrayList&Vector&LinedList&Stack浅析
•JavaSE集合篇#Set之实现类HashSet&TreeSet&LinkedHashSet浅析
•建议收藏|JavaSE集合篇#Collection&Map等系列#结构关系图解