Map集合

介绍

 

Map在Java⾥边是⼀个接⼝,常⻅的实现类有HashMap、LinkedHashMap、TreeMap和ConcurrentHashMap

 

 内嵌类

 

 

 

 

Map是java中的接口,Map.Entry是Map的一个内部接口

Map集合封装键值对类型的数据,在Map集合的底层,每一个Key-Value键值对都被封装到一个Entry类型的子容器中,一个Entry对象只包含一个键值对,Map集合底层使用entry对象来存储这些键值对,在遍历Map集合时,可以通过遍历Key来遍历Map集合,还可以通过遍历entry对象来遍历集合。

Entry接口提供了getKey()方法和getValue()方法,用来获取entry对象存储的key和value值。

map  中的key使用set来进行存放的,注意由于是set来进行存储的,   那么不允许key是重复的

Map 接口提供三种collection 视图,允许以键集、值集或键-值映射关系集的形式查看某个映射的内容。

k,v默认是object类型

 常见实现

 

 

 

 

 常用API

包含键/值

 

 

 

存储/复制存储/如果不存在则存储否则返回当前值

 

 移除

 

 

 

三种视图

结论:使用键值映射关系集,entry视图

键值映射关系集   

推荐   直接通过Entry对象获取键和值,一个Entry只存储一个键值对

 

 

 1     @Test
 2     public void mapTest() {
 3         HashMap<String,Object>map=new HashMap<>();
 4         map.put("1号",1);
 5         map.put("2号","222");
 6         map.put("3号",666);
 7         for(Map.Entry<String,Object> e:map.entrySet()
 8         ){
 9             System.out.println(e.getKey()+"="+e.getValue());
10         }
11     }

 

 

键集

只获取Map的键,返回Set

 

 

1         //返回键的set视图
2         Set<String>se=map.keySet();
3         for(String a:se
4         ){
5         System.out.println("k:"+a+map.get(a));
6 }

 

值集

只获取Map的值,返回Collection,而且没有通过值找键的直接API

1         //返回值的Collection视图
2         Collection<Object>k=map.values();
3         for(Object o:k
4         ){
5         //该视图没有通过值找键的方法,Entry里面有直接获取键、值的方法
6         System.out.println(o);
7 }

 

 

HashMap

结果和hashset一样,无序,不能保证映射的顺序
通过hashcodeequals来判断键是否重复
基于hash表的Map接口实现,HashMap底层数据结构是数组+链表/红⿊树
此实现提供所有可选的映射操作,并允许使用null值和null键

HashMap类大致相当于Hashtable ,除了它是不同步的,并允许null

 

JDK1.8与JDK1.7的区别

数据结构:JDK1.8是数组+链表/红黑树;JDK1.7是数组+链表

存储对象:JDK1.8存的是节点对象,Node(单链),树节点(TreeNode,双向链表);JDK1.7存的是Entry对象

hash算法:JDK1.8是hashCode与高16位进行异或;JDK1.7是对String类型有个单独处理sun.misc.Hashing.stringHash32((String) k),再一堆异或尽量分散;

扩容:JDK1.8是插入后扩容,JDK1.7是插入前扩容

插入:JDK1.8是尾插法;JDK1.7是头插法,原因HashMap非线程安全,解决多线程死循环问题,多线程下推荐使用ConCurrentHashMap(线程安全的HashMap)

 

 

 

 

 

 

 

构造方法

负载因子是在容量自动增加之前允许哈希表得到满足的度量

通常,默认负载因子(0.75)提供了时间和空间成本之间的良好折衷。 更高的值会减少空间开销,但会增加查询条目的时间成本

 

 

 

常用API

 

 

 

 

 

 

 

 

 

 

 

 

 

 HashMap new机制

 HashMap有⼏个构造⽅法,但最主要的就是指定初始值⼤⼩负载因⼦的⼤⼩。默认HashMap的⼤⼩为16,负载因⼦的⼤⼩为0.75

HashMap的⼤⼩只能是2次幂的,假设你传⼀个10进去,实际上最终HashMap的⼤⼩是16,你传⼀个7进去,HashMap最终的⼤⼩是8,具体的实现在tableSizeFor可以看到。

把元素放进HashMap的时候,需要算出这个元素所在的位置(hash),在HashMap⾥⽤的是位运算来代替取模,能够更加⾼效地算出该元素所在的位置。

