javaSE学习笔记(五)集合框架-Collection,List,Set,Map,HashMap,Hashtable,ConcurrentHashMap

目录

四、集合框架

1.集合概述

集合的作用

集合和数组的区别

集合继承体系

数组和链表

数组集合

链表集合

2.Collection

方法

集合遍历

并发修改异常

3.List

List集合的特有功能(核心是索引)

集合遍历

并发修改异常产生解决方案ListIterator

List的三个子类的特点

Vector,ArrayList,LinkedList区别

4.ArrayList

5.Vector

Vector的特有功能

6.LinkedList

LinkedList类特有功能

7.Set

如何保证元素的唯一性

比较的逻辑

为什么不直接使用equals()进行比较

为什么还需要equals()

为什么重写equals()就一定要重写hashCode()

通过Set去重

8.HashSet

9.LinkedHashSet

10.TreeSet

怎么实现排序

方式一

对于compareTo()

方式二

对于compare()

11.Map

特点

Map和Collection区别

方法

Map嵌套

Map集合遍历

在类中声明Map成员时给定初始值

12.LinkedHashMap

13.TreeMap

14.HashMap

概念

数据结构

红黑树

红黑树四个特点

为什么需要红黑树

红黑树如何保持平衡

哈希算法

哈希函数的评价标准

哈希表

初始化

数据存储-put方法

计算hash值

计算元素存放在数组的位置

存储

哈希冲突(碰撞)

数据寻址-hash方法

扩容-resize方法

何时扩容

扩容过程

总结

HashMap在JDK7和8中的区别

HashMap和Hashtable的区别

HashMap线程不安全

线程不安全的体现

线程不安全的解决方案

15.ConcurrentHashMap,线程安全

原理

数据结构

核心过程

其他


四、集合框架

1.集合概述

集合的作用

数组长度是固定,当添加的元素超过了数组的长度时需要对数组重新定义,太麻烦

因此,java内部给我们提供了集合类,能存储任意对象,长度是可以改变的,随着元素的增加而增加,随着元素的减少而减少

集合和数组的区别

元素不同

数组既可以存储基本数据类型,又可以存储引用数据类型,基本数据类型存储的是值,引用数据类型存储的是地址值

集合只能存储引用数据类型(对象),基本数据类型在存储的时候会自动装箱变成对象

长度不同

数组长度是固定的,不能自动增长

集合长度可变,可以根据元素个数增减

数组和集合什么时候用

如果元素个数是固定的推荐用数组

如果元素个数不是固定的推荐用集合

集合继承体系

数组和链表

数组集合

查询快修改也快,直接通过索引找到值,进行修改;增删慢

原因:

数组一旦被初始化,长度就不会被改变

初始长度是10,每次add的时候 都会先判断一下 size+1是否超过了数组的长度,一旦超过,那么就创建一个新数组,长度增加int oldCapacity /2,将数据复制到新数组中,原数组就作废了

在某个索引位置增加时,要将包括该元素的后面的每个元素都往后移动

在某个索引位置删除时,要将包括该元素的后面的每个元素都向前移动,被移动的最后位置置null

数组实现的集合:ArrayList

链表集合

查询慢,修改也慢;增删快

原因:

每个存储单元,会记住链中前后存储单元的地址,从而形成链

查询时,先判断是从前还是从后找(二分判断离头尾哪个近),然后依次挨个存储单元找,遍历

指定索引插入元素时,只需要插入元素记住该索引前后单元的地址,就插入成功

删除也是,拿出一个元素,前后索引修改记忆的前后单元的地址

链表实现的集合:LinkedList

2.Collection

集合的根接口

方法

boolean add(E e) //增加

boolean remove(Object o) //删除

void clear() //清空

boolean contains(Object o) //判断是否包含

boolean isEmpty() //判断是否为空

int size() //获取元素个数

boolean addAll(Collection c) 添加所有元素

boolean removeAll(Collection c) 删除的是交集

boolean containsAll(Collection c) 判断是否包含c中的每个元素(重复的也算包含)

boolean retainAll(Collection c) 判断C是否包含调用者集合

集合遍历

