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()时直接返回元素的个数
相关文章:

javaSE学习笔记(五)集合框架-Collection,List,Set,Map,HashMap,Hashtable,ConcurrentHashMap
目录 四、集合框架 1.集合概述 集合的作用 集合和数组的区别 集合继承体系 数组和链表 数组集合 链表集合 2.Collection 方法 集合遍历 并发修改异常 3.List List集合的特有功能(核心是索引) 集合遍历 并发修改异常产生解决方案ListItera…...

web3 React dapp项目通过事件从区块链中拿到 已取消 已完成 和所有的订单数据 并存入redux中
好 上文web3通过antd 在React dapp中构建订单组件基本结构我们算是把一个基本的订单组件展示做出来了 然后 我们继续 起一下环境先 ganache 终端运行 ganache -dMetaMask 登录一下 然后 打开项目 发布一下合约 truffle migrate --reset然后 运行一下 测试脚本 转入交易所 E…...
25、Flink 的table api与sql之函数(自定义函数示例)
Flink 系列文章 1、Flink 部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接 13、Flink 的table api与sql的基本概念、通用api介绍及入门示例 14、Flink 的table api与sql之数据类型: 内置数据类型以及它们的属性 15、Flink 的ta…...
MybatisPlus —注解汇总
本文将介绍 MybatisPlus 注解包相关类详解(更多详细描述可点击查看源码注释) 注解类包源码:👉 mybatis-plus-annotation(opens new window) 一、#TableName(opens new window) 描述:表名注解,标识实体类对…...
flink对状态ttl进行单元测试
背景 在处理键值分区状态时,使用ttl设置过期时间是我们经常使用的,但是任何代码的修改都需要首先进行单元测试,本文就使用单元测试来验证一下状态ttl的设置是否正确 测试状态ttl超时的单元测试 首先看一下处理函数: // 处理函…...

Mac电脑安装打印机驱动
1.在打印机背面找到型号,当想要安装的驱动在官网找不到时可直接搜索该系列:比如MF系列 2.安装完成后需要添加打印机 当打印机和电脑在同一个WiFi下的时候查找打印机IP,输入IP后可以查到对应的打印机,添加后即可使用...

