Java 集合 HashMap & HashSet 拾遗

@author ixenos

摘要:HashMap内部结构分析

Java HashMap采用的是冲突链表方式

  • 从上图容易看出,如果选择合适的散列函数,put()get()方法可以在常数时间内完成,因为较好的散列减少了散列冲突,使时间主要花在对桶寻址上(数组),而较少去遍历桶中的链表。但在对HashMap进行迭代时,需要遍历整个table以及后面跟的冲突链表。因此对于迭代比较频繁的场景,不宜将HashMap的初始大小设的过大。
  • 有两个参数可以影响HashMap的性能:初始容量(inital capacity)和负载系数(load factor)。初始容量指定了初始table的大小,负载系数用来指定自动扩容的临界值。当entry的数量超过capacity*load_factor时,容器将自动扩容并重新哈希。对于插入元素较多的场景,将初始容量设大可以减少重新哈希的次数。
  • 将对向放入到HashMap或HashSet中时,有两个方法需要特别关心:hashCode()equals()hashCode()方法决定了对象会被放到哪个bucket里,当多个对象的哈希值冲突时,equals()方法决定了这些对象是否是“同一个对象”。所以,如果要将自定义的对象放入到HashMapHashSet中,需要@Override hashCode()equals()方法。

由Value得Key


  • many-to-one ( 多Key映射一Value ):遍历整个Map的entry然后得到所要求的key
  • public static <T, E> Set<T> getKeysByValue(Map<T, E> map, E value) {
    Set<T> keys = new HashSet<T>();
    for (Entry<T, E> entry : map.entrySet()) {
    //判断当前entry是否含有value
    if (Objects.equals(value, entry.getValue())) {
    //通过含有value的entry得到对应的key
    keys.add(entry.getKey());
    }
    }
    return keys;
    }
    public static <T, E> Set<T> getKeysByValue(Map<T, E> map, E value) {
    return map.entrySet()
    .stream()
    .filter(entry -> Objects.equals(entry.getValue(), value))
    .map(Map.Entry::getKey)
    .collect(Collectors.toSet());
    }

    In Java 8: Lambda

  • one-to-one ( 一Key映射一Value ):
    • 同样遍历,但一遇到Key直接return
    • public static <T, E> T getKeyByValue(Map<T, E> map, E value) {
      for (Entry<T, E> entry : map.entrySet()) {
      if (Objects.equals(value, entry.getValue())) {
      //一找到就return
      return entry.getKey();
      }
      }
      return null;
      }
    • 如果需要大批量,则直接把Key和Value对调存放在Map中,再getValue就好
    • 还可以不用Java集合框架,用Google的开源框架Guava,其中的BiMap可以由value得key

    • BiMap<Token, Character> tokenToChar =
      ImmutableBiMap.of(Token.LEFT_BRACKET, '[', Token.LEFT_PARENTHESIS, '(');
      Token token = tokenToChar.inverse().get('(');
      Character c = tokenToChar.get(token);

put方法对重复键的处理


  • 找到key对应的entry,如果非空,则添加时(只是)覆盖value

  •     //e是一个node<K,V>对象,也就是一个entry
    //value是put进来的
    if (e != null) { // existing mapping for key
    V oldValue = e.value;
    if (!onlyIfAbsent || oldValue == null)
    //从这里可以看出是找到对应entry然后改变值
    e.value = value;
    afterNodeAccess(e);
    return oldValue;
    }

    put源代码片段

  • 同样hashTable也是(只是)覆盖value
  • 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 = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    @SuppressWarnings("unchecked")
    Entry<K,V> entry = (Entry<K,V>)tab[index];
    for(; entry != null ; entry = entry.next) {
    //先判断哈希是因为比equals快,而且用的逻辑与
    if ((entry.hash == hash) && entry.key.equals(key)) {
    V old = entry.value;
    //可以看出还是替换掉了旧有的值
    entry.value = value;
    return old;
    }
    } addEntry(hash, key, value, index);
    return null;
    }

    HashTable的put源码

从containsValue的源码看数据结构


  • /**
    * Returns <tt>true</tt> if this map maps one or more keys to the specified value.
    * 即“如果这个映射表有一个或多个key映射到一个值上时,返回true”
    * @param value value whose presence in this map is to be tested
    * @return <tt>true</tt> if this map maps one or more keys to the
    * specified value
    */
    public boolean containsValue(Object value) {
    Node<K,V>[] tab; V v;
    if ((tab = table) != null && size > 0) {
    //遍历每一个桶bucket(同样hash值的entry(Node)在一个桶中),一个桶存放一个双链表
    for (int i = 0; i < tab.length; ++i) {
    //遍历桶中的链表,把所有映射value的key揪出来
    for (Node<K,V> e = tab[i]; e != null; e = e.next) { //后继结点e.next指向的是另一个同hash的entry的前驱结点key(Node是双链表结点)
    if ((v = e.value) == value ||
    (value != null && value.equals(v)))
    return true;
    }
    }
    }
    return false;
    }