为什么HashMap的⼤⼩只能是2次幂,因为只有⼤⼩为2次幂时,才能合理⽤位运算替代取模(

右移是按2的倍数进行放缩, 2/4 =》2/ 2^2 =》2右移2位010 右移两位 =》 2/ 2^2的商,被移掉的两位=》2/ 2^2 的余数)

 

 

 

 负载因⼦的⼤⼩决定着哈希表的扩容和哈希冲突,作为一般规则,默认负载因子(.75)提供了时间和空间成本之间的良好折中。 更高的值会降低空间开销,但会增加查找成本、发生hash碰撞的概率

⽐如现在我默认的HashMap⼤⼩为16,负载因⼦为0.75,这意味着数组最多只能放12个元素,⼀旦超过12个元素,则哈希表需要扩容,HashMap的⼤⼩只能是2次幂,所以是2倍扩容

                                                                              --java3y, JDK8API文档

 

HashMap判断完全相同

hash一致&&(key地址一致或者keyequals相同)

1 first.hash == hash && // always check first node
2                 ((k = first.key) == key || (key != null && key.equals(k)))

 

HashMap Put机制

Key的哈希值计算

 先通过hashCode()正常计算哈希值,然后与⾼16位做异或运算,产⽣最终的哈希值。

增加随机性。

1     public V put(K key, V value) {
2         return putVal(hash(key), key, value, false, true);
3     }
1     static final int hash(Object key) {
2         int h;
      // 低位和高位都参与运算 增加数据分散性
3 return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); 4 }

生成Index

再与桶数-1做与运算 (取模运算的变种,前提是桶数为2的幂次,位运算更快) 以确定Index

 

 

 put大致过程

计算hash后调用putval(),

数组为空就调resize初始化数组,通过(n-1)&hash位运算得到index

未发生碰撞直接新建Node存入

发生碰撞,判断是否是树,是的话遍历,按树结构新建TreeNode存入,发现完全相同的key则直接替换

不是树就是链表了,遍历按链表结构存入,发现相同的key则直接替换,尾插式,链表长度大于8,调treeifyBin如果数组长度大于64则会转为红黑树,否则调resize进行扩容先存入节点再扩容

最后会判断是否超过阈值,超过阈值也会扩容(先存入节点再扩容)

resize对树按高低位进行拆分成两个链表后,长度<6的树会退化成链表Node

 

链表和树的复杂度

链表查询时间复杂度O(N),插⼊时间复杂度O(1),红⿊树查询和插⼊时间复杂度O(logN)

