HashMap和ConcurrentHashMap的区别,HashMap的底层源码。

  Hashmap本质是数组加链表。根据key取得hash值,然后计算出数组下标,如果多个key对应到同一个下标,就用链表串起来,新插入的在前面。

  ConcurrentHashMap:在hashMap的基础上,ConcurrentHashMap将数据分为多个segment,默认16个(concurrency level),然后每次操作对一个segment加锁,避免多线程锁的几率,提高并发效率。

 

一、HashMap概述

 

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

 

  值得注意的是HashMap不是线程安全的,如果想要线程安全的HashMap,可以通过Collections类的静态方法synchronizedMap获得线程安全的HashMap。

1  Map map = Collections.synchronizedMap(new HashMap());

 

二、HashMap的数据结构

 HashMap的底层主要是基于数组和链表来实现的,它之所以有相当快的查询速度主要是因为它是通过计算散列码来决定存储的位置,能够很快的计算出对象所存储的位置。HashMap中主要是通过key的hashCode来计算hash值的,只要hashCode相同,计算出来的hash值就一样。如果存储的对象对多了,就有可能不同的对象所算出来的hash值是相同的,这就出现了所谓的hash冲突。学过数据结构的同学都知道,解决hash冲突的方法有很多,HashMap底层是通过链表来解决hash冲突的。

                                      技术分享

从上图中可以看出,HashMap底层就是一个数组结构,数组中存放的是一个Entry对象,如果产生的hash冲突,也就是说要存储的那个位置上面已经存储了对象了,这时候该位置存储的就是一个链表了。我们看看HashMap中Entry类的代码:

 1 static class Entry<K,V> implements Map.Entry<K,V> {2         final K key;3         V value;4         Entry<K,V> next;5         final int hash;6 7         /**8          * Creates new entry.9          */
10         Entry(int h, K k, V v, Entry<K,V> n) {
11             value = v;
12             next = n; //hash值冲突后存放在链表的下一个
13             key = k;
14             hash = h;
15         }
16 
17         .........
18     }

HashMap其实就是一个Entry数组,Entry对象中包含了键和值,其中next也是一个Entry对象,它就是用来处理hash冲突的,形成一个链表。

三、HashMap源码分析

  先看看HashMap类中的一些关键属性:

1 transient Entry[] table;//存储元素的实体数组
2 
3 transient int size;//存放元素的个数
4 
5 int threshold; //临界值   当实际大小超过临界值时,会进行扩容threshold = 加载因子*容量
6 
7 final float loadFactor; //加载因子
8 
9 transient int modCount;//被修改的次数

    其中加载因子是表示Hash表中元素的填满的程度.若:加载因子越大,填满的元素越多,好处是,空间利用率高了,但:冲突的机会加大了.反之,加载因子越小,填满的元素越少,