零碎知识


  • 尽量返回接口而非实际的类型,如返回List、Set、Map而非ArrayList、HashSet、HashMap,便于更换数据结构,而客户端代码不用改变。这就是针对抽象编程

  • Map.entrySet 方法返回Map映射的 Set 视图Set<Map.Entry<K,V>>,维护entry键值对

    • 该 set 受Map映射支持,所以对Map映射的更改可在此 set 中反映出来,反之亦然!

    • 如果对该 set 进行迭代的同时修改了Map映射,外部modCount改变而内部的xxxmodCount还在自己的节奏(通过迭代器自己的 remove 操作,或者通过对迭代器返回的映射项执行 setValue 操作除外),则迭代结果是不确定的;简单来说就是iterator的对象不受Map支持,Map自行修改的时候不会通知到他,modCount异常,会发生如NullPointException之类的异常

    • 该set 支持元素移除,通过 Iterator.removeSet.removeremoveAllretainAll 和 clear这几种操作操作可从映射中移除相应的映射关系,但它不支持 add 或 addAll 操作

Java 集合 HashMap & HashSet 拾遗的更多相关文章

  1. Java 集合:HashSet 与 ArrayList

    Set 集合是无序不可以重复的的.List 集合是有序可以重复的. Java 集合:HashSet 与 hashCode.equals 博客里面已经说到这个问题,但是解释的还是不够清楚. 看一个小例子 ...

  2. 【Java集合】HashSet源码解析以及HashSet与HashMap的区别

    HashSet 前言 HashSet是一个不可重复且元素无序的集合.内部使用HashMap实现. 我们可以从HashSet源码的类注释中获取到如下信息: 底层基于HashMap实现,所以迭代过程中不能 ...

  3. 死磕 java集合之HashSet源码分析

    问题 (1)集合(Collection)和集合(Set)有什么区别? (2)HashSet怎么保证添加元素不重复? (3)HashSet是否允许null元素? (4)HashSet是有序的吗? (5) ...

  4. Java集合:HashSet的源码分析

    Java集合---HashSet的源码分析   一.  HashSet概述: HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持.它不保证set 的迭代顺序:特别是它不保证该 ...

  5. Java集合--HashMap分析

    HashMap在Java开发中有着非常重要的角色地位,每一个Java程序员都应该了解HashMap. 本文主要从源码角度来解析HashMap的设计思路,并且详细地阐述HashMap中的几个概念,并深入 ...

  6. Java集合---HashMap源码剖析

    一.HashMap概述二.HashMap的数据结构三.HashMap源码分析     1.关键属性     2.构造方法     3.存储数据     4.调整大小 5.数据读取           ...

  7. [转载] Java集合---HashMap源码剖析

    转载自http://www.cnblogs.com/ITtangtang/p/3948406.html 一.HashMap概述 HashMap基于哈希表的 Map 接口的实现.此实现提供所有可选的映射 ...

  8. 1.Java集合-HashMap实现原理及源码分析

    哈希表(Hash  Table)也叫散列表,是一种非常重要的数据结构,应用场景及其丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表,而HashMap的实现原理也常常 ...

  9. Java集合之HashSet/TreeSet原理

    Set集合 1.HashSet  只去重复, 没有顺序  HashSet的add方法会调用hashCode和equals, 所以存储在HashSet中的对象需要重写这两个方法. 2.TreeSet   ...

随机推荐

  1. http缓存与cdn相关技术

    阅读目录 一 http缓存 二.Http缓存概念解析 三.cdn相关技术 摘要:最近要做这个主题的组内分享,所以准备了一个星期,查了比较多的资料.准备的过程虽然很烦很耗时间,不过因为需要查很多的资料, ...

  2. php学习笔记——语言切换

    现在的网站很多都可以实现多语言,于是记录一下多语言的实例. 方法一:通过将所有显示在页面的字段放在一个message文件里面来实现 思路如下图: test代码: main.php: <?php ...

  3. SpringMVC @ResponseStatus 的用法

    @ResponseStatus 用于修饰一个类或者一个方法,修饰一个类的时候,一般修饰的是一个异常类,如下, 声明一个异常类在类上面加上ResponseStatus注解,就表明,在系统运行期间,抛出A ...

  4. 分享基于.NET MVC+EF CodeFirst+IOC+EasyUI的框架设计

    **注:要做工,没什么时间,等有空时会上传到GIT,项目结构如上,简单的说一下: **支持IOC及多数据库等,各项目由MVC区域隔离: 主要使用基于接口与抽象类进行高度的抽象与接口隔离,与其它框架比较 ...

  5. JavaSE中主要的类与方法

    1.instanceof:测试左边的对象是否是右边类的实例;  数组.length:返回的是数组的长度; 2.Scanner类     nextInt():读取键盘录入的整数;  next():读取键 ...

  6. LanSoEditor_common ---android平台的视频编辑SDK

    当前版本是LanSoEditor-v1.4 主要使用在音视频的: 裁剪,剪切,分离,合并,转换,拼接,水印,叠加,混合,转码等场合; 我们是针对android平台对ffmpeg做了硬件加速优化,经过多 ...

  7. JDK根目录介绍

    /bin 存放可执行程序(编译器javac.exe 运行器java.exe 文档生成器javadoc.exe等 ). /db  小型数据库文件. /jre JRE. /include 形成jdk的c. ...

  8. 各种编码之间的关系以及getBytes的使用

    编码基础知识参考http://my.oschina.net/chape/blog/201725 我对此作了简单的概括 iso8859-1 (通常叫做Latin-1) 属于单字节编码,最多能表示的字符范 ...

  9. 动态调用WCF

    最近在做的一个项目中需要动态调用WCF地址,因为有很多终端服务器,而每台终端服务器上都部署一个WCF服务,中央服务器需要不定时调用其中某个或者多个WCF服务执行相关操作,因此添加引用及配置文件配置的方 ...

  10. 1.1 office安装

    2013版安装链接: https://pan.baidu.com/s/1qYMuidA 密码: uj9e