源码分析

  小破站视频 https://www.bilibili.com/video/BV1oR4y1N77G/?p=6&spm_id_from=pageDriver&vd_source=152ad2dc192867dca92d66a24472c851

  1 /**
  2  * put源码
  3  */
  4 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
  5 boolean evict) {
  6 // 用局部变量操作,多次取值,从栈里面比从堆中取快
  7 Node<K,V>[] tab; Node<K,V> p; int n, i;
  8 // 判断索引数组是否为空 为空就初始化数组 resize可以初始化/扩容数组 
  9 if ((tab = table) == null || (n = tab.length) == 0)
 10 n = (tab = resize()).length;
 11 // (n-1)&hash 位运算代替取模(前提是容量是2的幂次) 算出index
 12 if ((p = tab[i = (n - 1) & hash]) == null)
 13 // 没有发生hash碰撞 直接存入node节点
 14 tab[i] = newNode(hash, key, value, null);
 15 // 发生hash碰撞
 16 else {
 17 Node<K,V> e; K k;
 18 // 如果key完全相等  hash相同再比较(引用地址||equals)
 19 if (p.hash == hash &&
 20 ((k = p.key) == key || (key != null && key.equals(k))))
 21 e = p;
 22 // 如果key不是完全相等
 23 // 判断是否是树节点
 24 else if (p instanceof TreeNode)
 25 // 红黑树的插入操作
 26 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
 27 // 如果是链表
 28 else {
 29 // 遍历 binCount记录当前链表上的节点数
 30 for (int binCount = 0; ; ++binCount) {
 31 // 尾差 1.7是头插
 32  if ((e = p.next) == null) {
 33      p.next = newNode(hash, key, value, null);
 34     //  -1是因为 0-7是8
 35      if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
 36     //  转红黑树,8 + 新要添加的该节点,实际是9个 treeIfyBin里面会进行扩容
 37          treeifyBin(tab, hash);
 38      break;
 39  }
 40 //  如果在当前链表下发现一样的key,更新链表节点
 41  if (e.hash == hash &&
 42      ((k = e.key) == key || (key != null && key.equals(k))))
 43      break;
 44  p = e;
 45 }
 46 }
 47 // 补充:key相同的旧节点存在
 48 if (e != null) { // existing mapping for key 
 49 V oldValue = e.value;
 50 // onlyIfAbsent缺少才会存入  一个标记 对应putIfAbsent()
 51 if (!onlyIfAbsent || oldValue == null)
 52 // 更新节点值
 53  e.value = value;
 54 afterNodeAccess(e);
 55 // 返回旧值
 56 return oldValue;
 57 }
 58 }
 59 ++modCount;
 60 // 判断是否> 阈值 要进行扩容
 61 if (++size > threshold)
 62 resize();
 63 afterNodeInsertion(evict);
 64 return null;
 65 }
 66 
 67 /------------------------------------------------------------树化处理
 68 
 69 /**
 70  * Replaces all linked nodes in bin at index for given hash unless
 71  * table is too small, in which case resizes instead.
 72  * 
 73  * 树化处理,真正处理是treeify()
 74  * Node改为TreeNode,单项链表变成双向链表
 75  */
 76 final void treeifyBin(Node<K,V>[] tab, int hash) {
 77     int n, index; Node<K,V> e;
 78     // 索引数组的长度小于64 会resize重排
 79     if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
 80         resize();
 81     else if ((e = tab[index = (n - 1) & hash]) != null) {
 82         TreeNode<K,V> hd = null, tl = null;
 83         do {
 84             TreeNode<K,V> p = replacementTreeNode(e, null);
 85             if (tl == null)
 86                 hd = p;
 87             else {
 88                 // 单项链表改为双向链表
 89                 p.prev = tl;
 90                 tl.next = p;
 91             }
 92             tl = p;
 93             // 遍历Node节点 生成TreeNode 
 94         } while ((e = e.next) != null);
 95         if ((tab[index] = hd) != null)
 96         // 真正改为红黑树 
 97             hd.treeify(tab);
 98     }
 99 }
100 
101 
102 
203 
204 
205 
206 /**
207  * Splits nodes in a tree bin into lower and upper tree bins,
208  * or untreeifies if now too small. Called only from resize;
209  * see above discussion about split bits and indices.
210  *
211  * @param map the map
212  * @param tab the table for recording bin heads
213  * @param index the index of the table being split
214  * @param bit the bit of hash to split on
215  * 
216  * 分割树节点为高低位树节点,或者如果太小则取消树化。被resize方法调用
217  */
218 final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
219     TreeNode<K,V> b = this;
220     // Relink into lo and hi lists, preserving order
221     TreeNode<K,V> loHead = null, loTail = null;
222     TreeNode<K,V> hiHead = null, hiTail = null;
223     int lc = 0, hc = 0;
224     // 遍历 拆分高低位树节点
225     for (TreeNode<K,V> e = b, next; e != null; e = next) {
226         next = (TreeNode<K,V>)e.next;
227         e.next = null;
228         if ((e.hash & bit) == 0) {
229             if ((e.prev = loTail) == null)
230                 loHead = e;
231             else
232                 loTail.next = e;
233             loTail = e;
234             ++lc;
235         }
236         else {
237             if ((e.prev = hiTail) == null)
238                 hiHead = e;
239             else
240                 hiTail.next = e;
241             hiTail = e;
242             ++hc;
243         }
244     }
245 
246     if (loHead != null) {
247         // 低位节点个数<6  转成链表  =》退化 TreeNode-> Node
248         if (lc <= UNTREEIFY_THRESHOLD)
249             tab[index] = loHead.untreeify(map);
250         else {
251             tab[index] = loHead;
252             if (hiHead != null) // (else is already treeified)
253                 loHead.treeify(tab);
254         }
255     }
256     if (hiHead != null) {
257         if (hc <= UNTREEIFY_THRESHOLD)
258             tab[index + bit] = hiHead.untreeify(map);
259         else {
260             tab[index + bit] = hiHead;
261             if (loHead != null)
262                 hiHead.treeify(tab);
263         }
264     }
265 }

 

