Java基础-集合

(创作不易,感谢有你,你的支持,就是我前行的最大动力,如果看完对你有帮助,请留下您的足迹)

目录

前言

一、Java集合框架概述

二、Collection接口及其实现

[2.1 Collection接口](#2.1 Collection接口)

[2.2 List接口及其实现](#2.2 List接口及其实现)

[2.3 Set接口及其实现](#2.3 Set接口及其实现)

三、Map接口及其实现

[3.1 Map的基本操作](#3.1 Map的基本操作)

四、集合的遍历与迭代

[4.1 使用for-each循环遍历](#4.1 使用for-each循环遍历)

[4.2 使用迭代器遍历](#4.2 使用迭代器遍历)

[4.3 使用分割器遍历(Java 8+)](#4.3 使用分割器遍历(Java 8+))

五、Spliterator的高级应用

[5.1 Spliterator简介](#5.1 Spliterator简介)

[5.2 Spliterator的特性](#5.2 Spliterator的特性)

[5.3 使用Spliterator](#5.3 使用Spliterator)

六、并发集合

[6.1 并发集合概览](#6.1 并发集合概览)

[6.2 并发集合的使用场景](#6.2 并发集合的使用场景)

[6.3 并发集合与Spliterator的集成](#6.3 并发集合与Spliterator的集成)


前言

在Java的世界里,集合框架(Collections Framework)是编程中不可或缺的一部分,它为数据的管理和操作提供了丰富的接口和类。无论是简单的列表、集合、映射,还是复杂的队列、栈、排序集等,Java集合框架都为我们提供了高效、灵活的实现方式。本文旨在深入探讨Java集合框架的基本概念、核心接口与类、使用场景以及通过代码示例展示其强大功能。

一、Java集合框架概述

Java集合框架是一个统一架构,用于表示和操作集合,它包含了接口、实现类以及算法,可以独立于表示方式(如列表、集合或映射)来操作集合。这一框架的主要目的是简化集合的操作,提高代码的可读性和复用性。

Java集合框架主要包括两大接口体系:Collection接口和Map接口。Collection接口是List、Set等集合的根接口,而Map接口则用于存储键值对。

二、Collection接口及其实现

2.1 Collection接口

Collection接口是Java集合框架中最基本的接口,它为集合(Collection)定义了统一的操作方法,如添加、删除、遍历等。但Collection接口不直接实现任何集合类,而是由它的子接口(如ListSet)或实现类(如ArrayListHashSet)来具体实现。

2.2 List接口及其实现

List接口继承自Collection接口,它代表一个有序的集合,允许包含重复的元素。List接口的实现类主要有ArrayListLinkedListVector等。

  • ArrayList:基于动态数组实现的列表,查询效率高,但插入和删除操作需要移动元素,效率较低。
  • LinkedList:基于链表实现的列表,插入和删除操作效率高,但随机访问效率低。
  • Vector:是早期Java版本中提供的动态数组实现,现已较少使用,因为它在大多数操作上都是同步的,影响性能。

示例代码

java 复制代码
import java.util.ArrayList;  
import java.util.List;  
  
public class ListExample {  
    public static void main(String[] args) {  
        List<String> list = new ArrayList<>();  
        list.add("Apple");  
        list.add("Banana");  
        list.add("Cherry");  
  
        System.out.println(list); // 输出:[Apple, Banana, Cherry]  
  
        // 访问元素  
        System.out.println(list.get(1)); // 输出:Banana  
  
        // 遍历列表  
        for (String fruit : list) {  
            System.out.println(fruit);  
        }  
    }  
}

2.3 Set接口及其实现

Set接口同样继承自Collection接口,但它不包含重复元素。Set接口的实现类主要有HashSetLinkedHashSetTreeSet等。

  • HashSet:基于哈希表实现,元素无序且不保证迭代顺序。
  • LinkedHashSet :继承自HashSet,同时维护了一个运行于所有条目的双向链表,这使得元素的迭代顺序与插入顺序一致。
  • TreeSet:基于红黑树实现,元素处于排序状态。

示例代码

java 复制代码
import java.util.HashSet;  
import java.util.Set;  
  
public class SetExample {  
    public static void main(String[] args) {  
        Set<String> set = new HashSet<>();  
        set.add("Apple");  
        set.add("Banana");  
        set.add("Apple"); // 重复添加,不会加入集合  
  
        System.out.println(set); // 输出可能:[Banana, Apple] 或其他顺序,因为HashSet无序  
  
        // 遍历集合  
        for (String fruit : set) {  
            System.out.println(fruit);  
        }  
    }  
}

三、Map接口及其实现

Map接口用于存储键值对(key-value pairs),一个键可以映射到最多一个值。Map接口的实现类主要有HashMapLinkedHashMapTreeMapProperties等。

  • HashMap :基于哈希表实现,允许使用null值和null键。
  • LinkedHashMap :继承自HashMap,维护了一个运行于所有条目的双向链表,这保证了元素会按照插入顺序进行迭代。
  • TreeMap :基于红黑树实现,映射按照键的自然顺序或创建映射时提供的Comparator进行排序。
  • Properties :继承自Hashtable,但主要用于处理属性文件(.properties),它将键和值作为字符串存储,并允许使用load(InputStream)store(OutputStream, String)方法从和向文件加载和存储属性。

3.1 Map的基本操作

Map接口定义了多种操作键值对的方法,包括添加、删除、查找和遍历等。以下是一些常用的Map操作:

  • put(K key, V value): 将指定的值与此映射中的指定键关联(可选操作)。
  • get(Object key): 返回指定键所映射的值;如果此映射不包含该键的映射,则返回null
  • remove(Object key): 如果存在该键的映射,则将其从此映射中移除(可选操作)。
  • containsKey(Object key): 如果此映射包含指定键的映射,则返回true
  • containsValue(Object value): 如果此映射将一个或多个键映射到指定值,则返回true
  • entrySet(): 返回此映射中包含的映射的Set视图。
  • keySet(): 返回此映射中所包含的键的Set视图。
  • values(): 返回此映射中包含的值的Collection视图。

示例代码

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("Apple", 100);  
        map.put("Banana", 200);  
        map.put("Cherry", 300);  
  
        // 访问元素  
        System.out.println(map.get("Banana")); // 输出:200  
  
        // 检查键是否存在  
        if (map.containsKey("Grape")) {  
            System.out.println("Found Grape");  
        } else {  
            System.out.println("Grape not found");  
        }  
  
        // 遍历Map  
        for (Map.Entry<String, Integer> entry : map.entrySet()) {  
            System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());  
        }  
  
        // 使用keySet和values遍历  
        for (String key : map.keySet()) {  
            System.out.println("Key = " + key);  
        }  
  
        for (Integer value : map.values()) {  
            System.out.println("Value = " + value);  
        }  
    }  
}

四、集合的遍历与迭代

Java集合框架提供了多种遍历集合的方法,包括使用for-each循环(也称为增强型for循环)、迭代器(Iterator)和分割器(Spliterator)等。

4.1 使用for-each循环遍历

for-each循环是Java 5引入的一种简化数组和集合遍历的语法。它隐藏了迭代器的细节,使代码更加简洁。

示例代码(已在上述List和Map示例中展示)。

4.2 使用迭代器遍历

迭代器(Iterator)是Java集合框架中的一个重要接口,它提供了一种统一的方法来遍历集合中的元素,而无需知道集合的内部结构。迭代器通常通过调用集合的iterator()方法来获取。

示例代码

java 复制代码
import java.util.ArrayList;  
import java.util.Iterator;  
import java.util.List;  
  
public class IteratorExample {  
    public static void main(String[] args) {  
        List<String> list = new ArrayList<>();  
        list.add("Apple");  
        list.add("Banana");  
        list.add("Cherry");  
  
        Iterator<String> iterator = list.iterator();  
        while (iterator.hasNext()) {  
            String fruit = iterator.next();  
            System.out.println(fruit);  
  
            // 可以在遍历过程中删除元素,但不能用集合的remove方法  
            // 如果需要删除元素,可以使用迭代器的remove方法  
            // if ("Banana".equals(fruit)) {  
            //     iterator.remove(); // 正确的方式  
            // }  
        }  
  
        // 注意:如果尝试在遍历过程中直接修改集合(如使用list.remove()),  
        // 则可能会抛出ConcurrentModificationException异常。  
    }  
}

4.3 使用分割器遍历(Java 8+)

从Java 8开始,引入了Spliterator接口,它是Iterator的并行版本,用于并行遍历数据源(如集合)。Spliterator提供了更多关于Java集合框架中Spliterator的详细信息和高级用法,以及与其他并发集合和并行流(Parallel Streams)的集成。

五、Spliterator的高级应用

5.1 Spliterator简介

Spliterator(可分割迭代器)是Java 8中引入的一个新接口,位于java.util.Spliterator包中。它旨在作为并行算法的数据源,允许数据源被分割成多个部分,这些部分可以并行处理,从而提高大数据集的处理效率。Spliterator不仅继承了Iterator的遍历能力,还增加了对并行性和数据分割的支持。

5.2 Spliterator的特性

  • 分割性Spliterator可以递归地将数据源分割成更小的部分,这些部分可以被不同的线程并行处理。
  • 特性支持 :通过characteristics()方法,Spliterator可以报告其支持的特性,如ORDERED(有序)、DISTINCT(无重复元素)、SORTED(已排序)等,这些特性有助于优化并行算法的性能。
  • 估算大小 :通过estimateSize()方法,Spliterator可以估算剩余元素的数量,这对于负载平衡和提前终止处理可能很有用。
  • 遍历和分割tryAdvance(Consumer<? super T> action)方法用于逐个遍历元素,而trySplit()方法用于尝试将Spliterator分割成两个或多个子Spliterator

5.3 使用Spliterator

通常,我们不会直接操作Spliterator,而是通过集合框架的并行流(Parallel Streams)或并发工具类(如ForkJoinPool)间接使用它。但是,了解Spliterator的工作原理可以帮助我们更好地理解和优化并行处理。

示例代码

假设我们有一个大型列表,并希望并行地处理其中的每个元素。虽然我们可以直接使用parallelStream(),但以下示例展示了如何手动获取和使用Spliterator

java 复制代码
import java.util.ArrayList;  
import java.util.List;  
import java.util.Spliterator;  
import java.util.function.Consumer;  
  
public class SpliteratorExample {  
    public static void main(String[] args) {  
        List<Integer> numbers = new ArrayList<>();  
        for (int i = 0; i < 1000000; i++) {  
            numbers.add(i);  
        }  
  
        Spliterator<Integer> spliterator = numbers.spliterator();  
  
        // 自定义遍历逻辑  
        processSpliterator(spliterator, System.out::println);  
  
        // 假设我们想并行处理,可以手动分割Spliterator  
        // 注意:这里只是示例,实际并行处理应使用Parallel Streams或ForkJoinPool  
        Spliterator<Integer> left = null, right = null;  
        if (spliterator.trySplit()) {  
            left = spliterator; // 分割后的第一个Spliterator  
            right = numbers.spliterator(); // 注意:这里应该是spliterator.trySplit()的返回值,但此处仅为示例  
        }  
  
        // 假设我们有两个线程来并行处理  
        // new Thread(() -> processSpliterator(left, x -> { /* 处理逻辑 */ })).start();  
        // new Thread(() -> processSpliterator(right, x -> { /* 处理逻辑 */ })).start();  
    }  
  
    private static void processSpliterator(Spliterator<Integer> spliterator, Consumer<Integer> action) {  
        spliterator.forEachRemaining(action);  
    }  
  
    // 注意:上面的代码片段中,尝试手动分割spliterator并分配给两个线程是不正确的,  
    // 因为right = numbers.spliterator(); 实际上又创建了一个新的spliterator,  
    // 而不是从原始spliterator中分割出来的。  
    // 正确的做法是使用spliterator.trySplit()的返回值。  
}  
  
// 正确的并行处理示例(使用Parallel Streams)  
public static void processParallel(List<Integer> numbers) {  
    numbers.parallelStream().forEach(System.out::println);  
    // 注意:由于println不是线程安全的,上面的代码在并行流中可能会导致输出混乱。  
    // 在实际应用中,应该使用线程安全的操作或收集器。  
}

六、并发集合

Java并发包(java.util.concurrent)提供了一系列支持高并发级别的集合类,这些类比传统的集合类提供了更好的并发性能。

6.1 并发集合概览

  • ConcurrentHashMap :一个线程安全的哈希表,它实现了Map接口,允许多个读操作并发进行,同时支持一定数量的写操作并发进行,通过使用分段锁(在Java 8及以后版本中,改为使用Node数组加CAS操作与synchronized锁结合的方式)来减少锁的粒度,从而提高并发性能。
  • CopyOnWriteArrayList:一个线程安全的变体ArrayList。通过在读操作时不加锁,而在写操作(如add、set等)时通过复制底层数组来实现线程安全。这种策略适用于读多写少的并发场景。

  • ConcurrentSkipListMap :一个线程安全的可排序映射表,基于Skip List数据结构实现。它可以在高并发环境下提供比TreeMap更高的并发级别。

  • BlockingQueue :一组支持两个附加操作的队列,这两个操作是:在元素从队列中取出时,如果没有可用的元素,则等待队列变为非空;当元素添加到满队列时,如果队列没有空间,则等待队列可用。BlockingQueue接口的实现类包括ArrayBlockingQueueLinkedBlockingQueue等。

  • ConcurrentLinkedQueue:一个基于链接节点的无界线程安全队列。此队列按照 FIFO(先进先出)排序元素。队列的头部是队列中时间最长的元素。队列的尾部是队列中时间最短的元素。新元素将插入到队列的尾部,队列检索操作则获取队列头部的元素。

6.2 并发集合的使用场景

  • ConcurrentHashMap:适用于需要高并发读写操作的哈希表场景,如缓存系统、高频访问的数据结构等。

  • CopyOnWriteArrayList:适用于读多写少的并发场景,如事件监听器列表、配置参数列表等。由于写操作代价较高(需要复制整个底层数组),因此不适合写操作频繁的场景。

  • ConcurrentSkipListMap:适用于需要排序的映射表,并且要求较高的并发访问性能。它可以在保持元素有序的同时,提供高效的并发读写操作。

  • BlockingQueue:适用于生产者-消费者场景,如任务队列、消息队列等。通过阻塞队列,可以方便地实现生产者和消费者之间的解耦和同步。

  • ConcurrentLinkedQueue:适用于高并发环境下的无界队列,特别是当队列长度不可预测或需要动态增长时。由于它是无界的,因此使用时需要注意内存管理,避免内存溢出。

6.3 并发集合与Spliterator的集成

虽然Spliterator主要是与流(Streams)和并行流(Parallel Streams)一起使用的,但并发集合也可以与Spliterator结合使用,以提高并行处理的效率。

对于支持并行操作的并发集合(如ConcurrentHashMap),当使用其并行流(通过parallelStream()方法)时,内部会利用Spliterator来分割数据源,实现并行处理。然而,直接操作Spliterator来遍历或分割并发集合的情况并不常见,因为并发集合通常已经提供了足够的并发支持和遍历方法。

但在某些特殊情况下,如果需要更细粒度的控制或优化,可以通过获取并发集合的Spliterator来实现。例如,可以使用ConcurrentHashMapkeySet()values()entrySet()方法返回的集合的spliterator()来获取Spliterator,然后手动分割或遍历。但请注意,这样做可能会破坏并发集合的并发性保证,因此需要谨慎使用。

总的来说,并发集合和Spliterator都是Java并发编程中非常重要的工具。它们各自有不同的特点和适用场景,但都可以帮助开发者在并发环境下构建高效、可靠的应用程序。

相关推荐
学习编程的Kitty17 小时前
算法——位运算
java·前端·算法
斑点鱼 SpotFish17 小时前
用Python可视化国庆期间旅游概况与消费趋势
开发语言·python·旅游
only-lucky17 小时前
在Qt中使用VTK
开发语言·qt
用户9047066835717 小时前
如何使用 Spring MVC 实现 RESTful API 接口
java·后端
刘某某.17 小时前
数组和小于等于k的最长子数组长度b
java·数据结构·算法
程序员飞哥17 小时前
真正使用的超时关单策略是什么?
java·后端·面试
用户9047066835717 小时前
SpringBoot 多环境配置与启动 banner 修改
java·后端
小杰帅气17 小时前
类与对象1
开发语言·c++
chenyuhao202418 小时前
《C++二叉引擎:STL风格搜索树实现与算法优化》
开发语言·数据结构·c++·后端·算法
小old弟18 小时前
后端三层架构
java·后端