最近又研究了一下关于map衍生的类,hashMap LinkedHashMap TreeMap hashTable ConcurrentHashMap,没有对代码的粘贴,纯粹用文字根据自己的理解看了一遍,以此简单的整理。
/**
*定义:Map java中的一种数据存储结构,它的存储类型是key,val形式的,key,val都可以为空,但只保存一份,key的存储是基于键的hash
*值进行一些运算得到物理存储的地址。
*对于Map的实现类,我们着重讲一下HashMap Hashtable ConcurrentHashMap
*HashMap: 内部数据存储的方式是基于数组和链表的形式进行存储的。数组的下标就是根据key的hashCode值进行运算的出来的。下面我们看下
*它是如何计算的:(h = key.hashCode()) ^ (h >>> 16) 这个是计算hash的值防止hash的一个冲突(^相同为0,不同为1)
*计算下标:(n - 1) & hash ( &全1为1 其他为0 ) 计算下标值,其下标值不会超过map自定义的下标值
*计算完下标的值以后,接下来就是解决hash冲突,和值得替换。
*如果映射到的下标上面值为空,直接创建一个新的节点放到对应的
*下标下面就可以了。数组放的对象Node是实现Map内部的Entry接口,储存的值有hash,键,值,链表的下一个元素。
*如果不为空,判断hash值和值是否与此下标下的值相等,如果相等就覆盖,不等遍历链表上的值进行比较。
*首先:这里需要注意的是binCount 这个值是判断在解决冲突的使用链表还是用红黑树
*第二:在遍历的时候,其实很简单的就是有一个无限的for循环,然后就是修改e的值,以及链表转树的过程。
*接下来你会发现两个方法:afterNodeAccess afterNodeInsertion,这两个是用于排序的,但是hashmap并没有实现,所以是无须的,
*而LinkedHashMap实现了这两个方法,所以LinkedHashMap是有序的。
*最后最重要的就是一个扩容机制,注意两点吧:
*第一:扩容后的大小为多少,一般是原来的2倍,其他情况下可能是是integer的最大值
*第二:在扩容的时候,(e.hash & oldCap) == 0 这个我也不明白
基本上插入就说完了。
下面是取数:
*取数据就很简单了,首先根据hash找到对应数据的下标,再分三种情况去找到对应的数据。
其实在hashMap中有几个关键值需要掌握以下:
*DEFAULT_INITIAL_CAPACITY 默认容量大小
*MAXIMUM_CAPACITY 最大的容量
*DEFAULT_LOAD_FACTOR 扩张因子,用于判断当前的容量是否需要扩容的一个因子
*threshold 扩容标识带下,大于就扩容
*TREEIFY_THRESHOLD 链表转平衡二叉树的标识
**/
/**
LinkedHashMap 是一个有顺序的map,本身继承与HashMap 只是他实现了afterNodeAccess afterNodeInsertion 这个两个方法
所以他是一个有顺序的map,而且里面维护的是一个双端链表
*LinkedHashMap 里面的Entry 继承制hashmap里的node
*对于插入的操作,本身用到得是父类的方法,只是实现了其中的两个方法。
*afterNodeAccess:move node to last 访问过就排到最后
*
*afterNodeInsertion:possibly remove eldest
*
*对于上面的两部分的理解,其实拿出5分钟画个简单的草图就理解了。
*
除了LRU ,其他的功能和hashmap差不多。
**/
/**
*treemap 一种可以自定义排序map,内部引用Comparator实现排序。
*默认是根据键的compareTo 进行排序的
*可以在实例化treemap的时候 自己实现Comparator类,来实现自己定义的排序规则
*
*/
/**
*在研究hashMap的时候我们忘说了关于多线程的问题,其实 hashMap是线程不安全的,这样我们来看下,为什么
*Hashmap不是线程安全的。
*首先线程的定义:当在多线程的情况下,我们的数据不会因为线程切换而导致数据异常就是线程安全的。
*而对于hashmap来说,它在多线程的情况下,在添加,删除,查询的时候回出现异常。
*
*
**/
/**
*基于hashmap是线程不安全的,而我们现在都是多线程的环境下,jdk也有线程线程安全的类,hashtable,ConcurrentHashMap
*对于hashtable来说,它在实现线程安全的时候是在所有线程不安全的方法上面添加synchronized关键字,而ConcurrentHashMap 1.8之前是针对于Segment
*的分段锁外,1.8之后是基于乐观锁的思想进行的一个添加和获取,是一个并发散列映射表的实现,它允许完全并发的读取,并且支持给定数量的并发更新,
*加volatile来做线程安全的。但是在一定的程度上,ConcurrentHashMap 不能完全代替hashtable,虽然分段锁的锁粒度小,运行的速度快,
*但是它在极限的条件下会出现一个修改异常,但这个是很小概率发生的,所以我们在用的时候都不太会考虑这个问题。对于hashtable基本就是在
*hashmap上面进行添加synchronized关键字,没什么好讲的,接下来我们重点分析一下ConcurrentHashMap
*
*ConcurrentHashMap,从初始化,插入数据,查询数据,删除数据四个层面说
*
*初始化:初始化和hashmap基本一样的
*
*put:在添加数据的时候,hash的值的计算是一样的,首先获取一下添加数据计算出来的位置上面是否有数据,用到了,unsafe
*安全类,在获取到为空的情况下,会设置一个空值casTabAt(tab, i, null,new Node<K,V>(hash, key, value, null)),底层
*的实现是U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v)
*如果当前的值得hash为-1就 如果调整了大小,可以帮助转移。基本在添加的时候也是基平衡树。
*插入是并发式的,锁是基于synchronized 的做线程安全,其中优化表大小,以及扩容机制
*
*get:也是可以并发的读,内部的实现是通过unfade的getObjectVolatile根据内存地址进行获取,保证了除table是Volatile
*下而内部对象不是Volatile会出现数据异常的情况
*
*
**/