扩容resize

大致过程

1.数组无数据就初始化,容量设置为16,返回数组

2.数组有数据,判断是否满载    >=MAXIMUM_CAPACITY 2^30,满载阈值拉满Integer.MAX_VALUE 2^32 -1,直接返回oldTab

3.否则按2倍容量新建一个Node数组 Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]

4.遍历oldTab,重新计算节点index,存入新数组。这里有个规律,新index=旧index/旧index+oldCap,节点有四种情况,无数据,一个,多个(树/链表)。

5.只有一个节点的直接移过去就行,树和单链表各按高低位进行拆分(hash&oldTab==0表示低位,index不变的节点)再遍历,低位index不变,高位index=旧index+oldCap,另外拆分后长度<6的树会退化成链表

 

 

 

扩容后重新计算index规律/源码算法

新index = 旧index/旧index+oldCap

 

 

 

 源码

  1 /**
  2  * Initializes or doubles table size.  If null, allocates in
  3  * accord with initial capacity target held in field threshold.
  4  * Otherwise, because we are using power-of-two expansion, the
  5  * elements from each bin must either stay at same index, or move
  6  * with a power of two offset in the new table.
  7  *
  8  * @return the table
  9  * 
 10  * 初始化或者双倍扩容数组
 11  */
 12 final Node<K,V>[] resize() {
 13     Node<K,V>[] oldTab = table;
 14     int oldCap = (oldTab == null) ? 0 : oldTab.length;
 15     int oldThr = threshold;
 16     int newCap, newThr = 0;
 17     // 老数组容量>0
 18     if (oldCap > 0) {
 19         // 判断老数组是不是已经非常大了 2^30
 20         if (oldCap >= MAXIMUM_CAPACITY) {
 21             // 0x7fffffff 2^32 - 1  ,容量拉满返回旧数组
 22             threshold = Integer.MAX_VALUE;
 23             return oldTab;
 24         }
 25         // 2倍扩容后容量<最大值且旧容量大于初始值16则阈值进行2倍扩容
 26         else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
 27                     oldCap >= DEFAULT_INITIAL_CAPACITY)
 28                     // 阈值*2
 29             newThr = oldThr << 1; // double threshold
 30     }
 31     // 旧数组非空
 32     else if (oldThr > 0) // initial capacity was placed in threshold
 33     // 初始容量设置为旧阈值
 34         newCap = oldThr;
 35     // 数组大小为0,还未初始化 
 36     else {               // zero initial threshold signifies using defaults
 37         // 初始化数组 大小为默认值16
 38         newCap = DEFAULT_INITIAL_CAPACITY;
 39         // 计算初始阈值 负载因子*初始容量
 40         newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
 41     }
 42     // 新的阈值若还未计算,计算新的阈值
 43     if (newThr == 0) {
 44         // 新容量*负载因子
 45         float ft = (float)newCap * loadFactor;
 46         // 如果新容量<最大容量且计算阈值<最大容量 则使用计算阈值,否则拉满
 47         newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
 48                     (int)ft : Integer.MAX_VALUE);
 49     }
 50     // 使用新阈值刷新阈值属性
 51     threshold = newThr;
 52     @SuppressWarnings({"rawtypes","unchecked"})
 53     Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
 54     table = newTab;
 55     // 老数组不为空
 56     if (oldTab != null) {
 57         // 遍历数组,一个位置四种情况,无数据,1个数据,多数据(链表,树)
 58         for (int j = 0; j < oldCap; ++j) {
 59             Node<K,V> e;
 60             //  该位置有数据, 一个数据,多数据(链表,树) 三种情况
 61             if ((e = oldTab[j]) != null) {
 62                 oldTab[j] = null;
 63                 // next==nul,表示只有一个数据,直接转移位置
 64                 if (e.next == null)
 65                 // 重新计算index 存入新数组
 66                     newTab[e.hash & (newCap - 1)] = e;
 67                     // 如果是树
 68                 else if (e instanceof TreeNode)
 69                 // 对树按高低位拆分处理,得到两条链表,长度小于6树退化
 70                     ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
 71                 // 如果是链表  根据(n-1)&hash 得到规律=》 扩容的新index = 旧index(前提条件:e.hash & oldCap == 0)/旧index+旧数组容量(反之)
 72                 else { // preserve order
 73                     Node<K,V> loHead = null, loTail = null;
 74                     Node<K,V> hiHead = null, hiTail = null;
 75                     Node<K,V> next;
 76                     // 将链表拆成高位链表和低位链表,分别放到新的index位置
 77                     do {
 78                         next = e.next;
 79                         // 通过e.hash & oldCap == 0 区分高低位节点,该类节点重新计算index的结果是index不变
 80                         // e.hash & oldCap == 0 说明newCap-1的最高位&hash的对位是0,则,(newCap-1)&hash == (oldCap-1)&hash
 81                         if ((e.hash & oldCap) == 0) {
 82                             if (loTail == null)
 83                             // 作为低位链表的头结点 
 84                                 loHead = e;
 85                             else
 86                                 loTail.next = e;
 87                             loTail = e;
 88                         }
 89                         // 该类节点新index = 旧index + oldCap
 90                         else {
 91                             if (hiTail == null)
 92                             // 作为高位链表的头结点
 93                                 hiHead = e;
 94                             else
 95                                 hiTail.next = e;
 96                             hiTail = e;
 97                         }
 98                     } while ((e = next) != null);
 99                     // 设置最后节点的指针指向null
100                     if (loTail != null) {
101                         loTail.next = null;
102                         // 设置低位连边的头节点
103                         newTab[j] = loHead;
104                     }
105                     if (hiTail != null) {
106                         // 设置高位链表最后节点指针指向null
107                         hiTail.next = null;
108                         // 设置高位链表头结点
109                         newTab[j + oldCap] = hiHead;
110                     }
111                 }
112             }
113         }
114     }
115     return newTab;
116 }

 

 HashMap get机制

