一,集合概念
在进行开发的时候处理多个数据时,最原始的就是使用数组来进行存储,但是数组有一些缺点就是长度是固定的,需要开发者手动去维护!
在JDK1.2 时候,推出集合系列,提供一些用于存储数据的集合,存放在java.util 包下;通常在实现业务的时候,会先有一个标准;集合的标准就是Collection 接口;该接口提供集合一套处理存储数据的标准API;
集合的具体实现就是通过链表,数组,二叉树,队列,栈等数据结构来实现数据的存储!
1.1 数组的时间复杂度
【查询】如果知道数组索引的情况下,时间复杂度为:O(1); 在不知道索引的情况下,需要遍历数组,此时的时间复杂度为 O(n);
【插入】在数组中插入元素涉及到元素的移动操作。如果在已知位置插入元素,时间复杂度为O(n),因为需要移动该位置之后的所有元素。如果是插入到末尾,这种操作是知道索引的位置,故时间复杂度为:O(1);
【删除】在数组中删除元素涉及到元素的移动操作。如果在已知位置删除元素,时间复杂度为O(n),因为也需要移动之后元素的位置。如果是删除末尾删除,时间复杂度为O(1); 因为末尾不需要移到元素。
【二分查找法】数组支持二分查找算法,在二分查找算法里,每次都将查询范围缩小一半,因此它的时间复杂度是对数级别的。它的时间复杂度为:O(log2n)
1.2 链表的时间复杂度
链表的时间复杂度取决于具体操作,主要有如下几种情况:
【查询】:在单向链表中,查询操作的平均时间复杂度为O(n), 因为链表是没有索引的,只能逐个遍历查询,最坏的情况需要遍历整个链表;
【插入】:在单向链表中,插入操作的平均时间复杂度为O(1)。因为只需要知道插件位置的前一个节点,只需要修改前一个节点即可。不需要移动其他节点;
【删除】:在单向链表中,删除操作的平均时间复杂度也为O(1)。因为也只需要知道修改位置的前一个节点的指针即可。同样不需要移动其他节点。如果是双向链表的插入和删除操作可能涉及到更多的指针操作,因此其平均时间复杂度可能略高于O(1), 但仍高于常熟级别的。
【跳表机制 Skip List】如果考虑到跳表机制的因素的话,时间复杂度为O(log2N); 跳表是一种可以快速查询,插入和删除元素的数据结构,类似于多层的有序链表,通过添加索引层来加快查询速度。
查资料的时候,发现跳表机制,有些资料说是logn 有些资料是说log2n; 通常情况下,当我们提及对数时间复杂度时,如果没有特别说明,默认使用的是以2为底的对数,即log2n。这是因为在计算机科学中,对数的底数通常认为2。因此在跳表机制中,没有特别说明,一般会用logn表示对数时间复杂度,实际上表示的是以2为低的对数。
1.3 树的时间复杂度
平衡二叉树和链表的时间复杂度通常不相同,因为它们在不同的操作上有不同的性能表现。
【插入/删除/查询】这几项的平均时间复杂度为O(log n) 其中n为树节点的数量,这是因为平衡二叉树保持了数的平衡性,使得每个节点的子树高度差不会过大,而从保证了查询的效率。当然,如果树节点的数据只向一边走,则会演变成链表结构。这个时候时间复杂度为 O(n); 如下图正式表示数据往一边走导致平衡二叉树演变为链表
绘画树在线网址: Data Structure Visualization (usfca.edu)
1.4 队列的时间复杂度
队列是一种先进先出(FIFO)的数据结构,通常用于描述在程序中的任务队列执行的情况。队列的时间复杂度是取决于队列的操作,常见操作包括入队(enqueue), 出队(dequeue), 访问对头元素等等,java里面的双向队列还支持访问对头和队尾的操作处理。
【入队(enqueue)】:将元素加入到队列的末尾,时间复杂度为 O(1)。
【出队(dequeue)】:从队列的头部取出一个元素,时间复杂度为 O(1)。
【访问队头元素】:获取队列头部的元素,时间复杂度为 O(1)。
【其他操作】:如遍历队列中的元素,时间复杂度为 O(n),其中 n 为队列中的元素数量。
1.5 栈帧的时间复杂度
栈帧是指在函数调用过程中,每个函数所占用的内存空间,包括函数的参数、局部变量、返回地址等信息。栈帧通常是通过栈来管理的,因此具有栈的特性,即后进先出(LIFO)。栈帧的时间复杂度取决于栈的操作,常见操作包括压栈(push)、弹栈(pop)、访问栈顶元素等
【压栈 push】: 向栈中压入一个元素,时间复杂度为O(1);
【弹栈 pop】: 从栈中弹出一个元素,时间复杂度为O(1);
【访问栈顶元素】:获取栈顶元素,时间复杂度为O(1);
【其他操作】:如遍历栈中的元素,时间复杂度为O(n), 其中 n 为 栈中的元素数量
1.6 时间复杂度图
在线图形网址:Desmos | 图形计算器
O(1)的时间复杂度图
O(n) 的时间复杂度
O(logn) 的时间复杂度
O(log2N) 的时间复杂度
二,集合继承结构
在Collection 最为常见的子接口:List(有序并且元素可重复),Set(无需并且元素不可重复),Queue(队列)
2.1 Collection 接口
Collection 继承结构先从红框框开始讲
Iterator和Iterable专门放到集合迭代小章节中进行讲解。
Collection 接口为集合提供了业务标准;有查询业务标准,修改业务标准,批量处理业务标准,对比和hashCode计算等业务标准
package java.util;
import java.util.function.IntFunction;
import java.util.function.Predicate;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
* @since 1.2
*/
public interface Collection<E> extends Iterable<E> {
// 查询操作
/** 集合大小 */
int size();
/** 集合是否为空 */
boolean isEmpty();
/** 集合是否包含指定内容 */
boolean contains(Object o);
/** 集合的迭代器 */
Iterator<E> iterator();
/** 集合转换为数组 */
Object[] toArray();
/** 集合转化为指定类型的数组 */
<T> T[] toArray(T[] a);
/**
* @since 11 利用 函数式接口的进行转换
*/
default <T> T[] toArray(IntFunction<T[]> generator) {
return toArray(generator.apply(0));
}
// 修改操作
/** 添加元素 */
boolean add(E e);
/** 删除指定元素 */
boolean remove(Object o);
// 批量处理
/** 集合是否包含指定集合的数据 */
boolean containsAll(Collection<?> c);
/** 批量添加元素 */
boolean addAll(Collection<? extends E> c);
/** 批量删除元素 */
boolean removeAll(Collection<?> c);
/**
* @since 1.8 根据谓词来判断是否有内容;有内容删除 返回true, 否则繁反之
*/
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
/** 保留指定C集合里面的里面的内容 */
boolean retainAll(Collection<?> c);
/*
*/
void clear();
boolean equals(Object o);
int hashCode();
/**
* 对集合进行分割迭代
* @since 1.8
*/
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, 0);
}
/**
* 将集合数据转换成Java8的 数据 Stream
* @since 1.8
*/
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
/**
* 将集合数据转换成Java8的 数据 Stream 支持并发
* @since 1.8
*/
default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
}
2.2 AbstractCollection 抽象类
AbstractCollection 抽象类,为Collection接口业务标准,实现了一套共有的业务。并且为集合的业务定义了一套基本骨架。也被称为“模板”;而里面的方法也就是“模板方法”;这是一种设计模式。叫做模板方法设计模式。先看源码
package java.util;
import jdk.internal.util.ArraysSupport;
/**
* @author Josh Bloch
* @author Neal Gafter
* @see Collection
* @since 1.2
*/
public abstract class AbstractCollection<E> implements Collection<E> {
protected AbstractCollection() {}
// Query Operations
public abstract Iterator<E> iterator();
public abstract int size();
/** 提供了判断是否为空的业务模板 */
public boolean isEmpty() {
return size() == 0;
}
/** 提供了是否包含指定元素的业务模板 */
public boolean contains(Object o) {
Iterator<E> it = iterator();
if (o==null) {
while (it.hasNext())
if (it.next()==null)
return true;
} else {
while (it.hasNext())
if (o.equals(it.next()))
return true;
}
return false;
}
/** 提供了转换数组的业务模板 */
public Object[] toArray() {
// Estimate size of array; be prepared to see more or fewer elements
Object[] r = new Object[size()];
Iterator<E> it = iterator();
for (int i = 0; i < r.length; i++) {
if (! it.hasNext()) // fewer elements than expected
return Arrays.copyOf(r, i);
r[i] = it.next();
}
return it.hasNext() ? finishToArray(r, it) : r;
}
/** 提供了转换指定类型数组的业务模板 */
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
// Estimate size of array; be prepared to see more or fewer elements
int size = size();
T[] r = a.length >= size ? a :
(T[])java.lang.reflect.Array
.newInstance(a.getClass().getComponentType(), size);
Iterator<E> it = iterator();
for (int i = 0; i < r.length; i++) {
if (! it.hasNext()) { // fewer elements than expected
if (a == r) {
r[i] = null; // null-terminate
} else if (a.length < i) {
return Arrays.copyOf(r, i);
} else {
System.arraycopy(r, 0, a, 0, i);
if (a.length > i) {
a[i] = null;
}
}
return a;
}
r[i] = (T)it.next();
}
// more elements than expected
return it.hasNext() ? finishToArray(r, it) : r;
}
/** AbstractCollection 抽象类内部的真正的转换数组方法;private 只能在内部调用 */
@SuppressWarnings("unchecked")
private static <T> T[] finishToArray(T[] r, Iterator<?> it) {
int len = r.length;
int i = len;
while (it.hasNext()) {
if (i == len) {
len = ArraysSupport.newLength(len,
1, /* minimum growth */
(len >> 1) + 1 /* preferred growth */);
r = Arrays.copyOf(r, len);
}
r[i++] = (T)it.next();
}
// trim if overallocated
return (i == len) ? r : Arrays.copyOf(r, i);
}
// Modification Operations
public boolean add(E e) {
throw new UnsupportedOperationException();
}
/** 提供了删除的业务模板 */
public boolean remove(Object o) {
Iterator<E> it = iterator();
if (o==null) {
while (it.hasNext()) {
if (it.next()==null) {
it.remove();
return true;
}
}
} else {
while (it.hasNext()) {
if (o.equals(it.next())) {
it.remove();
return true;
}
}
}
return false;
}
// Bulk Operations
/** 提供了批量包含处理的业务模板 */
public boolean containsAll(Collection<?> c) {
for (Object e : c)
if (!contains(e))
return false;
return true;
}
/** 提供了批量添加的业务模板 */
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
/** 提供了批量删除的业务模板 */
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
boolean modified = false;
Iterator<?> it = iterator();
while (it.hasNext()) {
if (c.contains(it.next())) {
it.remove();
modified = true;
}
}
return modified;
}
/** 提供了保留指定集合C里面元素业务的模板 */
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
boolean modified = false;
Iterator<E> it = iterator();
while (it.hasNext()) {
if (!c.contains(it.next())) {
it.remove();
modified = true;
}
}
return modified;
}
/** 提供了清除的业务模板 */
public void clear() {
Iterator<E> it = iterator();
while (it.hasNext()) {
it.next();
it.remove();
}
}
// String conversion
/** 提供了toString()的业务模板 */
public String toString() {
Iterator<E> it = iterator();
if (! it.hasNext())
return "[]";
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {
E e = it.next();
sb.append(e == this ? "(this Collection)" : e);
if (! it.hasNext())
return sb.append(']').toString();
sb.append(',').append(' ');
}
}
}
在抽象类中这样写的目的是什么,两个目的,复用和扩展,复用怎么理解?后面具体的子类像ArrayList要转换成数组,可以不用在写一个单独的toArray()方法,直接用AbstractCollection上面提供的进行转换即可。Set的集合也是一样的。这样就达到将公共方法业务抽离。而这些方法可以看做成一个模板,直接拿来用。而这种设计方式也被称为:模板设计模式。
这些方法体现出模板的效果;统一的一套标准业务流程;
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
boolean modified = false;
Iterator<?> it = iterator();
while (it.hasNext()) {
if (c.contains(it.next())) {
it.remove();
modified = true;
}
}
return modified;
}
public void clear() {
Iterator<E> it = iterator();
while (it.hasNext()) {
it.next();
it.remove();
}
}
public boolean retainAll(Collection<?> c) {
Objects.requireNonNull(c);
boolean modified = false;
Iterator<E> it = iterator();
while (it.hasNext()) {
if (!c.contains(it.next())) {
it.remove();
modified = true;
}
}
return modified;
}
第二个就是扩展,指定的不是代码上的扩展,而是指定的是框架的扩展性,集合Collection这个接口往后追加各种数据结构构成一个集合业务的扩展性得到了保障!今天是List, Set,Queue,往后如果还想加图形数据结构等等。当然如果真的要加,肯定会新建一个接口,像Map一样,对于树形数据结构就开了一个接口。
2.3 ValueCollection 类
它是Hashtable里面的一个内部类;作用是什么?我想各位看官看到values()方法就已经知道values()的作用是获取Hashtable集合中所有的value值;Hashtable.ValueCollection是Hashtable类的内部类,表示Hashtable对象所有值的集合。它实现了Collection接口,也就意味着ValueCollection具有聚合相应的业务方法和属性。
package java.util;
import java.io.*;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.BiFunction;
import jdk.internal.access.SharedSecrets;
/**
* @author Arthur van Hoff
* @author Josh Bloch
* @author Neal Gafter
* @since 1.0
*/
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable {
private transient volatile Collection<V> values;
// ......
public Collection<V> values() {
if (values==null)
values = Collections.synchronizedCollection(new ValueCollection(),
this);
return values;
}
private class ValueCollection extends AbstractCollection<V> {
public Iterator<V> iterator() {
return getIterator(VALUES);
}
public int size() {
return count;
}
public boolean contains(Object o) {
return containsValue(o);
}
public void clear() {
Hashtable.this.clear();
}
// ......
}
}
2.3.1【ValueCollection案例】
package org.toast.collection;
import java.util.Collection;
import java.util.Hashtable;
/**
* @author toast
* @time 2024/4/8
* @remark
*/
public class TestValueCollection {
public static void main(String[] args) {
Hashtable<Integer, String> hashtable = new Hashtable<>();
hashtable.put(1, "A-TOAST");
hashtable.put(2, "B-TOAST");
hashtable.put(3, "C-TOAST");
Collection<String> values = hashtable.values();
for (String value : values) {
System.out.println(value);
}
}
}
2.4 AbstractImmutableCollection
AbstractCollection和AbstractImmutableCollection 一个是提供集合公共业务的基本骨架,AbstractImmutableCollection 是AbstractCollection 子类,意味着有集合业务功能,并且该子类下的集合都是不可变的; Immutable 不可变的,该抽象类的集合是不可变的;具体是怎么实现的。看源码;
static UnsupportedOperationException uoe() {
//
return new UnsupportedOperationException();
}
@jdk.internal.ValueBased
static abstract class AbstractImmutableCollection<E> extends AbstractCollection<E> {
// all mutating methods throw UnsupportedOperationException
@Override public boolean add(E e) { throw uoe(); }
@Override public boolean addAll(Collection<? extends E> c) { throw uoe(); }
@Override public void clear() { throw uoe(); }
@Override public boolean remove(Object o) { throw uoe(); }
@Override public boolean removeAll(Collection<?> c) { throw uoe(); }
@Override public boolean removeIf(Predicate<? super E> filter) { throw uoe(); }
@Override public boolean retainAll(Collection<?> c) { throw uoe(); }
}
从源码得知,所有的add,addAll,clear,remove等等涉及到修改处理的操作都是直接抛出异常表示不允许执行操作的异常 UnsupportedOperationException;所以一旦调用新增,删除等修改操作就会触发异常。以这种设计方式来达到不可修改的目的。从这里也可以看出该抽象类指定的不可修改指定的是内容。而该对象的引用指针还是可以被修改的。
2.4.1 继承结构
它还有具体的三个不可修改集合的实现子类;分别是List, Set和Map。
AbstractImmutableList 的具体实现子类是 List12
AbstractImmutableSet 的具体实现子类是 Set12
AbstractImmutableMap 的具体实现子类是 Map1
它们四个不可修改的类都是ImmutableCollections的内部类
并且由于ImmutableCollections 这个工具类是没有public修饰,意味着只能被java.util包的类使用。由于这种不可变的集合是JDK9提供的。在List,Set, Map里面JDK9都追加一个of()一个函数。
我们看一看List, Set, Map of里面用的是什么,源码如下:
2.4.2【List集合不可修改案例】
package org.toast.collection;
import java.util.List;
/**
* @author toast
* @time 2024/4/10
* @remark
*/
public class TestImmutableCollection {
public static void main(String[] args) {
List<String> immutableList = List.of("TOAST-A", "TOAST-B");
System.out.println("=======================List不可变=======================");
for (String item : immutableList) {
System.out.println(item);
}
// 试图修改列表将抛出UnsupportedOperationException异常
immutableList.add("加油!!!");
}
}
2.4.3【Set集合不可修改案例】
public class TestImmutableCollection {
public static void main(String[] args) {
Set<String> immutableSet = Set.of("TOAST-A", "TOAST-B");
System.out.println("=======================Set不可变=======================");
for (String item : immutableSet) {
System.out.println(item);
}
// 试图修改列表将抛出UnsupportedOperationException异常
immutableSet.add("加油!!!");
}
}
2.4.4【Map集合不可修改案例】
public class TestImmutableCollection {
public static void main(String[] args) {
Map<String, Integer> immutableMap = Map.of("apple", 1, "banana", 2, "cherry", 3);
System.out.println("=======================Map不可变=======================");
for (Map.Entry<String, Integer> entry : immutableMap.entrySet()) {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
// 试图修改映射将抛出UnsupportedOperationException异常
immutableMap.put("date", 4);
}
}
2.5 UnmodifiableCollection
它也是不可变/修改的集合,在JDK1.2就提供了。既然在JDK1.2就提供了,为什么在JDK9还要提供一个AbstractImmutableCollection集合呢?它们的区别是什么?
2.5.1【List集合不可修改案例】
package org.toast.collection;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* @author toast
* @time 2024/4/8
* @remark 测试List不可变
*/
public class TestImmutableCollection {
public static void main(String[] args) {
List<String> immutableList = Collections.unmodifiableList(Arrays.asList("TOAST-A", "TOAST-B", "TOAST-C"));
System.out.println("=======================可不变List=======================");
for (String item : immutableList) {
System.out.println(item);
}
// 试图修改列表将抛出UnsupportedOperationException异常
immutableList.add("加油!!!");
}
}
2.5.2 【List集合不可修改-缺点显露】
public class TestUnmodifiableCollection {
public static void main(String[] args) {
List<String> data = new ArrayList();
data.add("TOAST-A");
data.add("TOAST-B");
data.add("TOAST-C");
List<String> immutableList = Collections.unmodifiableList(data);
data.add("水果忍者-LIST");
System.out.println("=======================可不变List=======================");
for (String item : immutableList) {
System.out.println(item);
}
// 试图修改列表将抛出UnsupportedOperationException异常
immutableList.add("加油!!!");
}
}
2.5.3【Set集合不可修改案例】
package org.toast.collection;
import java.util.*;
/**
* @author toast
* @time 2024/4/8
* @remark
*/
public class TestImmutableCollection {
public static void main(String[] args) {
Set<String> immutableSet = Collections.unmodifiableSet(new HashSet<>(Set.of("SET-TOAST-A", "SET-TOAST-B", "SET-TOAST-C")));
System.out.println("=======================可不变Set=======================");
for (String item : immutableSet) {
System.out.println(item);
}
// 试图修改集合将抛出UnsupportedOperationException异常
immutableSet.add("加油!!!");
}
}
2.5.4【Set集合不可修改-缺点显露】
public class TestUnmodifiableCollection {
public static void main(String[] args) {
HashSet<String> data = new HashSet<>(Set.of("SET-TOAST-A", "SET-TOAST-B", "SET-TOAST-C"));
Set<String> immutableSet = Collections.unmodifiableSet(data);
data.add("水果忍者-SET");
System.out.println("=======================可不变Set=======================");
for (String item : immutableSet) {
System.out.println(item);
}
// 试图修改集合将抛出UnsupportedOperationException异常
immutableSet.add("加油!!!");
}
}
2.5.5【Map集合不可修改案例】
package org.toast.collection;
import java.util.*;
/**
* @author toast
* @time 2024/4/8
* @remark
*/
public class TestImmutableCollection {
public static void main(String[] args) {
Map<String, String> immutableMap = Collections.unmodifiableMap(new HashMap<>(Map.of("apple", "1", "banana", "2", "cherry", "3")));
System.out.println("=======================可不变Map=======================");
for (Map.Entry<String, String> entry : immutableMap.entrySet()) {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
// 试图修改映射将抛出UnsupportedOperationException异常
immutableMap.put("TOAST-KEY", "TOAST-VALUE");
}
}
2.5.6【Map集合不可修改-缺点显露】
package org.toast.collection;
import java.util.*;
/**
* @author toast
* @time 2024/4/8
* @remark 只能包装现有的集合,而不能直接创建一个新的不可变集合。这意味着如果原始集合发生了改变,
* 那么通过 unmodifiableCollection 方法创建的不可变集合也会受到影响
*/
public class TestUnmodifiableCollection {
public static void main(String[] args) {
HashMap<String, String> data = new HashMap<>(Map.of("apple", "1", "banana", "2", "cherry", "3"));
Map<String, String> immutableMap = Collections.unmodifiableMap(data);
data.put("水果忍者", "切切切"); // 如果原始集合发生了改变, 那么通过 unmodifiableCollection 方法创建的不可变集合也会受到影响
System.out.println("=======================可不变Map=======================");
for (Map.Entry<String, String> entry : immutableMap.entrySet()) {
System.out.println(entry.getKey() + " -> " + entry.getValue());
}
// 试图修改映射将抛出UnsupportedOperationException异常
immutableMap.put("TOAST-KEY", "TOAST-VALUE");
}
}
包装了一层的immutableMap集合尽管数据是不可变的,但是通过data源集合下手进行添加,immutableMap集合还是收到影响!不管是List,Set都是一样的
2.5.7 UnmodifiableCollection 与AbstractImmutableCollection 区别
JDK 1.2 的提供 UnmodifiableCollection 方法来创建不可变集合,但是它有一个缺点,即它只能包装现有的集合,而不能直接创建一个新的不可变集合。这意味着如果原始集合发生了改变,那么通过 unmodifiableCollection 方法创建的不可变集合也会受到影响。这些缺点在上面的缺点显露案例当中List,Set,Map都已有体现;
为了解决这个问题,在JDK 9中,引入了AbstractImmutableCollection 接口,它允许直接创建不可变集合,而不是简单地包装现有的可变集合。通过直接创建不可变集合,可以更好地确保集合的不可变性,避免了对原始集合的依赖,并提高了代码的清晰度和健壮性。
比如List,Set,Map 集合当中,JDK9 提供的一系列of()方法就是用来创建不可修改的集合;而这些集合全部都会输 AbstractImmutableCollection 一脉;
尽管 Collections.unmodifiableCollection 方法在早期版本中已经存在,但在JDK9版本之后引入的 AbstractImmutableCollection 接口可以提供更好的方式来创建和管理不可变集合。
2.6 CollectionView
CollectionView 是ConcurrentHashMap 特有的一个概念,用于提供对ConcurrentHashMap中元素的快照视图。它主要包括KeySetView, ValuesView 和 EntrySetView 三个子类,分别用于表示建的集合,值的集合以及键-值对的集合。
快照视图:快照的作用就是在内容迭代的时候不会受到其他线程对映射内容的修改影响。
安全迭代:通过提供快照视图,允许对ConcurrentHashMap 进行安全的迭代操作,无需担心其他线程的并发修改。
提高性能:ConcurrentHashMap的CollectionView 类提供了高效的迭代和检查操作,使得在多线程环境下能够高效地查看和操作映射内容。
只有ConcurrentHashMap里面有这个类。ConcurrenthashMap是为了高并发环境而设计的,他需要提供一种安全且高效的方式来操作映射内容。
2.6.1【测试案例】
package org.toast.collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author toast
* @time 2024/4/11
* @remark
*/
public class TestCollectionView {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("TOAST-A", 10001);
map.put("TOAST-B", 10002);
map.put("TOAST-C", 10003);
System.out.println("============keys==========");
System.out.println("keys: " + map.keySet()); // 输出:keys: [TOAST-C, TOAST-B, TOAST-A]
System.out.println("============values==========");
System.out.println("values: " + map.values()); // 输出:values: [10003, 10002, 10001]
// 获取键-值对的集合
System.out.println("============entries==========");
Set<Map.Entry<String, Integer>> entries = map.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
}
2.7 SynchronizedCollection
SynchronizedCollection 该类处理集合内容与线程安全方面的业务。在java.util包中提供了一个工具类Collections ;该工具类提供了一系列方法可以将不安全的集合转化为安全的集合。
另外,集合在线程安全处理方面JDK1.5中JUC(java.util.concurrent)包中也提供对应的线程安全集合类;
List的线程安全集合是:java.util.concurrent.CopyOnWriteArrayList
Set的线程安全集合是:java.util.concurrent.ConcurrentSkipListSet
Map的线程安全集合是:java.util.concurrent.ConcurrentHashMap
那SynchronizedCollection 将集合封装成线程安全和 JDK1.5 JUC包中提供的线程安全集合谁比较好?
答案是:JDK1.5 的性能要比较好!SynchronizedCollection仅仅只是对上一把锁进行同步处理,性能比较低。而JDK1.5的安全得到保证,并且性能也比较好!后期小编讲JUC篇章
好了,回归正题,SynchronizedCollection 方法列表如下:
2.7.1 转换成安全的List集合
转换成安全的Set集合
转换成Map的安全集合
上面三个例子,除了Map不是SynchronizedCollection 子类实现的,毕竟一个是双向列表集合,一个是单向列表集合。但是Set, List是SynchronizedCollection的子类。
【】
package org.toast.collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
/**
* @author toast
* @time 2024/4/11
* @remark
*/
public class TestSynchronizedCollection {
public static void main(String[] args) {
// 创建一个普通的 ArrayList
ArrayList<String> list = new ArrayList<>();
// 使用 Collections.synchronizedCollection 方法将 ArrayList 包装成线程安全的集合
Collection<String> synchronizedList = Collections.synchronizedCollection(list);
// 创建两个线程来同时操作这个线程安全的集合
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
synchronizedList.add("线程-A-" + i);
try {
Thread.sleep(100); // 线程休眠 100 毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
synchronizedList.add("线程-B-" + i);
try {
Thread.sleep(100); // 线程休眠 100 毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 启动线程
thread1.start();
thread2.start();
// 等待两个线程执行结束
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输出线程安全集合中的元素
System.out.println("Synchronized Collection: " + synchronizedList);
}
}
通过使用SynchronizedCollection,我们可以确保多个线程在操作集合时不会出现并发修改异常,从而确保线程安全性。
2.7.2【使用普通集合案例】
package org.toast.collection;
import java.util.ArrayList;
import java.util.Iterator;
/**
* @author toast
* @time 2024/4/11
* @remark
*/
public class TestSynchronizedCollection {
public static void main(String[] args) {
// 创建一个普通的 ArrayList
ArrayList<String> list = new ArrayList<>();
list.add("toast-A");
list.add("toast-B");
list.add("toast-C");
list.add("toast-D");
list.add("toast-E");
list.add("toast-F");
list.add("toast-G");
// 创建一个线程,用于迭代并输出集合中的元素
Thread iteratorThread = new Thread(() -> {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
try {
Thread.sleep(100); // 线程休眠 100 毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 启动迭代线程
iteratorThread.start();
// 主线程也迭代并修改集合
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String string = iterator.next();
System.out.println(string);
if (string.equals("toast-E")) iterator.remove(); // 在迭代过程中删除元素
}
// 等待迭代线程执行结束
try {
iteratorThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输出最终的集合内容
System.out.println("集合内容: " + list);
}
}
2.7.3【转换成安全集合案例】
这样会处理并发修改异常,因为集合在迭代的时候是不允许修改数据的,这样就可能会触发迭代器的fail-fast机制抛出ConcurrentModificationException 异常。以上案例可以通过Collections工具类将其普通集合封装成一个安全的集合这样就不会抛出并发修改异常。
package org.toast.collection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
/**
* @author toast
* @time 2024/4/11
* @remark
*/
public class TestSynchronizedCollection {
public static void main(String[] args) {
// 创建一个普通的 ArrayList
ArrayList<String> list = new ArrayList<>();
list.add("toast-A");
list.add("toast-B");
list.add("toast-C");
list.add("toast-D");
list.add("toast-E");
list.add("toast-F");
list.add("toast-G");
// 使用 Collections.synchronizedCollection 方法将 ArrayList 包装成线程安全的集合
Collection<String> synchronizedList = Collections.synchronizedCollection(list);
// 创建一个线程,用于迭代并输出集合中的元素
Thread iteratorThread = new Thread(() -> {
synchronized (synchronizedList) {
Iterator<String> iterator = synchronizedList.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
try {
Thread.sleep(100); // 线程休眠 100 毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
// 启动迭代线程
iteratorThread.start();
System.out.println("==========================华丽的分割线========================");
// 主线程也迭代并修改集合
synchronized (synchronizedList) {
Iterator<String> iterator = synchronizedList.iterator();
while (iterator.hasNext()) {
String string = iterator.next();
System.out.println(string);
if (string.equals("toast-E")) iterator.remove(); // 在迭代过程中删除元素
}
}
// 等待迭代线程执行结束
try {
iteratorThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 输出最终的集合内容
System.out.println("集合内容: " + synchronizedList);
}
}
细心的同学,会发现,不是通过Collections 工具类将集合转为线程安全的集合了吗?为什么还要加synchronized 锁。这是因为Collections.synchronizedCollection 将集合封装成一个安全的集合指定的是集合内部的安全。保证的是集合内部的操作(新增,删除,获取)都是原子性的。不会被其他线程所干扰。所以指定的是本身的安全。
在示例中,虽然 synchronizedList 是线程安全的,但如果在迭代器过程中另一个线程修改了集合,可能会导致迭代器抛出ConcurrentModificationException 并发修改异常。因此,在迭代和修改集合过程中需要使用额外的synchronized 锁确定安全性。