前面,已经系统的对List进行了学习。接下来,先学习Map,然后再学习Set;因为Set的实现类都是基于Map来实现的(如:HashSet是通过HashMap实现的,TreeSet是通过TreeMap实现的)。

一、Map架构

         

如上图:
(1)、 Map 是映射接口,Map中存储的内容是键值对(key-value)
(2)、 AbstractMap 是继承于Map的抽象类,它实现了Map中的大部分API。其它Map的实现类可以通过继承AbstractMap来减少重复编码。
(3) 、SortedMap 是继承于Map的接口。SortedMap中的内容是排序的键值对,排序的方法是通过比较器(Comparator)。
(4) 、NavigableMap 是继承于SortedMap的接口。相比于SortedMap,NavigableMap有一系列的导航方法;如"获取大于/等于某对象的键值对"、“获取小于/等于某对象的键值对”等等。 
(5) 、TreeMap 继承于AbstractMap,且实现了NavigableMap接口;因此,TreeMap中的内容是“有序的键值对”!
(6) 、HashMap 继承于AbstractMap,但没实现NavigableMap接口;因此,HashMap的内容是“键值对,但不保证次序”!
(7)、 Hashtable 虽然不是继承于AbstractMap,但它继承于Dictionary(Dictionary也是键值对的接口),而且也实现Map接口;因此,Hashtable的内容也是“键值对,也不保证次序”。但和HashMap相比,Hashtable是线程安全的,而且它支持通过Enumeration去遍历。
(8) 、WeakHashMap 继承于AbstractMap。它和HashMap的键类型不同,WeakHashMap的键是“弱键”

二、Map

1、定义:

java.util public Interface Map<K,V>

2、Map接口特点:

  • Map提供了一种映射关系,其中的元素是以键值对(key-value)的形式存储的,能够实现根据key快速查找value。
  • Map中的键值对以Entry类型的对象实例形式存在。
  • 键(key值)不可重复,value值可以重复,一个value值可以和很多key值形成对应关系,每个建最多只能映射到一个值。
  • Map支持泛型,形式如:Map<K,V>。
  • Map 映射顺序:有些实现类,可以明确保证其顺序,如 TreeMap;另一些映射实现则不保证顺序,如 HashMap 类。

3、常用方法

abstract void                 clear()
abstract boolean              containsKey(Object key)
abstract boolean              containsValue(Object value)
abstract Set<Entry<K, V>>     entrySet()
abstract boolean              equals(Object object)
abstract V                    get(Object key)
abstract int                  hashCode()
abstract boolean              isEmpty()
abstract Set<K>               keySet()
abstract V                    put(K key, V value)
abstract void                 putAll(Map<? extends K, ? extends V> map)
abstract V                    remove(Object key)
abstract int                  size()
abstract Collection<V>        values()

4、Map.Entry:Map是java中的接口,Map.Entry是Map的一个内部接口。

(1)interface Entry<K,V> { }   

(2)Map.Entry的常用方法

abstract boolean       equals(Object object)
abstract K             getKey()
abstract V             getValue()
abstract int           hashCode()
abstract V             setValue(V object)

三、HashMap类

1、简介 :

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
  • HashMap 继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口。
  • HashMap是Map的一个重要实现类,也是最常用的,基于哈希表实现。
  • HashMap中的Entry对象是无序排列的。
  • Key值和value值都可以为null,但是一个HashMap只能有一个key值为null的映射(key值不可重复)。
  • HashMap是线程不安全的。

2、构造函数

// 默认构造函数。
HashMap()

// 指定“容量大小”的构造函数
HashMap(int capacity)

// 指定“容量大小”和“加载因子”的构造函数
HashMap(int capacity, float loadFactor)

// 包含“子Map”的构造函数
HashMap(Map<? extends K, ? extends V> map)

       容量是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。通常,默认加载因子是 0.75, 在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。

3、HashMap的常用方法