好处是:冲突的机会减小了,但:空间浪费多了.冲突的机会越大,则查找的成本越高.反之,查找的成本越小.因而,查找时间就越小.因此,必须在 "冲突的机会"与"空间利用率"之间寻找一种平衡与折衷. 这种平衡与折衷本质上是数据结构中有名的"时-空"矛盾的平衡与折衷.

  如果机器内存足够,并且想要提高查询速度的话可以将加载因子设置小一点;相反如果机器内存紧张,并且对查询速度没有什么要求的话可以将加载因子设置大一点。不过一般我们都不用去设置它,让它取默认值0.75就好了。

  下面看看HashMap的几个构造方法:

 1 public HashMap(int initialCapacity, float loadFactor) {2         //确保数字合法3         if (initialCapacity < 0)4             throw new IllegalArgumentException("Illegal initial capacity: " +5                                                initialCapacity);6         if (initialCapacity > MAXIMUM_CAPACITY)7             initialCapacity = MAXIMUM_CAPACITY;8         if (loadFactor <= 0 || Float.isNaN(loadFactor))9             throw new IllegalArgumentException("Illegal load factor: " +
10                                                loadFactor);
11 
12         // Find a power of 2 >= initialCapacity
13         int capacity = 1;   //初始容量
14         while (capacity < initialCapacity)   //确保容量为2的n次幂,使capacity为大于initialCapacity的最小的2的n次幂
15             capacity <<= 1;
16 
17         this.loadFactor = loadFactor;
18         threshold = (int)(capacity * loadFactor);
19         table = new Entry[capacity];
20         init();
21     }
22 
23     public HashMap(int initialCapacity) {
24         this(initialCapacity, DEFAULT_LOAD_FACTOR);
25     }
26 
27     public HashMap() {
28         this.loadFactor = DEFAULT_LOAD_FACTOR;
29         threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
30         table = new Entry[DEFAULT_INITIAL_CAPACITY];
31         init();
32     }

  我们可以看到在构造HashMap的时候如果我们指定了加载因子和初始容量的话就调用第一个构造方法,否则的话就是用默认的。默认初始容量为16,默认加载因子为0.75。我们可以看到上面代码中13-15行,这段代码的作用是确保容量为2的n次幂,使capacity为大于initialCapacity的最小的2的n次幂,至于为什么要把容量设置为2的n次幂,我们等下再看。

  下面看看HashMap存储数据的过程是怎样的,首先看看HashMap的put方法:

 1 public V put(K key, V value) {2         if (key == null) //如果键为null的话,调用putForNullKey(value)3             return putForNullKey(value);4         int hash = hash(key.hashCode());//根据键的hashCode计算hash码5         int i = indexFor(hash, table.length);6         for (Entry<K,V> e = table[i]; e != null; e = e.next) { //处理冲突的,如果hash值相同,则在该位置用链表存储7             Object k;8             if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { //如果key相同则覆盖并返回旧值9                 V oldValue = e.value;
10                 e.value = value;
11                 e.recordAccess(this);
12                 return oldValue;
13             }
14         }
15 
16         modCount++;
17         addEntry(hash, key, value, i);
18         return null;
19     }

当我们往hashmap中put元素的时候,先根据key的hash值得到这个元素在数组中的位置(即下标),然后就可以把这个元素放到对应的位置中了。如果这个元素所在的位子上已经存放有其他元素了,那么在同一个位子上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。从hashmap中get元素时,首先计算key的hashcode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。

具体的实现是:

当你的key为null时,会调用putForNullKey,HashMap允许key为null,这样的对像是放在table[0]中。

如果不为空,则调用int hash = hash(key.hashCode());这是hashmap的一个自定义的hash,在key.hashCode()基础上进行二次hash

1 static int hash(int h) {  
2         h ^= (h >>> 20) ^ (h >>> 12);  
3         return h ^ (h >>> 7) ^ (h >>> 4);  
4   }  

得到hash码之后就会通过hash码去计算出应该存储在数组中的索引,计算索引的函数如下:

1 static int indexFor(int h, int length) {  
2        return h & (length-1);  
3    }  

 这个方法非常巧妙,它通过 h & (table.length -1) 来得到该对象的保存位,而HashMap底层数组的长度总是 2 的n 次方,这是HashMap在速度上的优化。当length总是 2 的n次方时,h& (length-1)运算等价于对length取模,也就是h%length,但是&比%具有更高的效率。当数组长度为2的n次幂的时候,不同的key算得得index相同的几率较小,那么数据在数组上分布就比较均匀,也就是说碰撞的几率小,相对的,查询的时候就不用遍历某个位置上的链表,这样查询效率也就较高了。

下面我们继续回到put方法里面,前面已经计算出索引的值了,看到第6到14行,如果数组中该索引的位置的链表已经存在key相同的对象,则将其覆盖掉并返回原先的值。如果没有与key相同的键,则调用addEntry方法创建一个Entry对象,addEntry方法如下:

1 void addEntry(int hash, K key, V value, int bucketIndex) {
2         Entry<K,V> e = table[bucketIndex]; //如果要加入的位置有值,将该位置原先的值设置为新entry的next,也就是新entry链表的下一个节点
3         table[bucketIndex] = new Entry<>(hash, key, value, e);
4         if (size++ >= threshold) //如果大于临界值就扩容
5             resize(2 * table.length); //以2的倍数扩容
6     }

