Map接口简介
Map与Collection并列存在。用于保存具有映射关系的数据:Key-Value
Map 中的 key 和value 都可以是任何引用类型的数据。
Map 中的 key 用Set来存放,不允许重复,即同一个 Map 对象所对应的类,须重写hashCode()和equals()方法。
常用String类作为Map的“键”。
key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到唯一的、确定的 value。
Map接口的常用实现类:HashMap、TreeMap和Properties。
Map常用方法:
添加、删除操作:
Object put(Object key,Object value)
Object remove(Object key)
void putAll(Map t)
void clear()
元视图操作的方法:
Set keySet()
Collection values()
Set entrySet()
元素查询的操作:
Object get(Object key)
boolean containsKey(Object key)
boolean containsValue(Object value)
int size()
boolean isEmpty()
boolean equals(Object obj)
Map.Entry接口:
Map.Entry是Map中内部定义的一个接口,专门用来保存key,value的内容。
Map.Entry接口定义:public static interface Map.Entry<K,V>
Map的遍历:
//这里的非系统类Teacher作为Map的key,必须重写Object的hashCode()和equals()方法
//1.将Map的实例通过entrySet()方法变为Set接口
Set<Map.Entry<Teacher, String>> set = map.entrySet();
//2.通过Set接口实例为Iterator实例化
Iterator<Map.Entry<Teacher, String>> it = set.iterator();
//3.通过Iterator迭代输出,每个内容都是Map.Entry的对象
while(it.hasNext()){
System.out.println(it.next()); //输出全部的key->value
}
Set<Teacher> keys = map.keySet();
Iterator<Teacher> it1 = keys.iterator();
while(it1.hasNext()){
System.out.println(it1.next()); //输出全部的key
}
Collection<String> cols = map.values();
Iterator<String> it2 = cols.iterator();
while(it2.hasNext()){
System.out.println(it2.next()); //输出全部的value
}
Map实现类之一:HashMap
HashMap是 Map 接口使用频率最高的实现类。
HashMap 是基于哈希表的 Map 接口的非同步实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。与HashSet一样,此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
HashMap 判断两个 key 相等的标准是:两个 key 通过 equals() 方法返回 true,hashCode 值也相等。
HashMap 判断两个 value相等的标准是:两个 value 通过 equals() 方法返回 true。
HashMap的构造器
HashMap():构建一个初始容量为16,负载因子为 0.75 的 HashMap。
HashMap(int initialCapacity):构建一个初始容量为 initialCapacity,负载因子为 0.75 的 HashMap。
HashMap(int initialCapacity, float loadFactor):以指定初始容量、指定的负载因子创建一个 HashMap。
initialCapacity: HashMap 的最大容量,即为底层数组的长度。
loadFactor:负载因子 loadFactor 定义为:散列表的实际元素数目(n)/ 散列表的容量(m)。
HashMap的数据结构
在Java 编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的, HashMap 也不例外。 HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。
从上图中可以看出,HashMap 底层就是一个数组结构,数组中的每一项又是一个链表。当新建一个 HashMap 的时候,就会初始化一个数组。
源码如下:
transient Entry[] table;
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
……
}
可以看出,Entry 就是数组中的元素,每个Map.Entry 其实就是一个 key-value 对,它持有一个指向下一个元素的引用,这就构成了链表。
HashMap的存取实现
存储实现:
当我们往 HashMap 中 put 元素的时候,先根据 key 的hashCode 重新计算 hash 值,根据 hash 值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。
public V put(K key, V value) {
// HashMap 允许存放 null 键和 null 值。
// 当 key 为 null 时,调用 putForNullKey 方法,将 value 放置在数组第一个位置。
if (key == null)
return putForNullKey(value);
// 根据 key 的 keyCode 重新计算 hash 值。
int hash = hash(key.hashCode());
// 搜索指定 hash 值在对应 table 中的索引。
int i = indexFor(hash, table.length);
// 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
// 如果 i 索引处的 Entry 为 null,表明此处还没有 Entry。
modCount++;
// 将 key、 value 添加到 i 索引处。
addEntry(hash, key, value, i);
return null;
}
读取实现:
从 HashMap 中 get 元素时,首先计算 key 的 hashCode,找到数组中对应位置的某一元素,然后通过 key 的 equals 方法在对应位置的链表中找到需要的元素。
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry<K,V> e = table[indexFor(hash, table.length)];e != null;e = e.next){
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
归纳起来简单地说,HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。 HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当需要存储一个 Entry 对象时,会根据 hash 算法来决定其在数组中的存储位置,在根据 equals方法决定其在该数组位置上的链表中的存储位置;当需要取出一个 Entry 时,也会根据 hash算法找到其在数组中的存储位置,再根据 equals 方法从该位置上的链表中取出该 Entry。
Map实现类之二:LinkedHashMap
LinkedHashMap 是 HashMap 的子类。
与LinkedHashSet类似,LinkedHashMap 可以维护 Map 的迭代顺序:迭代顺序与 Key-Value 对的插入顺序一致。
Map实现类之三:TreeMap
TreeMap存储 Key-Value 对时,需要根据 key-value 对进行排序。TreeMap 可以保证所有的 Key-Value 对处于有序状态。
TreeMap 的 Key 的排序:
自然排序:TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException。
定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对 TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现 Comparable 接口。
TreeMap判断两个key相等的标准:两个key通过compareTo()方法或者compare()方法返回0。
若使用自定义类作为TreeMap的key,所属类需要重写equals()和hashCode()方法,且equals()方法返回true时,compareTo()方法应返回0。
Map实现类之四:Hashtable
Hashtable是个古老的 Map 实现类,线程安全。
与HashMap不同,Hashtable 不允许使用 null 作为 key 和 value
与HashMap一样,Hashtable 也不能保证其中 Key-Value 对的顺序
Hashtable判断两个key相等、两个value相等的标准,与hashMap一致。
HashMap和Hashtable的区别:
比较点 |
HashMap |
Hashtable |
推出时间 |
JDK1.2之后推出的,属于新的操作类 |
JDK1.0时推出的,属于旧的操作类 |
性能 |
采用异步处理方式,性能更高 |
采用同步处理方式,性能较低 |
线程安全 |
属于非线程安全的操作类 |
属于线程安全的操作类 |
设置空值 |
允许设置null值 |
不能设置null值,否则出现NullPointerException |
Map实现类之四:Properties
Properties 类是 Hashtable 的子类,该对象用于处理属性文件。
由于属性文件里的 key、value 都是字符串类型,所以 Properties 里的 key 和 value 都是字符串类型。
存取数据时,建议使用setProperty(String key,String value)方法和getProperty(String key)方法。
Properties pros = new Properties();
pros.load(new FileInputStream("jdbc.properties"));
String user = pros.getProperty("user");
System.out.println(user);
@Test
public void testPro() throws IOException{
Properties p = new Properties();
File f = new File("1.properties");
OutputStream os = new FileOutputStream(f);
p.setProperty("username", "刘大成");
p.put("password", "王五赵六"); //父类HashTable的方法
p.store(os, "login");
InputStream is = new FileInputStream(f);
p.load(is);
String username = p.getProperty("username");
String password = (String) p.get("password");
System.out.println(username);
System.out.println(password);
}
@Test
public void testXML() throws IOException{
Properties p = new Properties();
File f = new File("1.xml");
OutputStream os = new FileOutputStream(f);
p.setProperty("username", "刘星雨");
p.put("password", "张三李四"); //父类HashTable的方法
p.storeToXML(os, "login");
InputStream is = new FileInputStream(f);
p.loadFromXML(is);
String username = p.getProperty("username");
String password = (String) p.get("password");
System.out.println(username);
System.out.println(password);
}
Map的其他实现类
WeakHashMap:
弱引用的Map集合,当集合中的某些内容不再使用时,可以清除掉无用的数据,可以使用gc进行回收。如果希望集合可以自动清理暂时不用的数据就可以使用WeakHashMap类。
IdentityHashMap:key可以重复的Map集合。
使用IdentityHashMap子类就可以加入重复的key,使用此类的时候只要地址不相等(key1!=key2)就表示不是重复的key,就可以添加到集合之中。
IdentityHashMap<Teacher, String> map = new IdentityHashMap<Teacher, String>();
Teacher t1 = new Teacher("AA",25); //Teacher中重写了hashCode()和equals()方法
Teacher t2 = new Teacher("AA",25); //所以对象t1和t2可以一起作为key添加到map中
map.put(t1, "12");
map.put(t2, "676");
Java 中的 HashMap 的工作原理是什么?
1)Java 中的 HashMap 是以键值对(key-value)的形式存储元素的。
2)HashMap 需要一个 hash 函数,它使用 hashCode()和 equals()方法来向集合添加和从集合中检索元素。
3)当调用put()方法的时候,HashMap 会计算 key 的hash值,然后把键值对存储在集合中合适的索引上。
4)如果 key已经存在了,value 会被更新成新值。
HashMap 的一些重要的特性是它的容量(capacity),负载因子(load factor)和扩容极限(threshold resizing)。
hashCode()和 equals()方法的重要性体现在什么地方?
Java 中的 HashMap 使用 hashCode()和 equals()方法来确定键值对的索引,当根据键获取值的时候也会用到这两个方法。如果没有正确的实现这两个方法,两个不同的键可能会有相同的hash 值,因此,可能会被集合认为是相等的。而且,这两个方法也用来发现重复元素。所以这两个方法的实现对 HashMap 的精确性和正确性是至关重要的。
HashMap 和 Hashtable 有什么区别?
HashMap 和 Hashtable 都实现了 Map 接口,因此很多特性非常相似。但是,他们有以下不同点:
1)HashMap 允许键和值是 null,而 Hashtable不允许键或者值是 null。
2)HashMap采用异步处理方式,性能更高;而 Hashtable采用同步处理方式,性能较低。
3)HashMap属于非线程安全的操作类,更适合于单线程环境;而Hashtable属于线程安全的操作类,适合于多线程环境。
4)HashMap 提供了可供应用迭代的键的集合,因此,HashMap 是快速失败的。另一方面,Hashtable 提供了对键的列举(Enumeration)。一般认为 Hashtable 是一个遗留的类。