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()判断集合中是否仍有元素可以迭代
    System.out.println(it.next());	//it.next()返回迭代的下一个元素,且移动迭代器的指针到下一个元素
}
//增强for,简化数组和Collection集合的遍历(底层是迭代器实现),所以实际开发一般不用迭代器遍历元素
for(元素数据类型 变量 : 数组或者Collection集合) {
    使用变量即可,该变量就是元素
}

并发修改异常

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

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

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

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

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集合中的所有元素添加到lhs
    list.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由 数组,链表,红黑树 组成

红黑树

红黑树四个特点

每一个结点都有一个颜色,要么为红色,要么为黑色;

树的根结点为黑色;

树中不存在两个相邻的红色结点(即红色结点的父结点和孩子结点均不能是红色);

从任意一个结点(包括根结点)到其任何后代 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.mfbz.cn/a/121978.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ubuntu下Anaconda环境安装GPU的pytorch(docker镜像)

实验室需要给每个人分配docker的container环境&#xff0c;为了节省系统的空间&#xff0c;打算把anaconda和深度学习的开发环境配置好拉取镜像以省时间。 基础环境配置 apt更新了清华源 安装了基础环境 gcc vim Linux文本编辑库 openssh-server ssh远程连接库 net-tools 包含…

Visual Leak Detector 2.5.1 (VLD)下载、安装与使用

目录 1 软件介绍 2 下载与安装 2.1 工具下载地址 2.2 工具安装 3 配置与使用 3.1 配置环境变量 3.2 配置vs 3.3 VLD的配置 4 测试代码与报告生成 1 软件介绍 Visual Leak Detector 2.5.1 (VLD)是一个小巧内存检测工具&#xff0c;是为Visual C用户设计的。其特色为&a…

基于开源项目OCR做一个探究(chineseocr_lite)

背景&#xff1a;基于图片识别的技术有很多&#xff0c;应用与各行各业&#xff0c;我们公司围绕电子身份证识别自动录入需求开展&#xff0c;以下是我的研究心得 技术栈&#xff1a;python3.6&#xff0c;chineseocr_lite的onnx推理 环境部署&#xff1a;直接上截图&#xff…

ARMday2(环境创建+工程配置+创建文件+单步调试)

目录 一、汇编环境的创建 二、为工程配置链接脚本&#xff08;map.lds&#xff09; 三、为工程创建汇编文件 start.s 编程调试 接下来我们需要建立一个 start.s 汇编文件添加到我们的工程中去 四、对汇编代码进行单步调试&#xff08;仿真&#xff09; 五、汇编工程的编译 …

手握“发展密钥”,TCL科技或迎价值重估?

在高度竞争且快速变化的泛半导体产业&#xff0c;每一次周期性或结构性的变化&#xff0c;都会对企业经营策略带来深远的影响。 2023年前三季度&#xff0c;泛半导体产业迎来结构性复苏。其中&#xff0c;主流显示领域供需关系趋向健康化&#xff0c;半导体显示行业整体上量价…

iOS 16.4 之后真机与模拟器无法使用Safari调试H5页面问题

背景 iOS 16.4之后用真机调试H5时候发现&#xff0c;Safari中开发模块下面无法调试页面 解决方案 在WKWebView中设置以下代码解决 if (available(iOS 16.4, *)) {[_webView setInspectable:YES];}然后再次调试就可以了

redis持久化和Redis事务

一)Redis持久化之RDBredisDataBase: 什么是持久化: 1)持久性:和持久化说的是同一回事&#xff0c;衡量持久性的前提是重启进程或者是重启主机以后数据是否还存在 持久:把数据存储在硬盘上&#xff0c;那么就是持久性 不持久:把数据存储在内存中 2)redis是一个内存级别的数据库&…

一个“Hello, World”Flask应用程序

如果您访问Flask网站&#xff0c;会看到一个非常简单的示例应用程序&#xff0c;只有5行代码。为了不重复那个简单的示例&#xff0c;我将向您展示一个稍微复杂一些的示例&#xff0c;它将为您编写大型应用程序提供一个良好的基础结构。 应用程序将存在于包中。在Python中&…

在 Vue3 中使用 mitt 进行组件通信

npm 包地址 mitt 是一个轻量级的 JavaScript 事件触发器&#xff0c; 只有200b。有基本的事件触发、订阅和取消订阅功能&#xff0c;还支持用命名空间来进行更高级的事件处理。 功能特点&#xff1a; Microscopic —— weighs less than 200 bytes gzippedUseful —— a wil…

