Java的Map接口

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++;

// keyvalue 添加到 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 是一个遗留的类。

 

猜你喜欢

转载自blog.csdn.net/lxxiang1/article/details/81277592