`
jiuyuehe
  • 浏览: 184228 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

边读边写【2】 ----java 集合包之深入Map

阅读更多
Collection 中还有一个Set 但是常用的Set 都是基于HashMap 实现的,因此先看完HashMap 更好理解一点。Listhttp://jiuyuehe.iteye.com/blog/1480165


Map 接口
map 都是基于 k-v 对的实现。
常用的有 HashMap 跟 TreeMap 。


HashMap  api 中有这么一段话来描述它:
基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

此实现假定哈希函数将元素适当地分布在各桶之间,可为基本操作(get 和 put)提供稳定的性能。迭代 collection 视图所需的时间与 HashMap 实例的“容量”(桶的数量)及其大小(键-值映射关系数)成比例。所以,如果迭代性能很重要,则不要将初始容量设置得太高(或将加载因子设置得太低)。


static final int DEFAULT_INITIAL_CAPACITY = 16;  // init capacity
 static final int MAXIMUM_CAPACITY = 1 << 30;    // max_size;
 static final float DEFAULT_LOAD_FACTOR = 0.75f; // 默认加载因子
 transient Entry[] table;                        //装k-v 对
 transient int size;            
  int threshold;                                 //阀值
  final float loadFactor;                        //加载因子

                          //初始化
   public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        // Find a power of 2 >= initialCapacity
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;

        this.loadFactor = loadFactor;
        threshold = (int)(capacity * loadFactor); //阀值 用来rehash 的标志
        table = new Entry[capacity];
        init();
    }
     
    public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        table = new Entry[DEFAULT_INITIAL_CAPACITY];
        init();
    }




put 操作。
put 一个值的时候,key 是允许为空的。如果有,更新value 如果没有创建一个,如果大小超过阀值了,rehash 一下 重新设定阀值。key  不是null 的时候,更具key 本身对象的hashCode,再做hash操作
 static int hash(int h) {
        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

在与数组长度-1 进行&运算。得到index。这里就可能产生hash冲突
解决办法是找到此处的value ,如果有,则更新,如果没有创造一个。



  public V put(K key, V value) {
        if (key == null)  //允许为空
            return putForNullKey(value);
          // 给key hash 找到对应的位置i
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
         //解决hash 冲突
            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;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

  /**
     * Offloaded version of put for null keys
     * key 为null 时 put
      */
    private V putForNullKey(V value) {
          //默认从第一个元素开始找,找到将value 更新,返回oldValue
          //找不到 新增一个 Entry 
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
     //默认桶的index 是0;
        addEntry(0, null, value, 0);
        return null;
    }
// 新增一个Entry
    void addEntry(int hash, K key, V value, int bucketIndex) {
	Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
         //大小大于阀值的时候,rehash.
        if (size++ >= threshold)
            resize(2 * table.length);
    }



get 操作
他的流程跟put 操作基本一直。基于entry.next 进行遍历。

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

 private V getForNullKey() {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
    }


remove 操作

一句话就是将当前对象的前一个对象的next 指向后一个对象
final Entry<K,V> removeEntryForKey(Object key) {
        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;

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



HashMap 总结
HashMap 采用 k-v 对构成对象存储,无容量限制,基于key hash 寻找entry对象存放的数组位置,对于hash 冲突采用链表的方式来解决,插入元素时要扩容,并且重新计算hash.
HashMap 是非线性安全的。如果在并发场景中不保持同步的话很可能会死循环,cpu100%.最好的办法是用ConcurrentHashMap。
jdk 中这么说明:
注意,此实现不是同步的。如果多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须 保持外部同步。(结构上的修改是指添加或删除一个或多个映射关系的任何操作;仅改变与实例已经包含的键关联的值不是结构上的修改。)这一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedMap 方法来“包装”该映射。最好在创建时完成这一操作,以防止对映射进行意外的非同步访问,如下所示:

   Map m = Collections.synchronizedMap(new HashMap(...));由所有此类的“collection 视图方法”所返回的迭代器都是快速失败 的:在迭代器创建之后,如果从结构上对映射进行修改,除非通过迭代器本身的 remove 方法,其他任何时间任何方式的修改,迭代器都将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就会完全失败,而不冒在将来不确定的时间发生任意不确定行为的风险。

-----------------------------------分割线-----------------------------------------
TreeMap


是一个支持排序的Map实现,实现方式和HashMap 不相同。
  private final Comparator<? super K> comparator;

  private transient Entry<K,V> root = null;

   public TreeMap() {
        comparator = null;
    }


put 操作  都写在注释里面了
  public V put(K key, V value) {
        Entry<K,V> t = root;
        if (t == null) {//首先判断root s是否为null 是创建一个新的Entry 给root
	    // TBD:
	    // 5045147: (coll) Adding null to an empty TreeSet should
	    // throw NullPointerException
	    //
	    // compare(key, key); // type check
            root = new Entry<K,V>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        if (cpr != null) { //如果root!=null 判断cpr 是否null 即:是否传入了指定的comparator.
            do { //是 则基于红黑树遍历 比较key 是放于树的左边还是右边。
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);  //找到key 直接替换。
            } while (t != null);
        }
        else {
            if (key == null)  //cpr =null 判断 key =null? 
                throw new NullPointerException();
            Comparable<? super K> k = (Comparable<? super K>) key;
            do {  //同上
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
           //全部未找到 ,new Entry 将上面找到的 元素设为 parent 
        Entry<K,V> e = new Entry<K,V>(key, value, parent);
        if (cmp < 0)  //比较 
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);  //红黑树 填值
        size++;
        modCount++;
        return null;
    }


get 操作
用的就是getEntry()
这是一个典型的红黑树查找。从跟对象开始往下找
  final Entry<K,V> getEntry(Object key) {
        // Offload comparator-based version for sake of performance
        if (comparator != null)
            return getEntryUsingComparator(key);
        if (key == null)
            throw new NullPointerException();
	Comparable<? super K> k = (Comparable<? super K>) key;
        Entry<K,V> p = root;  //根对象
        while (p != null) {  //一直往下找,找到为止。找不到回null
            int cmp = k.compareTo(p.key); 
            if (cmp < 0)
                p = p.left;
            else if (cmp > 0)
                p = p.right;
            else
                return p;
        }
        return null;
    }


remove 操作
  remove 是一个操作复杂的过程,他首先getEntry 就是上面的查找过程。然后调整红黑树的结构。[你说麻烦不。因此删除节点的代价是比较高的]
 public V remove(Object key) {
        Entry<K,V> p = getEntry(key);
        if (p == null)
            return null;

        V oldValue = p.value;
        deleteEntry(p);  //很麻烦的一个过程
        return oldValue;
    }



TreeMap 总结: 基于红黑树k-v 实现,无限制容量,非线程安全。

HashMap 与 TreeMap 的选择。
这2个东西其实是没有可比性的。
转一篇博客中的一段话: 针对Map 的选用说的十分的透彻
Map 选择 

也许您曾期望更复杂的考量,而这实际上是否显得太容易? 好的,让我们慢慢来。 首先,您应使用哪种 Map? 答案很简单: 不要为您的设计选择任何特定的 Map,除非实际的设计需要指定一个特殊类型的 Map。 设计时通常不需要选择具体的 Map 实现。 您可能知道自己需要一个 Map,但不知道使用哪种。 而这恰恰就是使用 Map 接口的意义所在。 直到需要时再选择 Map 实现 — 如果随处使用“Map”声明的变量,则更改应用程序中任何特殊 Map 的 Map 实现只需要更改一行,这是一种开销很少的调整选择。 是否要使用默认的 Map 实现? 我很快将谈到这个问题。 

同步 Map 

同步与否有何差别? (对于同步,您既可以使用同步的 Map,也可以使用 Collections.synchronizedMap() 将未同步的 Map 转换为同步的 Map。 后者使用“同步的包装器”)这是一个异常复杂的选择,完全取决于您如何根据多线程并发访问和更新使用 Map,同时还需要进行维护方面的考虑。 例如,如果您开始时未并发更新特定 Map,但它后来更改为并发更新,情况将如何? 在这种情况下,很容易在开始时使用一个未同步的 Map,并在后来向应用程序中添加并发更新线程时忘记将此未同步的 Map 更改为同步的 Map。 这将使您的应用程序容易崩溃(一种要确定和跟踪的最糟糕的错误)。 但如果默认为同步,则将因随之而来的可怕性能而序列化执行多线程应用程序。 看起来,我们需要某种决策树来帮助我们正确选择。 

Doug Lea 是纽约州立大学奥斯威戈分校计算机科学系的教授。 他创建了一组公共领域的程序包(统称 util.concurrent),该程序包包含许多可以简化高性能并行编程的实用程序类。 这些类中包含两个 Map,即 ConcurrentReaderHashMap 和 ConcurrentHashMap。 这些 Map 实现是线程安全的,并且不需要对并发访问或更新进行同步,同时还适用于大多数需要 Map 的情况。 它们还远比同步的 Map(如 Hashtable)或使用同步的包装器更具伸缩性,并且与 HashMap 相比,它们对性能的破坏很小。 util.concurrent 程序包构成了 JSR166 的基础;JSR166 已经开发了一个包含在 Java 1.5 版中的并发实用程序,而 Java 1.5 版将把这些 Map 包含在一个新的 java.util.concurrent 程序包中。 

所有这一切意味着您不需要一个决策树来决定是使用同步的 Map 还是使用非同步的 Map, 而只需使用 ConcurrentHashMap。 当然,在某些情况下,使用 ConcurrentHashMap 并不合适。 但这些情况很少见,并且应具体情况具体处理。 这就是监测的用途。 








分享到:
评论

相关推荐

    易语言仿java集合 list map源码

    本主题聚焦于易语言中的面向对象编程,特别是模仿Java集合框架的List和Map接口的实现。这些数据结构在编程中扮演着核心角色,用于组织和管理数据。 首先,让我们深入了解易语言的面向对象编程概念。面向对象编程...

    深入Java集合学习系列

    Java集合框架是Java编程语言中的核心组件之一,它为存储、管理和操作对象提供了一套高效且灵活的工具。本系列深入讲解了Java集合框架中的重要组成部分,包括HashMap、ArrayList、LinkedHashMap、HashSet以及...

    Java-Interview-超全集合github上评分最高的jiva面试题

    "Java-Interview-超全集合github上评分最高的jiva面试题"就是一个这样的宝藏,它涵盖了Java编程语言、Git版本控制工具以及面试策略等多个方面的知识点。以下是这些内容的详细解析: 1. **Java基础** - **数据类型...

    【IT十八掌徐培成】Java基础第11天-04.Map集合-集合整理.zip

    本教程“Java基础第11天-04.Map集合-集合整理”由IT十八掌徐培成讲解,旨在深入解析Map集合的使用和特性。 首先,Map接口中的核心方法包括: 1. `put(key, value)`: 向Map中添加一个键值对。如果键已存在,则会...

    java-遍历map

    `Map`接口是Java集合框架的一部分,它提供了存储和检索唯一键对象及其对应的值对象的方法。一个`Map`中不能包含重复的键:每个键最多只能映射到一个值。`Map`的主要实现类有`HashMap`、`TreeMap`、`LinkedHashMap`、...

    对java中Map集合的讲解

    Map是Java集合框架中的一个重要组成部分,它提供了一种存储键值对(key-value pair)数据结构的方式。与List和Set不同,Map并没有直接继承自`Collection`接口,而是独立于`Collection`体系之外。Map的主要特点是它通过...

    Crazy-JAVA-mind-map.zip_Crazy JAVA mind map_crazy_java-mindmap_m

    《疯狂JAVA讲义》是Java学习者们非常熟悉的一本经典教材,它深入浅出地讲解了Java语言的基础知识和高级特性。这份"Crazy-JAVA-mind-map.zip"压缩包包含了一个名为"Crazy JAVA mind map.mmap"的思维导图文件,这个导...

    Java基础----集合类汇总

    本文将深入探讨Java集合类的汇总,包括List、Set和Map这三大核心接口及其实现类。 首先,让我们从List接口开始。List是一种有序的集合,允许有重复元素,并且支持通过索引来访问元素。ArrayList和LinkedList是List...

    01大数据面试复习----Java基础---集合类、多线程、JVM.zip

    Java集合框架是处理对象组的重要工具,它包括了数组、列表、队列、集合、映射等数据结构。主要分为两大接口:`Collection`和`Map`。`Collection`接口下有`List`(有序可重复)和`Set`(无序不可重复)两个子接口,而...

    Java集合框架总结

    本文档将深入探讨Java集合框架的关键组成部分、它们之间的关系以及如何有效地使用它们。 #### 二、Java集合框架结构 Java集合框架的核心部分包括以下几类: - **集合接口**:主要包括`Collection`、`Set`、`List`...

    ptf4--------JAVA经典教程

    【标题】"ptf4--------JAVA经典教程"揭示了这是一份关于Java编程语言的教程,可能是一个系列的第四部分,通常这样的命名方式表明它是一个逐步深入的学习资源。"PTF"可能是“Programming Tutorial for”或者是某个...

    IBM-ETP-java培训03.Java 基础 2.ppt

    还有,Java集合框架是处理数据集合的统一接口,包括List、Set和Map等接口及其实现类。这些数据结构提供了不同的存储和操作方式,如ArrayList和LinkedList的区别,HashSet和HashMap的使用场景等。理解并熟练运用这些...

    java源码整理包-集合

    这些源码对于深入理解Java集合的工作原理、优化代码性能以及学习设计模式有着极大的帮助。 1. **List接口**:`List`是Java中的有序集合接口,它允许元素有重复,并且提供了按索引访问元素的功能。`ArrayList`和`...

    2JAVA编程高级-集合类.pdf

    Java集合框架由一系列接口和实现这些接口的类组成,位于`java.util`包内。 **集合类的特点**: - 集合类用来存放对象,而不是基本数据类型。 - 集合类能够根据需要动态扩展其大小。 - 集合类支持多种不同的存储...

    【IT十八掌徐培成】Java基础第11天-03.Map集合-hash原理2.zip

    首先,Map接口是Java集合框架的一部分,提供了存储和管理键值对的能力。常见的Map实现包括HashMap、TreeMap、LinkedHashMap等。在这个视频中,重点是HashMap,它是基于哈希表的数据结构,通过哈希函数来实现快速查找...

    java基础-中级-高级-深入·

    - **集合概述**:Java集合框架是一组用于存储和操作对象的接口和类。 - **List与Set**:List是有序集合,允许重复元素;Set不允许重复元素。 - **Map**:键值对的集合,每个键都是唯一的。 ### Java高级 #### 1. ...

    Java调用存储过程--传入集合参数

    在完成Java集合到Oracle数组的转换后,接下来是实际调用存储过程的过程。这通常通过`CallableStatement`接口完成,其中使用`setARRAY`方法将转换后的数组设置为参数。 ```java public static int updateADInfo...

    JAVA 编程 API基础 JAVA开发平台,JAVA编程资源----JAVA API.zip

    2. **集合框架**:Java API中的集合框架是编程中最常用的部分,它包括List、Set、Map等接口以及实现这些接口的类。例如,ArrayList和LinkedList实现了List接口,提供了动态数组和链表两种存储方式;HashSet和TreeSet...

Global site tag (gtag.js) - Google Analytics