Map
Map用于保存具有映射关系的数据,因此Map集合里保存这两组值,一组值用于保存Map里的key,另一组值用于保存Map里的value,key和value都是可以是任何引用类型的数据。
注意:
Map 的key不允许重复,因为同一个Map对象的任何两个对象key通过equals方法比较总是返回false。
key和value之间存在单向一对一关系,即通过指定key,总能找到唯一、确定的value。
如果把Map里所有key放在一起来看,它们就组成了一个Set集合(所有的key没有顺序,key与key之间不能重复),实际上Map中确实包含了一个keySet()方法,用于返回Map里所有key 组成的Set集合。而Map中所有的value看起来非常类似一个List,元素与元素之间可以重复,每个元素可以根据索引来查找,只是Map中的索引不再使用整数值,而是以另外一个对象作为索引。
例如:如果需要从List集合中取出元素,则需要提供该元素的数字索引;如果需要从Map中取出元素,则需要提供该元素的key索引。
Map提供的方法:
- void clear()
删除该Map对象中的所有key-value对。 - boolean containsKey(Object key)
查询Map中是否包含指定key,包含则返回true。 - boolean containsValue(Object value)
查询Map中是否包含指定value,包含返回true。 - Set entrySet()
返回Map中包含的key-value对所组成的Set集合,每个集合元素都是Map.Entry(Entry是Map的内部类)对象。 - Object get(Object key)
返回指定key所对应的value;如果Map中不包含该key,则返回null。 - boolean isEmpty()
查询该Map是否为空(即不包含认可key-value对),如果为空则返回true。 - Sey keySet()
返回该Map中所有key组成的Set集合。 - Object put(Object key ,Object value)
添加一个key-value对,如果当前Map中已有一个与该key相等的key-value对,则新的key-value对会覆盖原来的key-value对。 - void putAll(Map m)
将指定的Map中的key-value对复制到本Map中。 - Object remove(Object key)
删除指定key、value所阴影的key-value对。如果从该Map中成功的删除该key-value对,该方法返回true,否则返回false。 - int size()
返回Map里的key-value对的个数。 - Collection values()
返回Map里所有value组成的Collection。 - Object getKey()
返回Entry里包含的key值。 - Object getValue()
返回Entry里包含的value值。 - Object setValue(V value)
设置该Entry里包含的value值,并返回新设置的value值。
示例:
package com.map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class MapTest {
public static void main(String[] args) {
Map map = new HashMap();
map.put("Java疯狂讲义", 99);
map.put("C++疯狂讲义", 89);
map.put("C#疯狂讲义", 99);
map.put("Java之大话西游", 109);
System.out.println(map);
//如果新的value覆盖旧的value,则返回旧的value 109
System.out.println(map.put("Java之大话西游", 19));
System.out.println(map.containsKey("C#疯狂讲义"));//true
System.out.println(map.containsValue(99));//true
for (Object key : map.keySet()) {
System.out.println(key);
}
map.remove("C++疯狂讲义");
//{Java之大话西游=19, Java疯狂讲义=99, C#疯狂讲义=99}
System.out.println(map);
Set<Map.Entry<String, Integer>> set = map.entrySet();
Iterator iterator = set.iterator();
while(iterator.hasNext()){
Map.Entry<String, Integer> maps = (Entry<String, Integer>) iterator.next();
System.out.println(maps.getKey() + ":"+maps.getValue());
/**
* 输出:
* Java之大话西游:19
Java疯狂讲义:99
C#疯狂讲义:99
*/
}
}
}
Java8为Map新增的方法
- Object compute(Object key,BiFunction remappingFunction)
该方法使用remappingFunction根据原key-value对计算一个新的value。只要新value不为null,就使用新value覆盖原value;如果原value不为null,但新value为null,则删除原key-value对:如果原value、新value同时为null,那么该方法不改变任何key-value对,直接返回null。 - Object computeIfAbsent(Object key ,Function mappingFunction)
如果传给该方法的key参数在Map中对应的value为null,则使用mappingFunction根据key计算一个新的结果,如果计算结果不为null,则用计算结果覆盖原有value。如果原Map原来不包含该key,那么该方法可能会添加一组key-value对。 - Object computeIfPresent(Object key ,BiFunction remappingFunction)
如果传给该方法的key参数在Map中对应的value不为null,该方法将使用remappingFunction根据原key、value计算一个新的结果,如果计算结果不为null,则使用该结果覆盖原来的value,如果计算的结果为null,则删除原key-value对。 - void forEach(BiConsumer action)
该方法是Java 8为Map新增的一个遍历key-value对的方法。 - Object getOrDefault(Object key ,V defaultValue)
获取指定key对应的value。如果该key不存在则返回defaultValue。 - Object merge(Object key , Object value ,BiFunction remappingFunction)
该方法会先根据Key参数获取该Map中对应的value。如果获得value为null,则直接用传入的value覆盖原有的value(在这中情况下,可能要添加一组key-value对);如果获取的value不为null,则使用remappingFunction 函数根据原value、新value计算一新的结果,并用得到的结果去覆盖原有的value。 - Object putIfAbsent(Object key ,Object value)
该方法会自动检测指定key对应的value是否为null,如果该key对应的value为null,该方法将会用新value代替原来的null值。 - Object replace(Object key ,Object value)
将Map中指定key对应的value替换成新的value。与传统的put方法不同的是,该方法不可能添加新的key-value对。如果尝试替换的key在原Map中不存在,该方法不会添加key-value对,而是返回null。 - boolean replace(K key ,V oldValue, V newValue)
将Map中指定key-value对的原value提换成新value。如果在Map中找到指定的key-value对,则执行替换并返回true,否额返回false。 - replaceAll(Bifunction function)
该方法使用Bifunction 对原key-value对执行计算,并将计算结果作为key-value对的value的值。
HashMap和Hashtable
HashMap和Hashtable都是Map接口典型的实现类,它们之间的关系类似于ArrayList和Vector的关系。Hashtable是一个古老的Map实现类,它从JDK 1.0起就已经出现了。
HashMap与Hashtable区别:
- Hashtable是一个线程安全大的Map实现,但HashMap是线程不安全的实现,所以HashMap比Hashtable的性能高一点;但如果有多个线程访问同一个Map对象时,使用Hashtable实现类会更好。
- Hashtable不允许使用null作为key和value,如果试图把null值放进Hashtable中,将会引发NullPointerException异常;但HashMap可以使用null作为key或value。由于HashMap里key不能重复,所以HashMap里最多只有一个key-value对的key为null,但可以有无数多个key-value对的value为null。
注意:
Hashtable类是一个古老的类,它的命名甚至没有遵守Java的命名规范:每个单词的首字母都应该大写。大量的程序中使用了Hashtable类,所以这个类名也就不能改为HashTable了,否则将导致大量程序需要改写。与vector类似的是,尽量少用Hashtable实现类,即使需要创建线程安全的Map实现类,也无需使用Hashtable实现类,可以使用Collections工具类把HashMap变成线程安全的。
为了成功的在HashMap、Hashtable中存储、获取对象,用作key的对象必须实现hashCode()方法和equals()方法。
与HashSet集合不能保证元素的顺序一样,HashMap、Hashtable也不能保证其中key-value对的顺序。
类似于HashSet,HashMap和Hashtable判断两个key相等的标准也是:
两个key通过equals()方法比较返回true,两个key的hashCode值也相等。
除此之外,HashMap、Hashtable中还包含一个containsValue()方法,用于判断是否包含指定的value。
HashMap、Hashtable判断两个value相等的标准是:
只要两个对象通过equals()方法比较返回true即可。
示例:
package com.map;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
public class HashtableTest {
public static void main(String[] args) {
Hashtable htb = new Hashtable();
htb.put(new A(100), "Java讲义");
htb.put(new A(90), "Html5精讲");
htb.put(new A(1234), new B());
System.out.println(htb);
//只要亮的对象通过equals()方法,返回true
//Hashtable就认为它们相等
//由于Hashtable中有一个对象B
//它与任何对象通过equals()方法都想等,所以下面返回true
System.out.println( htb.containsValue("测试字符串"));
//只要两个对象的count相等,它们通过equals()方法比较返回true,且hashCode()值相等
//Hasntable 即认为它们是相同的key,返回true
System.out.println( htb.containsKey(new A(100)));
System.out.println( htb.remove(new A(1234)));
System.out.println(htb);
}
}
class A {
int count;
public A(int count) {
this.count = count;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj != null && obj.getClass() == A.class) {
A a = (A) obj;
return a.count == count;
}
return false;
}
@Override
public int hashCode() {
return count;
}
}
class B {
@Override
public boolean equals(Object obj) {
return true;
}
}
与HashSet类似的是,如果使用可变对象作为HashMap、Hashtable的key,并且程序修改了作为key的可变对象,则也可能出现与HashSet类似的情形:程序再也无法准确访问Map中被修改过的key。
示例:
package com.map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class HashMapErrTest {
public static void main(String[] args) {
HashMap map = new HashMap();
book book1 = new book(123);
map.put(book1, "java大话西游");
book book2 = new book(300);
map.put(book2, "java精要");
System.out.println(map);
//{com.map.book@7b=java大话西游, com.map.book@12c=java精要}
Iterator iterator = map.keySet().iterator();
book first = (book) iterator.next();
first.count = 666;
map.remove(book1);
System.out.println(map.get(book1));
//null
System.out.println(map);
//{com.map.book@29a=java大话西游, com.map.book@12c=java精要}
}
}
class book {
int count;
public book(int count){
this.count = count;
}
@Override
public boolean equals(Object obj) {
if(obj == this)
return true;
if(obj != null && obj.getClass() == A.class){
A a = (A) obj;
return a.count == count;
}
return super.equals(obj);
}
@Override
public int hashCode() {
return count;
}
}
注意:
如果确实需要使用可变对象的作为HashMap、Hashtable的key,则尽量不要在程序中修改作为key的可变的对象。
Properties
Properties类是Hashtable类的子类,该对象在处理属性文件时特别方便(Windows操作平台上ini文件就是一种属性文件)。
Properties类可以把Map对象和属性文件关联起来,从而可以把Map对象中的key-value对写入属性文件中,也可以把属性文件中的“属性名=属性值”加载到Map对象中。
由于属性文件里的属性名、属性值只能是字符串类型,所以Properties里的key、value都是字符串类型。
- String getProperty(String key)
获取Properties中指定属性名对应的属性值,类似与Map的get(Object key)。 - String getProperty(String key,String defalultValue)
该方法与前一个方法基本相似。该方法多一个功能,如果Properties中不存在指定key时,则该方法指定默认值。 - Object setProperty(String key,String value)
设置属性值,类似与Hashtable的put()方法。 - void load(InputStream is)
从属性文件(以输入流表示)中加载key-value对,把加载到的key-value对追加到Properties里(Properties是Hashtable的子类,它不保证key-value对之间的次序)。 - void store(OutputStream out,String comments)
将Properties中的key-value对输出到指定的属性文件(以输出流表示)中。
示例:
package com.map;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;
public class HashtablePropertiesTest {
public static void main(String[] args) {
Properties proper = new Properties();
proper.setProperty("username", "wu");
proper.setProperty("password", "123");
try {
//将Properties中的key-value对输出到指定的属性文件(以输出流表示)中
proper.store(new FileOutputStream("a.ini"), "connmen line");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(proper);
//{password=123, username=wu}
Properties proper2 = new Properties();
proper2.setProperty("gender", "male");
try {
//从属性文件(以输入流表示)中加载key-value对,把加载到的key-value对追加到Properties里
//(Properties是Hashtable的子类,它不保证key-value对之间的次序)。
proper2.load(new FileInputStream("a.ini"));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(proper2);
//{password=123, gender=male, username=wu}
}
}
各Map实现类的性能分析
Hashtable由于是一个古老的、线程安全的,因此HashMap通常比Hashtable要快。
TreeMap通常比HashMap、Hashtable要慢(尤其是在插入、删除key-value对时更慢),因为TreeMap底层采用红黑树来管理Key-value对(红黑树每个节点就是一个key-value对)。
优点:
TreeMap有一个好处,TreeMap中的key-value对总是处于有序状态,无序专门进行排序操作。
当TreeMap被填充之后,就可以调用keySet(),取得由key组成的Set,然后使用toArray()方法生成key的数组,接下来使用Arrays的binarySearch()方法在已排序的数组中快速查询对象。
一般场景应该使用HashMap,因为HashMap正式为快速查询设计的(HashMap底层其实也是采用数组来存储key-value对)。但如果程序需要一个总是排序,则应该考虑使用TreeMap。
LinkedHashMap比HashMap慢一点,因为它需要维护链表来保持Map中key-value时的日添加顺序。IdentityHashMap性能没有特别出色之处,因为它采用与HashMap基本类似的实现,只是它使用==而不是equals()方法来判断元素相等。
EnumMap的性能最好,但它只能使用同一个枚举类的枚举值作为key。