hashhMap
# hashMap原理 #
HashMap是一个双列集合,是线程不安全的。以key、value的形式储存值。底层是由数组+链表+红黑树组成的,数组是HashMap的主干,链表则是主要为了解决哈希冲突而存在的,根据key计算出hash值,存储在数组里面,当hsah值冲突的时候,通过equals方法比较,如果不同就创建链表存储在链表里面。当链表长度超过8的时候,会自动转化为红黑树。他的容量initialCapacity默认为16,负载因子loadFactory默认为0.75。当存储的容量大于hashmap的容量乘以0.75的时候,就会自动扩容。
我们通过hash方法计算索引,得到数组中保存的位置,看一下源码
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
HashMap中的hash算法是通过key的hashcode值与其hashcode右移16位后得到的值进行异或运算得到的
扩展问题:为什么扩容的时候为啥一定必须是2的多少次幂?
因为如果只有2的n次幂的情况时最后一位二进制数才一定是1,这样能最大程度减少hash碰撞。
链表数据插入方法
l HashMap 1.7使用的是头插法,扩容后位置与原链表位置相反。防止尾部遍历,不然每次插入的时候都要定位到尾部节点,如果多线程情况下容易形成环;
l HashMap 1.8使用的是尾插法,因为其中会使用到红黑树,构建红黑树有个过程,扩容后位置与原链表相同;
扩展:因为HashMap 1.7是用单链表进行的纵向延伸,当采用头插法时会容易出现逆序且环形链表死循环问题。但是在HashMap 1.8之后是因为加入了红黑树使用尾插法,能够避免出现逆序且链表死循环的问题。
因为新数组table下标并不是根据循环逐步递增
的,而是通过(table.length-1)& hash计算得到,因此扩容后,存放的位置就
可能发生变化,那么到底发生怎样的变化呢,就是由下面的算法得到.
* 通过e.hash & oldCap来判断节点位置通过再次hash算法后,是否会发生改变,如
* 果为0表示不会发生改变,如果为1表示会发生改变。到底怎么理解呢,举个例子:
* e.hash = 13 二进制:0000 1101
* oldCap = 32 二进制:0001 0000
* &运算: 0 二进制:0000 0000
* 结论:元素位置在扩容后不会发生改变
*/
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
/**
* e.hash = 18 二进制:0001 0010
* oldCap = 32 二进制:0001 0000
* &运算: 32 二进制:0001 0000
* 结论:元素位置在扩容后会发生改变,那么如何改变呢?
* newCap = 64 二进制:0010 0000
* 通过(newCap-1)&hash
* 即0001 1111 & 0001 0010 得0001 0010,32+2 = 34
*/
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
/**
* 若(e.hash & oldCap) == 0,下标不变,将原表某个下标的元素放到扩容表同样
* 下标的位置上
*/
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
/**
* 若(e.hash & oldCap) != 0,将原表某个下标的元素放到扩容表中
* [下标+增加的扩容量]的位置上
*/
newTab[j + oldCap] = hiHead;
}
}
}
# hashMap和hashTable的区别 #
hashmap线程不安全,hashtable线程安全。
hashmap键值可以为null。
hashtable键值不可以为null。
hashMap和hashTable的实现原理区别
继承的父类不同
对外提供的接口不同
对Null key 和Null value的支持不同
线程安全性不同
遍历方式的内部实现上不同
初始容量大小和每次扩充容量大小的不同
计算hash值的方法不同
ConcurrentHashMap
1、悲观锁与乐观锁:
悲观锁是指如果一个线程占用了一个锁,而导致其他所有需要这个锁的线程进入等待,一直到该锁被释放,换句话说就是这个锁被独占,比如说典型的就是synchronized;乐观锁是指操作并不加锁,而是抱着尝试的态度去执行某项操作,如果操作失败或者操作冲突,那么就进入重试,一直到执行成功为止。
2、原子性,指令有序性和线程可见性:
这三个性质在多线程编程中是核心的问题。原子性和事务的原子性一样,对于一个操作或者多个操作,要么都执行,要么都不执行。指令有序性是指,在我们编写的代码中,上下两个互不关联的语句不会被指令重排序。指令重排序是指处理器为了性能优化,在无关联的代码的执行是可能会和代码顺序不一致。比如说int i = 1;int j = 2;那么这两条语句的执行顺序可能会先执行int j = 2;线程可见性是指一个线程修改了某个变量,其他线程能马上知道。
3、无锁算法(nonblocking algorithms):
使用低层原子化的机器指令, 保证并发情况下数据的完整性。典型的如CAS算法。
4、内存屏障:
在《深入理解JVM》中解释是:它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;它会强制将对缓存的修改操作立即写入主存;如果是写操作,它会导致其他CPU中对应的缓存行无效。在使用volatile修饰的变量会产生内存屏障。
volatile关键字
在基本清除了java内存模型之后,我们开始详细说明一下volatile关键字,在concurrentHashMap之中,有很多的成员变量都是用volatile修饰的。被volatile修饰的变量有如下特性:
①使得变量更新变得具有可见性,只要被volatile修饰的变量的赋值一旦变化就会通知到其他线程,如果其他线程的工作内存中存在这个同一个变量拷贝副本,那么其他线程会放弃这个副本中变量的值,重新去主内存中获取
②产生了内存屏障,防止指令进行了重排序,关于这点的解释,请看下面一段代码:
public class VolatileTest { int a = 0; //
int b = 1; //
volatile int c = 2; //
int d = 3; //
int e = 4; // }
在如上的代码中,因为c变量是用volatile进行修饰,那么就会对该段代码产生一个内存屏障,用以保证在执行语句3的时候语句1和语句2是绝对执行完毕的,而且在执行语句3的时候,语句4和语句5肯定没有执行。同时说明一下,在上述代码中虽然保证了语句3的执行顺序不可变换,但是语句1和语句2,语句4和语句5可能发生指令重排序哦。
hashhMap的更多相关文章
随机推荐
- 自然数幂求和——第二类Strling数
这个问题似乎有很多种求法,但感觉上第二类Strling数的做法是最方便的. 问题 求下面这个式子: ∑i=0nik\sum_{i=0}^n i^ki=0∑nik nnn的范围可以很大. 第二类Str ...
- HAVING方法也是连贯操作之一
HAVING方法也是连贯操作之一,用于配合group方法完成从分组的结果中筛选(通常是聚合条件)数据. having方法只有一个参数,并且只能使用字符串,例如: $this->field('us ...
- Redis深度历险——核心原理与应用实践
高可用架构」的各位老铁们,你们好!你是否还记得上个月发布的文章中,有两篇深入讲解Redis的文章,分别是和,广大粉丝读者们对这两篇文章整体评价颇高.而我就是这两篇文章的原创作者「老钱」(钱文品),我是 ...
- Sharepoint常见概念
有待补充: 1.环境部署(AD+DNS+SQL+SharePoint前端): SharePoint基本都是这样的结构,可以在多台服务器中,也就是场,当然也可以在一台服务器上.说说这几部分的功能 (1) ...
- Django logging 的配置方法
做开发离不开日志,以下是我在工作中写Django项目常用的logging配置. BASE_LOG_DIR = os.path.join(BASE_DIR, "log") LOGGI ...
- 07_springmvc校验
一.概述 项目中,通常使用较多是前端的校验,比如页面中js校验.对于安全要求较高点建议在服务端进行校验. 服务端校验: 控制层conroller:校验页面请求的参数的合法性.在服务端控制层conrol ...
- gulpfile.js demo
var config = require("./build.config") //获取build.config.js文件里的内容 var gulp = require(" ...
- mysql下突然丢失权限
mysql Can't read dir of 今天打开mysql数据库突然报了这个错误 查后发现是表没权限,进行了mysql的data目录权限修改 1.show global variables ...
- 利用zepto.js实现移动页面图片全屏滑动
HTML <%-- Created by IntelliJ IDEA. User: fanso2o Date: 2017/2/28 Time: 16:09 To change this temp ...
- pip报错ImportError: cannot import name main
编辑pip sudo gedit /usr/bin/pip 修改pip文件: 源文件 from pip import main if __name__ == '__main__': sys.exit( ...