抖店怎么做才会快速起店?跟着这个思路来,一周搞定!

大家好&#xff0c;我是电商糖果 有不少朋友&#xff0c;自己开了一家抖店。 因为不懂运营&#xff0c;店铺一直没有流量&#xff0c;也不出单。 糖果做抖店三年多了&#xff0c;不敢自吹有多么优秀&#xff0c;但是做店还是有一套自己的方法的。 按照糖果这个思路做店&…

edge浏览器无法进入中国知网,但可以进入其他网站需要怎么解决

最近使用edge浏览器进入中国知网&#xff0c;加载了很长时间都打不开&#xff0c;好不容易打开了&#xff0c;结果出现&#xff1a;“嗯...无法访问此页面”。即使无法进入知网&#xff0c;但可以进入哔哩哔哩或其他网站&#xff0c;甚是苦恼&#xff0c;下面是一个方法&#x…

Echarts示例

一.概念 ECharts&#xff08;Enterprise Charts&#xff09;是百度开源的一个基于JavaScript的可视化图表库。它提供了多种常见的数据可视化图表&#xff0c;包括折线图、柱状图、散点图、饼图、雷达图等等。使用ECharts&#xff0c;用户可以通过简单的配置和接口调用来创建交…

rabbitmq入门学习

写在前面 本文看下rabbit mq的基础概念以及使用。 1&#xff1a;简单介绍 为了不同进程间通信的解耦&#xff0c;出现了消息队列&#xff0c;为了规范消息队列的具体实现&#xff0c;Java制定了jms规范&#xff0c;这是一套基于接口的规范&#xff0c;因此是绑定语言的&…

人脸识别中的人工智能

随着人工智能技术的快速发展&#xff0c;人脸识别作为其中的一个重要应用领域&#xff0c;已经在各个行业和场景中展现出了巨大的潜力和价值。人脸识别技术通过对人脸图像进行采集、处理和分析&#xff0c;基于人工智能算法对人脸进行识别和验证&#xff0c;并在安防监控、金融…

安防监控EasyCVR视频汇聚平台无法接入Ehome5.0是什么原因?该如何解决?

视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。安防平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视频录像、云存储、回放…

彻底删除Ubuntu双系统(联想小新2022)

彻底卸载Ubuntu双系统 以里联想小新pro16 i9-12900h为例子 把开机启动项设为默认Windows启动 以联想电脑为例子&#xff0c;关机后一直点击Fn F2进入Bios把windows启动项移到最上面&#xff0c;这样可以开机默认启动windows了删除ubuntu系统分区 使用磁盘管理软件 DiskGeniu…

多无人机在线路径规划的新算法

南京航空航天大学自动化学院使用NOKOV度量动作捕捉系统获取多架无人机的精确位置信息&#xff0c;实现多架无人机协同实时路径规划。 研究背景 近年来&#xff0c;无人机越来越多地应用于执行战场侦察、目标识别、跟踪打击等任务。 由多架无人机协同执行任务&#xff0c;通过…

Luatos Air700 改变BL0942串口波特率

LuatOs 改变模块串口波特率思路参照 luatos 改变AIR530串口波特率 BL0942默认串口波特率可以通过SCLK_BPS引脚接3.3V电源设置到9600bps 但如果调整到38400bps需要修改0x19寄存器 bl0942 v1.06版的特殊寄存器说明&#xff0c;注意早期版本特殊寄存器说明存在错误 完整代码 mai…

Kubernetes 问题排查全景图

伴随着混沌的微服务架构&#xff0c;多语言和多网络协议混杂。以及下沉的基础设施能力屏蔽实现细节&#xff0c;问题定界越发困难。 企业急需一种支持多语言&#xff0c;多通信协议的技术&#xff0c;并在产品层面尽可能覆盖软件栈端到端的可观测性需求。 「Kubernetes 问题排查…

java版直播商城免费搭建平台规划及常见的营销模式+电商源码+小程序+三级分销+二次开发

1. 涉及平台 平台管理、商家端&#xff08;PC端、手机端&#xff09;、买家平台&#xff08;H5/公众号、小程序、APP端&#xff08;IOS/Android&#xff09;、微服务平台&#xff08;业务服务&#xff09; 2. 核心架构 Spring Cloud、Spring Boot、Mybatis、Redis 3. 前端框架…
最新文章