文章目录
- ==集合Collection==
- ==List集合==
- ==Set集合==
- ==Queue集合==
- ==Map集合==
- LinkedHashMap的实现原理?
- ==&== HashMap的实现原理/底层数据结构?JDK1.7和JDK1.8
- HashMap的put方法的执行过程?
- HashMap的get方法的执行过程?
- HashMap的resize方法的执行过程?
- JDK1.8之后,HashMap头插法改为尾插法?
- HashMap的size为什么必须是2的整数次方?
- HashMap多线程死循环问题?
- HashMap的get方法可以判断某个元素在map中?
- HashMap与HashTable的区别是什么?
- HashMap与ConcurrentHashMap的区别是什么?
- HashTable与ConcurrentHashMap的区别是什么?
- ConcurrentHashMap的实现原理是什么?
- ==其他==
集合Collection
java中常用的容器有哪些?
常见容器有两种:Collection 和 Map 两种。
Collection 存储着对象的集合(单列),Map 存储着键值对(两个对象)的映射表(双列)。
Collection集合又分为List、Set、Queue,这三个子接口;
List集合中有ArrayList、LinkedList、Vector实现类;
Set集合中有TreeSet、HashSet、LinkedHashSet实现类;
Queue集合中有LinkedList、priorityQueue实现类;
Map集合中的实现类有:TreeMap、HashMap、LinkedHashMap、HashTable、ConcurrentHashMap;
Collection常用方法
方法名 | 说明 |
---|---|
boolean add(Object o) | 添加元素对象 |
boolean remove(Element e) | 删除元素对象 |
boolean isEmpty() | 判断集合是否为空 |
boolean contains(Object o) | 判断集合中是否存在指定元素 |
int size() | 返回集合对象 |
void clear() | 清空集合元素 |
Iterator< E > iterator() | 返回此集合元素的迭代器 |
2022/11/26 迭代器
Collection集合的对象可以使用迭代器遍历元素;
Collection→Set
类 | 说明 |
---|---|
TreeSet | 基于红黑树实现,支持有序性操作。 TreeSet时间复杂度为O(logN) |
HashSet | 基于哈希表实现,支持快速查找,不支持有序性操作。 HashSet时间复杂度为O(1) 使用Iterator遍历HashSet得到的结果是不准确的 |
LinkedHashSet | 具有HashSet的查找效率,且内部使用双向链表维护元素的插入顺序。 |
Set集合中,没有特殊方法,所有方法都继承于Collection集合
Collection→List
类 | 说明 |
---|---|
ArrayList | 基于动态数组实现,支持随机访问 线程不安全 |
Vector | 基于动态数组实现,支持随机访问 线程安全 线程安全是有条件的:Vector的单个操作具有原子性=线程安全,如果两个原子操作复合而来,组合的方法是非线程安全的,需要使用锁来保证线程安全 |
LinkedList | 基于双向链表实现,只能顺序访问。 但可以快速地在链表中间插入和删除元素, 还可以用作栈、队列和双向队列 |
备注2022/8/20
- 动态数组
存储的是Object类型的变元素;数组长度是可以改变的的,如果长度不够,集合本身是会进行扩容的;
List常用方法
方法名 | 说明 |
---|---|
void add(int index,E e ) | 向集合中添加元素 |
E remove(int index) | 删除指定索引值的元素 |
E set(int index,E e) | 修改执行索引值位置的元素 |
E get(int indxe) | 获取指定索引值处的元素 |
int size() | 获取集合中元素的个数 |
注意事项:
- List的常用方法,所涉及的参数大多数有index
- ArrayList常用方法:add、remove、set、get、size
- LinkedList常用方法:addFirst、addLast、removeFirst、removeLast、getFirst、getLast。
Collection→Queue
类 | 说明 |
---|---|
LinkedList | 用来实现双向队列 |
PriorityQueue | 基于堆结构实现,用它来实现优先队列 |
Map集合
类 | 说明 |
---|---|
TreeMap | 基于红黑树实现 |
HashMap | 基于哈希表实现,线程不安全 |
HashTable | 基于哈希表实现,线程安全, 同一时刻多个线程可以同时写入HashTable并且不会导致数据不一致。 它是遗留类,不应该使用,替换的是 ConcurrentHashMap来支持线程安全,并且它的效率更高,因为它引入了分段锁 |
LinkedHashMap | 使用双向链表来维护元素的顺序(插入顺序 或者 最近最少使用顺序(LRU)) |
Map集合常用方法
方法名 | 说明 |
---|---|
V put(K,V) | 向集合中添加键值对元素,返回值 |
V remove( Object key) | 通过键删除集合中的元素,返回值 |
boolean containsKey(key) | 判断集合是否包含指定的键 key |
boolean containsValue(value) | 判断集合是否包含指定的值 value |
int size() | 返回集合的长度 |
void clear() | 清空集合 |
boolean isEmpty() | 判断是否为空 |
V get(Object key) | 根据键获取对应的值 |
Set< E> keySet() | 返回集合中所有键组成的Set集合 |
Collection< T> values() | 返回集合中所有值组成的Collection集合 |
Set<Map.entry<K,V>> entrySet() | 返回集合中所有键值对对象组成的Set集合 |
fail-fast和fail-safe有什么区别?
前者迭代期间不能修改元素、后者遍历的是复制后的集合
两者最大不同在于:fail-fast不允许在迭代期间修改集合元素,否则会报Concurrent Modification Exception;
fail-safe允许在迭代期间修改集合元素,它遍历的是集合元素的复制集合,缺点是对集合的修改,它不会察觉到;
fail-fast
- 概念:
快速失败;可能出现fail-fast机制的集合有ArrayList、HashMap。 - 案例:
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。
迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
这里异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出,因此,这个异常只建议用于检测并发修改的bug。 - 场景:
java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。
fail-safe
- 概念:
安全失败;常见的fail-safe方式遍历的容器有ConcurrentHashMap 和 CopyOnWriteArrayList。 - 案例:
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。 - 缺点:
基于拷贝内容的优点是避免了Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
Collection和Collections有什么区别?
前者是集合、后者是集合的工具类
Collection:是基本的集合接口,一个Collection代表一组Object(即Collection的元素),它的直接继承接口有List、Queue、Set接口。
Collections:不属于java的集合框架,是一个包装类,是集合类的一个工具类/帮助类。不能被实例化,服务于Collection框架。包含有关集合操作的静态、多态方法(对各种集合的搜索、排序、线程安全等操作)。
常用的方法有sort排序、reverse列表反转、shuffle随机排列指定的数组;
List、Map、Set三个接口,存取元素时,各有什么特点?
顺序可重复、无序不可重复、键值对无序
List:存储有顺序,且可以重复;
Set:存储无序的,不可以重复,具有唯一性;
Map:存储的是键值对,也是无序的;
List集合
ArrayList的扩容机制?
发生在add()方法被调用的时候,初始大小是10,每次扩容内存空间增加1.5倍
扩容实现方法:ensureCapacityInternal。
具体细节是:
- 通过calculateCapacity方法获取数据所需最小容量;
- 获取的最小容量通过ensureExplicitCapacity判断是否需要扩容,通过比较最小容量和元素存储空间的大小得出结论;需要则执行grow方法扩容。
- 具体扩容的实现采用的是oldCapacity>>1再加上oldCapacity实现;
add方法源码
// ArrayList的add方法 是java1.8版本的,1.9版本和这个不一样。
public boolean add(E e) {
//扩容
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
// ensureCapacityInternal就是用来扩容的,形参为最小扩容量
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
// 也就说 minCapacity(最小扩容量) = 实际数据所需要的内存空间
// elementData是ArrayList集合中的属性,transient Object[] elementData;
// 在进行ArrayList对象创建的时候,它的构造方法中就对elementData进行了赋值。
}
// calculateCapacity(Object[] elementData, int minCapacity) 计算容量,要存放数据的最小的目标容量。
private static int calculateCapacity(Object[] elementData, int minCapacity) {
// 如果传入的是个空数组 则 最小容量取默认容量与minCapacity之中的最大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
// ensureExplicitCapacity 判断是否需要扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 最小需要空间比elementData的内存空间更大,则需要扩容。
if (minCapacity - elementData.length > 0)
//扩容
grow(minCapacity);
}
// 扩容关键方法
private void grow(int minCapacity) {
// 获取ArrayList中的elementData数组的内存空间长度
int oldCapacity = elementData.length;
// 扩容为原来的1.5倍 验证下为什么是1.5倍,假设oldCapacity =5,则newCapacity =5+2=7 是1.4444倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
// 判断新数组容量够不够,够了直接使用这个长度创建新数组。
// 不够就直接将数组长度设置为需要的长度
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 若预设值大于默认的最大值 检测是否溢出
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 调用Arrays.copyof方法将elementData数组指向新的内存空间是newCapacity的连续空间
// 并将elementData的数组复制到新的内存空间
elementData = Arrays.copyOf(elementData, newCapacity);
}
扩展
- 知识点1 > 和 >> 和 >>> 的区别?> 表示大于;>> 表示右移,案例int i = 15,则i>>2 之后 i=3 分析:15的二进制00001111,右移两位就是00000011;>>> 进行无符号的位移处理,它不会将所处理的值的最高位视为正负符号,移位处理时直接空出来的高位补0。
ArrayList和LinkedList的区别?
区别有两点:
- 底层实现不同:ArrayList采用动态数据形式;LinkedList采用双向链表的形式
- 特点不同:ArrayList查找快、增删慢;LinkedList查找慢、增删快;
理解
- 查询 ArrayList快 LinkedList慢。
因为ArrayList可以使用下标直接索引对应的元素,而LinkedList需要一个个遍历。 - 增删 ArrayList慢 LinkedList快
非末项添加的情况,因为增加元素,ArrayList需要移动数组内对象的位置。
ArrayList增删不一定比LinkedList慢
- 假如增删都在末尾操作,LinkedList使用的是add/offer和remove/poll,此时的ArrayList则不需要移动和复制数组来进行操作。当数据是百万级时,ArrayList速度会比LinkedList快;
- 删除的数据在中间位置,LinkedList主要费时在遍历上,ArrayList主要费时在移动和删除上,当数据是百万级时,ArrayList会快于LinkedList。
ArrayList实现RandomAccess接口有何作用?为何LinkedList却没有实现这个接口?
ArrayList遍历使用for循环、LinkedList遍历使用迭代器
ArrayList使用for循环比使用迭代器遍历更快,而LinkedList使用迭代器比使用for循环遍历更快。
RandomAccess接口是一个标志接口,实现这个接口的类,都支持快速随机访问,也就是for循环的形式遍历元素;
而ArrayList实现该接口,可以提升它的遍历效率;
Array和ArrayList有和区别?什么更适合用Array?
- 概念不同:
Array是编程基础组件;ArrayList是java集合中的一个实现类; - 长度不同:
Array长度固定,创建是设置好的,通过length属性可以得到长度;ArrayList长度不固定,初始长度是10,每次扩容1.5倍,通过size()查看长度; - 存储对象不同:
Array基本数据类型和对象;ArrayList对象; - 泛型:
Array不支持泛型;ArrayList支持泛型; - 遍历方式不同:
Array可以采用简单for循环遍历;ArrayList遍历方式有:简单for循环、增强for循环、迭代器;
Array的使用场景
- 列表的大小已经指定,操作集中于存储和遍历元素;
- 遍历基本数据类型;(虽然Collections有自动装箱操作,将基本数据类型变成其包装类,但是对于长度固定的基本数据类型数据来说,采用Array数组存储比ArrayList更便捷);
- 想要多维数组,使用[][] 比使用List<<>>更容易。
Iterator怎么使用?有什么特点?
特点
是一个对象,可以遍历并选择序列中的对象;
通常被称为“轻量级”对象,因为创建它的代价小,功能简单,只可以单向移动;
使用方法
方法名 | 说明 |
---|---|
Iterator<?> iterator() | 返回一个Iterator对象 Collection集合对象所拥有的方法 |
E next() | 返回序列的下一个元素 若第一次调用,则返回序列的第一个元素 |
boolean hasNext() | 检查序列中是否有元素 |
default void remove() | 删除迭代器新返回的元素 |
Iterator和ListIterator有什么区别?
- 遍历对象不同:
Iterator 可以遍历Set和List集合;ListIterator 只可以遍历List集合; - 遍历形式不同:
Iterator只能前向遍历;ListIterator可以前向、后向遍历; - 共有方法:
next、hasnext、remove; - ListItrator特有方法:
previous、hasPrevious、nextIndex、previousIndex、set;
E previous() 返回列表中的上一个元素
Boolean hasPrevious() 列表迭代器在相反的方向具有更多元素,返回true
int nextIndex() 返回由后续调用返回的元素的索引值,也就是返回next()方法返回值的索引
int previousIndex()返回由后续调用返回的元素的索引值,即返回previous()方法返回值的索引
void set(E e) 用指定的元素替换next()或previous()返回的最后一个元素
Iterator和Enumeration接口的区别?
- 概念不同:
Iterator是迭代器,用于实现Colleciton集合元素的遍历;
Enumeration实现枚举类型数据的生成,一次生成一个; - 接口方法不同:
Iterator有三个方法,分别是next、hasnext、remove;
Enumeration有两个方法,分别是hasMoreElements、nextElements; - 安全性:
Iterator更安全,因为实现了fail-fast机制,不允许在迭代过程中修改元素;
Enumeration不安全;
ArrayList、Vector、LinkedList的区别?
- 底层实现不同:
ArrayList和Vector采用动态数组的形式,LinkedList采用双向链表的形式; - 线程安全性不同:
ArrayList线程不安全,Vector和LinkedList线程安全; - 访问形式不同:
ArrayList和Vector可以随机访问,LinkedList只能顺序访问;
Vector线程安全的原因
java底层针对Vector线程不安全的操作都加了synchronized;
ArrayList线程不安全的原因
ArrayList的add方法中有size++的操作,该操作不是原子性的,当前a线程操作该集合进行size++的过程中,另一个线程b也对该集合进行操作,那么此时该集合对象的size就不准确了;
Set集合
HashSet的实现原理、以及如何保证元素不重复?
底层通过HashMap实现;
HashSet构造方法创建对象的时候,底层会创建一个HashMap对象。
HashSet集合存储的元素会存放在HashMap集合的key键位置。由于HashMap的key具有唯一性,因而HashSet存储的元素具有唯一性。
底层HashMap中value的位置存储是present(仅是一个占位符)。
Set用什么方法来区分重复与否呢?
以HashSet为例。
- HashSet集合,底层的实现是基于 HashMap,HashSet的值 存放在 HashMap的key 的位置,由于Key具有唯一性,那么HashSet的元素就不能重复。
- HashMap的key 唯一性如何保证。
当执行put(key,value)操作的时候,①通过hashCode()计算key对应的hash值 ②计算index = hash &(length-1)③判断哈希表index位置是否有元素,有元素则比较hash值是否相同 ④ 哈希值不相同 则存储成功;相同 则继续比较key ⑤如果key不同 则存储成功;相同,则会替换原本的value。
整体的比较过程是:先计算hash值、再计算index索引、然后查看索引位置、有元素比较哈希值、不相同存储、相同再比较key值,不相同存储、相同将其替换
Queue集合
Map集合
LinkedHashMap的实现原理?
LinkedHashMap是通过 哈希表 和 链表 实现的。
它是基于HashMap实现的,不同之处是定义了一个Entry header(头指针) 和 Entry tail(尾指针),不在table中,独立于table。
继承了HashMap的Entry,并添加了两个属性 before 和 after,before、after、Entry header组成一个链表,实现按插入顺序或访问顺序排序。此时Entry元素保存的有:当前对象的引用、上一个元素before引用、下一个元素after的引用。
当前对象的引用指的是 Value?
& HashMap的实现原理/底层数据结构?JDK1.7和JDK1.8
区别
- 底层结构不同:
JDK1.7 = 采用数组+链表的形式;JDK1.8 = 采用数组+链表/红黑树的形式;
链表什么情况会变成红黑树:当某个索引位置上以链表形式存在的元素的存储个数>8,并且当前数组的长度>64时候,此索引位置上链表会变成红黑树; - 新元素添加方式不同:
JDK1.7使用新元素指向旧元素,头插法;JDK1.8是旧元素指向新元素,尾插法; - 实例化不同
创建数组的长度都是16,JDK1.7底层创建Entry[] table;JDK1.8底层创建Node[]
实例化
HashMap hm = new HashMap(); 【以JDK1.7为例】
- 底层创建长度是16的一维数组Entry[] table
- 执行hm.put(key1,values1);
- 调用类的hashCode计算key1的哈希值,再利用哈希值计算index索引,索引值为0-15之间,映射到Entry数组;
(计算过程是:index = hashcode & (length -1)) - 若位置为空,则(key1、value1)添加成功;
- 若位置不为空,比较key1和已经存在的数据 比较其哈希值
- 都不同,则(key1、value1)添加成功;
- 存在相同的,则进一步通过equals比较key
- 返回值是false,(key1、value1)添加成功;
- 返回值是true,就是value1替换已存在的值。
HashMap的put方法的执行过程?
- 创建HashMap对象后,底层会产生一个长度为16的entry[]数组。
put方法的功能是向HashMap集合中添加键值对元素。put(key,value) - 根据key计算对应的hash值,根据hash值确定在链表中的位置;【本质哈希值%16取余确定位置,但具体的实现是通过index = hash & (数组.length-1)】
- 该位置没有元素,则成功添加;
- 有元素,则需要将此元素hash值与位置中存储元素的hash值进行比较。
- 没有一个相同,则成功添加;
- 存在相同,则比较具体的key
- 相同,则用新的value覆盖旧的value;
- 不同,则成功添加。
HashMap的get方法的执行过程?
hash值、index、判断是否为空-是否是要找的值、再判断第二个点不为空判断数据类型、链表采用equals、红黑树采用getTreeNode查找
- 根据hash方法获得key的hash值
- 通过hash&(length-1)的方式获得key对应的Entry[]数组下标。
hash&(length-1) 替换的是 hash % length 的操作 hash=25(11001) length=16 计算结果是满足的。 - 每次都会判断结点是否为空,不为空则判断是否是要找的值,不是则继续判断,是则返回null
- 判断第二个结点是否为空,为空则返回null,不为空则判断数据结构是链表还是红黑树
- 链表则进行顺序遍历,使用==和equals判断key是否相同,满足条件则返回,遍历完都没有找到返回null
- 红黑树结构,使用getTreeNode()查找操作。
HashMap的resize方法的执行过程?
-
基础:
HashMap初始大小是16,扩容采用2倍的形式; -
调用resize方法的情况:
第一次调用HashMap的put方法的时候,会调用resize方法对table数组进行初始化,不传入指定值,默认大小是16;
扩容时调用resize,即size>threshold时,table数组大小会翻倍; -
resize方法触发时机:
初始化HashMap的默认扩容是cap=16,threshold=12, 的Node<K,V>[] newTab
当hashMap的size>threshold的时候,再次扩容,容量cap=162,阈值threshold=122
当table中的Node链表>8,且tab.length<64,hash再次double扩容;
JDK1.8之后,HashMap头插法改为尾插法?
修改原因是:
- 并发情况下,头插法可能会形成数据环,get数据时死循环;
- 为了代码书写方便:jdk1.7链表结构,采用头插法更方便;jdk1.8红黑树结构,采用尾插法更方便;
HashMap的size为什么必须是2的整数次方?
计算速度快、内存空间浪费少
优点:
- 能保证HashMap的底层数组长度是2的n次方,此条件满足的话,hash % length 操作可以被 替换成 hash &(length-1),后者速度比直接取模速度快;计算速度
- length是2的幂次,length-1必定是11111…的形式,与hash进行&操作效率更高,且空间没有浪费。
解释:如果length=15,那么length-1 = 14 》1110 则与hash&操作之后,最后一位都是0,那么0001、0011、0101、0111 、1001 、1011、1101、1111这几个位置永远不会放元素。内存空间浪费
HashMap多线程死循环问题?
当多线程同时put时,且同时触发扩容操作,会导致HashMap中的链表中出现循环节点,进而使得get的时候,出现死循环。
描述:
链表存储key分别是3 7 5 由于hash表的size是2,引起扩容4,之后的存储就是index0123 对应的是5 73
上述是单线程的情况,要是多线程会存在循环的情况;
线程1想要扩容的时候,突然被挂起了;但是线程1的e指向key3;
线程2开始扩容并且已经完成,具体的存储是index0123 对应的是5 73;
线程1被唤醒之后,会执行e = next 也就是e = e.next 此时指向了key 7 ;
接下来再取元素key 3放在index 3的链表头部,此时就会产生head→key 3→key 7→key 3的情况。
HashMap的get方法可以判断某个元素在map中?
不能判断。
因为HashMap中允许key、value为null。
通过get方法获得value为null,不能确定是value是null,还是key不存在于HashMap集合中。
HashMap与HashTable的区别是什么?
- 父类不同:
HashMap父类是AbstactMap;HashTable父类是Dictionary; - key、value不同:
HashMap允许key、value为null;HashTable不允许key、value为null; - 线程安全性:
HashMap线程不安全、HashTable线程安全;
安全性采用的是锁机制,使用synchronized锁住整张表; - 底层数组不同:
HashMap底层数组长度是16、扩容是2倍、index计算方式是hash & (tab.legnth-1);
HashTable底层数据长度是11、扩容是old*2+1,index 的计算方式是 (hash & 0x7FFFFFFF) % tab.length
HashMap与ConcurrentHashMap的区别是什么?
- 线程安全:
HashMap线程不安全、ConcurrentHashMap线程安全; - 并发扩容:
HashMap不支持、ConcurrentHashMap通过锁来实现并发扩容;
采用分段锁,将整个hash桶进行分段segment,即将底层数组分段segment,每个小段都加锁,插入元素需要考虑插入哪一段,并且需要获得对应segment段的锁,这样减少了锁的粒度。
HashTable与ConcurrentHashMap的区别是什么?
- 效率问题:
HashTable效率低:使用synchronized关键字对操作进行加锁,且锁的是整张Hash表,线程只可以单独运行;
ConcurrentHashMap效率高:内存分段,给每个段加锁,执行操作的时候会先判断是哪个Segment,然后再去获取对应的锁,实现了多线程并发编程。
ConcurrentHashMap的实现原理是什么?
JDK1.7
实现形式:HashEntry(数组) + Segment(分段锁) 的方式
将数据分成一段一段存储,给每一段都分配一个锁,当线程占用锁访问其中一个数据段的时候,其余数据段可供其他线程访问。
- 采用ConcurrentHashMap存储数据,数据区被分成多个Segment(即Segment数组),每个Segment都会被分配锁。
- Segment是数组和链表结构,每个Segment包含一个HashEntry数组,HashEntry中的每个元素都是链表结构的,Segment守护所包含的HashEntry数组里的元素,当对HashEntry数组的元素进行修改的时候,必须先获得Segment锁。
- ConcurrentHashMap有两个内部静态类:HashEntry用来封装映射表的键值对;Segment用来表示锁。
- Segment是一种可重入锁(ReentrantLock),它是Lock接口的子类。
JDK1.8
实现形式:Node + CAS + Synchronized 来保证并发安全进行实现。(也有说是 数组 + 链表 + 红黑树)
底层是Node类型的数组,引入CAS机制避免加锁操作,仍会出现同步代码,锁的粒度相对于分段锁而言更加细粒度。
CAS = Compare And Swap,比较并替换。常用的三个操作数:内存地址V、旧的预期值A、要修改的新值B
工作原理:更新一个变量,只有当变量的预期值A和内存中的实际值相同的时候,才会将内存地址的值改为新值B。
java中的原子类的底层利用了CAS机制。
其他
不同数据类型获取长度的方法?
数据类型 | 获取长度的方式 |
---|---|
数组 | 数组.length属性 |
String | String.length()方法 |
集合 | 集合.size() 方法 collection集合的方法 |
文件 | 文件.length()方法 |
遗留的集合有哪些?
HashTable、Stack、Vector。