迭代器直接遍历集合元素
Collection c = new ArrayList();
c.add("a");
c.add("b");
c.add("c");
c.add("d");Iterator it = c.iterator();	//获取迭代器的引用
while(it.hasNext()) {		//it.hasNext()判断集合中是否仍有元素可以迭代//it.next()返回迭代的下一个元素,且移动迭代器的指针到下一个元素//迭代的过程中,不可以对集合的元素进行增删,即不可以改变集合的结构,因为迭代器无法知晓集合的结构变化,会造成并发修改异常System.out.println(it.next());
}
//增强for,简化数组和Collection集合的遍历(底层是迭代器实现),所以实际开发一般不用迭代器遍历元素
for(元素数据类型 变量 : 数组或者Collection集合) {使用变量即可,该变量就是元素
}

并发修改异常

fail-fast,快速失败机制,并发修改异常

是Java集合的一种错误检查机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生 fail-fast 机制,注意只是有可能,不是一定,单线程的情况下,也可能产生

多线程:线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构,这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制

单线程:在集合迭代的过程中,对集合结构进行了改变(增删元素)

public static void main(String[] args) {ArrayList<Integer> arrayList = new ArrayList<>();arrayList.add(1);arrayList.add(2);for (Integer integer : arrayList) {arrayList.add(1);        //报错,ConcurrentModificationException}
}

3.List

特点:元素有索引,有序,可重复

List集合的特有功能(核心是索引)

void add(int index,E element) 指定索引位置添加元素

E remove(int index) 删除指定索引位置元素,并返回该元素

E get(int index) 获取,显然可以通过get(int index)方法遍历

E set(int index,E element) 修改

default void sort(Comparator c) 排序,可以给定比较器

List<Map<String, Object>> demoList = xxMapper.getList(xx); demoList.sort(Comparator.comparingInt((Map o) -> Integer.parseInt(o.get("XH").toString())));

集合遍历

通过size()和get()方法结合使用遍历

List list = new ArrayList();
list.add(new Student("张三", 18));
list.add(new Student("李四", 18));
list.add(new Student("王五", 18));
list.add(new Student("赵六", 18));for(int i = 0; i < list.size(); i++) {Student s = (Student)list.get(i);System.out.println(s.getName() + "," + s.getAge());
}

并发修改异常产生解决方案ListIterator

ListIterator lit = list.listiterator()

方法

boolean hasNext()是否有下一个

boolean hasPrevious()是否有前一个

Object next()返回下一个元素

Object previous();返回上一个元素

//判断集合里面有没有"world"这个元素,如果有,添加一个"javaee"元素
List list = new ArrayList();
list.add("a");
list.add("b");
list.add("world");
list.add("d");
list.add("e");Iterator it = list.iterator();
while(it.hasNext()) {String str = (String)it.next();if(str.equals("world")) {list.add("javaee");//这里会抛出ConcurrentModificationException并发修改异常,原因是在迭代的时候进行了集合的增删改操作,但是迭代器并不知道,这会影响迭代}
}解决方案
如果想在遍历的过程中添加元素,可以用ListIterator中的add方法
ListIterator lit = list.listIterator();
while(lit.hasNext()) {String str = (String)lit.next();if(str.equals("world")) {lit.add("javaee");	}
}

List的三个子类的特点

ArrayList

底层数据结构是数组,查询快,增删慢

线程不安全,效率高(异步)

LinkedList

底层数据结构是链表,查询慢,增删快

线程不安全,效率高

Vector(不用了)

底层数据结构是数组,查询快,增删慢

线程安全,效率低(同步)

Vector,ArrayList,LinkedList区别

数据结构

ArrayList,Vector:数组,查询修改快

LinkedList:链表,增删快,查询修改慢

线程安全

Vector:线程安全,效率低

ArrayList,LinkedList:线程不安全,效率高

Vector是线程安全的,效率低

4.ArrayList

//ArrayList去重
public static ArrayList getSingle (ArrayList list){ArrayList newList = new ArrayList();		//创建一个新集合Iterator it = list.iterator();				//获取迭代器while (it.hasNext()) {					//判断老集合中是否有元素String temp = (String) it.next();		//将每一个元素临时记录住if (!newList.contains(temp)) {			//如果新集合中不包含该元素newList.add(temp);				//将该元素添加到新集合中}}return newList;						//将新集合返回
}

5.Vector

vector实现了list接口,但已经被ArrayList取代了

Vector的特有功能

public void addElement(E obj)

public E elementAt(int index)

public Enumeration elements()

//Vector的迭代
Vector v = new Vector();
v.addElement("a");
v.addElement("b");
v.addElement("c");
v.addElement("d");	Enumeration en = v.elements();		//获取枚举,这不是迭代,是枚举
while(en.hasMoreElements()) {		//判断集合中是否有元素System.out.println(en.nextElement());	//获取集合中的元素
}

6.LinkedList

LinkedList类特有功能

public void addFirst(E e)及addLast(E e)

public E getFirst()及getLast()

public E removeFirst()及public E removeLast()

7.Set

特点:元素无索引,无序(指的是存放并不是按add的顺序),不可重复

如何保证元素的唯一性

存入时通过对象的hashCode()和equals()比较元素,已经存在的不存入Set

jdk提供的类,比如基本数据类型包装类,jdk已经对equals()做了重写

自定义的类,也需要重写equals(),给定比较的规则

比较的逻辑

在hashCode()值相同时,才会进一步调用equals()进行比较,否则直接认定对象不一样

为什么不直接使用equals()进行比较

因为hashCode()效率高,而equals()中的操作一般都比较复杂,效率较低

为什么还需要equals()

因为hashCode()是一个算法,并不完全可靠,当hashCode()不同,则两个对象肯定不同,但当hashCode()相同,两个对象不一定相同,采用这样的组合比较方式,可以兼顾效率和可靠性

为什么重写equals()就一定要重写hashCode()

1.因为hashCode()在equals()之前调用,如果不重写,很可能永远调用不到equals()

2.java约定两个对象如果equals()判定相同,那么hashCode()也必须判定相同

通过Set去重

public static void getSingle(List<String> list) {LinkedHashSet<String> lhs = new LinkedHashSet<>();lhs.addAll(list);	//将list集合中的所有元素添加到lhslist.clear();		//清空原集合list.addAll(lhs);	//将去除重复的元素添回到list中,可以直接修改
}

8.HashSet

常用的Set子类

9.LinkedHashSet

常用的Set子类

链表结构使元素能保持有序,即可以保证怎么存就怎么取,存进去是a,b,c,d,取出来还是a,b,c,d

10.TreeSet

元素排序

排序的原理:底层是二叉树结构

怎么实现排序

方式一

元素的类implments Comparable,重写compareTo()

TreeSet类的add()方法中会把存入的对象提升为Comparable类型,调用对象的compareTo()方法和集合中的对象比较,根据compareTo()方法返回的结果进行存储

基本数据类型包装类默认已经实现了Comparable接口重写过compareTo();对于自定义对象,可以实现Comparable接口并重写compareTo()

对于compareTo()

return 0 那么集合中只存一个元素,因为每次返回0都被TreeSet认为是一样的

return 正数 那么集合中怎么存怎么取

return 负数 那么集合中倒序存储

方式二

比较器顺序(Comparator)

比较器类implments Comparator,重写compare()方法,将Comparator的实现类对象传给TreeeSet()对象构造方法,TreeSet就会按照比较器中的顺序排序

add()方法内部会自动调用Comparator接口中compare()方法排序

对于compare()

调用的对象是compare方法的第一个参数,集合中的对象是compare方法的第二个参数

11.Map

定义:将键映射到值的对象

特点

键具有唯一性,键可以是对象,需要重写hashCode()和equals()保证键的唯一性

Set集合的底层是Map,隐藏了值,展示的是键

Map和Collection区别

Map是双列的,Collection是单列的

Map子类的数据结构指的是键的数据结构,比如HashMap,TreeMap的Hash和Tree针对的都是键;Collection集合的数据结构是针对元素有效

方法

添加

V put(K key,V value):添加元素,返回的是被覆盖的值

V putIfAbsent(K key, V value):如果Map中已经有当前key,不会覆盖

删除

void clear():移除所有的键值对元素

V remove(Object key):根据键删除键值对元素,并把值返回

判断

boolean containsKey(Object key):判断集合是否包含指定的键

boolean containsValue(Object value):判断集合是否包含指定的值

boolean isEmpty():判断集合是否为空

获取

Set> entrySet():获取所有键值对

V get(Object key):根据键获取值

Set keySet():获取集合中所有键的集合

Collection values():获取集合中所有值的集合

int size():返回集合中的键值对的个数

Map嵌套

Map可以嵌套,即Map map = new HashMap

Map集合遍历

//先拿到键,根据键查找值
Set<String> keySet = hm.keySet();		//获取集合中所有的键
Iterator<String> it = keySet.iterator();	//获取迭代器
while(it.hasNext()) {			//判断单列集合中是否有元素String key = it.next();		//获取集合中的每一个元素,其实就是双列集合中的键Integer value = hm.get(key);		//根据键获取值System.out.println(key + "=" + value);	//打印键值对
}for(String key :hm.keySet()) {		//增强for循环迭代双列集合第一种方式System.out.println(key + "=" + hm.get(key));
}
//直接获取键值对象,Map.Entry是Map的内部接口Entry,将键值对封装成Entry对象,存储在Set集合中
Set<Map.Entry<String, Integer>> entrySet = hm.entrySet();	//获取所有的键值对象的集合
Iterator<Entry<String, Integer>> it = entrySet.iterator();	//获取迭代器
while(it.hasNext()){Entry<String, Integer> en = it.next();              	//获取键值对对象String key = en.getKey();				//根据键值对对象获取键Integer value = en.getValue();				//根据键值对对象获取值System.out.println(key + "=" + value);
}for(Entry<String, Integer> en :hm.entrySet()){System.out.println(en.getKey() + "=" + en.getValue());
}

在类中声明Map成员时给定初始值

Map<Integer, Integer> map = new HashMap<Integer, Integer>() {{put(1,0);...}
}
双花括号的含义
第一个括号是定义了一个匿名内部类
第二个括号是在这个匿名内部类中定义了一个初始化代码块
put相当于this.put,this指的是这个匿名内部类的对象本身

12.LinkedHashMap

链表结构,可以保证怎么存就怎么取

13.TreeMap

键有序

统计字符串中每个字符出现的次数

String str = "aaaabbbcccccccccc";
char[] arr = str.toCharArray();		//将字符串转换成字符数组
HashMap<Character, Integer> hm = new HashMap<>();	//创建双列集合存储键和值for(char c : arr) {			//遍历字符数组if(!hm.containsKey(c)) {		//如果不包含这个键,就将键和值为1添加hm.put(c, 1);}else {				//如果包含这个键,就将值加1添加进来hm.put(c, hm.get(c) + 1);
}hm.put(c, !hm.containsKey(c) ? 1 : hm.get(c) + 1);
Integer i = !hm.containsKey(c) ? hm.put(c, 1) : hm.put(c, hm.get(c) + 1);for (Character key : hm.keySet()) {	//遍历双列集合System.out.println(key + "=" + hm.get(key));
}

14.HashMap

概念

key-value 键值对的形式存放元素(并封装成 Node 对象)

允许使用 null 键和 null 值,但只允许存在一个键为 null,并且存放在 Node[0] 的位置

线程不安全:采用 Fail-Fast 机制,底层通过一个 modCount 值记录修改的次数,对 HashMap 的修改操作都会增加这个值。迭代器在初始过程中会将这个值赋给 exceptedModCount ,在迭代的过程中,如果发现 modCount 和 exceptedModCount 的值不一致,代表有其他线程修改了Map,就立刻抛出异常

数据结构

JDK7:HashMap由 数组,链表 组成

JDK8:HashMap由 数组,链表,红黑树 组成

红黑树

可以把红黑树简单理解为接近平衡的二叉树

特点

每个节点或者是黑色,或者是红色

根节点是黑色

每个叶子节点(NIL)是黑色,注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点

如果一个节点是红色的,则它的子节点必须是黑色的

从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点,这样确保没有一条路径会比其他路径长出俩倍

为什么需要红黑树

在Java 8中如果桶数组的同一个位置上的链表数量超过TREEIFY_THRESHOLD默认是8,链表会转为一棵红黑树

AVL更平衡,但在频繁增删的情况下,为了维持平衡会进行很多的旋转操作,此时红黑树的性能更高,相对的,对于增删较少,查询频繁的情况,AVL更具优势

假如客户端实现了一个性能拙劣的hashCode方法,元素较多的情况下,采用红黑树可以保证HashMap的读写复杂度不会低于O(lgN)

红黑树如何保持平衡

通过旋转和重新着色

黑高bh(x):从某个结点 x 出发(不包含该结点)到达一个叶结点的任意一条简单路径上包含的黑色结点的数目,显然,黑高最多也就是h/2

哈希算法

任意长度的输入,通过散列算法,变换成固定长度的输出,这个输出是一个地址值,通过这个地址可以访问

哈希函数的评价标准

计算出来的哈希值足够散列,能够有效减少哈希碰撞

本身能够快速计算得出,因为HashMap每次调用get和put的时候都会调用hash方法

哈希表

根据关键码值(Key value)而直接进行访问的数据结构

通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度

这个映射函数叫做散列函数,存放记录的数组叫做散列表

初始化

HashMap的实现的基础数据结构是数组,每一对key->value的键值对组成Entity类以双向链表的形式存放到这个数组中

初始化时,不会占用内存,第一次put时会调用inflateTable计算bucket数组的长度,开辟bucket数组,占用内存

数组长度是2的整数幂,初始size为16,扩容:newsize = oldsize*2

为什么一定是2的整数幂:因为这样可以通过构造位运算快速寻址定址

数据存储-put方法

计算hash值

将key-value封装成Node,拿到key.hashCode()

调用hash()重新计算hash值,防止拙劣hashCode(),从而使hash值相对分散,jdk8之后,对hash()进行了优化,使hashCode的高16位参与运算,保证了数组较小时的hash值分散度

计算元素存放在数组的位置

将hash值与tablel.length-1进行位与运算,得到元素存放位置

此处就可以理解为什么HashMap的底层数组长度总是2的n次方幂:因为当 length 为2的n次方时,h & (length - 1) 就相当于对 length 取模,而且速度比直接取模要快得多,二者等价不等效,这是HashMap在性能上的一个优化

存储

如果计算出的数组位置上为空,那么直接将node放到该位置中

如果数组该位置上已经存在链表,即有多个key的hash值通过哈希算法得到的数组下标相同,即发生了哈希碰撞,此时:

挨个节点通过equals()对比key,如果返回true,则覆盖此节点,如果都返回false,则树形-挂到树上,链表-添加到末尾(Jdk1.7及以前的版本使用的头插法)

如果插入元素后,如果链表的节点数是否超过8个,则调用 treeifyBin() 将链表节点转为红黑树节点

最后判断 HashMap 总容量是否超过阈值 threshold,则调用 resize() 方法进行扩容,扩容后数组的长度变成原来的2倍

哈希冲突(碰撞)

通过hash计算出的hash值相同,继而导致存放位置相同即为hash冲突

hashMap的处理方式是拉链法,即将所有hash值相同的元素放在同一个链表中

数据寻址-hash方法

先调用k的hashCode()方法得出哈希值,并通过hash()算法转换成数组的下标

通过数组下标快速定位到某个位置上

如果这个位置上什么都没有,则返回null

如果这个位置上有链表,那么它就会拿着K和链表上的每一个Node的K进行equals()

如果所有equals方法都返回false,则get方法返回null

如果其中一个节点的K和参数K进行equals返回true,那么此时该节点的value就是我们要找的value了,get方法最终返回这个要找的value

扩容-resize方法

为什么需要扩容:减少哈希碰撞,从而减小链表长度/树高,让value分配更均匀,从而提升读取效率

何时扩容

HashMap 有两个影响性能的关键参数:初始容量,加载因子

容量 capacity:就是哈希表中数组的数量,默认初始容量是16,容量必须是2的N次幂

加载因子 loadfactor:在 HashMap 扩容之前,容量可以达到多满的程度,默认值为 0.75

扩容阈值 threshold = capacity * loadfactor

扩容形式:扩容针对整个Map,每次扩容时,原来数组中的元素依次重新计算存放位置,并重新插入,如果扩容两倍,则有一半的节点需要存到扩容的部分中

扩容过程

重新建立一个新的数组,长度为原数组的两倍

遍历旧数组的每个数据,重新计算每个元素在新数组中的存储位置。使用节点的hash值与旧数组长度进行位与运算,如果运算结果为0,表示元素在新数组中的位置不变;否则,则在新数组中的位置下标=原位置+原数组长度

将旧数组上的每个数据使用尾插法逐个转移到新数组中,并重新设置扩容阈值

为什么扩容时节点重 hash 只可能分布在原索引位置或者 原索引长度+oldCap 位置:由确定存址的位运算特征决定的

总结

为何随机增删、查询效率都很高:增删是在链表上完成的,而查询只需扫描部分,则效率高

HashMap在JDK7和8中的区别

①数据结构:

JDK7及之前的版本:数组+链表

JDK8及之后的版本:数组+链表+红黑树,当链表的长度超过8时,链表就会转换成红黑树,从而降低时间复杂度(由O(n) 变成了 O(logN))

②对数据重哈希:

JDK8 及之后的版本,对 hash() 方法进行了优化,重新计算 hash 值时,让 hashCode 的高16位参与异或运算,目的是在 table 的 length较小的时候,在进行计算元素存储位置时,也让高位也参与运算

③插入元素的方式:

在 JDK7 及之前的版本,在添加元素的时候,采用头插法,所以在扩容的时候,会导致之前元素相对位置倒置了,在多线程环境下扩容可能造成环形链表而导致死循环的问题

DK1.8之后使用的是尾插法,扩容是不会改变元素的相对位置

④扩容时重新计算元素的存储位置的方式:

JDK7 及之前的版本重新计算存储位置是直接使用 hash & (table.length-1);

JDK8 使用节点的hash值与旧数组长度进行位与运算,如果运算结果为0,表示元素在新数组中的位置不变;否则,则在新数组中的位置下标=原位置+原数组长度

⑤扩容决策不同

JDK7 是先扩容后插入,这就导致无论这次插入是否发生hash冲突都需要进行扩容,但如果这次插入并没有发生Hash冲突的话,那么就会造成一次无效扩容;

JDK8是先插入再扩容的,优点是减少这一次无效的扩容,原因就是如果这次插入没有发生Hash冲突的话,那么其实就不会造成扩容

HashMap和Hashtable的区别

线程安全

HashMap线程不安全,效率高

HashTable线程安全,效率低

null键值

HashMap可以存储null键和null值

HashTable不可以存储null键和null值

数据结构

HashMap使用数组+链表+红黑树

HashTable使用数组+链表

初始容量和扩容方式

HashMap默认初始容量为16,每次扩容为原来的2倍

HashTable默认初始容量为11,每次扩容为原来的2倍+1

元素的Hash值

HashMap会重计算

HashTable直接使用object.hashCode()

继承的父类

HashMap继承自AbstractMap类

HashTable继承自Dictionary类

HashMap线程不安全

线程不安全的体现

头插法导致同一位置节点顺序相反,导致出现死循环

多线程操作HashMap插入元素,数据可能丢失

线程不安全的解决方案

使用HashTable

使用Collections.synchronizedMap()方法来获取一个线程安全的集合,底层原理是使用synchronized来保证线程同步

使用ConcurrentHashMap

15.ConcurrentHashMap,线程安全

原理

JDK8相比JDK7,抛弃了 JDK7 版本的 Segment分段锁的概念,而是采用了 synchronized + CAS 算法来保证线程安全,可以大大减少使用加锁造成的性能消耗

JDK7中,ConcurrentHashMap使用分段锁机制,数据结构可以看成是Segment锁+Entry数组+链表,一个ConcurrentHashMap实例中包含若干个Segment实例组成的数组,每个Segment实例又包含由若干个桶,每个桶中都是由若干个HashEntry对象链接起来的链表

JDK8 降低了锁的粒度,采用table数组元素作为锁,从而实现对每行数据进行加锁,进一步减少并发冲突的概率,并使用synchronized来代替ReentrantLock,因为在低粒度的加锁方式中,synchronized并不比ReentrantLock差,在粗粒度加锁中ReentrantLock可以通过Condition来控制各个低粒度的边界,更加的灵活,而在低粒度中,Condition的优势就没有了

数据结构

JDK7:Segment数组+HashEntry数组+链表

JDK8:HashEntry数组+链表+红黑树

核心过程

存储时对key进行重哈希,通过segmentFor()计算出元素属于哪个Segment,插入前,使用lock()进行加锁,之后使用头插法插入元素,锁是基于Segment,其他插入操作只要Segment没有被锁,不受影响

插入元素之前,会检测本次操作会不会超过Segment元素数量超过扩容阈值,超过则执行扩容操作后再插入

其他

size():

JDK7:ConCurrentHashMap没有使用全局计数器,而是给每个Segment定义自己的计数器,执行size()时,先尝试不对Segment加锁统计,如果统计过程中元素个数发生了变化,再对所有的Segment加锁统计

JDK8:扩容和addCount()中先行处理,等到调用size()时直接返回元素的个数

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

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

相关文章

PowerPoint to HTML5 SDK Crack

Convert PowerPoint to HTML5 Retaining Animations, Transitions, Hyperlinks, Smartart, Triggers and other multimedia effects World’s first and industry best technology for building web/mobile based interactive presentations directly from PowerPoint – that …

在Win11中使用docker安装Oracle19c

在Win11中使用docker安装Oracle19c 首先是去docker官网下 docker for windows安装oracle19c首先下载image运行镜像在工具中登录可能遇到的问题 首先是去docker官网下 docker for windows 官网&#xff1a; https://www.docker.com/get-started/ 如果Windows是专业版&#xff0…

HTML跳转锚点

跳转锚点适用于本页面和其他页面的任意标签的跳转以及JavaScript的运行 使用方法即给标签加上独一无二的id属性&#xff0c;再使用a标签跳转 如果是其他页面的标签只需加上其他页面的路径&#xff0c;eg.href"其他页面的路径#zp1" id属性的最好不要使用数字开头 <…

第 117 场 LeetCode 双周赛题解

A 给小朋友们分糖果 I 动态规划&#xff1a;设 p [ k ] [ i ] p[k][i] p[k][i] 为将 i i i 个糖果分给 k k k 个小朋友的方案数&#xff0c;先求 p [ 2 ] [ i ] p[2][i] p[2][i] &#xff0c;再求 p [ 3 ] [ n ] p[3][n] p[3][n] class Solution { public:using ll long …

CH11_重构API

将查询函数和修改函数分离&#xff08;Separate Query from Modifier&#xff09; function getTotalOutstandingAndSendBill() {const result customer.invoices.reduce((total, each) > each.amount total, 0);sendBill();return result; }function totalOutstanding() …

写在 Chappyz 即将上所之前:基于 AI 技术对 Web3 营销的重新定义

前不久&#xff0c;一个叫做 Chappyz 的项目&#xff0c;其生态代币 $CHAPZ 在 Seedify、Poolz、Decubate、ChainGPT、Dao Space 等几大 IDO 平台实现了上线后几秒售罄&#xff0c;并且 Bitget、Gate.io、PancakeSwap 等几大平台也纷纷表示支持&#xff0c;并都将在 11 月 13 日…

关于el-table+el-input+el-propover的封装

一、先放图片便于理解 需求&#xff1a; 1、el-input触发focus事件&#xff0c;弹出el-table(当然也可以为其添加搜索功能、分页) 2、el-table中的复选共能转化成单选共能 3、选择或取消的数据在el-input中动态显示 4、勾选数据后&#xff0c;因为分页过多&#xff0c;原先选好…

【Linux网络】系统调优之聚合链路bonding,可以实现高可用和负载均衡

一、什么是多网卡绑定 二、聚合链路的工作模式 三、实操创建bonding设备&#xff08;mode1&#xff09; 1、实验 2、配置文件解读 3、查看bonding状态,验证bonding的高可用效果 三、nmcli实现bonding 一、什么是多网卡绑定 将多块网卡绑定同一IP地址对外提供服务&#xf…

aws亚马逊:什么是 Amazon EC2?

Amazon Elastic Compute Cloud&#xff08;Amazon EC2&#xff09;在 Amazon Web Services&#xff08;AWS&#xff09;云中按需提供可扩展的计算容量。使用 Amazon EC2 可以降低硬件成本&#xff0c;因此您可以更快地开发和部署应用程序。您可以使用 Amazon EC2 启动所需数量的…

Lua更多语法与使用

文章目录 目的错误处理元表和元方法垃圾回收协程模块面向对象总结 目的 在前一篇文章&#xff1a; 《Lua入门使用与基础语法》 中介绍了一些基础的内容。这里将继续介绍Lua一些更多的内容。 同样的本文参考自官方手册&#xff1a; https://www.lua.org/manual/ 错误处理 下…

原型模式(创建型)

一、前言 原型模式是一种创建型设计模式&#xff0c;它允许在运行时通过克隆现有对象来创建新对象&#xff0c;而不是通过常规的构造函数创建。在原型模式中&#xff0c;一个原型对象可以克隆自身来创建新的对象&#xff0c;这个过程可以通过深度克隆或浅克隆来实现。简单说原型…

【开源】基于Vue.js的生活废品回收系统的设计和实现

目录 一、摘要1.1 项目介绍1.2 项目详细录屏 二、研究内容三、界面展示3.1 登录注册3.2 资源类型&资源品类模块3.3 回收机构模块3.4 资源求购/出售/交易单模块3.5 客服咨询模块 四、免责说明 一、摘要 1.1 项目介绍 生活废品回收系统是可持续发展的解决方案&#xff0c;旨…

云效流水线docker部署 :node.js镜像部署VUE项目

文章目录 引言I 流水线配置1.1 项目dockerfile1.2 Node.js 镜像构建1.3 docker 部署引言 云效流水线配置实现docker 部署微服务项目:https://blog.csdn.net/z929118967/article/details/133687120?spm=1001.2014.3001.5501 配置dockerfile-> 镜像构建->docker部署。 …

【探索Linux】—— 强大的命令行工具 P.14(进程间通信 | 匿名管道 | |进程池 | pipe() 函数 | mkfifo() 函数)

阅读导航 引言一、进程间通信概念二、进程间通信目的三、进程间通信分类四、管道1. 什么是管道2. 匿名管道&#xff08;1&#xff09;创建和关闭⭕pipe() 函数⭕创建匿名管道⭕关闭匿名管道 &#xff08;2&#xff09;通信方式&#xff08;3&#xff09;用法示例&#xff08;4&…

NLP领域的突破催生大模型范式的形成与发展

当前的大模型领域的发展&#xff0c;只是范式转变的开始&#xff0c;基础大模型才刚刚开始改变人工智能系统在世界上的构建和部署方式。 1、大模型范式 1.1 传统思路&#xff08;2019年以前&#xff09; NLP领域历来专注于为具有挑战性的语言任务定义和设计系统&#xff0c…

Leetcode刷题详解—— 目标和

1. 题目链接&#xff1a;494. 目标和 2. 题目描述&#xff1a; 给你一个非负整数数组 nums 和一个整数 target 。 向数组中的每个整数前添加 或 - &#xff0c;然后串联起所有整数&#xff0c;可以构造一个 表达式 &#xff1a; 例如&#xff0c;nums [2, 1] &#xff0c;可…

在GORM中使用并发

一个全面的指南&#xff0c;如何安全地使用GORM和Goroutines进行并发数据处理 效率是现代应用程序开发的基石&#xff0c;而并发在实现效率方面发挥着重要作用。GORM&#xff0c;这个强大的Go对象关系映射库&#xff0c;使开发人员能够通过Goroutines embrace并行性。在本指南…

男科医院服务预约小程序的作用是什么

医院的需求度从来都很高&#xff0c;随着技术发展&#xff0c;不少科目随之衍生出新的医院的&#xff0c;比如男科医院、妇科医院等&#xff0c;这使得目标群体更加精准&#xff0c;同时也赋能用户可以快速享受到服务。 当然相应的男科医院在实际经营中也面临痛点&#xff1a;…

最简WebClient 同步、异步调用示例

目录 一&#xff0c;序言二&#xff0c;简单示例1. 引入依赖2. 日志配置3. 调用代码4. 运行结果 三&#xff0c;完整代码 一&#xff0c;序言 WebClient是Spring WebFlux模块提供的一个非阻塞的基于响应式编程的进行Http请求的客户端工具&#xff0c;从Spring5.0开始WebClient…

C语言——求 n 以内(不包括 n)同时能被 3 和 7 整除的所有自然数之和的平方根 s,n 从键盘输入。

#define _CRT_SECURE_NO_WARNINGS 1#include<stdio.h> #include<math.h> int main() {int i,n;double s0.0;printf("输入任意一个自然数&#xff1a; ");scanf("%d",&n);for(i1;i<n;i) {if(i%30&&i%70){si;}}ssqrt(s);printf(…