精华内容
下载资源
问答
  • Leetcode 447. 回旋镖的数量 从暴力到hashmap优化.pdf
  • Leetcode 454. 四数相加 II (HashMap优化,分两组).pdf
  • Leetcode 1248 统计「优美子数组」 (前缀和加HashMap优化).pdf
  • Leetcode 523. 连续的子数组和 (前缀和加HashMap优化).pdf
  • Java8新特性[HashMap优化]前言其他主要新特性HashMap优化HashMap1.7HashMap1.7存在死链问题HashMap每次扩容为什么是2倍JDK1.8结构变化ConcurrentHashMap变化为何JDK8要放弃分段锁?内存结构优化总结 前言 本文开始...

    前言

    本文开始重温Java8新特性之HashMap优化,后续还会重温其他主要新特性,敬请期待,点点关注不迷路哦!!

    其他主要新特性

    HashMap优化

    HashMap1.7

    在JDK1.7 到 JDK1.8的时候,对HashMap做了优化

    首先JDK1.7的HashMap当出现Hash碰撞的时候,最后插入的元素会放在前面,这个称为 “头插法”

    JDK7用头插是考虑到了一个所谓的热点数据的点(新插入的数据可能会更早用到),但这其实是个伪命题,因为JDK7中rehash的时候,旧链表迁移新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置(就是因为头插) 所以最后的结果 还是打乱了插入的顺序 所以总的来看支撑JDK7使用头插的这点原因也不足以支撑下去了 所以就干脆换成尾插 一举多得

    在这里插入图片描述

    HashMap1.7存在死链问题

    参考:hashmap扩容时死循环问题

    在JDK1.8以后,由头插法改成了尾插法,因为头插法还存在一个死链的问题

    在说死链问题时,我们先从Hashmap存储数据说起,下面这个是HashMap的put方法

    public V put(K key, V value)
    {
        ......
        //计算Hash值
        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;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        //该key不存在,需要增加一个结点
        addEntry(hash, key, value, i);
        return null;
    }
    

    这里添加一个节点需要检查是否超出容量,出现一个负载因子

    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);
        //查看当前的size是否超过了我们设定的阈值threshold,如果超过,需要resize
        if (size++ >= threshold)
            resize(2 * table.length);//扩容都是2倍2倍的来的,
    }
    

    HashMap有 负载因子:0.75,以及 初始容量:16,扩容阈值:16*0.75 = 12,当HashMap达到扩容的条件时候,会把HashMap中的每个元素,重新进行运算Hash值,打入到扩容后的数组中。

    既然新建了一个更大尺寸的hash表,然后把数据从老的Hash表中迁移到新的Hash表中。

    void resize(int newCapacity)
    {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        ......
        //创建一个新的Hash Table
        Entry[] newTable = new Entry[newCapacity];
        
        //将Old Hash Table上的数据迁移到New Hash Table上
        transfer(newTable);
        table = newTable;
        threshold = (int)(newCapacity * loadFactor);
    }
    

    重点在这个transfer()方法

    void transfer(Entry[] newTable)
    {
        Entry[] src = table;
        int newCapacity = newTable.length;
        //下面这段代码的意思是:
        //  从OldTable里摘一个元素出来,然后放到NewTable中
        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);
            }
        }
    }
    

    do循环里面的是最能说明问题的,当只有一个线程的时候:

    在这里插入图片描述

    最上面的是old hash 表,其中的Hash表的size=2, 所以key = 3, 7, 5,在mod 2以后都冲突在table[1]这里了。接下来的三个步骤是Hash表 扩容变成4,然后在把所有的元素放入新表

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

    而我们的线程二执行完成了。于是我们有下面的这个样子

    在这里插入图片描述

    在这里插入图片描述

    注意,因为Thread1的 e 指向了key(3),而next指向了key(7),其在线程二rehash后,指向了线程二重组后的链表。我们可以看到链表的顺序被反转后。
    这里的意思是线程1这会还没有完全开始扩容,但e和next已经指向了,线程2是正常的扩容的,那这会在3这个位置上,就是7->3这个顺序。
    然后线程一被调度回来执行:

    先是执行 newTalbe[i] = e;
    然后是e = next,导致了e指向了key(7),
    而下一次循环的next = e.next导致了next指向了key(3)
    注意看图里面的线,线程1指向线程2里面的key3.

    在这里插入图片描述

    线程一接着工作。把key(7)摘下来,放到newTable[i]的第一个,然后把e和next往下移。

    在这里插入图片描述

    这时候,原来的线程2里面的key7的e和key3的next没了,e=key3,next=null。

    当继续执行,需要将key3加回到key7的前面。
    e.next = newTable[i] 导致 key(3).next 指向了 key(7)

    注意:此时的key(7).next 已经指向了key(3), 环形链表就这样出现了。

    在这里插入图片描述

    线程2生成的e和next的关系影响到了线程1里面的情况。从而打乱了正常的e和next的链。于是,当我们的线程一调用到,HashTable.get(11)时,即又到了3这个位置,需要插入新的,那这会就e 和next就乱了

    HashMap每次扩容为什么是2倍

    参考:HashMap初始容量为什么是2的n次幂

    首先看向HashMap中添加元素是怎么存放的

    在这里插入图片描述

    在这里插入图片描述

    第一个截图是向HashMap中添加元素putVal()方法的部分源码,可以看出,向集合中添加元素时,会使用(n - 1) & hash的计算方法来得出该元素在集合中的位置;而第二个截图是HashMap扩容时调用resize()方法中的部分源码,可以看出会新建一个tab,然后遍历旧的tab,将旧的元素进过e.hash & (newCap - 1)的计算添加进新的tab中,也就是(n - 1) & hash的计算方法,其中n是集合的容量,hash是添加的元素进过hash函数计算出来的hash值

    HashMap的容量为什么是2的n次幂,和这个(n - 1) & hash的计算方法有着千丝万缕的关系,符号&是按位与的计算,这是位运算,计算机能直接运算,特别高效,按位与&的计算方法是,只有当对应位置的数据都为1时,运算结果也为1,当HashMap的容量是2的n次幂时,(n-1)的2进制也就是1111111***111这样形式的,这样与添加元素的hash值进行位运算时,能够充分的散列,使得添加的元素均匀分布在HashMap的每个位置上,减少hash碰撞,下面举例进行说明。

    当HashMap的容量是16时,它的二进制是10000,(n-1)的二进制是01111,与hash值得计算结果如下:

    在这里插入图片描述

    上面四种情况我们可以看出,不同的hash值,和(n-1)进行位运算后,能够得出不同的值,使得添加的元素能够均匀分布在集合中不同的位置上,避免hash碰撞,下面就来看一下HashMap的容量不是2的n次幂的情况,当容量为10时,二进制为01010,(n-1)的二进制是01001,向里面添加同样的元素,结果为:

    在这里插入图片描述

    可以看出,有三个不同的元素进过&运算得出了同样的结果,严重的hash碰撞了。

    终上所述,HashMap计算添加元素的位置时,使用的位运算,这是特别高效的运算;另外,HashMap的初始容量是2的n次幂,扩容也是2倍的形式进行扩容,是因为容量是2的n次幂,可以使得添加的元素均匀分布在HashMap中的数组上,减少hash碰撞,避免形成链表的结构,使得查询效率降低

    JDK1.8结构变化

    由JDK1.7的,数组 + 链表

    JDK1.8变为:数组 + 链表 + 红黑树

    具体触发条件为:某个链表连接的个数大于8,并且总的容量大于64的时候,那么会把原来的链表转换成红黑树

    这么做的好处是什么:除了添加元素外,查询和删除效率比链表快

    红黑树查询、增加和删除的时间复杂度:O(log2n)

    链表的查询和删除的时间复杂度: O(n),插入为:O(1)

    ConcurrentHashMap变化

    为何JDK8要放弃分段锁?

    由原来的分段锁,变成了CAS,也就是通过无锁化设计替代了阻塞同步的加锁操作,性能得到了提高。

    通过分段锁的方式提高了并发度。分段是一开始就确定的了,后期不能再进行扩容的,其中的段Segment继承了重入锁ReentrantLock,有了锁的功能,同时含有类似HashMap中的数组加链表结构(这里没有使用红黑树),虽然Segment的个数是不能扩容的,但是单个Segment里面的数组是可以扩容的。

    JDK1.8的ConcurrentHashMap摒弃了1.7的segment设计,而是JDK1.8版本的HashMap的基础上实现了线程安全的版本,即也是采用数组+链表+红黑树的形式,虽然ConcurrentHashMap的读不需要锁,但是需要保证能读到最新数据,所以必须加volatile。即数组的引用需要加volatile,同时一个Node节点中的val和next属性也必须要加volatile。

    至于为什么抛弃Segment的设计,是因为分段锁的这个段不太好评定,如果我们的Segment设置的过大,那么隔离级别也就过高,那么就有很多空间被浪费了,也就是会让某些段里面没有元素,如果太小容易造成冲突

    内存结构优化

    取消永久区,把方法区 放在 元空间中

    方法区主要用于存储一些类模板

    在这里插入图片描述

    OOM错误发生概率降低

    同时相关JVM调优命令变为:

    MetaspaceSize

    MaxMetaspaceSize
    gment设置的过大,那么隔离级别也就过高,那么就有很多空间被浪费了,也就是会让某些段里面没有元素,如果太小容易造成冲突

    总结

    点赞+关注,谢谢

    展开全文
  • 使用hashmap优化压缩Redis内存使用 原

    千次阅读 2018-10-15 14:22:43
    使用hashmap优化压缩Redis内存使用 背景 近来公司内部dsp架构升级,需要能够根据请求中的设备id实时的获取到该设备的用户画像相关信息,于是选用每天使用离线任务把用户数据灌入redis里面,供线上服务实时查询。 ...

    https://my.oschina.net/nalenwind/blog/897744

    使用hashmap优化压缩Redis内存使用


    背景

    近来公司内部dsp架构升级,需要能够根据请求中的设备id实时的获取到该设备的用户画像相关信息,于是选用每天使用离线任务把用户数据灌入redis里面,供线上服务实时查询。

    需求评估

    需求是筛选出最近一个月活跃的设备,将其用户画像属性灌入redis中。于是筛选出30天的活跃设备总量有24亿。这么大的量如果直接使用设备id作为key直接写入redis,按value占用16字节来算,大概要用230G内存的redis集群,这成本还是比较可观的。

    内存使用评估方法

    为了评估我们设计的方法具体将占用多大的内存,上网查了写资料,发现跟redis内存容量使用和优化相关的只有这一篇文章《REDIS内存容量的预估和优化》,其他都是相互转载的,虽然不知道作者是谁,但是向作者致敬。

    方案设计

    大家知道Hash表空间大小和Key的个数决定了冲突率(或者用负载因子衡量),再合理的范围内,key越多自然hash表空间越大,消耗的内存自然也会很大。再加上大量指针本身是长整型,所以内存存储的膨胀十分可观。所以主要优化思路是考虑如何把key的个数减少。

    在网上搜了下相关的博客,发现了这篇文章《Redis百亿级Key存储方案》,同样也是相互转载的特别多,已不知出处,里面bucket的思路启发了我,这里深深感谢作者。

    原文的意思是设计一个映射函数,将设备id经过变换,映射到一个桶id上,桶的数量比原始数据的数量小2-3个数量级,而映射函数的作用是能够尽可能平均的将key映射到桶id上,使用桶id作为redis中的key,value使用hashmap结构,原始数据中的key和value作为hashmap中的subkey和value。这样能够大幅减少redis中全局key的数量。所以这里这个映射函数是一个比较核心的东西,要设计一个好的映射函数是很困难的。这里我经过多次尝试,方案如下:

    选用CRC32算法对设备id做映射,再将结果对$2^{26}$取模作为桶id。这样能够将桶的数量控制在$2^{26}$ 这个数量上,同时使桶内的subkey分布尽可能均匀,减少数据倾斜的情况出现。同时使用BKDRHash算法对作为subkey的device id进行变换和压缩,从36字节变为4字节。

    但是我们使用引用的第一篇文章中的公式来算一下: 首先我们有24亿条数据,这里桶的个数定为$2^{26}$个,平均每个桶里有35个子key。为了存储这$2^{26}$个key,我们需要key的长度为4个字节。 key字节数为4 + 9 ~ 16字节 subkey字节数为4字节 value字节数为16字节 则单个hashmap的大小为

    35 × (4 + 16 + 3) + 2 = 807 字节 ~ 1024
    

    总内存大小为

    2 ^ 26 × (16 + 16 + 16 + 1024) + 2 ^ 26 × 4 = 67 G
    

    可以看到总内存只使用了67G

    总结

    1.经过了上面的优化,从230G的内存占用到67G,内存使用减少了3.5倍,满满的成就感啊。 2.利用redis的hashmap可以极大的减少key的数量,和减少内存使用。

    展开全文
  • 项目中遇到Spark Streaming吞吐量太低的问题,redis集群使用HashMap优化键值存储结构提升Spark Streaming吞吐量。 场景: 设备将运行报文发送到kafka,Spark Streaming对报文进行加工处理,生成6类不同报文信息以...

    项目中遇到Spark Streaming吞吐量太低的问题,redis集群使用HashMap优化键值存储结构提升Spark Streaming吞吐量。

    场景:
    设备将运行报文发送到kafka,Spark Streaming对报文进行加工处理,生成6类不同报文信息以json字符串形式set进redis集群。

    问题:
    kafka消费速度跟不上。

    分析:
    对于1000万设备一个上报周期上报1000万条报文,极端情况下同时上报,Spark Streaming消费1000万条报文,生成6000万个键值对存储入redis集群。
    优化思路,降低Spark Streaming操作redis的频率,降低key的数量。提升Spark Streaming吞吐量,提升redis集群的查询速度以及后续key的更新速度。计划使用HashMap结构将6类json报文的属性提取出来作为HashMap的field。

    测试:
    相同集群环境下set操作和hset操作均使用基于Lettuce的Spring data redis客户端。使用pipliene的方式进行命令提交。每批处理命令1000条。
    set模式使用3个json报文共32个属性,hset模式使用一个相同32属性的Map。则set模式下存储报文量为hset模式下的3倍。

    结果:

    sethset速度提升
    10万数量级场景1.3min(30万)51s(10万)34%
    100万数量级场景1.3min(300万)51s(100万)27%
    1000万数量级场景1.789hours(3000万)1.295hous(1000万)27%

    结论:
    使用HashMap结构降低redsi集群键值数量可以提升存储性能,提升Spark Streaming吞吐量。提升预估20%~30%,待现网验证,补充结果。

    展开全文
  • HashMap基础 基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了不同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是...

    HashMap基础

    基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了不同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。另外,HashMap是非线程安全的,也就是说在多线程的环境下,可能会存在问题,而Hashtable是线程安全的。

     

    HashMap类图:

     

    一、HashMap常用方法

    package collection;
    
    import java.util.*;
    
    /**
     * @author XuqiangDuan
     * @Date 2018/9/14 16:45
     **/
    public class MapDemo {
    
        /**
         * @deprecated 该标记为已弃用
         * HashMap 常用方法
         */
        public static void mapMethod() {
    
            HashMap<String, Integer> hashMap = new HashMap<>();
            hashMap.put("a", 1);
            hashMap.put("b", 2);
            hashMap.put("c", 3);
            hashMap.put("d", 4);
            hashMap.put("e", 5);
    
            //size
            System.out.println("size: " + hashMap.size());
            //values
            System.out.println("values: " + hashMap.values());
            //keys
            System.out.println("keys: " + hashMap.keySet());
    
            //entry
            System.out.println("entrySet : " + hashMap.entrySet());
            Set set = hashMap.entrySet();
            Iterator iterator = set.iterator();
            while (iterator.hasNext()) {
                Map.Entry entry = (Map.Entry) iterator.next();
                System.out.println("key:" + entry.getKey() + " - value:" + entry.getValue());
            }
    
            /*
             * putAll
             * 合并时,key相同value更新
             */
            HashMap<String, Integer> hashMapN = new HashMap<>();
            hashMapN.put("e", 55);
            hashMapN.put("f", 66);
            hashMapN.put("g", 77);
            hashMapN.put("h", 88);
            hashMapN.put("i", 99);
            hashMapN.putAll(hashMap);
            System.out.println("N合并后 size: " + hashMapN.size());
            System.out.println("N合并后 values: " + hashMapN.values());
    
            //JDK8-lambda表达式
            hashMapN.forEach((k, v) -> {
                System.out.println(k + " : " + v);
            });
    
            //remove
            hashMap.remove("a");
            System.out.println("remove后的size : " + hashMap.size());
            //clear
            hashMap.clear();
            System.out.println("clear后的size : " + hashMap.size());
    
        }
    
        /**
         * 主方法
         *
         * @param args
         */
        public static void main(String[] args) {
    
            //HashMap方法
            MapDemo.mapMethod();
    
        }
        
    }
    

     

    二、Java 8 HashMap优化特性

    参考链接:Java 8系列之重新认识HashMap

     

    小结

    • 扩容是一个特别耗性能的操作,所以当程序员在使用HashMap的时候,估算map的大小,初始化的时候给一个大致的数值,避免map进行频繁的扩容;
    • 负载因子是可以修改的,也可以大于1,但是建议不要轻易修改,除非情况非常特殊;
    • HashMap是线程不安全的,不要在并发的环境中同时操作HashMap,建议使用ConcurrentHashMap;
    •  JDK1.8引入红黑树大程度优化了HashMap的性能。

     

    展开全文
  • JDK1.8中HashMap优化分析

    2020-08-18 17:45:05
    HashMap底层实现是数组,这里分析下jdk1.8中对HashMap优化 1. hash算法优化 // jdk1.8 HashMap中hash源码 static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h...
  • HashMap优化的几种简单方法

    千次阅读 2018-08-05 11:39:45
    先贴出HashMap源码普及一下几个概念: public class HashMap&lt;K,V&gt;extends AbstractMap&lt;K,V&gt;implements Map&lt;K,V&gt;, Cloneable, Serializable { // 默认的初始容量(容量...
  • 在Android开发时,我们使用的大部分都是Java的api,比方HashMap这个api,使用率非常高,可是对于Android这样的对内存非常敏感的移动平台,非常多时候使用一些java的api并不能达到更好的性能,相反反而更消耗内存,...
  • 我并没有和HashMap杠上,想着重新开始写点技术的东西,就拿HashMap开头了。最近开始重新学习数据结构和算法,其中有些东西学完之后,对于HashMap的理解和运用又有新的认识。虽然之...
  • HashMap来缓存ActivityGroup加载过的View,Eclipse给出了一个警告,之前考虑项目进度没怎么在意,这次仔细看了下提示,如下: Use new SparseArrayView> (...) instead for better performance 意思就是说...
  • SparseArray是官方针对安卓所写的容器,与HashMap类似,不过性能比HashMap好。 首先看看SparseArray的用法: SparseArray的构造与我们用惯的HashMap,ArrayList一样也是new出一个实例然后使用。 SparseArr
  • 直接暴力的话就是O(N^3), 那么如果优化? 对于任意一个点,循环枚举建立距离为key,个数为value的hashmap,然后统计完所有距离后再遍历一次HashMap,这样总的时间复杂度就是O(N^2) class Solution { public: ...
  • 这时候我们会想到的其中一种解决方法就是利用Hashmap在查找数据的高效上来优化双层For 我利用下面的代码来模拟测试两种情况的性能: public static void main(String[] args) { for (int i = 0; i ...
  • 使用hashmap优化压缩Redis内存使用 背景 近来公司内部dsp架构升级,需要能够根据请求中的设备id实时的获取到该设备的用户画像相关信息,于是选用每天使用离线任务把用户数据灌入redis里面,供线上服务实时查询。...
  • @Service public class TestServiceTwo implements InitializingBean { ... hashmap.put(StrategyTestEnum.STRATEGY_THREE.getTitle(), this.applicationContext.getBean(TestThree.class)); } }
  • HashMap优化

    2020-11-27 14:01:29
    HashMap结构的优化 HashMap的复杂度 查找/获取 添加/删除 空间 ArrayList O(1) O(N) O(N) LinkedList O(N) O(1) O(N) HashMap O(N/Buckets) O(N/Buckets) O(N) ...
  • 10分钟拿下 HashMap

    万次阅读 多人点赞 2018-10-18 11:18:04
    请相信我,你一定会更优秀! 文章目录: 1、什么是 HashMap?什么时候选择HashMap?...4、如何优化 HashMap? 1、什么是 HashMap?什么时候选择HashMap? 说到容器,你肯定会想到 Java中对象存储容器还有Arr...
  • hashMap实现原理

    万次阅读 多人点赞 2019-07-31 18:35:50
    1. HashMap概述:  HashMap是基于哈希表的Map接口的非同步实现(Hashtable跟HashMap很像,唯一的区别是Hashtalbe中的方法是线程安全的,也就是同步的)。此实现提供所有可选的映射操作,并允许使用null值和null键...
  • hashmap1.8计算优化

    2021-04-06 23:15:23
    hashmap1.8计算优化
  • jdk1.8HashMap优化

    2020-10-21 22:29:33
    拉链过长会严重影响hashmap的性能,所以1.8的hashmap引入了红黑树。 在链表元素数量超过8时改为红黑树,少于6时改为链表,中间7不改是避免频繁转换降低性能。 相对于链表,改为红黑树后碰撞元素越多查询效率越高...
  • 文章目录一.HashMap是什么二.HashMap继承类对比分析三.HashMap源码相关单词含义四.HashMap如何确定哈希桶数组索引位置五. HashMap 的 put 方法分析六.HashMap扩容机制七.HashMap线程安全性 一.HashMap是什么 ...
  • HashMap计数方法优化

    千次阅读 2013-10-28 22:07:21
    大家可能经常会用HashMap来计算数据库或文本中某些特定内容出现的频率,这篇文章比较了三种HashMap计数器的实现方法,大家可做个参考。 首先介绍最基础的一种实现方式,代码如下 String s = "one two three two ...
  • HashMap优化与实践

    千次阅读 2016-08-29 09:17:17
    HashMap优化与实践 本文是基于作者在github上的Android 问题交流讨论坛提问而产生的一篇文章,也是自己早打算开坑的一篇文章。文章首先介绍了hashMap的一些基本知识,然后介绍了它在JDK8下的实现原理,最后着重...
  • jdk8中HashMap优化 HashMap是基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了不同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证...
  • HashMap

    2019-09-04 14:52:25
    HashMap是java中最重要的数据结构之一,下面我们就基于jdk1.8来聊聊HashMap HashMap数据结构?怎么put数据的?...ConcurrentHashMap怎么保证线程安全的,jdk1.8做了哪些优化HashMap数据结构?怎么put数据...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 120,503
精华内容 48,201
关键字:

hashmap如何优化