一、关于集合框架
1.1简介
Java 集合框架(Collection Framework)是 Java 提供的一组用于存储和操作对象的类和接口集合。这些集合类提供了不同的数据结构,使得数据的管理和操作更加方便和高效。
Java 集合框架提供了各种类型的数据结构,如列表(List)、集合(Set)、映射(Map)等,以便开发人员能够更有效地管理和操作数据。
1.2特点
- 接口与实现分离:集合框架采用接口与实现分离的设计模式,使得不同数据结构的实现可以相互替换。
- 泛型支持:集合框架中大多数类都支持泛型,可以指定集合中存储的元素类型。
- 高性能:Java 集合框架提供了高效的数据结构和算法实现,能够满足大部分场景的需求。
1.3主要接口和类
-
Collection 接口:表示一组对象的集合,包括 List 和 Set。
- List 接口:有序集合,元素可以重复。常见实现类有 ArrayList、LinkedList。
- Set 接口:无序集合,不允许重复元素。常见实现类有 HashSet、TreeSet。
-
Map 接口:键值对的集合,每个键最多只能映射到一个值。
- 常见实现类有 HashMap、TreeMap、LinkedHashMap。
-
Queue 接口:队列,通常用于存储和处理元素的顺序。
- 常见实现类有 LinkedList、PriorityQueue。
为此,整个集合框架就围绕一组标准接口而设计。你可以直接使用这些接口的标准实现,诸如: LinkedList, HashSet, 和 TreeSet 等,除此之外你也可以通过这些接口实现自己的集合。
1.4数组与集合的区别
1.数组是静态的,有固定大小,且创建之后无法改变;而集合是可以动态扩容的,可以根据需要动态改变大小。如果要存储基本数据类型,并且也有固定的个数,如果元素个数是固定的,推荐用数组如果元素个数不是固定的, 推荐用集合,因为数组的长度是固定的(数组是静态的,一个数组实例具有固定大小,一旦创建,无法改变),集合长度是可以改变的(根据需要动态改变大小,而且集合提供了更多的成员方法,可以满足更多的需求),简单来说,元素个数固定,推荐使用数组,若元素个数不固定,推荐使用集合。
2.数组既可以存储基本数据类型,又可以存储引用数据类型(基本数据类型存储的是值, 引用数据类型存储的是地址值);集合只能存储引用数据类型(也就是对象), 集合中也可以存储基本数据类型,但是在存储的时候会自动装箱(JDK1.5新特性)变成对象。
3.数组和集合都是java中的容器,但是数组声明了它容纳的元素类型,而集合不声明。
4.数组是java语言内置的数据类型,是线性排列的数组,所以可以快速访问元素,正因为数组有这样的优点,大家可以看到很多集合的底层结构就是数组。
5.使用场景不同,数组一般使用在数据长度固定的情况,并且主要进行的是数据的查找操作。而集合一般是用在需要同时存储具有一对一关系的数据,也就是保存键值对数据的情况下,都是使用集合,并且在处理数据重复问题的时候就可以直接使用Set集合解决这个问题(Set集合的特点是元素唯一,且不可重复)。
6.我们在定义数组的时候必须指定数组元素的类型,但是集合如果不定义的话就默认所有的元素都是Object(Object类是所有类的父类)。
7.我们无法直接获取数组中实际存储的元素个数,使用length()也只能获取数组的长度,但是集合可以直接用size()直接获取集合中实际存储的元素个数。
8.集合有多种实现方式和不同的适用场合,比如:List、Set、Map等,但是数组只采用分配连续的空间方式。而且集合以接口和类的形式存在,具有封装、继承、多态等关于类的特点,所以通过方法和属性的调用就可以实现一些各种复杂的操作,这样可以有效的提高软件的开发效率。
二、Collection(根接口)
2.1关于Collection
2.1.1简介
Java 中的 Collection 接口是集合框架中的根接口,它表示一组对象的集合。Collection 接口派生出了 List 和 Set 这两个子接口,以及它们的实现类。
2.1.2特点
- 不允许存储基本数据类型:Collection 接口只能存储对象类型而不能存储基本数据类型,但可以通过装箱和拆箱操作实现基本数据类型的存储。
- 允许存储重复元素:Collection 接口的一些实现类(如 List)允许存储重复的元素,而另一些实现类(如 Set)则不允许。
- 提供基本的集合操作方法:Collection 接口定义了一系列操作集合的方法,如添加元素、删除元素、判断是否包含某个元素等。
2.1.3方法
- boolean add(E e) :向集合中添加一个元素。
- boolean remove(Object o) :从集合中移除指定的元素。
- boolean contains(Object o) :判断集合中是否包含指定的元素。
- int size() :返回集合中元素的个数。
- void clear() :清空集合中的所有元素。
- boolean isEmpty() :判断集合是否为空。
其中,有几个比较常用的方法,比如方法 add() 添加一个元素到集合中,addAll() 将指定集合中的所有元素添加到集合中,contains()方法检测集合中是否包含指定的元素,toArray() 方法返回一个表示集合的数组。
另外,Collection 中有一个iterator()函数,它的作用是返回一个 Iterator 接口。通常,我们通过 Iterator 迭代器来遍历集合。ListIterator 是 List 接口所特有的,在 List 接口中,通过ListIterator()返回一个 ListIterator 对象。
2.1.4示例
如果你想要创建一个 Collection 对象,你应该选择一个具体的实现类,然后使用该实现类的构造函数来创建对象。例如,要创建一个 ArrayList 的实例,你可以这样做:
java
Collection<String> list = new ArrayList<>();
在这个例子中,我们使用了 ArrayList 的无参构造函数来创建了一个 Collection 对象的实例。这个实例实际上是 ArrayList 类的一个对象,但由于 ArrayList 实现了 Collection 接口,所以可以用 Collection 来引用它。
示例2:
java
import java.util.Collection;
import java.util.ArrayList;
public class CollectionExample {
public static void main(String[] args) {
Collection<String> collection = new ArrayList<>();
collection.add("Apple");
collection.add("Banana");
collection.add("Orange");
System.out.println("Collection size: " + collection.size());
System.out.println("Contains Banana? " + collection.contains("Banana"));
collection.remove("Apple");
System.out.println("Collection size after removal: " + collection.size());
}
}
2.1.5遍历
使用Iterator
for循环增强
2.2List接口(子接口)
2.2.1关于List
简介
List 接口是 Collection 接口的子接口,表示一个有序的集合,允许存储重复元素。List 接口的实现类提供了按索引访问元素、插入元素、删除元素等操作,是一种常用的数据结构。
特点
- 有序性:List 接口维护了元素的插入顺序,可以按照元素在列表中的位置来访问和操作元素。
- 允许存储重复元素:与 Set 不同,List 允许在列表中存储相同的元素。
- 可通过索引访问元素:List 接口提供了根据索引访问元素的方法,例如 get(int index) 和 set(int index, E element)。
方法
- void add(int index, E element) :在指定索引位置插入元素。
- E get(int index) :获取指定索引位置的元素。
- E set(int index, E element) :将指定索引位置的元素替换为新的元素。
- int indexOf(Object o) :返回指定元素在列表中第一次出现的索引。
- int lastIndexOf(Object o) :返回指定元素在列表中最后一次出现的索引。
- E remove(int index) :移除指定索引位置的元素。
Stream<e> stream() 构建Stream对象
常见实现类
- ArrayList:基于数组实现的 List,适合随机访问元素。
- LinkedList:基于链表实现的 List,适合频繁插入、删除操作。
- Vector:线程安全的 List,较少在现代代码中使用。
示例
java
import java.util.List;
import java.util.ArrayList;
public class ListExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Orange");
System.out.println("Element at index 1: " + list.get(1));
list.remove(0);
for (String fruit : list) {
System.out.println(fruit);
}
}
}
2.2.2ArrayList实现类
简介
ArrayList 是 Java 中的 List 接口的一个常见实现类,它基于数组实现动态数组,可以根据需要动态增加其大小。ArrayList 提供了一系列方法来操作元素,是 Java 集合框架中最常用的数据结构之一。
特点和优势
- 动态数组:ArrayList 内部使用数组来存储元素,可以根据需要动态增加其大小,而不需要手动处理数组扩容的问题。
- 按索引访问:由于基于数组实现,ArrayList 可以通过索引快速访问元素,具有良好的随机访问性能。
- 允许存储重复元素:与 Set 不同,ArrayList 允许在列表中存储相同的元素。
- 易于使用:ArrayList 提供了丰富的操作方法,如添加元素、删除元素、获取元素等,使用方便。
构造方法
-
ArrayList() :创建一个空的 ArrayList,初始容量为 10。
ArrayList<String> list = new ArrayList<>();
-
ArrayList(int initialCapacity) :创建一个具有指定初始容量的 ArrayList。
ArrayList<String> list = new ArrayList<>(20);
-
ArrayList(Collection<? extends E> c) :使用指定 collection 中的元素来构造一个 ArrayList。
List<String> sourceList = new ArrayList<>();
sourceList.add("Apple");
sourceList.add("Banana");ArrayList<String> targetList = new ArrayList<>(sourceList);
在上述构造方法中,E
表示 ArrayList 中存储的元素类型。您可以根据需要选择合适的构造方法来初始化 ArrayList 实例。
方法
方法 | 描述 |
---|---|
add() | 将元素插入到指定位置的 arraylist 中 |
addAll() | 添加集合中的所有元素到 arraylist 中 |
clear() | 删除 arraylist 中的所有元素 |
clone() | 复制一份 arraylist |
contains() | 判断元素是否在 arraylist |
get() | 通过索引值获取 arraylist 中的元素 |
indexOf() | 返回 arraylist 中元素的索引值 |
removeAll() | 删除存在于指定集合中的 arraylist 里的所有元素 |
remove() | 删除 arraylist 里的单个元素 |
size() | 返回 arraylist 里元素数量 |
isEmpty() | 判断 arraylist 是否为空 |
subList() | 截取部分 arraylist 的元素 |
set() | 替换 arraylist 中指定索引的元素 |
sort() | 对 arraylist 元素进行排序 |
toArray() | 将 arraylist 转换为数组 |
toString() | 将 arraylist 转换为字符串 |
ensureCapacity() | 设置指定容量大小的 arraylist |
lastIndexOf() | 返回指定元素在 arraylist 中最后一次出现的位置 |
retainAll() | 保留 arraylist 中在指定集合中也存在的那些元素 |
containsAll() | 查看 arraylist 是否包含指定集合中的所有元素 |
trimToSize() | 将 arraylist 中的容量调整为数组中的元素个数 |
removeRange() | 删除 arraylist 中指定索引之间存在的元素 |
replaceAll() | 将给定的操作内容替换掉数组中每一个元素 |
removeIf() | 删除所有满足特定条件的 arraylist 元素 |
forEach() | 遍历 arraylist 中每一个元素并执行特定操作 |
- void add(E element) :向列表末尾添加一个元素。
- void add(int index, E element) :在指定索引位置插入元素。
- E get(int index) :获取指定索引位置的元素。
- E set(int index, E element) :将指定索引位置的元素替换为新的元素。
- int size() :返回列表中元素的个数。
- boolean isEmpty() :判断列表是否为空。
- E remove(int index) :移除指定索引位置的元素。
示例
java
import java.util.ArrayList;
import java.util.List;
public class ArrayListExample {
public static void main(String[] args) {
List<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Orange");
System.out.println("Size of the list: " + fruits.size());
fruits.remove(1);
for (String fruit : fruits) {
System.out.println(fruit);
}
}
}
在上述示例中,我们创建了一个 ArrayList 对象,向其中添加了几个元素,并演示了如何获取列表的大小、移除元素以及遍历列表的操作。
-新建list集合,并添加元素
方法一
java
// 创建List<String>集合
List<String> list = new ArrayList<>();
// 添加元素到集合
list.add("元素1");
list.add("元素2");
list.add("元素3");
方法二
java
List<Long> skuIds = Arrays.asList(goodsSkuDO.getSkuId());
-添加元素
java
public static void main(String[] args) {
List<String> list=new ArrayList<String>();
list.add("wangyi");
list.add("lsi");
list.add("wangzu");
for (int i = 0; i < list.size(); i++) {
System.out.println(list);
}
-addAll()
skuDTOs.addAll(skuDTOs1)
这行代码会将 skuDTOs1
中的所有元素添加到 skuDTOs
集合中。实际上,addAll()
方法会修改调用它的集合(即 skuDTOs
),并将目标集合(即 skuDTOs1
)中的元素全部加入到调用者集合中。
这意味着执行完 skuDTOs.addAll(skuDTOs1)
后,skuDTOs
集合中会包含原来的元素以及 skuDTOs1
中的所有元素
可以通过以下代码验证:
java
List<SkuDTO> skuDTOs = new ArrayList<>();
skuDTOs.add(new SkuDTO("A"));
skuDTOs.add(new SkuDTO("B"));
List<SkuDTO> skuDTOs1 = new ArrayList<>();
skuDTOs1.add(new SkuDTO("C"));
skuDTOs1.add(new SkuDTO("D"));
skuDTOs.addAll(skuDTOs1);
System.out.println(skuDTOs); // 输出 [A, B, C, D]
-获取交集
retainAll()
java
@Test
public void test6(){
List<Integer> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
for (int i = 0; i < 20; i++) {
list1.add(i);
if (i%2 == 0 ){
list2.add(i);
}
}
System.out.println(list1); //[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
System.out.println(list2); //[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
list1.retainAll(list2);
System.out.println(list1); //[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
}
-判断元素是否在 arraylist
-判断两个list是否相等(忽略顺序)
Set<Long>
java
List<Long> list1 = new ArrayList<>();
list1.add(1L);
list1.add(2L);
list1.add(3L);
List<Long> list2 = new ArrayList<>();
list2.add(3L);
list2.add(2L);
list2.add(1L);
Set<Long> set1 = new HashSet<>(list1);
Set<Long> set2 = new HashSet<>(list2);
boolean areEqual = set1.equals(set2);
在上述示例中,我们首先创建两个 HashSet\long\ 集合,分别用于存储 list1 和 list2 中的元素。然后,我们使用 equals() 方法比较这两个集合是否相等。
请注意,这种方法会忽略原始列表中元素的顺序,并且也不会改变原始列表的顺序。
2.2.3LinkedList实现类
简介
LinkedList 是 Java 中 List 接口的另一个常见实现类,它使用双向链表实现数据存储。与 ArrayList 基于数组实现不同,LinkedList 通过节点之间的引用来连接各个元素,适合频繁插入、删除操作的场景。
特点和优势
- 双向链表:LinkedList 内部使用双向链表来存储元素,每个节点包含对前一个和后一个元素的引用,便于在列表中进行插入和删除操作。
- 插入和删除效率高:由于基于链表实现,插入和删除操作的效率比较高,不需要像数组那样涉及数据的搬移。
- 支持高效的迭代:LinkedList 实现了 List 接口和 Queue 接口,可以作为队列或栈来使用,并且支持高效的迭代操作。
- 占用更多内存:相比于 ArrayList,LinkedList 的每个节点都需要额外的空间存储指向前后节点的引用,可能占用更多的内存。
构造方法
- LinkedList() :创建一个空的 LinkedList。
示例:
LinkedList<String> linkedList = new LinkedList<>();
- LinkedList(Collection<? extends E> c) :创建一个包含指定集合中的元素的 LinkedList,这样就可以通过将现有集合传递给构造函数来初始化 LinkedList。
示例:
List<String> initialData = new ArrayList<>();
initialData.add("Apple");
initialData.add("Banana");
initialData.add("Orange");
LinkedList<String> linkedList = new LinkedList<>(initialData);
通过这些构造方法,我们可以实例化一个 LinkedList 对象,
方法
- void add(E element) :向列表末尾添加一个元素。
- void add(int index, E element) :在指定索引位置插入元素。
- E get(int index) :获取指定索引位置的元素。
- E set(int index, E element) :将指定索引位置的元素替换为新的元素。
- int size() :返回列表中元素的个数。
- boolean isEmpty() :判断列表是否为空。
- E remove(int index) :移除指定索引位置的元素。
示例
java
import java.util.LinkedList;
import java.util.List;
public class LinkedListExample {
public static void main(String[] args) {
List<String> animals = new LinkedList<>();
animals.add("Dog");
animals.add("Cat");
animals.add("Elephant");
System.out.println("Size of the list: " + animals.size());
animals.remove(1);
for (String animal : animals) {
System.out.println(animal);
}
}
}
在上述示例中,我们创建了一个 LinkedList 对象,向其中添加了几个元素,并演示了如何获取列表的大小、移除元素以及遍历列表的操作。
2.3Set接口
2.3.1关于set
简介
Set 接口是 Java 集合框架中的一种集合,它代表了一组不包含重复元素的集合。Set 接口继承自 Collection 接口,因此它具有 Collection 接口定义的大部分方法,同时又添加了不能包含重复元素的特性。
特点和优势
- 不包含重复元素:Set 中不允许包含重复的元素,每个元素在 Set 中都是唯一的。
- 无序性:Set 不保证元素的顺序,即元素存储的顺序和插入的顺序不一定相同,但可以使用特定的实现类如 TreeSet 来维护元素的排序状态。
- 常用实现类:Java 中常见的 Set 接口的实现类包括 HashSet、TreeSet 和 LinkedHashSet。
- 用途广泛:由于其元素不重复的特性,Set 通常用于需要确保元素唯一性的场景,例如去重操作、查找某个元素是否存在等。
方法
- boolean add(E e) :向 Set 中添加一个元素,如果该元素已经存在,则不会添加并返回 false。
- boolean contains(Object o) :判断 Set 中是否包含指定的元素。
- boolean remove(Object o) :从 Set 中移除指定的元素。
- int size() :返回 Set 中的元素个数。
- boolean isEmpty() :判断 Set 是否为空。
示例
java
import java.util.HashSet;
import java.util.Set;
public class SetExample {
public static void main(String[] args) {
Set<String> fruitSet = new HashSet<>();
fruitSet.add("Apple");
fruitSet.add("Banana");
fruitSet.add("Orange");
System.out.println("Size of the set: " + fruitSet.size());
fruitSet.remove("Banana");
for (String fruit : fruitSet) {
System.out.println(fruit);
}
}
}
2.3.2HashSet实现类
简介
HashSet 是 Java 中 Set 接口的一个常见实现类,它基于哈希表实现。HashSet 不保证集合中元素的顺序,允许包含 null 元素,但不是线程安全的。
特点和优势
- 不包含重复元素:与 Set 接口一致,HashSet 保证集合中不包含重复的元素,每个元素在 HashSet 中是唯一的。
- 基于哈希表:HashSet 内部使用哈希表来存储元素,这使得查找、插入和删除操作具有很高的性能。
- 无序性:HashSet 不保证元素的顺序,即元素存储的顺序和插入的顺序不一定相同。
- 允许 null 元素:HashSet 允许包含一个 null 元素。
方法
- boolean add(E e) :向 HashSet 中添加一个元素,如果该元素已经存在,则不会添加并返回 false。
- boolean contains(Object o) :判断 HashSet 中是否包含指定的元素。
- boolean remove(Object o) :从 HashSet 中移除指定的元素。
- int size() :返回 HashSet 中的元素个数。
- boolean isEmpty() :判断 HashSet 是否为空。
示例
java
import java.util.HashSet;
import java.util.Set;
public class HashSetExample {
public static void main(String[] args) {
Set<String> fruitSet = new HashSet<>();
fruitSet.add("Apple");
fruitSet.add("Banana");
fruitSet.add("Orange");
System.out.println("Size of the set: " + fruitSet.size());
fruitSet.remove("Banana");
for (String fruit : fruitSet) {
System.out.println(fruit);
}
}
}
2.3.3TreeSet实现类
简介
TreeSet 是 Java 中 Set 接口的另一个常见实现类,它基于红黑树(Red-Black tree)实现。TreeSet 通过红黑树保证了集合元素的有序性,并且不允许包含 null 元素。
特点和优势
- 不包含重复元素:与 Set 接口一致,TreeSet 保证集合中不包含重复的元素,每个元素在 TreeSet 中是唯一的。
- 有序性:TreeSet 通过红黑树实现,可以保证集合中的元素是有序的,通常是按照元素的自然顺序或者通过 Comparator 接口定义的比较规则进行排序。
- 基于红黑树:红黑树是一种自平衡的二叉搜索树,能够保持高效的插入、删除和查找操作。
- 不允许 null 元素:TreeSet 不允许包含 null 元素,否则会抛出 NullPointerException 异常。
方法
- boolean add(E e) :向 TreeSet 中添加一个元素,如果该元素已经存在,则不会添加并返回 false。
- boolean contains(Object o) :判断 TreeSet 中是否包含指定的元素。
- boolean remove(Object o) :从 TreeSet 中移除指定的元素。
- int size() :返回 TreeSet 中的元素个数。
- boolean isEmpty() :判断 TreeSet 是否为空。
示例
java
import java.util.TreeSet;
import java.util.Set;
public class TreeSetExample {
public static void main(String[] args) {
Set<String> fruitSet = new TreeSet<>();
fruitSet.add("Apple");
fruitSet.add("Banana");
fruitSet.add("Orange");
System.out.println("Size of the set: " + fruitSet.size());
fruitSet.remove("Banana");
for (String fruit : fruitSet) {
System.out.println(fruit);
}
}
}
遍历
1
java
public class Demo1 {
public static void main(String[] args) {
Set<String> treeSet= new TreeSet<>();
Set<String> hashSet = new HashSet<>();
treeSet.add("0012");
treeSet.add("00143");
System.out.println("TreeSet:");
for (String s:treeSet) {
System.out.println(s+" ");
}
hashSet.add("0012");
hashSet.add("008");
System.out.println("\nHashset:");
for (String s:hashSet){
System.out.println(s+" ");
}
}
2.4Queue接口
2.4.1关于
简介
Queue 接口是 Java 集合框架中定义的一个接口,它代表了一种队列数据结构,遵循先进先出(FIFO)的原则。Queue 接口继承自 Collection 接口,提供了用于操作队列的方法。
Queue,也就是队列,通常遵循先进先出(FIFO)的原则,新元素插入到队列的尾部,访问元素返回队列的头部。
方法
-
先进先出(FIFO) :队列中的元素按照其被插入的顺序排列,第一个插入的元素将会是第一个被移除的元素。
-
添加元素:
- boolean add(E e) :将指定元素插入队列,如果队列已满则抛出异常。
- boolean offer(E e) :将指定元素插入队列,如果队列已满则返回 false。
-
获取并移除元素:
- E remove() :获取并移除队列的头部元素,如果队列为空则抛出异常。
- E poll() :获取并移除队列的头部元素,如果队列为空则返回 null。
-
获取但不移除元素:
- E element() :获取但不移除队列的头部元素,如果队列为空则抛出异常。
- E peek() :获取但不移除队列的头部元素,如果队列为空则返回 null。
-
其他方法:
- int size() :返回队列中的元素个数。
- boolean isEmpty() :判断队列是否为空。
常见实现类
- LinkedList:实现了 Queue 接口,可以作为队列使用。
- ArrayDeque:同样也实现了 Queue 接口,是一个基于数组实现的双端队列,可以作为队列使用。
应用场景
Queue 接口在 Java 中有许多实际的应用场景,特别是在需要进行异步处理、任务调度、消息传递等方面。以下是一些常见的 Queue 接口的应用场景:
- 任务调度:在多线程编程中,可以使用队列来实现任务调度,将需要执行的任务按照一定的顺序或优先级放入队列中,然后由工作线程按照队列的顺序依次取出并执行这些任务。
- 消息传递:在消息队列(Message Queue)系统中,消息被发送者放入队列,接收者从队列中获取消息并处理。这种方式可以实现解耦和异步通信,常见的消息队列如 RabbitMQ、Kafka 等都是基于队列的实现。
- 生产者消费者模式:队列常常用于实现生产者消费者模式,生产者向队列中放入数据,消费者从队列中取出数据并进行处理,实现了生产和消费的解耦。
- 线程池任务管理:线程池通常使用队列来存储待执行的任务,任务提交到线程池后会被放入队列中等待执行,线程池中的线程会从队列中取出任务并执行。
- 网络爬虫:在网络爬虫程序中,可以使用队列来存储待访问的 URL,爬虫程序从队列中取出 URL 进行访问和解析,将新发现的 URL 放回队列中继续爬取。
- 事件驱动编程:使用事件队列可以实现事件驱动编程模型,将事件按顺序放入队列中,然后由事件处理器逐个处理这些事件。
总的来说,Queue 接口在很多需要临时存储、排序或者调度的场景下都有着重要的作用。通过队列的特性,可以有效地管理数据流、任务流或者事件流,提高系统的效率和灵活性。
示例
java
import java.util.Queue;
import java.util.LinkedList;
public class QueueExample {
public static void main(String[] args) {
Queue<String> queue = new LinkedList<>();
queue.add("Apple");
queue.add("Banana");
queue.add("Orange");
System.out.println("Size of the queue: " + queue.size());
System.out.println("Removing element: " + queue.poll());
System.out.println("Element at the head: " + queue.peek());
}
}
2.4.2ArrayDeque实现类
从名字上可以看得出,ArrayDeque 是一个基于数组实现的双端队列,为了满足可以同时在数组两端插入或删除元素的需求,数组必须是循环的,也就是说数组的任何一点都可以被看作是起点或者终点。
这是一个包含了 4 个元素的双端队列,和一个包含了 5 个元素的双端队列。
head 指向队首的第一个有效的元素,tail 指向队尾第一个可以插入元素的空位,因为是循环数组,所以 head 不一定从是从 0 开始,tail 也不一定总是比 head 大。
2.4.3PriorityQueue实现类
PriorityQueue 是一种优先级队列,它的出队顺序与元素的优先级有关,执行 remove 或者 poll 方法,返回的总是优先级最高的元素。要想有优先级,元素就需要实现 Comparable 接口或者 Comparator 接口。
三、Map(根接口-映射-字典)(重点)
3.1关于
3.1.1简介
Map 接口代表着一种映射关系,用来存储键值对。每个键都是唯一的,而值则可以重复。Map 接口提供了将键映射到值的功能,同时也允许通过键来检索对应的值。
"Map"是一种键值对(Key-Value)的映射结构,它存储了一组唯一的键和对应的值。"Map"通常被翻译为"映射"或"字典",这两个词都强调了键值对之间的映射关系。
3.1.2特点
- 键值对映射:Map 中的数据以键值对的形式存储,每个键都与一个值相关联。
- 键的唯一性:Map 中的键是唯一的,同一个键只能对应一个值。
- 值的重复性:Map 中的值可以重复,不同的键可以映射到相同的值。
- 键和值均可为 null:Map 中的键和值都可以为 null。
- 实现类可以根据需求选择:Java 提供了多个实现 Map 接口的类,如 HashMap、TreeMap、LinkedHashMap、ConcurrentHashMap 等,每种实现类都有自己的特点和适用场景。
Map中的key不能重复
示例:
java
Map<String, String> featureNameToSubjectMap = nameList.stream()
.flatMap(name -> subjectToAllSynonymMap.entrySet().stream()
.filter(entry -> entry.getValue().contains(name))
.map(entry -> new AbstractMap.SimpleEntry<>(name, entry.getKey())))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1));
nameList
中有重复元素,
在接下来的操作中,我们使用了 flatMap
方法来遍历 nameList
中的每个元素,并将其与 subjectToAllSynonymMap.entrySet()
中的每个元素进行匹配。这里的 subjectToAllSynonymMap
是一个 Map<String, List<String>>
类型的映射,其中键是主词,值是包含该主词的所有同义词的列表。
如果 nameList
中有重复元素,那么在执行 flatMap
操作时,可能会产生重复的键值对。然而,在最后的 collect
操作中,我们使用了 Collectors.toMap
方法来将键值对收集到一个 Map
中。如果存在重复的键,则会抛出 IllegalStateException
错误。
为了避免这种错误,你可以在 toMap
方法中指定一个合适的合并函数,用于处理重复的键。例如,你可以使用 Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1)
来指定当有重复键时保留第一个值。
修改后的代码如下所示:
java
Map<String, String> featureNameToSubjectMap = nameList.stream()
.flatMap(name -> subjectToAllSynonymMap.entrySet().stream()
.filter(entry -> entry.getValue().contains(name))
.map(entry -> new AbstractMap.SimpleEntry<>(name, entry.getKey())))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1));
3.1.3子接口
3.1.4方法
- void clear() :清空 Map 中的所有键值对。
- boolean containsKey(Object key) :判断 Map 中是否包含指定的键。
- boolean containsValue(Object value) :判断 Map 中是否包含指定的值。
- V get(Object key) :获取指定键对应的值。
- boolean isEmpty() :判断 Map 是否为空。
- Set<K> keySet() :返回包含所有键的 Set 集合。
- V put(K key, V value) :向 Map 中添加键值对。
- V remove(Object key) :移除指定键对应的键值对。
- int size() :返回 Map 中键值对的数量。
- Collection<V> values() :返回包含所有值的 Collection 集合。
3.1.5示例
java
import java.util.HashMap;
import java.util.Map;
public class MapExample {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("Alice", 25);
map.put("Bob", 30);
map.put("Charlie", 28);
System.out.println("Age of Bob: " + map.get("Bob"));
System.out.println("Keys in the map: " + map.keySet());
System.out.println("Size of the map: " + map.size());
}
}
在上述示例中,我们使用 HashMap 实现类来创建一个 Map 对象,向其中添加了几组键值对,并演示了如何获取特定键对应的值、获取所有键的集合以及获取 Map 的大小等操作。
3.2HashMap类(重点)
3.2.1关于
简介
HashMap 是 Java 中最常用的 Map 接口的实现类之一,它基于哈希表实现,提供了快速的插入、删除和查找操作
特点
- 基于哈希表:HashMap 内部通过哈希表来存储键值对,通过哈希算法可以快速定位到存储位置,实现了常数时间复杂度的插入、删除和查找操作。
- 允许键和值为 null:HashMap 允许键和值都为 null,且可以有一个 null 键和多个 null 值。
- 非线程安全:HashMap 是非线程安全的,如果需要在多线程环境下使用,可以考虑使用 ConcurrentHashMap。
- 无序性:HashMap 不保证键值对的顺序,即插入顺序不会影响元素的遍历顺序。
- 初始容量和加载因子:HashMap 可以指定初始容量和加载因子,加载因子表示哈希表在 rehashing(扩容)之前可以达到多满的程度,默认加载因子为 0.75。
- 迭代器:HashMap 提供了迭代器(Iterator)来遍历键值对。
原理
-
内部结构:
- HashMap 底层通过数组和链表(或红黑树)组合实现。数组被称为"桶"(buckets),每个桶存储多个链表或红黑树节点。
- HashMap 根据 key 的 hashCode 值来确定其存储位置,通过 hashCode 来快速定位到对应的桶。
-
hashCode 和 equals 方法:
- 在 HashMap 中,key 的 hashCode 用于确定其在数组中的索引位置。
- 如果两个 key 的 hashCode 相同,HashMap 会使用 equals 方法来进一步判断它们是否相等。
-
解决哈希冲突:
- 当不同的 key 计算出相同的 hashCode 值时,就会发生哈希冲突。
- HashMap 使用链表或红黑树来存储具有相同 hashCode 的 key-value 对,以解决哈希冲突。
- 当链表长度过长(默认超过8个节点),链表会转换为红黑树,以提高检索效率。
-
扩容与负载因子:
- 当 HashMap 中的元素个数超过负载因子(默认为 0.75 * 容量)时,HashMap 会进行扩容操作。
- 扩容会创建一个新的更大的数组,并将原数组中的元素重新分配到新数组中,以减少哈希碰撞。
-
迭代顺序:
- HashMap 的遍历顺序并不是按照插入顺序或者自然顺序,而是根据 key 的 hashCode 来确定顺序。
- 因此,HashMap 中的元素遍历顺序是不确定的,不同的运行环境可能会有不同的遍历顺序。
-
线程安全性:
- HashMap 在多线程环境下不是线程安全的,如果需要在多线程环境中使用,可以考虑使用 ConcurrentHashMap。
3.2.2构造方法
HashMap 类有多个构造方法,主要用于创建不同初始容量和加载因子的 HashMap 对象。以下是 HashMap 类的常用构造方法:
- HashMap() :默认构造方法创建一个初始容量为 16,加载因子为 0.75 的空 HashMap。
- HashMap(int initialCapacity) :创建一个指定初始容量,加载因子为 0.75 的空 HashMap。
- HashMap(int initialCapacity, float loadFactor) :创建一个指定初始容量和加载因子的空 HashMap。
- HashMap(Map<? extends K, ? extends V> m) :使用指定 Map 中的键值对创建一个新的 HashMap。新 HashMap 的容量为原 Map 的两倍。
这些构造方法提供了灵活的选项,允许开发人员根据具体需求选择合适的初始化方式。例如,如果已经知道要存储的键值对数量,可以使用带有初始容量参数的构造方法来提前分配所需的内存空间,以避免频繁的 rehashing 操作;而在已有 Map 的情况下,也可以直接通过该 Map 创建一个新的 HashMap 对象。
java
// 使用不同的构造方法创建 HashMap 对象
HashMap<String, Integer> map1 = new HashMap<>(); // 默认构造方法
HashMap<String, Integer> map2 = new HashMap<>(20); // 指定初始容量的构造方法
HashMap<String, Integer> map3 = new HashMap<>(20, 0.8f); // 指定初始容量和加载因子的构造方法
// 使用已有的 Map 创建 HashMap 对象
Map<String, Integer> existingMap = new HashMap<>();
existingMap.put("A", 1);
existingMap.put("B", 2);
HashMap<String, Integer> map4 = new HashMap<>(existingMap);
方法二:
java
import com.google.common.collect.Maps;
Map<String, Long> countMap = Maps.newHashMap();
countMap.put("A", 10L);
countMap.put("B", 20L);
System.out.println(countMap);
Maps.newHashMap()
是 Google Guava 库中的一个方法,用于创建一个新的 HashMap
实例。
new HashMap()
是 Java 标准库提供的方式,用于创建一个新的 HashMap
实例。
两者的主要区别在于库的依赖和导入方式。如果你已经在项目中使用了 Google Guava 库,那么可以使用 Maps.newHashMap()
来创建 HashMap
实例。这个方法对比直接使用 new HashMap()
有以下几点好处:
- 避免了显式指定泛型类型参数。
- 可以直接在一行代码中完成
HashMap
的实例化和初始化。 - 在并发环境下,
Maps.newHashMap()
会使用更高效的线程安全实现。
需要注意的是,如果你的项目中没有使用 Google Guava 库,或者不想引入额外的库依赖,那么直接使用 new HashMap()
就足够了,在大多数情况下它们具有相同的功能和性能。
使用 Guava 的 Maps.newHashMap()
方法可以简化代码编写,尤其是在需要频繁创建 HashMap 对象时,能够提高代码的可读性和简洁性。
3.2.3方法
- put(K key, V value) :将指定的键值对存储到 HashMap 中。
- get(Object key) :获取指定键对应的值。
- remove(Object key) :移除指定键对应的键值对。
- containsKey(Object key) :判断是否包含指定的键。
- size() :返回 HashMap 中键值对的数量。
- keySet() :返回包含所有键的 Set 集合。
- values() :返回包含所有值的 Collection 集合。
3.2.4示例
java
import java.util.HashMap;
public class HashMapExample {
public static void main(String[] args) {
HashMap<String, Integer> ageMap = new HashMap<>();
ageMap.put("Alice", 25);
ageMap.put("Bob", 30);
ageMap.put("Charlie", 28);
System.out.println("Age of Bob: " + ageMap.get("Bob"));
System.out.println("Keys in the map: " + ageMap.keySet());
System.out.println("Size of the map: " + ageMap.size());
}
}
在上述示例中,我们创建一个 HashMap 对象 ageMap,向其中添加了几组键值对,并演示了如何使用 get 方法获取特定键对应的值,使用 keySet 方法获取所有键的集合,以及使用 size 方法获取 HashMap 的大小。
clear方法
putAll方法
java
Map<KeyType, ValueType> map1 = new HashMap<>();
Map<KeyType, ValueType> map2 = new HashMap<>();
map1.putAll(map2);
上述代码将map2
中的所有键值对复制到map1
中,实现了将一个Map集合的数据赋值给另一个Map集合。
java
Map<String, List<String>> zhSubjectSynonymMap = Maps.newHashMap();
List<SelectFunctionPointSubjectTermDO> subjectTermDOS = functionPointSubjectTermService.lambdaQuery()
.eq(SelectFunctionPointSubjectTermDO::getEnableFlag, EnableFlagEnum.ENABLE.getCode())
.list();
//1.无中文主词
if (CollectionUtils.isEmpty(subjectTermDOS)) {
return Collections.EMPTY_MAP;
}
//2.有中文主词
List<Long> subjectIdS = subjectTermDOS.stream().map(SelectFunctionPointSubjectTermDO::getId).collect(Collectors.toList());
List<SelectFunctionPointSynonymDO> pointSynonymDOS = functionPointSynonymService.lambdaQuery()
.in(SelectFunctionPointSynonymDO::getSubjectTermId, subjectIdS)
.eq(SelectFunctionPointSynonymDO::getSynonymType, 1)
.eq(SelectFunctionPointSynonymDO::getEnableFlag, EnableFlagEnum.ENABLE.getCode())
.list();
//2.1 无同义词
if (CollectionUtils.isEmpty(pointSynonymDOS)) {
Map<String, List<String>> subjectTermMap = subjectTermDOS.stream()
.collect(Collectors.toMap(SelectFunctionPointSubjectTermDO::getSubjectTermCn,
e -> Collections.singletonList(e.getSubjectTermCn())));
zhSubjectSynonymMap.putAll(subjectTermMap);
}
获取map的值生成list集合
java
List<Long> spuIds = skuToSpuMap.values().stream().collect(Collectors.toList());
遍历
entrySet()方法
java
package com;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class Map01 {
public static void main(String[] args) {
HashMap map=new HashMap();
map.put("孙俪","邓超");
map.put("我","老婆");
map.put("张三","李四");
//通过EntrySet来获取k-v
Set entrySet=map.entrySet();
for (Object entry : entrySet) {
//将entry转成map.entry
Map.Entry m=(Map.Entry)entry;
System.out.println(m.getKey()+"-"+m.getValue());
}
//通过迭代器
Iterator iterator=entrySet.iterator();
while (iterator.hasNext()){
Object entry=iterator.next();
//向下转型Map.entry
Map.Entry m=(Map.Entry)entry;
System.out.println(m.getKey()+"-"+m.getValue());
}
}
}
遍历
java
public class Demo1Application {
public static void main(String[] args) {
SpringApplication.run(Demo1Application.class, args);
Map map=new HashMap();
map.put("no1","张三");
map.put("no2","李四");
map.put("no3","王五");
map.put(new Object(),"DXY");
//map.remove("no1");
//map.clear();
//System.out.println(map.containsKey("no2"));
//先取出所有key,在通过key去除value
Set keyset=map.keySet();
//(1)增强for
for (Object key : keyset) {
System.out.println(key+"-"+map.get(key));
}
//(2)迭代器
Iterator iterator=keyset.iterator();
while (iterator.hasNext()){
Object key=iterator.next();
System.out.println(key+""+map.get(key));
}
//把所有的value集合
Collection values = map.values();
//这里可以使用所有的collections所有方法
//(1)
for (Object value : values) {
System.out.println(value);
}
//(2)
Iterator iterator1=keyset.iterator();
while (iterator.hasNext()){
Object value=iterator.next();
System.out.println(value);
}
//通过entryset,来获取key-value
Set entrySet = map.entrySet();
for (Object entry : entrySet) {
Map.Entry m=(Map.Entry)entry;
System.out.println(m.getKey()+"-"+m.getValue());
}
// Set set = map.entrySet();
// System.out.println(set.getClass());
// for (Object obj : set) {
// //System.out.println(entry.getClass());
// //为了从hasnmap&node,取出k.v
// Map.Entry entry=(Map.Entry)obj;
// System.out.println(entry.getKey()+"-"+entry.getValue());
// }
//
// System.out.println("map="+map);
}
}
map转对象
java
public class Person {
private String userName;
private int age;
public String getUserName() {
return userName;
}
public int getAge() {
return age;
}
public void setUserName(String userName) {
this.userName = userName;
}
public void setAge(int age) {
this.age = age;
}
3.2.5LinkedHashMap类
关于LinkedHashMap
简介
LinkedHashMap
是Java中的一个类,它继承自HashMap
,并且保留了元素插入的顺序
大多数情况下,只要不涉及线程安全问题,Map基本都可以使用HashMap,不过HashMap有一个问题,就是迭代HashMap的顺序并不是HashMap放置的顺序,也就是无序。HashMap的这一缺点往往会带来困扰,因为有些场景,我们期待一个有序的Map。
于是 LinkedHashMap 就闪亮登场了。LinkedHashMap 是 HashMap 的子类,内部使用链表来记录插入/访问元素的顺序。
LinkedHashMap 可以看作是 HashMap + LinkedList 的合体,它使用了 哈希表来存储数据,又用了双向链表来维持顺序。
特点
- 保持插入顺序:
LinkedHashMap
会记住元素的插入顺序,并且迭代时会按照插入顺序返回元素。 - 内部采用双向链表维护顺序:在
LinkedHashMap
内部,元素以双向链表连接起来,这样可以保证元素的有序性。
构造方法
LinkedHashMap()
: 创建一个空的LinkedHashMap
,默认初始容量为16,加载因子为0.75。LinkedHashMap(int initialCapacity)
: 创建一个指定初始容量的LinkedHashMap
。LinkedHashMap(int initialCapacity, float loadFactor)
: 创建一个指定初始容量和加载因子的LinkedHashMap
。LinkedHashMap(Map<? extends K, ? extends V> m)
: 创建一个包含指定映射中的所有映射关系的LinkedHashMap
。
方法
put(K key, V value)
: 将键值对添加到LinkedHashMap
中。get(Object key)
: 获取指定键对应的值。remove(Object key)
: 移除指定键对应的映射关系。clear()
: 清空LinkedHashMap
中的所有映射关系。keySet()
: 返回包含所有键的Set视图。entrySet()
: 返回包含所有映射关系的Set视图。
示例
java
// 创建一个LinkedHashMap实例
LinkedHashMap<String, Integer> linkedHashMap = new LinkedHashMap<>();
// 向LinkedHashMap中添加元素
linkedHashMap.put("A", 1);
linkedHashMap.put("B", 2);
linkedHashMap.put("C", 3);
// 遍历LinkedHashMap并打印键值对(按插入顺序)
for (Map.Entry<String, Integer> entry : linkedHashMap.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// 输出结果将会按照插入顺序:
// A: 1
// B: 2
// C: 3
通过使用LinkedHashMap
,你可以保持元素的插入顺序,并且能够方便地进行按顺序遍历。这在某些场景下非常有用,例如需要记录元素插入的先后顺序或者实现LRU缓存等。
3.3TreeMap类
3.3.1关于TreeMap
简介
在 Java 中,TreeMap 类是基于红黑树数据结构实现的有序映射(键值对)集合。它继承自 AbstractMap 类并实现了 NavigableMap 接口。TreeMap 通过红黑树实现了按键的自然顺序或自定义顺序排序,并且提供了一些额外的功能。
特点
- 有序性: TreeMap 中的键值对是根据键的自然顺序或者通过 Comparator 进行排序的。这使得 TreeMap 能够按照键的顺序进行遍历和范围查询。
- 基于红黑树: TreeMap 内部使用红黑树作为数据结构来存储键值对,红黑树是一种自平衡的二叉搜索树,能够保持高效的插入、删除和查找操作。
- 支持子视图: TreeMap 提供了多种方法来获取子视图,如
subMap()
,headMap()
,tailMap()
,可以方便地对 TreeMap 进行范围操作。 - 性能: TreeMap 中的基本操作(插入、删除、查找)的时间复杂度为 O(log n),其中 n 为 TreeMap 中的元素个数。由于红黑树的自平衡性质,TreeMap 在大部分情况下能够保持较好的性能。
3.3.2构造方法
TreeMap()
: 创建一个空的TreeMap
,按照键的自然顺序进行排序。TreeMap(Comparator<? super K> comparator)
: 创建一个空的TreeMap
,按照指定比较器进行排序。TreeMap(Map<? extends K, ? extends V> m)
: 创建一个TreeMap
,其中包含指定映射中的所有映射关系。
你可以使用以下方式来创建TreeMap
实例:
java
// 创建一个空的TreeMap,按照键的自然顺序进行排序
TreeMap<String, Integer> treeMap1 = new TreeMap<>();
// 创建一个空的TreeMap,按照指定比较器进行排序
Comparator<String> customComparator = new CustomComparator(); // 假设CustomComparator是自定义的比较器
TreeMap<String, Integer> treeMap2 = new TreeMap<>(customComparator);
// 创建一个包含指定映射的TreeMap
Map<String, Integer> hashMap = new HashMap<>();
hashMap.put("A", 1);
hashMap.put("B", 2);
TreeMap<String, Integer> treeMap3 = new TreeMap<>(hashMap);
3.3.3方法
put(key, value)
:向 TreeMap 中插入键值对。get(key)
:获取指定键对应的值。remove(key)
:移除指定键及其对应的值。containsKey(key)
:判断 TreeMap 是否包含指定的键。size()
:返回 TreeMap 的大小(键值对数量)。keySet()
:返回包含所有键的 Set 集合。values()
:返回包含所有值的 Collection 集合。entrySet()
:返回包含所有键值对的 Set 集合。
3.3.4示例
java
import java.util.TreeMap;
public class TreeMapExample {
public static void main(String[] args) {
// 创建一个 TreeMap 实例
TreeMap<String, Integer> treeMap = new TreeMap<>();
// 向 TreeMap 中插入键值对
treeMap.put("apple", 10);
treeMap.put("banana", 20);
treeMap.put("orange", 15);
// 获取指定键对应的值
System.out.println("The value of apple: " + treeMap.get("apple"));
// 移除指定键及其对应的值
treeMap.remove("banana");
// 遍历 TreeMap 中的键值对
for (String key : treeMap.keySet()) {
System.out.println(key + ": " + treeMap.get(key));
}
}
}
我们创建了一个 TreeMap 对象,向其中插入了一些键值对,并且演示了获取、移除和遍历操作。
四、工具类
4.1Collections
4.1.1简介
Collections 是 JDK 提供的一个工具类,位于 java.util 包下,提供了一系列的静态方法,方便我们对集合进行各种骚操作,算是集合框架的一个大管家。
4.1.2方法
4.1.3示例
是
排序操作
reverse(List list)
:反转顺序shuffle(List list)
:洗牌,将顺序打乱sort(List list)
:自然升序sort(List list, Comparator c)
:按照自定义的比较器排序swap(List list, int i, int j)
:将 i 和 j 位置的元素交换位置
来看例子:
java
List<String> list = new ArrayList<>();
list.add("沉默王二");
list.add("沉默王三");
list.add("沉默王四");
list.add("沉默王五");
list.add("沉默王六");
System.out.println("原始顺序:" + list);
// 反转
Collections.reverse(list);
System.out.println("反转后:" + list);
// 洗牌
Collections.shuffle(list);
System.out.println("洗牌后:" + list);
// 自然升序
Collections.sort(list);
System.out.println("自然升序后:" + list);
// 交换
Collections.swap(list, 2,4);
System.out.println("交换后:" + list);Copy to clipboardErrorCopied
输出后:
java
原始顺序:[沉默王二, 沉默王三, 沉默王四, 沉默王五, 沉默王六]
反转后:[沉默王六, 沉默王五, 沉默王四, 沉默王三, 沉默王二]
洗牌后:[沉默王五, 沉默王二, 沉默王六, 沉默王三, 沉默王四]
自然升序后:[沉默王三, 沉默王二, 沉默王五, 沉默王六, 沉默王四]
交换后:[沉默王三, 沉默王二, 沉默王四, 沉默王六, 沉默王五]Copy to clipboardErrorCopied
查找操作
binarySearch(List list, Object key)
:二分查找法,前提是 List 已经排序过了max(Collection coll)
:返回最大元素max(Collection coll, Comparator comp)
:根据自定义比较器,返回最大元素min(Collection coll)
:返回最小元素min(Collection coll, Comparator comp)
:根据自定义比较器,返回最小元素fill(List list, Object obj)
:使用指定对象填充frequency(Collection c, Object o)
:返回指定对象出现的次数
来看例子:
java
System.out.println("最大元素:" + Collections.max(list));
System.out.println("最小元素:" + Collections.min(list));
System.out.println("出现的次数:" + Collections.frequency(list, "沉默王二"));
// 没有排序直接调用二分查找,结果是不确定的
System.out.println("排序前的二分查找结果:" + Collections.binarySearch(list, "沉默王二"));
Collections.sort(list);
// 排序后,查找结果和预期一致
System.out.println("排序后的二分查找结果:" + Collections.binarySearch(list, "沉默王二"));
Collections.fill(list, "沉默王八");
System.out.println("填充后的结果:" + list);Copy to clipboardErrorCopied
输出后:
java
原始顺序:[沉默王二, 沉默王三, 沉默王四, 沉默王五, 沉默王六]
最大元素:沉默王四
最小元素:沉默王三
出现的次数:1
排序前的二分查找结果:0
排序后的二分查找结果:1
填充后的结果:[沉默王八, 沉默王八, 沉默王八, 沉默王八, 沉默王八]Copy to clipboardErrorCopied
同步控制
HashMap 是线程不安全的,这个我们前面讲到了。那其实 ArrayList 也是线程不安全的,没法在多线程环境下使用,那 Collections 工具类中提供了多个 synchronizedXxx 方法,这些方法会返回一个同步的对象,从而解决多线程中访问集合时的安全问题。
使用起来也非常的简单:
SynchronizedList synchronizedList = Collections.synchronizedList(list);Copy to clipboardErrorCopied
看一眼 SynchronizedList 的源码就明白了,不过是在方法里面使用 synchronized 关键字加了一层锁而已。
Java
static class SynchronizedList<E>
extends SynchronizedCollection<E>
implements List<E> {
private static final long serialVersionUID = -7754090372962971524L;
final List<E> list;
SynchronizedList(List<E> list) {
super(list);
this.list = list;
}
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {return list.remove(index);}
}
}Copy to clipboardErrorCopied
那这样的话,其实效率和那些直接在方法上加 synchronized 关键字的 Vector、Hashtable 差不多(JDK 1.0 时期就有了),而这些集合类基本上已经废弃了,几乎不怎么用。
java
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}
public synchronized E remove(int index) {
modCount++;
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
int numMoved = elementCount - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--elementCount] = null; // Let gc do its work
return oldValue;
}
}Copy to clipboardErrorCopied
正确的做法是使用并发包下的 CopyOnWriteArrayList、ConcurrentHashMap。这些我们放到并发编程时再讲。
不可变集合
emptyXxx()
:制造一个空的不可变集合singletonXxx()
:制造一个只有一个元素的不可变集合unmodifiableXxx()
:为指定集合制作一个不可变集合
举个例子:
Java
List emptyList = Collections.emptyList();
emptyList.add("非空");
System.out.println(emptyList);
这段代码在执行的时候就抛出错误了。
java
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
at com.itwanger.s64.Demo.main(Demo.java:61)
这是因为 Collections.emptyList()
会返回一个 Collections 的内部类 EmptyList,而 EmptyList 并没有重写父类 AbstractList 的 add(int index, E element)
方法,所以执行的时候就抛出了不支持该操作的 UnsupportedOperationException 了。
这是从分析 add 方法源码得出的原因。除此之外,emptyList 方法是 final 的,返回的 EMPTY_LIST 也是 final 的,种种迹象表明 emptyList 返回的就是不可变对象,没法进行增伤改查。
java
public static final <T> List<T> emptyList() {
return (List<T>) EMPTY_LIST;
}
public static final List EMPTY_LIST = new EmptyList<>();