Java学习七—集合框架

一、关于集合框架

1.1简介

Java 集合框架(Collection Framework)是 Java 提供的一组用于存储和操作对象的类和接口集合。这些集合类提供了不同的数据结构,使得数据的管理和操作更加方便和高效。

Java 集合框架提供了各种类型的数据结构,如列表(List)、集合(Set)、映射(Map)等,以便开发人员能够更有效地管理和操作数据。

1.2特点

  1. 接口与实现分离:集合框架采用接口与实现分离的设计模式,使得不同数据结构的实现可以相互替换。
  2. 泛型支持:集合框架中大多数类都支持泛型,可以指定集合中存储的元素类型。
  3. 高性能:Java 集合框架提供了高效的数据结构和算法实现,能够满足大部分场景的需求。

1.3主要接口和类

  1. Collection 接口:表示一组对象的集合,包括 List 和 Set。

    • List 接口:有序集合,元素可以重复。常见实现类有 ArrayList、LinkedList。
    • Set 接口:无序集合,不允许重复元素。常见实现类有 HashSet、TreeSet。
  2. Map 接口:键值对的集合,每个键最多只能映射到一个值。

    • 常见实现类有 HashMap、TreeMap、LinkedHashMap。
  3. 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特点

  1. 不允许存储基本数据类型:Collection 接口只能存储对象类型而不能存储基本数据类型,但可以通过装箱和拆箱操作实现基本数据类型的存储。
  2. 允许存储重复元素:Collection 接口的一些实现类(如 List)允许存储重复的元素,而另一些实现类(如 Set)则不允许。
  3. 提供基本的集合操作方法: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 接口的实现类提供了按索引访问元素、插入元素、删除元素等操作,是一种常用的数据结构。

特点
  1. 有序性:List 接口维护了元素的插入顺序,可以按照元素在列表中的位置来访问和操作元素。
  2. 允许存储重复元素:与 Set 不同,List 允许在列表中存储相同的元素。
  3. 可通过索引访问元素: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 集合框架中最常用的数据结构之一。

特点和优势
  1. 动态数组:ArrayList 内部使用数组来存储元素,可以根据需要动态增加其大小,而不需要手动处理数组扩容的问题。
  2. 按索引访问:由于基于数组实现,ArrayList 可以通过索引快速访问元素,具有良好的随机访问性能。
  3. 允许存储重复元素:与 Set 不同,ArrayList 允许在列表中存储相同的元素。
  4. 易于使用:ArrayList 提供了丰富的操作方法,如添加元素、删除元素、获取元素等,使用方便。

构造方法
  1. ArrayList() :创建一个空的 ArrayList,初始容量为 10。

    ArrayList<String> list = new ArrayList<>();

  1. ArrayList(int initialCapacity) :创建一个具有指定初始容量的 ArrayList。

    ArrayList<String> list = new ArrayList<>(20);

  1. 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 通过节点之间的引用来连接各个元素,适合频繁插入、删除操作的场景。

特点和优势
  1. 双向链表:LinkedList 内部使用双向链表来存储元素,每个节点包含对前一个和后一个元素的引用,便于在列表中进行插入和删除操作。
  2. 插入和删除效率高:由于基于链表实现,插入和删除操作的效率比较高,不需要像数组那样涉及数据的搬移。
  3. 支持高效的迭代:LinkedList 实现了 List 接口和 Queue 接口,可以作为队列或栈来使用,并且支持高效的迭代操作。
  4. 占用更多内存:相比于 ArrayList,LinkedList 的每个节点都需要额外的空间存储指向前后节点的引用,可能占用更多的内存。

构造方法
  1. LinkedList() :创建一个空的 LinkedList。

示例:

LinkedList<String> linkedList = new LinkedList<>();

  1. 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 接口定义的大部分方法,同时又添加了不能包含重复元素的特性。

特点和优势
  1. 不包含重复元素:Set 中不允许包含重复的元素,每个元素在 Set 中都是唯一的。
  2. 无序性:Set 不保证元素的顺序,即元素存储的顺序和插入的顺序不一定相同,但可以使用特定的实现类如 TreeSet 来维护元素的排序状态。
  3. 常用实现类:Java 中常见的 Set 接口的实现类包括 HashSet、TreeSet 和 LinkedHashSet。
  4. 用途广泛:由于其元素不重复的特性,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 元素,但不是线程安全的。

特点和优势
  1. 不包含重复元素:与 Set 接口一致,HashSet 保证集合中不包含重复的元素,每个元素在 HashSet 中是唯一的。
  2. 基于哈希表:HashSet 内部使用哈希表来存储元素,这使得查找、插入和删除操作具有很高的性能。
  3. 无序性:HashSet 不保证元素的顺序,即元素存储的顺序和插入的顺序不一定相同。
  4. 允许 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 元素。