先通过hash计算index位置

当前位置为空则直接返回null,不为空判断key是否完全相同,是则直接返回

不是则根据树结构或者链表结构进行遍历,找不到仍然返回null

 

完全相同:

first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k)))

 

get源码

 1 /**
 2  * Returns the value to which the specified key is mapped,
 3  * or {@code null} if this map contains no mapping for the key.
 4  */
 5 public V get(Object key) {
 6     Node<K,V> e;
 7     return (e = getNode(hash(key), key)) == null ? null : e.value;
 8 }
 9 
10 /**
11  * Implements Map.get and related methods.
12  *
13  * @param hash hash for key
14  * @param key the key
15  * @return the node, or null if none
16  */
17 final Node<K,V> getNode(int hash, Object key) {
18     Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
19     // 通过hash计算索引位置,该位置对象为空则直接返回null,否则继续
20     if ((tab = table) != null && (n = tab.length) > 0 &&
21         (first = tab[(n - 1) & hash]) != null) {
22             // 如果key完全相同,返回该对象
23         if (first.hash == hash && // always check first node
24             ((k = first.key) == key || (key != null && key.equals(k))))
25             return first;
26             // 否则判断next是否存在
27         if ((e = first.next) != null) {
28             // 如果为树则从树中取
29             if (first instanceof TreeNode)
30                 return ((TreeNode<K,V>)first).getTreeNode(hash, key);
31             do {
32                 // 遍历链表
33                 if (e.hash == hash &&
34                     ((k = e.key) == key || (key != null && key.equals(k))))
35                     return e;
36             } while ((e = e.next) != null);
37         }
38     }
39     // 找不到返回null
40     return null;
41 }

 

HashMap和TreeMap选用

TreeMap是红黑树结构,按自然顺序或者自定义顺序排列,是有序的,适用于按自然顺序排列的场景。

HashMap是哈希表实现,数据时散列的,均匀的,不固定顺序,适用于在 Map中插入删除和定位数据,性能比TreeMap好。

 

concurrentHashMap

参考:https://www.bilibili.com/video/BV1F64y1R7v6/?p=2&spm_id_from=pageDriver&vd_source=152ad2dc192867dca92d66a24472c851

HashMap非线程安全,线程安全用ConcurrentHashMap

JUC下的线程安全的Map类实现(hashTable和用Collections包装出来的线程安全的Map都是用synchronize外套成 同步的,速度慢)

ConcurrentHashMap的底层数据结构是数组+链表/红⿊树它能⽀持⾼并发的访问和更新,是线程安全的