void                 clear()//清空HashMap。它是通过将所有的元素设为null来实现的。
Object               clone()//克隆一个HashMap对象并返回
boolean              containsKey(Object key) //判断HashMap是否包含key。
boolean              containsValue(Object value)//判断HashMap是否包含“值为value”的元素。
Set<Entry<K, V>>     entrySet() //返回“HashMap中所有Entry的集合”.
V                    get(Object key) //获取key对应的value
boolean              isEmpty()
Set<K>               keySet()
V                    put(K key, V value) //对外提供接口,让HashMap对象可以通过put()将“key-value”添加到HashMap中。
void                 putAll(Map<? extends K, ? extends V> map)
V                    remove(Object key)//删除“键为key”元素
int                  size()
Collection<V>        values()//返回此地图中包含的值的Collection视图。

4、HashMap 和 Map的关系

                             

java map实现类 java map接口实现类_加载

从图中可以看出: 
(1) HashMap继承于AbstractMap类,实现了Map接口。Map是"key-value键值对"接口,AbstractMap实现了"键值对"的通用函数接口。 
(2) HashMap是通过"拉链法"实现的哈希表。它包括几个重要的成员变量:table, size, threshold, loadFactor, modCount。
  table是一个Entry[]数组类型,而Entry实际上就是一个单向链表。哈希表的"key-value键值对"都是存储在Entry数组中的。 
  size是HashMap的大小,它是HashMap保存的键值对的数量。 
  threshold是HashMap的阈值,用于判断是否需要调整HashMap的容量。threshold的值="容量*加载因子",当HashMap中存储数据的数量达到threshold时,就需要将HashMap的容量加倍。
  loadFactor就是加载因子。 
  modCount是用来实现fail-fast机制的。

5、HashMap的原理

(1)  HashMap就是一个散列表,它是通过“拉链法”解决哈希冲突的。影响HashMap性能的有两个参数:初始容量(initialCapacity) 和加载因子(loadFactor)。

(2)  哈希表(HashTable)又叫做散列表,是根据关键码值(即键值对)而直接访问的数据结构,是一种数据结构能够快速地查找所需的对象。也就是说,它通过把关键码映射到表中一个位置来访问记录,以加快查找速度。这个映射函数就叫做散列(哈希)函数,存放记录的数组叫做散列表。散列码(hash code)是由对象的实例产生的一个整数。具有不同的数据域的对象将产生不同的散列码。

(3)  数组与链表。数组的特点就是查找容易,插入删除困难;而链表的特点就是查找困难,但是插入删除容易。既然两者各有优缺点,那么我们就将两者的有点结合起来,让它查找容易,插入删除也会快起来。哈希表就是讲两者结合起来的产物。

(4) 两个不同的关键字,由于散列函数值相同,因而被映射到同一表位置上。该现象称为冲突(Collision)或碰撞。

HashMap中采用的“拉链法”就是一种冲突解决的方式(hash函数的设计才是冲突避免,但不是一种完全的冲突解决方法),如下图所示为“拉链法”结构。

java map实现类 java map接口实现类_java map实现类_02

                              

java map实现类 java map接口实现类_System_03

 但是HashMap中的节点是Map.Entry类型的,而不是简单的value,如右上边是一个Node<K,V>[] table数组(在jdk6中是Entry<K,V>数组),Node是Map.Entry的实现类。

即key值不同的两个或多个Map.Entry<K,V>可能会插在同一个桶下面,但是当查找到某个特定的hash值的时候,下面挂了很多个<K,V>映射,怎么确定哪个是我要找的那个<K,V>呢?这就是HashMap底层结构的一个亮点,在它的Entry中不仅仅只是插入value的,他是插入整个Entry 的,里面包含key和value的,所以能识别同一个hash值下的不同Map.Entry。