特点和优势
  1. 不包含重复元素:与 Set 接口一致,TreeSet 保证集合中不包含重复的元素,每个元素在 TreeSet 中是唯一的。
  2. 有序性:TreeSet 通过红黑树实现,可以保证集合中的元素是有序的,通常是按照元素的自然顺序或者通过 Comparator 接口定义的比较规则进行排序。
  3. 基于红黑树:红黑树是一种自平衡的二叉搜索树,能够保持高效的插入、删除和查找操作。
  4. 不允许 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)的原则,新元素插入到队列的尾部,访问元素返回队列的头部。

方法
  1. 先进先出(FIFO) :队列中的元素按照其被插入的顺序排列,第一个插入的元素将会是第一个被移除的元素。

  2. 添加元素

    • boolean add(E e) :将指定元素插入队列,如果队列已满则抛出异常。
    • boolean offer(E e) :将指定元素插入队列,如果队列已满则返回 false。
  3. 获取并移除元素

    • E remove() :获取并移除队列的头部元素,如果队列为空则抛出异常。
    • E poll() :获取并移除队列的头部元素,如果队列为空则返回 null。
  4. 获取但不移除元素

    • E element() :获取但不移除队列的头部元素,如果队列为空则抛出异常。
    • E peek() :获取但不移除队列的头部元素,如果队列为空则返回 null。
  5. 其他方法

    • int size() :返回队列中的元素个数。
    • boolean isEmpty() :判断队列是否为空。

常见实现类
  • LinkedList:实现了 Queue 接口,可以作为队列使用。
  • ArrayDeque:同样也实现了 Queue 接口,是一个基于数组实现的双端队列,可以作为队列使用。

应用场景

Queue 接口在 Java 中有许多实际的应用场景,特别是在需要进行异步处理、任务调度、消息传递等方面。以下是一些常见的 Queue 接口的应用场景:

  1. 任务调度:在多线程编程中,可以使用队列来实现任务调度,将需要执行的任务按照一定的顺序或优先级放入队列中,然后由工作线程按照队列的顺序依次取出并执行这些任务。
  2. 消息传递:在消息队列(Message Queue)系统中,消息被发送者放入队列,接收者从队列中获取消息并处理。这种方式可以实现解耦和异步通信,常见的消息队列如 RabbitMQ、Kafka 等都是基于队列的实现。
  3. 生产者消费者模式:队列常常用于实现生产者消费者模式,生产者向队列中放入数据,消费者从队列中取出数据并进行处理,实现了生产和消费的解耦。
  4. 线程池任务管理:线程池通常使用队列来存储待执行的任务,任务提交到线程池后会被放入队列中等待执行,线程池中的线程会从队列中取出任务并执行。
  5. 网络爬虫:在网络爬虫程序中,可以使用队列来存储待访问的 URL,爬虫程序从队列中取出 URL 进行访问和解析,将新发现的 URL 放回队列中继续爬取。
  6. 事件驱动编程:使用事件队列可以实现事件驱动编程模型,将事件按顺序放入队列中,然后由事件处理器逐个处理这些事件。

总的来说,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特点

  1. 键值对映射:Map 中的数据以键值对的形式存储,每个键都与一个值相关联。
  2. 键的唯一性:Map 中的键是唯一的,同一个键只能对应一个值。
  3. 值的重复性:Map 中的值可以重复,不同的键可以映射到相同的值。
  4. 键和值均可为 null:Map 中的键和值都可以为 null。
  5. 实现类可以根据需求选择: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 接口的实现类之一,它基于哈希表实现,提供了快速的插入、删除和查找操作

特点
  1. 基于哈希表:HashMap 内部通过哈希表来存储键值对,通过哈希算法可以快速定位到存储位置,实现了常数时间复杂度的插入、删除和查找操作。
  2. 允许键和值为 null:HashMap 允许键和值都为 null,且可以有一个 null 键和多个 null 值。
  3. 非线程安全:HashMap 是非线程安全的,如果需要在多线程环境下使用,可以考虑使用 ConcurrentHashMap。
  4. 无序性:HashMap 不保证键值对的顺序,即插入顺序不会影响元素的遍历顺序。
  5. 初始容量和加载因子:HashMap 可以指定初始容量和加载因子,加载因子表示哈希表在 rehashing(扩容)之前可以达到多满的程度,默认加载因子为 0.75。
  6. 迭代器:HashMap 提供了迭代器(Iterator)来遍历键值对。