ConcurrentHashMap通过在部分加锁和利⽤CAS算法来实现同步,在get的时候没有加锁,Node都⽤了volatile给修饰

在扩容时,会给每个线程分配对应的区间,并且为了防⽌putVal导致数据不⼀致,会给线程的所负责的区间加锁

                                               --java3y《对线面试官》

 get不加锁,volatile修饰提供对其他线程的可见性

不支持key或者value为null:因为多线程环境下,如果get(key)返回null,就无法判断值是null还是没有该key(A线程调containsKey时,B线程把值改了或者删掉了该key);而单线程HashMap却可以用containsKey(key)判断是否包含了这个key
迭代器是弱一致性的,在遍历过程中,内部元素可能发生变化,如果发生变化在已遍历部分,迭代器就不会反映出来,如果发生在未遍历过的部分,迭代器就会发现并反应出来,这就是弱一致性。这样迭代器可以使用原来的老数据,而写线程也可以并发的完成改变,保证了多个线程并发执行的连续性和扩展性,是性能提升的关键

 

 

 

JDK1.7

  • ConcurrentHashMap 是通过数组 + 链表实现,由 Segment 数组和 Segment 元素里对应多个 HashEntry 组成
  • value 和链表都是 volatile 修饰,保证可见性
  • ConcurrentHashMap 采用了分段锁技术,分段指的就是 Segment 数组,其中 Segment 继承于 ReentrantLock
  • 理论上 ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量)的线程并发,每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment

通过segment数组和hashEntry构成,ConcurrentHashMsp把Hash桶数组切分成小数组(segment),每个小数组有n个HashEntry组成

将数据分为一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一端数据时,其它段数据也能被其他线程访问,实现并发访问

 

Segment是ConcurrentHashMap的一个内部类主要的组成,继承ReentrantLock,volatile修饰HashEntry<K,V>[] table可以保证在数组扩容时的可见性

 volatile修饰HashEntry的数据value和下一个节点next,保证了多线程环境下数据获取时的可见性

 

JDK1.8

 

和HashMap的结构一样,采用Node数组+链表+红黑树 

在锁的实现上,抛弃了原有的segment分段锁,采用CAS+synchronized实现更加细粒度的锁,将锁的级别控制在更加细粒度的哈希桶数组元素级别,只需要锁住这个链表头节点(/红黑树根节点),就不会影响其他的数组元素的读写

 

  • 抛弃了原有的 Segment 分段锁,采用了 CAS + synchronized 来保证并发安全性
  • HashEntry 改为 Node,作用相同
  • val next 都用了 volatile 修饰

 

put 方法逻辑:

  • 根据 key 计算出 hash 值
  • 判断是否需要进行初始化
  • 根据 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋
  • 如果当前位置的 hashcode == MOVED == -1,则需要扩容
  • 如果都不满足,则利用 synchronized 锁写入数据
  • 如果数量大于 TREEIFY_THRESHOLD 则转换为红黑树

 

get 方法逻辑:

(不需要加锁,有volatile修饰HashEntry的元素value和next指针)

  • 根据计算出来的 hash 值寻址,如果在桶上直接返回值
  • 如果是红黑树,按照树的方式获取值
  • 如果是链表,按链表的方式遍历获取值

 

JDK 1.7 到 JDK 1.8 中的 ConcurrentHashMap 最大的改动:

  • 链表上的 Node 超过 8 个改为红黑树,查询复杂度 O(logn)
  • ReentrantLock 显示锁改为 synchronized,说明 JDK 1.8 中 synchronized 锁性能赶上或超过 ReentrantLock

                                --参考:https://www.cnblogs.com/fsychen/p/9361858.html

 锁粒度不同:JDK7对需要进行数据操作的Segment进行加锁,JDK8为头结点进行加锁

线程安全机制不同:JDK7采用segment分段锁机制加锁,JDK8采用CAS+synchronized保证线程安全

时间复杂度不同:JDK7遍历链表0(n),JDK8遍历红黑树0(logn)

底层数据结构不同:JDK7底层使用数组+链表,JDK8数组+链表+红黑树

 

为什么用synchronized替换了ReentrantLock

synchronized在JDK6做了优化,引入了轻量级锁、偏向锁、锁消除、自适应自旋等。