// 判断两个Entry是否相等
     // 若两个Entry的“key”和“value”都相等,则返回true。
     // 否则,返回false
     public final boolean equals(Object o) {
         if (!(o instanceof Map.Entry))
             return false;
         Map.Entry e = (Map.Entry)o;
         Object k1 = getKey();
         Object k2 = e.getKey();
         if (k1 == k2 || (k1 != null && k1.equals(k2))) {
             Object v1 = getValue();
             Object v2 = e.getValue();
            if (v1 == v2 || (v1 != null && v1.equals(v2)))
                 return true;
         }
         return false;
     }

6、看看put() 和  remove()

(1)put() 的作用是对外提供接口,让HashMap对象可以通过put()将“key-value”添加到HashMap中

若要添加到HashMap中的键值对对应的key已经存在HashMap中,则找到该键值对;然后新的value取代旧的value,并退出!
若要添加到HashMap中的键值对对应的key不在HashMap中,则将其添加到该哈希值对应的链表中,并调用addEntry()

public V put(K key, V value) {
    // 若“key为null”,则将该键值对添加到table[0]中。
    if (key == null)
        return putForNullKey(value);
    // 若“key不为null”,则计算该key的哈希值,然后将其添加到该哈希值对应的链表中。
    int hash = hash(key.hashCode());
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        // 若“该key”对应的键值对已经存在,则用新的value取代旧的value。然后退出!
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }

    // 若“该key”对应的键值对不存在,则将“key-value”添加到table中
    modCount++;
    addEntry(hash, key, value, i);
    return null;
}

下面看看addEntry():addEntry() 的作用是新增Entry。将“key-value”插入指定位置,bucketIndex是位置索引。

void addEntry(int hash, K key, V value, int bucketIndex) {
    // 保存“bucketIndex”位置的值到“e”中
    Entry<K,V> e = table[bucketIndex];
    // 设置“bucketIndex”位置的元素为“新Entry”,
    // 设置“e”为“新Entry的下一个节点”
    table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
    // 若HashMap的实际大小 不小于 “阈值”,则调整HashMap的大小
    if (size++ >= threshold)
        resize(2 * table.length);
}

说到addEntry(),就不得不说另一个函数createEntry()。它们的作用都是将key、value添加到HashMap中。

(1) addEntry()一般用在 新增Entry可能导致“HashMap的实际容量”超过“阈值”的情况下。
       例如,我们新建一个HashMap,然后不断通过put()向HashMap中添加元素;put()是通过addEntry()新增Entry的。
       在这种情况下,我们不知道何时“HashMap的实际容量”会超过“阈值”;
       因此,需要调用addEntry()。
(2) createEntry() 一般用在 新增Entry不会导致“HashMap的实际容量”超过“阈值”的情况下。
        例如,我们调用HashMap“带有Map”的构造函数,它绘将Map的全部元素添加到HashMap中;
       但在添加之前,我们已经计算好“HashMap的容量和阈值”。也就是,可以确定“即使将Map中的全部元素添加到HashMap中,都不会超过HashMap的阈值”。 此时,调用createEntry()即可。

(2) remove(): remove() 的作用是删除“键为key”元素

public V remove(Object key) {
    Entry<K,V> e = removeEntryForKey(key);
    return (e == null ? null : e.value);
}


// 删除“键为key”的元素
final Entry<K,V> removeEntryForKey(Object key) {
    // 获取哈希值。若key为null,则哈希值为0;否则调用hash()进行计算
    int hash = (key == null) ? 0 : hash(key.hashCode());
    int i = indexFor(hash, table.length);
    Entry<K,V> prev = table[i];
    Entry<K,V> e = prev;

    // 删除链表中“键为key”的元素
    // 本质是“删除单向链表中的节点”
    while (e != null) {
        Entry<K,V> next = e.next;
        Object k;
        if (e.hash == hash &&
            ((k = e.key) == key || (key != null && key.equals(k)))) {
            modCount++;
            size--;
            if (prev == e)
                table[i] = next;
            else
                prev.next = next;
            e.recordRemoval(this);
            return e;
        }
        prev = e;
        e = next;
    }

    return e;
}