原理
  1. 内部结构

    • HashMap 底层通过数组和链表(或红黑树)组合实现。数组被称为"桶"(buckets),每个桶存储多个链表或红黑树节点。
    • HashMap 根据 key 的 hashCode 值来确定其存储位置,通过 hashCode 来快速定位到对应的桶。
  2. hashCode 和 equals 方法

    • 在 HashMap 中,key 的 hashCode 用于确定其在数组中的索引位置。
    • 如果两个 key 的 hashCode 相同,HashMap 会使用 equals 方法来进一步判断它们是否相等。
  3. 解决哈希冲突

    • 当不同的 key 计算出相同的 hashCode 值时,就会发生哈希冲突。
    • HashMap 使用链表或红黑树来存储具有相同 hashCode 的 key-value 对,以解决哈希冲突。
    • 当链表长度过长(默认超过8个节点),链表会转换为红黑树,以提高检索效率。
  4. 扩容与负载因子

    • 当 HashMap 中的元素个数超过负载因子(默认为 0.75 * 容量)时,HashMap 会进行扩容操作。
    • 扩容会创建一个新的更大的数组,并将原数组中的元素重新分配到新数组中,以减少哈希碰撞。
  5. 迭代顺序

    • HashMap 的遍历顺序并不是按照插入顺序或者自然顺序,而是根据 key 的 hashCode 来确定顺序。
    • 因此,HashMap 中的元素遍历顺序是不确定的,不同的运行环境可能会有不同的遍历顺序。
  6. 线程安全性

    • HashMap 在多线程环境下不是线程安全的,如果需要在多线程环境中使用,可以考虑使用 ConcurrentHashMap。

3.2.2构造方法

HashMap 类有多个构造方法,主要用于创建不同初始容量和加载因子的 HashMap 对象。以下是 HashMap 类的常用构造方法:

  1. HashMap() :默认构造方法创建一个初始容量为 16,加载因子为 0.75 的空 HashMap。
  2. HashMap(int initialCapacity) :创建一个指定初始容量,加载因子为 0.75 的空 HashMap。
  3. HashMap(int initialCapacity, float loadFactor) :创建一个指定初始容量和加载因子的空 HashMap。
  4. 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()​ 有以下几点好处:

  1. 避免了显式指定泛型类型参数。
  2. 可以直接在一行代码中完成 HashMap 的实例化和初始化。
  3. 在并发环境下,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内部,元素以双向链表连接起来,这样可以保证元素的有序性。

构造方法
  1. LinkedHashMap(): 创建一个空的LinkedHashMap,默认初始容量为16,加载因子为0.75。
  2. LinkedHashMap(int initialCapacity): 创建一个指定初始容量的LinkedHashMap
  3. LinkedHashMap(int initialCapacity, float loadFactor): 创建一个指定初始容量和加载因子的LinkedHashMap
  4. 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 通过红黑树实现了按键的自然顺序或自定义顺序排序,并且提供了一些额外的功能。

特点
  1. 有序性: TreeMap 中的键值对是根据键的自然顺序或者通过 Comparator 进行排序的。这使得 TreeMap 能够按照键的顺序进行遍历和范围查询。
  2. 基于红黑树: TreeMap 内部使用红黑树作为数据结构来存储键值对,红黑树是一种自平衡的二叉搜索树,能够保持高效的插入、删除和查找操作。
  3. 支持子视图: TreeMap 提供了多种方法来获取子视图,如 subMap(), headMap(), tailMap(),可以方便地对 TreeMap 进行范围操作。
  4. 性能: TreeMap 中的基本操作(插入、删除、查找)的时间复杂度为 O(log n),其中 n 为 TreeMap 中的元素个数。由于红黑树的自平衡性质,TreeMap 在大部分情况下能够保持较好的性能。

3.3.2构造方法

  1. TreeMap(): 创建一个空的TreeMap,按照键的自然顺序进行排序。
  2. TreeMap(Comparator<? super K> comparator): 创建一个空的TreeMap,按照指定比较器进行排序。
  3. 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<>();

相关推荐
云空12 分钟前
《Python 与 SQLite:强大的数据库组合》
数据库·python·sqlite
九圣残炎20 分钟前
【从零开始的LeetCode-算法】1456. 定长子串中元音的最大数目
java·算法·leetcode
wclass-zhengge22 分钟前
Netty篇(入门编程)
java·linux·服务器
lulu_gh_yu26 分钟前
数据结构之排序补充
c语言·开发语言·数据结构·c++·学习·算法·排序算法
Re.不晚1 小时前
Java入门15——抽象类
java·开发语言·学习·算法·intellij-idea
凤枭香1 小时前
Python OpenCV 傅里叶变换
开发语言·图像处理·python·opencv
雷神乐乐1 小时前
Maven学习——创建Maven的Java和Web工程,并运行在Tomcat上
java·maven
码农派大星。1 小时前
Spring Boot 配置文件
java·spring boot·后端
测试杂货铺1 小时前
外包干了2年,快要废了。。
自动化测试·软件测试·python·功能测试·测试工具·面试·职场和发展
顾北川_野1 小时前
Android 手机设备的OEM-unlock解锁 和 adb push文件
android·java