CAS+synchronized加锁方式,只对头结点进行加锁,锁粒度更细,如果重入锁也这样操作,就需要大量继承字ReentrantLock的对象,造成内存浪费

HashTable

 

 

 

此类实现一个哈希表,该哈希表将键映射到相应的值。任何非 null 对象都可以用作键或值
为了成功地在哈希表中存储和获取对象,用作键的对象必须实现 hashCode 方法和 equals 方法。
维护着一个运行于所有条目的双重链接列表

 Hashtable是同步的。 如果不需要线程安全的实现,建议使用HashMap代替Hashtable 。 如果需要线程安全的并发实现,那么建议使用ConcurrentHashMap代替Hashtable 。

 HashTable与HashMap的区别

  • HashTable是线程安全且同步的,方法用synchronized修饰,HashMap不是
  • HashTable继承于Dictionary,HashMap继承于AbstractMap
  • HashTable的key和value不支持null,HashMap支持
  • HashTable的初始长度是11,扩容时两倍+1,HashMap初始值是16,扩容是2倍
  • HashTable在构造方法里初始化,HashMap在第一次put元素的时候初始化
  • HashTable是key.hashCode()&0x7FFFFFFF再%数组长度算index,HashMap是key.hashCode()与高16位异或再(n-1)&hash得到index

LinkedHashmap

Map 接口的哈希表和链接列表实现,具有可预知的迭代顺序
此链接列表定义了迭代顺序,该迭代顺序通常就是将键插入到映射中的顺序(插入顺序)。

底层数据结构是数组+链表/红⿊树+双向链表

 

 

TreeMap

根据键的自然排序进行排序

自定义类可以自定义排序(和treeset相似),

  1. 实现comparable接口,重写comparetTo方法
  2. 定义比较器,实现comparator接口,重写compare方法

底层数据结构是红⿊树

 

排序

例子对象类

 1 package map;
 2 //对象类
 3 public class Users implements  Comparable<Users>{
 4    private String name;
 5    private Integer age;
 6 
 7     @Override
 8     public int compareTo(Users o) {
 9         // this-传入对象  顺序排列
10         int i=this.age-o.age;
11         return i==0?this.name.compareTo(o.name):i;
12     }
13 
14     @Override
15     public String toString() {
16         return "Users{" +
17                 "name='" + name + '\'' +
18                 ", age=" + age +
19                 '}';
20     }
21 
22     public Users() {
23     }
24 
25     public Users(String name, Integer age) {
26         this.name = name;
27         this.age = age;
28     }
29 
30     public String getName() {
31         return name;
32     }
33 
34     public void setName(String name) {
35         this.name = name;
36     }
37 
38     public Integer getAge() {
39         return age;
40     }
41 
42     public void setAge(Integer age) {
43         this.age = age;
44     }
45 }

 

两种排序

package map;


import java.util.Comparator;

import java.util.Map.Entry;
import java.util.TreeMap;
//TreeMap两种排序方法,同treeset
public class TreeMaps {

    public static void main(String[] args) {


       // test1();
        new TreeMaps().test2();
    }
    //此方法创建TreeMap
    private static TreeMap<Users,Object> creat(){
        TreeMap<Users,Object> tre=new TreeMap<>();
        tre.put(new Users("张",22),1);
        tre.put(new Users("李",12),"2号");
        tre.put(new Users("王",26),"3号");
        return tre;
    }
    //遍历方法-通过映射的set视图,k对象实现comparable,重写compareTo,为自定义自然排序
    public static void test1(){

        for (Entry<Users,Object> en:creat().entrySet()
             ) {
            System.out.println(en.getKey()+":"+en.getValue());
        }
    }
    //自定义排序,通过自定义的比较器
    private void test2(){
                                //传入比较器时,传的是实例化的比较器
        TreeMap<Users,Object> ttt=new TreeMap<>(new Com() );
        //复制映射关系到此映射中
            ttt.putAll(creat());
                 System.out.println(ttt);
     }

    }
    //自定义比较器类
class Com implements Comparator<Users> {
    @Override
    public int compare(Users o1, Users o2) {
        int i = o1.getName().length() - o2.getName().length();
        return  i== 0 ? o1.getAge()-o2.getAge() : i;

    }
}

 

 
posted on 2023-02-18 18:27  or追梦者  阅读(3)  评论(0编辑  收藏  举报