7、遍历HashMap

(1)先拿到key的集合,通过key拿到值:

for (String key: map.keySet()) {
    System.out.print(key+"="+map.get(key)+" ");
   }

(2)先拿到值的集合,通过迭代器迭代

Collection<Integer> c = map.values();
     Iterator<Integer> t = c.iterator();
     while(t.hasNext()) {
     System.out.println(t.next());
  }

8、操作示例

package MapTest;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;

public class HashMapTest {
	public static void main(String[] args) {
		HashMap<String, Integer> map= new HashMap<>();
		map.put("A", 1);
		map.put("B", 2);
		map.put("C", 3);
		map.put("D", 4);
		//直接输出内容:{A=1, B=2, C=3, D=4}
		System.out.println(map);
		//通过Key找Value:key为A-->value =1
		System.out.println("key为A-->value ="+map.get("A"));
		//找不到返回null:key为A-->value =null
		System.out.println("key为E-->value ="+map.get("E"));
		//删除:{B=2, C=3, D=4}
		map.remove("A");
		System.out.println(map);
		//不存在,返回null
		Object o = map.remove("E");
		System.out.println(o);
		//遍历:B=2 C=3 D=4 先拿到key的集合,通过key拿到值
		for (String key: map.keySet()) {
			System.out.print(key+"="+map.get(key)+" ");
		}
		System.out.println("------------");
		//先拿到值的集合,通过迭代器迭代
		Collection<Integer> c = map.values();
		Iterator<Integer> t = c.iterator();
		while(t.hasNext()) {
			System.out.println(t.next());
		}
		
		
	}
}

四、HashTable

1、简介:Hashtable类大致相当于HashMap ,除了它是同步的,不允许null。

  • Hashtable 继承于Dictionary,实现了Map、Cloneable、java.io.Serializable接口。
  • Hashtable 的函数都是同步的,这意味着它是线程安全的。它的key、value都不可以为null。此外,Hashtable中的映射不是有序。
  • Hashtable 一个实例有两个影响其性能的参数: 初始容量和负载因子 。通常,默认加载因子是 0.75。

2、需注意的点:

  • Hashtable的默认容量为11,默认负载因子为0.75.(HashMap默认容量为16,默认负载因子也是0.75)
  • Hashtable的容量可以为任意整数,最小值为1,而HashMap的容量始终为2的n次方。
  • 为避免扩容带来的性能问题,建议指定合理容量。
  • 跟HashMap一样,Hashtable内部也有一个静态类叫Entry,其实是个键值对对象,保存了键和值的引用。
  • HashMap和Hashtable存储的是键值对对象,而不是单独的键或值。
  • Hashtable每次扩容,容量都为原来的2倍加1,而HashMap为原来的2倍。

3、Hashtable存取删数据:put()方法、get()方法、remove()方法

(1)put方法:加了synchronized关键字

public synchronized V put(K key, V value) {//向哈希表中添加键值对  
        // Make sure the value is not null  
        if (value == null) {//确保值不能为空  
            throw new NullPointerException();  
        }  
        // Makes sure the key is not already in the hashtable.  
        Entry tab[] = table;  
        int hash = hash(key);//根据键生成hash值---->若key为null,此方法会抛异常  
        int index = (hash & 0x7FFFFFFF) % tab.length;//通过hash值找到其存储位置  
        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {/遍历链表  
            if ((e.hash == hash) && e.key.equals(key)) {//若键相同,则新值覆盖旧值  
                V old = e.value;  
                e.value = value;  
                return old;  
            }  
        }  
        modCount++;  
        if (count >= threshold) {//当前容量超过阈值。需要扩容  
            // Rehash the table if the threshold is exceeded  
            rehash();//重新构建桶数组,并对数组中所有键值对重哈希,耗时!  
            tab = table;  
            hash = hash(key);  
            index = (hash & 0x7FFFFFFF) % tab.length;//这里是取摸运算  
        }  
        // Creates the new entry.  
        Entry<K,V> e = tab[index];  
        //将新结点插到链表首部  
        tab[index] = new Entry<>(hash, key, value, e);//生成一个新结点  
        count++;  
        return null;  
    }

