`

Java容器学习笔记(三) Map接口及其重要实现类学习总结

    博客分类:
  • java
阅读更多
在本文中如果您发现了错误,请您花费几分钟的时间给予指出,谢谢!!



本文主要总结Map接口及其重要实现类的用法。



三.Map接口

Ø  Map中的每个成员方法由一个关键字(key)和一个值(value)构成。Map接口不直接继承于Collection接口,因为它包装的是一组成对的“键-值”对象的集合,而且在Map接口的集合中也不能有重复的key出现,因为每个键只能与一个成员元素相对应。

Ø  Map接口的子接口以及主要实现类有:

子接口:Bindings、ConcurrentMap、ConcurrentNavigableMap、MessageContext、LogicMessageContext、NavigableMap、SOAPMessageMap、SortedMap

实现类:AbstractMap, Attributes, AuthProvider, ConcurrentHashMap, EnumMap,ConcurrentSkipListMap,HashMap, Hashtable, IdentityHashMap, LinkedHashMap, PrinterStateReasons,Properties, Provider, RenderingHints, SimpleBindings, TabularDataSupport,TreeMap, UIDefaults,WeakHashMap


Map接口中定义的方法清单:

Ø  Map中定义的方法说明:

在Map接口中定义的通用方法并不是很多。

a)      添加和删除Map中的某个元素

•         put(K, V) : 将给定的“键-值”对放入到给定的Map当中

•         putAll(Map<? extends K, ? extends V) : 将指定的Map中的“键-值”对放入到给定的Map当中

•         remove(Object key) : 从该集合中移除指定的对象,并返回对应的value

•         clear() : 清空Map中的所有对象

b)      查询与Map有关的数据

•         int size() : 返回此Map中“键-值”对的个数

•         boolean isEmpty() : 判断此Map中“键-值”对的个数是否为0

•         boolean containsKey(Object key) : 测试此Map中是否有该key

•         boolean containsValue(Object value) : 测试此Map中是否包含该value

•         V get(Object key) : 通过指定的key查询Map中对应的value

•         Collection<Object value> values() : 取得Map中所有的value

•         Set<Object key> keySet() : 取得当前Map中key的集合

•         Set<Entry<K, V>> entrySet() : 取得当前Map中entry的集合

Ø  HashMap的特点、实现机制及使用方法

a)      HashMap的特点:

HashMap实现了Map、CloneMap、Serializable三个接口,并且继承自AbstractMap类。

b)      HashMap的实现机制:
HashMap基于hash数组实现,若key的hash值相同则使用链表方式进行保存,详见HashSet中的说明。我引用网上一个名词叫“链表散列”来形容这样的数据结构。

新建一个HashMap时会初始化一个大小为16,负载因子为0.75的空的HashMap。
    /** 
      * The table, resized as necessary. Length MUST Always be a power of two. 
      */  
     transient Entry[] table;  

那么Entry到底是怎么实现的呢?
    static class Entry<K,V> implements Map.Entry<K,V> {  
          final K key;  
          V value;  
          Entry<K,V> next;  
          final int hash;  
          ......  

上面代码其实告诉我们Entry是一个结点,它持有下一个元素的引用,这样就构成了一个链表。

那么,整体上来说HashMap底层就是使用这样一个数据结构来实现的。

我们提到使用Hash,但是Hash值如何与元素的存储建立关系呢?(Hash算法)

在数据结构课中我们学习过Hash的简单算法,就是给你一个Hash因子,通过对该元素的hashCode简单的求余,来实现对其快速的定位和索引。

在HashMap中有这样的代码:
    /** 
      * Returns index for hash code h. 
      */  
    static int indexFor(int h, int length) {  
         return h & (length-1);  
    }  

这个方法在HashMap中非常重要,凡是与查询、添加、删除有关的方法中都有调用该方法,为什么这么短的一个代码使用率这么高?根据代码注释我们知道,这个方法是根据hashCode及当前table的长度(数组的长度,不是map的size)得到该元素应该存放的位置,或者在table中的索引。

现在我们需要看一下当数据量已经超过初始定义的负载因子时,HashMap如何处理?

在HashMap中当数据量很多时,并且已经达到了负载限度时,会重新做一次哈希,也就是说会再散列。调用的方法为resize(),并且java默认传入的参数为2*table.length。先看一下JDK源码:
    /** 
         * Rehashes the contents of this map into a new array with a 
         * larger capacity.  This method is called automatically when the 
         * number of keys in this map reaches its threshold. 
         * 
         * If current capacity is MAXIMUM_CAPACITY, this method does not 
         * resize the map, but sets threshold to Integer.MAX_VALUE. 
         * This has the effect of preventing future calls. 
         * 
         * @param newCapacity the new capacity, MUST be a power of two; 
         *        must be greater than current capacity unless current 
         *        capacity is MAXIMUM_CAPACITY (in which case value 
         *        is irrelevant). 
         */  
        void resize(int newCapacity) {  
            Entry[] oldTable = table;  
            int oldCapacity = oldTable.length;  
            if (oldCapacity == MAXIMUM_CAPACITY) {  
                threshold = Integer.MAX_VALUE;  
                return;  
            }  
      
            Entry[] newTable = new Entry[newCapacity];  
            transfer(newTable);  
            table = newTable;  
            threshold = (int)(newCapacity * loadFactor);  
        }  
      
        /** 
         * Transfers all entries from current table to newTable. 
         */  
        void transfer(Entry[] newTable) {  
            Entry[] src = table;  
            int newCapacity = newTable.length;  
            for (int j = 0; j < src.length; j++) {  
                Entry<K,V> e = src[j];  
                if (e != null) {  
                    src[j] = null;  
                    do {  
                        Entry<K,V> next = e.next;  
                        int i = indexFor(e.hash, newCapacity);  
                        e.next = newTable[i];  
                        newTable[i] = e;  
                        e = next;  
                    } while (e != null);  
                }  
            }  
        }  

看到这里我们会发现resize(再哈希)的工作量是不是很大啊。再哈希是重新建一个指定容量的数组,然后将每个元素重新计算它要放的位置,这个工作量确实是很大的。

这里就产生了一个很重要的问题,那就是怎么让哈希表的分布比较均匀,也就是说怎么让它即不会成为一个单链表(极限情况,每个key的hash值都集中到了一起),又不会使hash空间过大(导致内存浪费)?

上面两个问题一个是解决了怎么计算hash值快速存取,一个是怎么实现再哈希,何时需要再哈希。快速存取的前提是元素分布均匀,不至于集中到一点,再哈希是元素过于零散,导致不断的重新构建表。

那么在第一个问题中我们看到了这样一个代码return h & (length-1);在第二个问题中我们说过内部调用传入的值为2*table.length;并且默认情况下HashMap的大小为一个16的数字,除了默认构造提供大小为16的空间外,如果我们使用

public HashMap(int initialCapacity, float loadFactor)

上面的构造方法,我们会发现这样的代码:
    // Find a power of 2 >= initialCapacity  
    int capacity = 1;  
    while (capacity < initialCapacity)  
          capacity <<= 1;  
    ……  
    table = new Entry[capacity];  

也就是说当我们传入1000时,它并没有给我们构造一个容量为1000的哈希表,而是构建了一个容量为1024大小的哈希表。

从整体上我们发现一个问题,那就是无论什么情况HashMap中哈希表的容量总是2的n次方的一个数。并且有这样一个公式:

       当length=2^n时,hashcode & (length-1) == hashcode % length

也就是这一点验证了第一个问题,hash索引值的计算方法其实就是对哈希因子求余。只有大小为2的n次方时,那样的计算才成立,所以HashMap为我们维护了一个这样大小的一个哈希表。(位运算速度比取模运算快的多)

c)      HashMap的使用方法:

我在很多代码中都用到了HashMap,原因是首先它符合存储关联数据的要求,其次它的存取速度快,这是一个选择的问题。

比较重要的是HashMap的遍历方法,在我的博客中有专门写到HashMap的遍历方法:http://blog.csdn.net/tsyj810883979/article/details/6746274

Ø  LinkedHashMap的特点、实现机制及使用方法

a)      LinkedHashMap的特点:

LinkedHashMap继承自HashMap并且实现了Map接口。和HashMap一样,LinkedHashMap允许key和value均为null。

于该数据结构和HashMap一样使用到hash算法,因此它不能保证映射的顺序,尤其是不能保证顺序持久不变(再哈希)。

如果你想在多线程中使用,那么需要使用Collections.synchronizedMap方法进行外部同步。

LinkedHashMap与HashMap的不同之处在于,LinkedHashMap维护者运行于所有条目的双重链接列表,此链接列表可以是插入顺序或者访问顺序。

b)      LinkedHashMap的实现机制:

无法总结下去,在网上看到这样一篇文章:http://zhangshixi.iteye.com/blog/673789

感觉真的没办法总结下去了。

Ø  HashMap与Hashtable的区别:

Hashtable实现Map接口,继承自古老的Dictionary类,实现一个key-value的键值映射表。任何非空的(key-value)均可以放入其中。

区别主要有三点:

1.      Hashtable是基于陈旧的Dictionary实现的,而HashMap是基于Java1.2引进的Map接口实现的;

2.      Hashtable是线程安全的,而HashMap是非线程安全的,我们可以使用外部同步的方法解决这个问题。

3.      HashMap可以允许你在列表中放一个key值为null的元素,并且可以有任意多value为null,而Hashtable不允许键或者值为null。

Ø  WeakHashMap的特点:

我没有使用过这个类。网摘:WeakHashMap是一种改进的HashMap,它对key实行“弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收。(后续使用后进行总结)

Ø  Properties及TreeMap在后续内容里进行总结。
分享到:
评论

相关推荐

    Java开发学习笔记

    Java集合框架是管理对象集合的API,包括List(如ArrayList和LinkedList)、Set(如HashSet和TreeSet)和Map(如HashMap和TreeMap)等接口及其实现类,提供了丰富的操作和功能。 七、多线程 Java内置对多线程的支持...

    java校招学习笔记

    4. **集合框架**:List(如ArrayList和LinkedList)、Set(如HashSet和TreeSet)和Map(如HashMap和TreeMap)接口及其实现类的使用。 5. **IO流**:输入/输出流的理解,包括字节流和字符流,以及缓冲流、对象流和...

    Java基础尚硅谷宋红康学习笔记

    7. **多线程**:Java内置了对多线程的支持,通过Thread类或实现Runnable接口可以创建并运行多个线程,实现并发执行。 8. **接口与内部类**:接口定义了一组方法签名,提供了一种实现多继承的方式。内部类(包括成员...

    达内core_java学习笔记

    集合框架是Java处理对象组的一种方式,包括List、Set和Map等接口,以及ArrayList、HashSet、HashMap等实现类。理解这些集合的特点和使用场景,是提升编程效率的关键。 七、IO流 Java的输入/输出(IO)流系统支持对...

    JAVA学习笔记和例子程序值得看看

    4. **集合框架**:Java集合框架包括List、Set、Map等接口及其实现类,如ArrayList、LinkedList、HashSet、HashMap等,这些都是存储和操作数据的重要工具。 5. **IO流**:Java的输入输出流系统支持对文件、网络、...

    java JDK 8学习笔记

    这使得接口可以扩展,而不会破坏已有的实现类。 5. **日期与时间API(java.time)**:JDK 8对日期和时间API进行了彻底的重构,用`java.time`包取代了`java.util.Date`和`java.util.Calendar`。新API更加直观、易用...

    JAVA学习笔记————————

    5. **集合框架**:JAVA集合框架是存放和操作对象的容器,包括List、Set、Map等接口以及ArrayList、HashSet、HashMap等实现类。学习笔记会详细介绍它们的使用场景和操作方法。 6. **IO流**:JAVA的输入/输出流系统...

    java私塾学习笔记整理

    ### Java私塾学习笔记整理 #### 第一章:Java入门 **一、Java是什么?** Java是一种广泛使用的高级编程语言,由Sun Microsystems于1995年推出。它旨在为跨平台开发提供一种通用的语言环境,使开发者能够在任何...

    java技术从入门到精通(孙鑫)学习笔记

    List、Set、Map接口及其实现类如ArrayList、LinkedList、HashSet、HashMap等,都是开发者需要熟练掌握的。此外,学习泛型、迭代器和枚举类型,可以提升代码的灵活性和可读性。 最后,Java EE(企业级应用)部分,...

    java学习笔记源代码

    4. **集合框架**:Java集合框架是处理对象数组的强大工具,包括List、Set、Map等接口和其实现类。源代码可能包含对这些集合类的使用,例如ArrayList、LinkedList、HashMap等。 5. **IO流与NIO**:源代码可能会涉及...

    Java学习笔记,容器(集合)

    Java 容器(集合)学习笔记 Java 中的容器(集合)是一种组织和管理数据的方式,通过“容器”可以容纳和管理数据。数组是最基本的容器,可以存储多个对象,但它有很多缺点,如长度必须在初始化时指定,数组采用连续...

    java学习笔记JDK6

    Java JDK 6学习笔记是Java开发者入门和进阶的重要参考资料,由知名作者林信良编著。本笔记主要涵盖了JDK 6版本的核心特性和关键概念,为读者提供了全面而深入的学习路径。以下是对其中重要知识点的详细阐述: 1. **...

    JAVA学习笔记(完整版)

    再者,JAVA的集合框架是另一个重要主题,包括List、Set和Map接口,以及ArrayList、LinkedList、HashSet、HashMap等实现类。这些集合类提供了数据存储和操作的各种方式,是处理数据的核心工具。 IO流(input/output ...

    非常详细javaSE学习笔记.rar

    这份“非常详细JavaSE学习笔记.rar”压缩包显然是一份全面的Java SE学习资源,包含了从基础知识到高级特性的全方位讲解。下面,我们将详细探讨这份笔记可能涵盖的关键知识点。 1. **Java起源与环境搭建**:笔记可能...

    JAVA SE学习笔记

    集合框架包括List、Set和Map接口,以及ArrayList、LinkedList、HashSet、HashMap等实现类。它们提供了存储和操作对象的容器,为数据管理提供了极大的便利。 9. **I/O系统** Java的I/O系统包括字节流、字符流、...

    Java JDK 6.0 学习笔记.pdf

    Java集合框架是管理对象的主要工具,包括List、Set、Map等接口及其实现类。掌握ArrayList、LinkedList、HashSet、HashMap等常用容器的使用方法,理解它们之间的区别和应用场景。 **输入输出** Java的I/O流处理涵盖...

    java学习比记-北大青鸟时做的笔记,每堂课都有

    - **接口与实现**:List、Set、Map接口及其具体实现类,如ArrayList实现了List接口,HashMap实现了Map接口。 5. **IO流** - **输入/输出流**:Java的IO流体系支持字符流和字节流,用于读写文件、网络通信等。 - ...

    CoreJava学习笔记

    ### CoreJava学习笔记 #### 一、JAVA特点与运行原理 **JAVA特点:** 1. **简单性**:Java的设计者们将C++语言中许多不易理解和容易混淆的部分去除,使得Java更容易理解与掌握。 2. **面向对象**:Java几乎一切都...

    Java学习笔记(JDK8)课内课后源码

    5. **接口默认方法**:JDK 8允许接口中定义默认方法,这种方法有具体实现,可以被接口的实现类直接继承。这使得接口在不破坏向后兼容性的情况下,可以增加新的功能。 6. **Optional类**:`Optional`是一个容器类,...

Global site tag (gtag.js) - Google Analytics