参数bucketIndex就是indexFor函数计算出来的索引值,第2行代码是取得数组中索引为bucketIndex的Entry对象,第3行就是用hash、key、value构建一个新的Entry对象放到索引为bucketIndex的位置,并且将该位置原先的对象设置为新对象的next构成链表。

  第4行和第5行就是判断put后size是否达到了临界值threshold,如果达到了临界值就要进行扩容,HashMap扩容是扩为原来的两倍。resize()方法如下:

 1 void resize(int newCapacity) {2         Entry[] oldTable = table;3         int oldCapacity = oldTable.length;4         if (oldCapacity == MAXIMUM_CAPACITY) {5             threshold = Integer.MAX_VALUE;6             return;7         }8 9         Entry[] newTable = new Entry[newCapacity];
10         transfer(newTable);//用来将原先table的元素全部移到newTable里面
11         table = newTable;  //再将newTable赋值给table
12         threshold = (int)(newCapacity * loadFactor);//重新计算临界值
13     }

扩容是需要进行数组复制的,上面代码中第10行为复制数组,复制数组是非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。

转载于:https://www.cnblogs.com/remember-forget/p/6021644.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/417857.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mudos开发简介

首先我们应该知道mudos提供哪些服务我们可以从许多lib中找到我们需要什么假设我们的lib还是拥有很多现在mud的特性 比如战斗 人物和世界我们需要一些最基本的东西比如 master.c user.c login.c <include>你完全可以使用不一样的文件名 甚至修改出自己的mudos 你也可以完全…

java hanoi塔问题_java 解决汉诺塔问题

//汉诺塔问题//HanYang 2016/10/15import java.util.Scanner;//输出public class Hanuota {public static void Show(String a,String b){System.out.print(" " a "->" b " " );}//从a移到cpublic static void Fun(int n, String a,Strin…

心跳防护程序

心跳防护程序的作用心跳防护程序用于实现无人值守的程序的自维护功能&#xff0c;使得被防护程序由于某种异常而导致崩溃时可以自动重新启动&#xff0c;满足无人值守的环境要求。心跳防护程序适用于那些需要无人值守&#xff0c;长期运行&#xff0c;但程序由于不确定性因素有…

[水煮 ASP.NET Web API2 方法论](1-1)在MVC 应用程序中添加 ASP.NET Web API

问题 怎么样将 Asp.Net Web Api 加入到现有的 Asp.Net MVC 项目中 解决方案 在 Visual Studio 2012 中就已经把 Asp.Net Web Api 自动地整合到了 MVC 项目模板中。自 Visual Studio 2013 以后&#xff0c;创建 ASP.Net Web应用程序使用的是一种新的"One ASP.NET"项目…

java io流文件损坏_java使用io流下载.docx. xlsx文件,出现文件损坏提示

介绍在使用io流下载服务器上的资源文件时&#xff0c;出现以下提示&#xff1a;但是选择“是”后文件可以正常打开参考 代码如下&#xff1a;[codesyntax lang"java"]RequestMapping(value "downLoadInstruction")public void downLoadInstruction(HttpSe…

java des对称加密_JAVA加密解密DES对称加密算法