1、Hasbtable并不允许值和键为空(null),若为空,会抛空指针。
2、HashMap计算索引的方式是h&(length-1),而Hashtable用的是模运算,效率上是低于HashMap的。
3、另外Hashtable计算索引时将hash值先与上0x7FFFFFFF,这是为了保证hash值始终为正数。
4、特别需要注意的是这个方法包括下面要讲的若干方法都加了synchronized关键字,也就意味着这个Hashtable是个线程安全的类,这也是它和HashMap最大的不同点。

(2)get()取数据:获取key对应的value,没有的话返回null

public synchronized V get(Object key) {//根据键取出对应索引  
    Entry tab[] = table;  
    int hash = hash(key);//先根据key计算hash值  
    int index = (hash & 0x7FFFFFFF) % tab.length;//再根据hash值找到索引  
    for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {//遍历entry链  
          if ((e.hash == hash) && e.key.equals(key)) {//若找到该键  
              return e.value;//返回对应的值  
          }  
      }  
    return null;//否则返回null  
}

(3)、remove():删除Hashtable中键为key的元素

public synchronized V remove(Object key) {
    Entry tab[] = table;
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    // 找到“key对应的Entry(链表)”
    // 然后在链表中找出要删除的节点,并删除该节点。
    for (Entry<K,V> e = tab[index], prev = null ; e != null ; prev = e, e = e.next) {
        if ((e.hash == hash) && e.key.equals(key)) {
            modCount++;
            if (prev != null) {
                prev.next = e.next;
            } else {
                tab[index] = e.next;
            }
        count--;
        V oldValue = e.value;
        e.value = null;
        return oldValue;
        }
    }
    return null;
}

五、TreeMap

1、简介

public class TreeMap<K,V> extends AbstractMap<K,V>
             implements NavigableMap<K,V>, Cloneable, Serializable

TreeMap 是一个有序的key-value集合,它是通过红黑树实现的。

  • TreeMap 继承于AbstractMap,所以它是一个Map,即一个key-value集合。
  • TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法。比如返回有序的key集合。
  • TreeMap 实现了Cloneable接口,意味着它能被克隆
  • TreeMap 实现了java.io.Serializable接口,意味着它支持序列化
  • TreeMap是非同步的。 它的iterator 方法返回的迭代器是fail-fastl的。
  • TreeMap的基本操作 containsKey、get、put 和 remove 的时间复杂度是 log(n) 。

     TreeMap基于红黑树(Red-Black tree)实现。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法。

2、构造函数

// 默认构造函数。使用该构造函数,TreeMap中的元素按照自然排序进行排列。
TreeMap()

// 创建的TreeMap包含Map
TreeMap(Map<? extends K, ? extends V> copyFrom)

// 指定Tree的比较器
TreeMap(Comparator<? super K> comparator)

// 创建的TreeSet包含copyFrom
TreeMap(SortedMap<K, ? extends V> copyFrom)

3、TreeMap的常用方法,详细看API