C语言 每日一题 牛客网 11.13 Day17
找零 Z国的货币系统包含面值1元、4元、16元、64元共计4种硬币,以及面值1024元的纸币。 现在小Y使用1024元的纸币购买了一件价值为N(0 < N≤1024)的商品,请问最少他会收到多少硬币? 思路 运用if语句进行判断分类 代码实现 int main() {…...
python读取npy和dat文件信息
前言 python读取.dat 和 .npy 数据 Code import numpy as np def read_dat():print("read data .dat \n")path "./c1_input.dat"data np.fromfile(path, np.float16).reshape(4,38,800)print(fdata :{data}, data shape:{data.shape}, data dtype:{d…...

【Git】第四篇:基本操作(理解工作区、暂存区、版本库)
Git 工作区、暂存区和版本库 工作区:就是我们创建的本地仓库所在的目录暂存区: stage或index,一般放在.git(可隐藏文件)目录下的index文件(.git/index)中,所以我们把暂存区有时候也叫做索引(in…...

Word转PDF简单示例,分别在windows和centos中完成转换
概述 本篇博客以简单的示例代码分别在Windows和Linux环境下完成Word转PDF的文档转换。 文章提供SpringBoot Vue3的示例代码。 文章为什么要分为Windows和Linux环境? 因为在如下提供的Windows后端示例代码中使用documents4j库做转换,此库需要调用命令行…...
推荐收藏!大模型算法工程师面试题来了(附答案)
自 ChatGPT 在去年 11 月底横空出世,大模型的风刮了整一年。 历经了百模大战、Llama 2 开源、GPTs 发布等一系列里程碑事件,将大模型技术推至无可争议的 C 位。基于大模型的研究与讨论,也让我们愈发接近这波技术浪潮的核心。 最近大模型相关…...
线程与进程
文章目录 什么是进程?什么是线程?线程、进程的区别多线程编程 什么是进程? 进程(Process)是计算机中的程序关于数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。简单来说,进程就…...

SparkSQL之Analyzed LogicalPlan生成过程
经过AstBuilder的处理,得到了Unresolved LogicalPlan。该逻辑算子树中未被解析的有UnresolvedRelation和UnresolvedAttribute两种对象。Analyzer所起到的主要作用就是将这两种节点或表达式解析成有类型的(Typed)对象。在此过程中,…...
Vue的状态管理有哪些?
在Vue中,有多种方式可以进行状态管理,以下是一些常见的Vue状态管理解决方案: 1:Vuex: Vuex是Vue官方提供的状态管理库,用于管理Vue应用程序中的状态。Vuex使用一个单一的全局状态树(state tre…...

1000道精心打磨的计算机考研题,408小伙伴不可错过
难度高! 知识点多! 复习时间短! 不要怕,计算机考研1000题来了! 不是数学考研1000题! 也不是政治考研1000题! 而是专属计算机考研小伙伴的超精选1000题! 计算机考研专业课需要大…...

Flink SQL 表值聚合函数(Table Aggregate Function)详解
使用场景: 表值聚合函数即 UDTAF,这个函数⽬前只能在 Table API 中使⽤,不能在 SQL API 中使⽤。 函数功能: 在 SQL 表达式中,如果想对数据先分组再进⾏聚合取值: select max(xxx) from source_table gr…...
pgsql_全文检索_使用空间换时间的方法支持中文搜索
pgsql_全文检索_使用空间换时间的方法支持中文搜索 一、环境 PostgreSQL 14.2, compiled by Visual C build 1914, 64-bit 二、引言 提到全文检索首先想到的就是ES(ElasticSearch)和Lucene,专业且强大。对于一些小众场景对于搜索要求不高,数据量也不…...

OpenGL_Learn10(颜色)
1. 颜色 我们在现实生活中看到某一物体的颜色并不是这个物体真正拥有的颜色,而是它所反射的(Reflected)颜色。换句话说,那些不能被物体所吸收(Absorb)的颜色(被拒绝的颜色)就是我们能够感知到的物体的颜色。例如,太阳光…...

使用Go语言抓取酒店价格数据的技术实现
目录 一、引言 二、准备工作 三、抓取数据 四、数据处理与存储 五、数据分析与可视化 六、结论与展望 一、引言 随着互联网的快速发展,酒店预订已经成为人们出行的重要环节。在选择酒店时,价格是消费者考虑的重要因素之一。因此,抓取酒…...

设计模式1
一、设计模式分类: 1、创建型模式:创建与使用分离,单例、原型、工厂、抽象、建造者。 2、结构型模式:用于描述如何将对象按某种更大的…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...

python/java环境配置
环境变量放一起 python: 1.首先下载Python Python下载地址:Download Python | Python.org downloads ---windows -- 64 2.安装Python 下面两个,然后自定义,全选 可以把前4个选上 3.环境配置 1)搜高级系统设置 2…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序
一、开发准备 环境搭建: 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 项目创建: File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...

淘宝扭蛋机小程序系统开发:打造互动性强的购物平台
淘宝扭蛋机小程序系统的开发,旨在打造一个互动性强的购物平台,让用户在购物的同时,能够享受到更多的乐趣和惊喜。 淘宝扭蛋机小程序系统拥有丰富的互动功能。用户可以通过虚拟摇杆操作扭蛋机,实现旋转、抽拉等动作,增…...

jdbc查询mysql数据库时,出现id顺序错误的情况
我在repository中的查询语句如下所示,即传入一个List<intager>的数据,返回这些id的问题列表。但是由于数据库查询时ID列表的顺序与预期不一致,会导致返回的id是从小到大排列的,但我不希望这样。 Query("SELECT NEW com…...
在golang中如何将已安装的依赖降级处理,比如:将 go-ansible/v2@v2.2.0 更换为 go-ansible/@v1.1.7
在 Go 项目中降级 go-ansible 从 v2.2.0 到 v1.1.7 具体步骤: 第一步: 修改 go.mod 文件 // 原 v2 版本声明 require github.com/apenella/go-ansible/v2 v2.2.0 替换为: // 改为 v…...