1 下面用DES对称加密算法(设定一个密钥&#xff0c;然后对所有的数据进行加密)来简单举个例子。23 首先&#xff0c;生成一个密钥KEY。4 我把它保存到key.txt中。这个文件就象是一把钥匙。谁拥有它&#xff0c;谁就能解开我们的类文件。代码参考如下&#xff1a;5 packagecom.n…

计算机的存储单位

位 bit (比特)(Binary Digits)&#xff1a;存放一位二进制数&#xff0c;即 0 或 1&#xff0c;最小的存储单位。字节 byte&#xff1a;8个二进制位为一个字节(B)&#xff0c;最常用的单位。计算机存储单位一般用B&#xff0c;KB&#xff0c;MB&#xff0c;GB&#xff0c;TB&am…

C内存管理详解

【规则1】用malloc或new申请内存之后&#xff0c;应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。 【规则2】不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。 【规则3】避免数组或指针的下标越界&#xff0c;特别要当心发生“多1”或者“…

Dubbo xml配置 和注解配置 写法

<?xml version"1.0" encoding"UTF-8"?><!-- - Copyright 1999-2011 Alibaba Group. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the Licen…

java solr_通过Java访问Solr服务实例及相关配置

一、通过Java访问Solr服务(手动创建索引库)1、创建项目&#xff0c;配置环境(导包及相关文件)1、SolrJ核心包 /solr-4.10.3/dist/solr-solrj-4.10.3.jar2、SolrJ依赖包 /solr-4.10.3/dist/solrj-lib下的所有包3、日志依赖包 /solr-4.10.3/example/lib/ext目录下的所有jar包4、J…

WCF RIA优缺点

优点&#xff1a;WCF RIA提供方便的数据访问形式&#xff0c;服务端代码写在.cs文件中继承DomainService&#xff0c;类前要声明[EnableClientAccess()]生成后sliverlight项目中/Generated_Code下会有对应的.g.cs文件&#xff0c;该文件会包含WCF RIA服务中各方法的访问方法。缺…

SpringMVC+MyBatis 返回时间格式转换的解决方案

Spring MVC 4.X ResponseBody 日期类型Json 处理 摘自http://tramp-zzy.iteye.com/blog/2090330 2014-07-10方法一&#xff1a;全局处理 <mvc:annotation-driven> <!-- 处理responseBody 里面日期类型 --> <mvc:message-converters> …

SqlServer Split功能函数

CREATE FUNCTION [dbo].[StrSplit](str nvarchar(max), --源字符串split varchar(32) --分隔符)RETURNS t TABLE(col nvarchar(max))AS BEGINWHILE(charindex(split,str)<>0) BEGININSERT t(col) VALUES(substring(str,1,charindex(split,str)-1))SET str stuff(s…

java 内存堆和栈_java堆内存和栈内存的处理

前段时间学习二叉树在处理删除操作的时候遇到一个头疼的问题&#xff1a;删除节点的时候明明已经置null了可树上该节点依旧存在&#xff0c;还必须执行node.father.left null;才可以删除node节点&#xff0c;寻找了一下原因发现还是因为对java内存管理理解不够深入。代码如下&…

maven编译项目时提示:cached in the local repository

1.今天使用命令mvn compile编译maven项目时提示错误信息&#xff0c;部分错误信息如下&#xff1a; Failure to transfer wsdl4j:wsdl4j:jar:1.6.3 from http://xx.xx.xx.xx:8081/nexus/content/groups/public was cached in the local repository, resolution will not be re…

MS SQLSERVER中如何快速获取表的记录总数

&#xff08;转自&#xff1a;http://www.cnblogs.com/pingkeke/archive/2006/05/29/411995.html&#xff09;在数据库应用的设计中&#xff0c;我们往往会需要获取某些表的记录总数&#xff0c;用于判断表的记录总数是否过大&#xff0c;是否需要备份数据等。我们通常的做法是…

java 压缩指定文件_Java将指定文件/文件夹压缩成zip、rar压缩文件

import org.apache.tools.zip.ZipEntry;import org.apache.tools.zip.ZipOutputStream;import java.io.*;import java.util.zip.CheckedOutputStream;import java.util.zip.CRC32;/*** author chenssy** 将指定文件/文件夹压缩成zip、rar压缩文件*/public class ZipCompressor …

python基础(十三)

项目实战&#xff1a;运维堡垒机开发 前景介绍 到目前为止&#xff0c;很多公司对堡垒机依然不太感冒&#xff0c;其实是没有充分认识到堡垒机的IT管理中的重要作用的&#xff0c;很多人觉得&#xff0c;堡垒机就是跳板机&#xff0c;其实这个认识是不全面的&#xff0c;跳板功…

使用 TABLESAMPLE 限制结果集

TABLESAMPLE 子句将从 FROM 子句中的表返回的行数限制到样本数或行数的某一百分比。例如&#xff1a; 复制代码 TABLESAMPLE (10 PERCENT) /*Return a sample 10 percent of the rows of the result set. */TABLESAMPLE (15 ROWS) /* Return a sample of 15 rows from the resu…

Catalan数(数论)

Catalan数 【参考网址】http://www.cnblogs.com/gongxijun/p/3232682.html 记得当时我们队写过一个&#xff0c;差点超时&#xff0c;现在找到了公式&#xff0c;感觉还是挺简单的。 还要注意&#xff0c;就算开long long 也只能表示到第33个&#xff0c;之后就会溢出。 &…