Entry<K, V>                ceilingEntry(K key)//返回与大于或等于给定键的最小键相关联的键值映射,如果没有此键,则 null 。 
K                          ceilingKey(K key) 返回大于/等于key的最小的键值对所对应的KEY,没有的话返回null
void                       clear()
Object                     clone()
Comparator<? super K>      comparator()//返回用于订购此地图中的键的比较器,或null如果此地图使用其键的natural ordering 。
boolean                    containsKey(Object key)
NavigableSet<K>            descendingKeySet()//返回此地图中包含的键的相反顺序NavigableSet 。
NavigableMap<K, V>         descendingMap()//返回此映射中包含的映射的反向排序视图。 
Set<Entry<K, V>>           entrySet() //返回此地图中包含的映射的Set视图。 
Entry<K, V>                firstEntry()//返回与该地图中的最小键相关联的键值映射
K                          firstKey() //返回此地图中当前的第一个(最低)键
V                          get(Object key)
boolean                    isEmpty()
Set<K>                     keySet() //返回此地图中包含的键的Set视图。 
Entry<K, V>                lastEntry()
K                          lastKey()
Entry<K, V>                lowerEntry(K key)
K                          lowerKey(K key)
V                          put(K key, V value)
V                          remove(Object key)
int                        size()

4、TreeMap本质(首先需要去了解下红黑树)

1)、TreeMap的本质是R-B Tree(红黑树),它包含几个重要的成员变量: root, size, comparator。
2)、root 是红黑数的根节点。它是Entry类型,Entry是红黑数的节点,它包含了红黑数的6个基本组成成分:key(键)、value(值)、left(左孩子)、right(右孩子)、parent(父节点)、color(颜色)。Entry节点根据key进行排序,Entry节点包含的内容为value。 
3)、红黑数排序时,根据Entry中的key进行排序;Entry中的key比较大小是根据比较器comparator来进行判断的。
4)、size是红黑数中节点的个数。

5)TreeMap是通过红黑树实现的,TreeMap存储的是key-value键值对,TreeMap的排序是基于对key的排序。
6)TreeMap提供了操作“key”、“key-value”、“value”等方法,也提供了对TreeMap这颗树进行整体操作的方法,如获取子树、反向树。

5、TreeMap的Entry相关函数

1、firstEntry()和getFirstEntry()的代码:可以看出 firstEntry() 和 getFirstEntry() 都是用于获取第一个节点。

public Map.Entry<K,V> firstEntry() {
    return exportEntry(getFirstEntry());
}

final Entry<K,V> getFirstEntry() {
    Entry<K,V> p = root;
    if (p != null)
        while (p.left != null)
            p = p.left;
    return p;
}

       firstEntry() 是对外接口; getFirstEntry() 是内部接口。而且,firstEntry() 是通过 getFirstEntry() 来实现的。这么做的目的是:防止用户修改返回的Entry。getFirstEntry()返回的Entry是可以被修改的,但是经过firstEntry()返回的Entry不能被修改,只可以读取Entry的key值和value值。 getFirstEntry()返回的是Entry节点,而Entry是红黑树的节点,我们可以调用Entry的getKey()、getValue()来获取key和value值,以及调用setValue()来修改value的值。

总结:

(1)、 firstEntry()是对外接口,而getFirstEntry()是内部接口。
(2)、 对firstEntry()返回的Entry对象只能进行getKey()、getValue()等读取操作;而对getFirstEntry()返回的对象除了可以进行读取操作之后,还可以通过setValue()修改值。

6、相关操作和遍历

package MapTest;

import java.util.TreeMap;

public class TreeMapTest {
	public static void main(String[] args) {
		TreeMap<String,Integer> map = new TreeMap<>();
		//添加
		map.put("A", 30);
		map.put("B", 28);
		map.put("C", 45);
		map.put("D", 33);
		map.put("E", 15);
		map.put("F", 27);
		//打印集合
		System.out.println(map);
		System.out.println("------------------------------");
		//删除集合
		map.remove("F");
		//遍历集合
		for (String key : map.keySet()) {
			System.out.println(key+"->"+map.get(key));
		}
		System.out.println("------------------------------");
		//获得key的反序
		System.out.println(map.descendingKeySet());
		System.out.println("------------------------------");
		//获得集合的反序
		System.out.println(map.descendingMap());
		System.out.println("------------------------------");
		//获得第一个集合
		System.out.println(map.firstKey());
	}
}

Map集合遍历方式都差不多,TreeMap其他遍历方式参考HashMap