`
jiuyuehe
  • 浏览: 181270 次
  • 性别: 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集合学习系列(四):LinkedHashMap的实现原理

    深入Java集合学习系列(四): LinkedHashMap的实现原理

    java8源码-CollectionAndMap:集合深入解析

    集合深入解析 Java集合 1.ArrayList源码分析 默认容量为10。 private static final int DEFAULT_CAPACITY = 10; 创建了一个大小为0的数组,在后面用到。 private static final Object[] EMPTY_ELEMENTDATA = {}; ...

    java高级编程必须知道的集合详细讲解

    这份资源旨在为您提供关于 Java 集合框架的详细讲解,涵盖了集合的类型、特性、应用场景以及使用方法。通过深入学习,您将建立坚实的集合框架知识,能够更好地选择和应用适合的集合来满足不同的编程需求。 集合框架...

    Java面试题合集最新版2024.zip

    集合框架:熟悉Java集合框架中的List、Set、Map等接口及其实现类,如ArrayList、HashSet、HashMap等。 泛型:理解泛型的概念及其在Java中的应用,如泛型类和泛型方法。 并发编程:了解Java中的线程、同步、锁等机制...

    Java面试题深入解析:在互联网公司面试程序员需要留意的六个问题.docx

    # Java面试题深入解析:在...Java集合是Java程序员必须熟练掌握的,包括List、Set、Map等等。在面试中,面试官可能会问及Java集合中的ArrayList和LinkedList的区别是什么?HashMap和TreeMap的区别是什么等等。 ## 4.

    Java 集合框架高难度进阶版面试题集锦解析

    提供了20道高难度的Java集合框架面试题及详细答案解析,涵盖了List、Set、Map、Iterator、Collections类等关键概念和操作方法。从数据结构、线程安全性、性能等多个角度深入探讨了集合框架的不同实现和应用场景。...

    Java中的list、set和map详解

    java集合的主要分为三种类型:  Set(集)  List(列表)  Map(映射)  要深入理解集合首先要了解下我们熟悉的数组:  数组是大小固定的,并且同一个数组只能存放类型一样的数据(基本类型/引用类型),...

    JavaSE基础篇 -- 集合框架详述_超集大合集

    本压缩包详述描述了Java的集合框架,用详尽的源码实例,深入浅出的介绍了包含List、Set、Map、泛型在内的所有内容。登录我的博客http://blog.csdn.net/zhongkelee还有惊喜!利用集合框架实现了斗地主洗牌发牌实例。

    Java超神之路.rar

    1.CoreJava,就是java基础、JDK的类库,很多童鞋都会说,JDK我懂,但是懂还不足够,知其然还要知其所以然,JDK的源代码写的非常好,要经常查看,对使用频繁的类,比如String,集合类(List,Map,Set)等数据结构要...

    JAVA的教程.txt

    JAVA的教程涵盖多个方面,从基础知识到高级编程都有详细的讲解。以下是一些主要的学习内容: ... JAVA集合框架与泛型:JAVA的集合框架提供了丰富的数据结构,如List、Set、Map等。你需要学习如何使用这些数据

    动力节点老杜推荐Java学习路线

    学习Java集合框架,包括List、Set、Map等数据结构的使用和常见操作。 深入理解异常处理机制,学会使用try-catch语句和自定义异常。 学习Java的多线程编程,掌握线程的创建、同步和通信等技术。 学习Java的IO编程,...

    集合 Collection

    深入讲述了JAVA中的集合,介绍了JAVA集合中所用到的数据结构的基础知识。详细解说List 、Set、Map以及他们的实现类,是学习JAVA集合不可多得的资料。欢迎下载!

    java后端宝典进阶版.zip

    Java集合框架:介绍Java中常用的集合类,如List、Set、Map等,以及它们的特点、用法和性能分析,帮助读者选择合适的集合类来解决实际问题。 Java并发编程:深入讲解Java中的线程、锁、并发容器等并发编程相关的知识...

    Java入门1·2·3:一个老鸟的Java学习心得.PART3(共3个)

    第1章 让自己的第一个Java程序跑起来 2 教学视频:19分钟 1.1 想要用Java改变这个世界吗? 2 1.1.1 Java有什么优势? 2 1.1.2 Java在哪儿? 3 1.2 准备好开始Java之旅 3 1.2.1 下载JDK 4 1.2.2 安装JDK 5 ...

    Java开发详解.zip

    000000_【课程介绍 —— 写在前面的话】_Java学习概述笔记.pdf 010101_【第1章:JAVA概述及开发环境搭建】_JAVA发展概述笔记.pdf 010102_【第1章:JAVA概述及开发环境搭建】_Java开发环境搭建笔记.pdf 010201_【第2...

    Android 初学中阶高阶书籍_集合打包2

    用,Android实现GPS定位,Android通过JNI调用驱动程序,Android网络开发详解,android写的google map api 应用,android学 习资料大全,Android音视频的编解码,Android应用框架原理与程序设计36技(高焕堂著、简体版),...

    JAVA入门1.2.3:一个老鸟的JAVA学习心得 PART1(共3个)

    第1章 让自己的第一个Java程序跑起来 2 教学视频:19分钟 1.1 想要用Java改变这个世界吗? 2 1.1.1 Java有什么优势? 2 1.1.2 Java在哪儿? 3 1.2 准备好开始Java之旅 3 1.2.1 下载JDK 4 1.2.2 安装JDK 5 ...

    Java面试题资料,包含核心知识,消息队列,大数据等

    这份资料不仅应该触及Java的基石——例如JVM的工作原理、内存模型、垃圾回收机制,以及Java的集合框架中的核心接口与实现类,如List、Set、Map等,更应对Java的异常处理机制有深入的剖析 此外,考虑到Java技术的...

    疯狂JAVA讲义

    7.1 Java集合概述 241 7.2 Collection和Iterator接口 243 7.2.1 使用Iterator接口遍历集合元素 244 7.2.2 使用foreach循环遍历集合元素 246 7.3 Set接口 247 7.3.1 HashSet类 247 学生提问:hashCode方法对于...

Global site tag (gtag.js) - Google Analytics