Java集合框架(二):万字长文深入详解Java集合常用接口

1.前言

    朋友们,今天,我们继续深入探索Java集合框架。我们将在这篇文章中为大家讲解在工作中常用的集合接口使用方法。集合类主要存放于 Java.util 包中,它们主要包括了List、Set和Map三种类型。其中List和Set接口继承了Collection接口,而Map没有继承Collection接口。

2.List

对于List接口,常用的实现类有ArrayList、Vector和LinkedList。List是有序的Collection,适用于需要按照插入顺序来保存元素的场景。

2.1 ArrayList

ArrayList 是 Java 中的一个动态数组实现,它实现了 List 接口。它与普通数组相比具有动态增长的能力,可以根据需要自动增长容量。以下是 ArrayList 接口的一些常用方法和代码示例:

 ArrayList 常用方法:

1. 添加元素:`add()` 方法用于向 ArrayList 中添加元素

2. 获取元素:`get()` 方法用于获取指定位置的元素、.indexOf(" ");获取指定元素的索引

3. 删除元素:`remove()` 方法用于删除指定位置或指定对象的元素

4. 获取大小:`size()` 方法返回 ArrayList 的大小(元素个数)。

5. 判断是否包含某元素:`contains()` 方法用于检查 ArrayList 是否包含某个元素。
6. 遍历 ArrayList:可以使用 for 循环或者迭代器来遍历 ArrayList。

7. 转换为数组:`toArray()` 方法将 ArrayList 转换为数组。

8. 清空 ArrayList:`clear()` 方法用于清空 ArrayList 中的所有元素。

9.检查元素为空:isEmpty():检查数组是否为空

### 示例代码:

import java.util.ArrayList;

public class ArrayListExample {
    public static void main(String[] args) {
        ArrayList fruits = new ArrayList<>();

        // 添加元素
        fruits.add("苹果");
        fruits.add("香蕉");
        fruits.add("橙子");

        // 获取元素
        String firstFruit = fruits.get(0); // 结果为"苹果"

        // 删除元素
        fruits.remove(1); // 删除第二个元素("香蕉")

        // 获取大小
        int size = fruits.size(); // 获取 ArrayList 大小

        // 判断是否包含某元素
        boolean hasApple = fruits.contains("苹果"); // 检查是否包含"苹果"

        // 遍历 ArrayList
        for (String fruit : fruits) {
            System.out.println(fruit);
        }

        // 转换为数组
        String[] fruitArray = fruits.toArray(new String[0]);

        // 清空 ArrayList
        fruits.clear(); // 清空 ArrayList
    }
}

2.2 Vector

`Vector` 是 Java 中的一个古老的集合类,它实现了 `List` 接口,与 `ArrayList` 类似,但在某些方面不同。以下是 `Vector` 接口的一些常用方法:

 Vector 常用方法与ArrayList基本相同,这里不在: Vector vector = new Vector<>();

扩容策略:与 ArrayList 不同,Vector 在扩容时采取的是翻倍扩容的策略,即容量不够时,会创建一个容量为原来两倍的新数组,并将数据复制过去。

与 ArrayList 不同的是,Vector 是线程安全的。然而,在绝大多数情况下,使用 `ArrayList` 或 `LinkedList` 更为常见,因为它们比 Vector 有更好的性能。如果你需要线程安全的列表,并且不需要同步方法带来的开销,可以考虑使用 `CopyOnWriteArrayList` 或者 `Collections.synchronizedList(new ArrayList<>())`。

2.3 LinkedList

LinkedList 是 Java 中的一个双向链表实现,实现了 List 接口,    底层实现了双向链表和双端队列。与 ArrayList 和 Vector不同,LinkedList中维护了两个属性first和last分别指向首结点和尾结点LinkedList 不是基于数组,而是由节点组成的链表结构。

基本操作用法也不再赘述,基本相同,但以下为注意事项:

1.LinkedList的CRUD操作对应方法,add()、remove()、set()、get()。其中remove()默认删除第一个结点。

2、所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高

3、可以添加任意元素(元素可以重复),包括null

4、线程不安全,没有实现同步

LinkedList的优势在于能够快速地进行插入和删除操作,因为它的底层是由节点组成的链表结构,不需要像数组一样移动元素。但是在随机访问方面性能较差,因为访问特定索引位置的元素需要从头开始遍历链表直到找到目标节点。因此,根据需要选择合适的数据结构是很重要的。

2.4小结

List集合的选择
在不考虑线程安全的情况下,使用ArrayList,否则Vector

1、如果改查的操作多,ArrayList

2、如果增删的操作多,选择LinkedList

3、一般来说,在程序中,大多数的操作都是查询,因此大部分情况下选择ArrayList

4、在一个项目中,根据业务灵活选择,也可能一个模块使用的是ArrayList,另外一个模块是LinkedList

3.Map

至于Map接口,它主要用于存储键值对(key-value)的数据结构,常用的实现类有HashMap、LinkedHashMap和TreeMap。当需要根据某个唯一标识来查找对应的值时,就可以使用Map。例如,一个存储学生信息和他们对应成绩的映射关系,可以使用Map来实现。

3.1 HashMap

`HashMap` 是 Java 中最常用的映射表实现之一,实现了 `Map` 接口,用于存储键值对。它基于哈希表实现,提供了快速的查找和插入操作。允许一条键为Null、多条值为Null、不支持线程同步。由数组+链表组成的,基于哈希表的Map实现。数组是HashMap的主体,存储一个个Entry对象。链表则是主要为了解决哈希冲突而存在的。以下是 `HashMap` 接口的一些常用方法和代码示例:

 HashMap 常用方法:

1. 添加键值对:`put()` 方法用于向 HashMap 中添加键值对。

2. 获取值:`get()` 方法用于根据键获取对应的值。

3. 删除键值对:`remove()` 方法用于删除指定键的键值对。

4. 判断是否包含键:`containsKey()` 方法用于检查 HashMap 是否包含指定的键。

5. 判断是否包含值:`containsValue()` 方法用于检查 HashMap 是否包含指定的值。

6. 获取大小:`size()` 方法返回 HashMap 中键值对的数量。

7. 遍历键值对:可以使用 `keySet()`、`values()` 或 `entrySet()` 方法来遍历 HashMap 中的键、值或者键值对。

   // 遍历键
    for (String key : hashMap.keySet()) {
        System.out.println("Key: " + key);
    }

    // 遍历值
    for (Integer value : hashMap.values()) {
        System.out.println("Value: " + value);
    }

    // 遍历键值对
    for (Map.Entry entry : hashMap.entrySet()) {
        System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
    }

示例代码:

import java.util.HashMap;
import java.util.Map;

public class HashMapExample {
    public static void main(String[] args) {
        HashMap fruitQuantity = new HashMap<>();

        // 添加键值对
        fruitQuantity.put("apple", 10);
        fruitQuantity.put("banana", 20);
        fruitQuantity.put("orange", 15);

        // 获取值
        int quantity = fruitQuantity.get("apple"); // 结果为10

        // 删除键值对
        fruitQuantity.remove("banana"); // 删除键为"banana"的键值对

        // 判断是否包含键或值
        boolean containsOrange = fruitQuantity.containsKey("orange"); // 检查是否包含键"orange"
        boolean hasValue20 = fruitQuantity.containsValue(20); // 检查是否包含值为20的键值对

        // 获取大小
        int size = fruitQuantity.size(); // 获取 HashMap 大小

        // 遍历键值对
        for (Map.Entry entry : fruitQuantity.entrySet()) {
            System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
        }
    }
}

`HashMap` 具有快速的查找和插入操作,对于大多数键值对存储的需求是很方便的。但需要注意的是,`HashMap` 不保证键值对的顺序,如果需要按照特定顺序遍历键值对,可以考虑使用 `LinkedHashMap`。在并发环境中,`HashMap` 是非线程安全的,如果需要在多线程环境中使用,可以考虑使用 `ConcurrentHashMap` 或者进行外部同步处理。

1.  LinkedHashMap:输出顺序和输入顺序相同,底层由HashMap和双向链表实现,解决了HashMap不能随时保持遍历顺序和插入顺序一致的问题。与HashMap相比,LinkedHashMap增加了两个属性用于保证迭代顺序,分别是 双向链表头结点header 和 标志位accessOrder (值为true时,表示按照访问顺序迭代;值为false时,表示按照插入顺序迭代)。
2.  ConcurrentHashMap:支持线程同步,原理是引入"分段锁"概念,把Map分成了N个Segment,具体可以理解为把一个大的Map拆分成N个小的HashTable,根据key.hashCode()决定把key放到哪个HashTable中。
 

3.2 LinkedHashMap

`LinkedHashMap` 是 `HashMap` 的一个子类,它保留了插入元素的顺序,即按照插入顺序或者访问顺序(可选)来迭代遍历。除了具备 `HashMap` 的功能外,`LinkedHashMap` 还维护了一个双向链表,用于维护插入顺序或者访问顺序。以下是 `LinkedHashMap` 接口的一些常用方法和代码示例:

LinkedHashMap 常用方法基本与HashMap相同:

更改迭代顺序:`LinkedHashMap` 还提供了构造函数,可以选择按照插入顺序或者访问顺序来遍历。

   LinkedHashMap accessOrderMap = new LinkedHashMap<>(16, 0.75f, true);
    // 设置为按照访问顺序迭代,默认为 false

`LinkedHashMap` 是一个有序的哈希表,能够保留插入顺序或者按照访问顺序来迭代遍历。在需要保留元素插入顺序或者按照最近访问顺序迭代遍历的情况下,使用 `LinkedHashMap` 是很有用的选择。如果需要按照键值对的顺序进行遍历,同时具备哈希表的快速查找特性,`LinkedHashMap` 是一个很好的选择。

3.3 TreeMap

 TreeMap基于红黑树(Red-Black tree)实现。  默认键值按升序排序,可以自定义顺序遍历键。TreeMap的基本操作containsKey、get、put、remove方法,它的时间复杂度是log(N)。

`TreeMap` 是基于红黑树(一种自平衡的二叉搜索树)实现的 `Map` 接口的实现类。它可以按照键的自然顺序或者提供的 Comparator 进行排序,因此它是有序的 `Map`。 `TreeMap` 接口的一些常用方法与HashMap基本相同

`TreeMap` 是一个有序的 `Map` 实现,它根据键的自然顺序或者提供的 Comparator 进行排序。在需要按照键的顺序进行遍历、查找范围内的键值对等场景下,使用 `TreeMap` 是一个很有用的选择。需要注意的是,由于 `TreeMap` 是基于红黑树实现的,因此插入、删除、查找等操作的时间复杂度为 O(log N),相较于哈希表实现的 `HashMap`,它在有序性上有优势,但是在性能上可能会稍逊一筹。

3.4  HashTable

`HashTable` 是 Java 中早期的哈希表实现,它实现了 `Map` 接口,提供了键值对的存储和检索。`HashTable` 是线程安全的,所有的方法都是同步的,这意味着它适用于多线程环境。然而,在 Java Collections Framework 中,`HashTable` 已经被更先进的实现替代,比如 `HashMap` 和 `ConcurrentHashMap`。

4.Set

Set接口则主要用于存储无序且不重复的元素。常用的实现类有HashSet、LinkedHashSet和TreeSet。

4.1 HashSet

 散列集HashSet是一个用于实现Set接口的具体类,HashSet是基于HashMap实现的,默认构造函数是构建一个初始容量为16,负载因子为0.75 的HashMap。只存储一个对象,而Map存储两个对象Key和Value(仅仅key对象有序)。它不允许包含重复元素,同时允许存储NULL元素。HashSet不保证元素的顺序,不像 LinkedHashSet或者 TreeSet那样保留元素的插入顺序或按照某种顺序排序。

常用方法不在给大家赘述基本与HashMap相同

        当向HashSet中加入一个元素时,它需要判断集合中是否已经包含了这个元素,从而避免重复存储。HashSet首先会调用对象的hashCode()方法获取其哈希码,并通过哈希码确定该对象在集合中存放的位置。假设这个位置之前已经存了一个对象,则HashSet会调用equals()对两个对象进行比较。若相等则说明对象重复,此时不会保存新加的对象。若不等说明对象不重复,但是它们存储的位置发生了碰撞,此时HashSet会采用链式结构在同一位置保存多个对象,即将新加对象链接到原来对象的之后。之后,再有新添加对象也映射到这个位置时,就需要与这个位置中所有的对象进行equals()比较,若均不相等则将其链到最后一个对象之后。

HashSet是一个适用于需要快速查找和不允许重复元素的场景下的理想选择。在添加和查询元素方面具有很高的性能。由于不保留元素的顺序,适用于不需要保持插入顺序或特定顺序的情况

4.2 LinkedHashSet

`LinkedHashSet` 是 `HashSet` 的一个子类,它继承了 `HashSet` 的特性,并且内部使用链表维护插入顺序,保留了元素的插入顺序。这意味着遍历 `LinkedHashSet` 将按照元素插入的顺序进行。

 主要特点和用法:

1. **维护插入顺序**:与 `HashSet` 不同,`LinkedHashSet` 内部使用链表维护元素的插入顺序。在迭代遍历时,会按照元素插入的顺序进行访问。

2. **基于哈希表**:同样具备哈希表的特性,即快速的查找和不允许重复元素。

3. **性能**:在添加、删除和包含操作上具有和 `HashSet` 类似的性能。但在迭代遍历时,由于维护了插入顺序,可能稍微慢于 `HashSet`。

`LinkedHashSet` 继承了 `HashSet` 的所有方法,它提供了类似于 `HashSet` 的常见方法,如 `add()`、`remove()`、`contains()`、`size()` 等。

`LinkedHashSet` 适用于需要保留元素插入顺序的场景,同时还需要快速查找和不允许重复元素的特性。由于它维护了插入顺序,适用于需要按照元素插入顺序进行迭代遍历的场景。

4.3 TreeSet

`TreeSet` 是 Java 中实现了 `NavigableSet` 接口的有序集合类,它基于红黑树(一种自平衡的二叉搜索树)实现。与 `HashSet` 不同,`TreeSet` 中的元素是有序的,它根据元素的自然顺序或者提供的 `Comparator` 进行排序。

 主要特点和用法:

1. **有序性**:`TreeSet` 中的元素是有序的。在默认情况下,元素会按照元素的自然顺序进行排序;如果提供了自定义的 `Comparator`,则根据指定的顺序排序。

2. **基于红黑树**:内部实现使用红黑树数据结构,这保证了元素的快速插入、删除和有序性。

3. **不允许重复元素**:`TreeSet` 不允许包含重复的元素。

4. **实现了 NavigableSet 接口**:除了提供了 `Set` 接口的常规操作外,`TreeSet` 还提供了诸如 `higher()`, `lower()`, `floor()`, `ceiling()` 等操作来支持导航查找,获取比特定元素大或小的元素。

常用方法:

`TreeSet` 提供了与 `Set` 接口相似的常用方法,并添加了支持有序集合的一些方法。

**获取元素**:`first()` 和 `last()` 方法分别返回 TreeSet 中的第一个和最后一个元素。

  int first = treeSet.first();
  int last = treeSet.last();

 **获取比指定元素大或小的元素**:`higher()`, `lower()`, `floor()`, `ceiling()` 等方法支持导航查找。

  int higher = treeSet.higher(4); // 返回大于4的最小元素
  int lower = treeSet.lower(6); // 返回小于6的最大元素

`TreeSet` 适用于需要有序集合并且不允许包含重复元素的场景下。它在添加、删除、查找等操作上有较好的性能,但与 `HashSet` 相比,在有序性上有一些额外开销。若需要保留插入顺序,可以考虑使用 `LinkedHashSet`。

5.迭代器 Iterator

迭代器(Iterator)是 Java 集合框架中的一种设计模式,它提供了一种顺序访问集合中元素的方法,而不需要暴露集合的内部结构。迭代器为各种集合类提供了一种统一的遍历方式,使得对集合元素的访问更加简单、统一和安全。

 迭代器的主要方法:

1. **hasNext()**:判断集合中是否还有下一个元素,如果有返回 `true`,否则返回 `false`。

2. **next()**:返回集合中的下一个元素,并将迭代器的位置移动到下一个位置。

3. **remove()**:从集合中移除迭代器返回的最后一个元素(可选操作)。

 迭代器的工作方式:

**获取迭代器**:通过调用集合的 `iterator()` 方法获取该集合对应的迭代器。

ArrayList list = new ArrayList<>();
    Iterator iterator = list.iterator();

**遍历集合**:通过迭代器的 `hasNext()` 和 `next()` 方法来遍历集合中的元素。

while (iterator.hasNext()) {
        String element = iterator.next();
        System.out.println(element);
    }

使用迭代器删除元素:可以使用迭代器的 `remove()` 方法来删除迭代器返回的最后一个元素(某些集合支持此操作)。

   iterator.remove();

 示例代码:

下面是一个简单的示例,演示了如何使用迭代器遍历 ArrayList:

import java.util.ArrayList;
import java.util.Iterator;

public class IteratorExample {
    public static void main(String[] args) {
        ArrayList list = new ArrayList<>();
        list.add("Apple");
        list.add("Orange");
        list.add("Banana");

        // 获取迭代器
        Iterator iterator = list.iterator();

        // 使用迭代器遍历集合
        while (iterator.hasNext()) {
            String fruit = iterator.next();
            System.out.println(fruit);

            // 删除元素(可选操作,不是所有集合都支持)
            if (fruit.equals("Orange")) {
                iterator.remove();
            }
        }

        // 打印删除元素后的集合内容
        System.out.println("After removal:");
        for (String fruit : list) {
            System.out.println(fruit);
        }
    }
}

迭代器提供了一种安全、统一的方式来遍历集合中的元素,并且可以在遍历过程中删除元素(某些集合支持此操作)。要注意的是,并非所有的集合都支持迭代器的 `remove()` 操作,对于不支持的集合进行删除操作会抛出 `UnsupportedOperationException` 异常。

5.1 Iterator和ListIterator的区别

Iterator和ListIterator的区别

`Iterator` 和 `ListIterator` 都是用于遍历集合的接口,但有一些重要的区别:

Iterator:

1. **适用范围**:
   - `Iterator` 是集合框架的基本迭代器,可以用于遍历 `Set`、`List`、`Queue` 和 `Map` 等集合类,但在遍历 `Map` 时,需要通过 `Map` 的 `entrySet()`、`keySet()` 或 `values()` 方法获取其迭代器。

2. **功能**:
   - `Iterator` 只能向前遍历集合,并且只能在迭代过程中进行元素的删除操作,不支持向前、向后、修改等操作。

4. **实现类**:
   - 大多数集合类都实现了 `Iterator` 接口,可通过 `collection.iterator()` 获取迭代器。

ListIterator:

1. **适用范围**:
   - `ListIterator` 继承自 `Iterator` 接口,但只能用于 `List` 接口的实现类,如 `ArrayList`、`LinkedList` 等。

2. **功能**:
   - `ListIterator` 可以向前和向后遍历列表,并且支持修改元素、添加元素、获取索引等操作。

3. **方法**:
   - `ListIterator` 提供了更丰富的方法:
     - `hasNext()` 和 `next()` 用于向后遍历列表。
     - `hasPrevious()` 和 `previous()` 用于向前遍历列表。
     - `add()` 用于在迭代器当前位置之前添加一个元素。
     - `set()` 用于替换迭代器最后一次返回的元素。
     - `remove()` 用于删除迭代器最后一次返回的元素。

4. **双向遍历**:
   - `ListIterator` 具有双向遍历的能力,可以在列表中向前或向后移动,并且可以在迭代过程中修改、添加和删除元素。

总结:

 `Iterator` 是集合框架的基本迭代器,适用于各种集合,但功能相对较少,只能向前遍历和删除元素。
`ListIterator` 是 `Iterator` 的子接口,只能用于列表类的集合(`List` 接口的实现类),支持双向遍历,可以在迭代过程中修改、添加和删除元素。

6.总结

选择集合类型时,要考虑以下几个关键点:

1. **数据唯一性**:
   - 如果需要确保集合中不包含重复元素,可以选择 `Set` 接口的实现类,如 `HashSet`、`TreeSet`、`LinkedHashSet`。
   - 如果允许包含重复元素,选择 `List` 接口的实现类,如 `ArrayList`、`LinkedList`。

2. **集合的有序性**:
   - 如果需要保留元素的插入顺序,可以选择 `LinkedHashSet`、`LinkedList` 或者 `LinkedHashMap`。
   - 如果需要按照元素的自然顺序或自定义顺序进行排序和存储,可以选择 `TreeSet` 或 `TreeMap`。

3. **性能需求**:
   - 对于频繁的插入、删除和查找操作,`HashMap` 和 `HashSet` 提供了较好的性能。
   - 如果需要有序性且性能要求不是特别高,可以选择 `LinkedHashSet` 和 `LinkedHashMap`。
   - `TreeSet` 和 `TreeMap` 在维持有序性的同时,对于大型数据集的操作可能性能略低。

4. **并发性**:
   - 如果需要在多线程环境中使用集合,可以考虑使用线程安全的实现类,如 `ConcurrentHashMap`、`CopyOnWriteArrayList` 等。

5. **空间与时间复杂度**:
   - 不同的集合实现在空间和时间复杂度上可能有所不同。例如,`HashMap` 提供了 O(1) 的平均时间复杂度,而 `TreeMap` 的插入、删除和查找操作则为 O(log N)。

6. **功能需求**:
   - 根据需要选择不同的集合功能,例如需要根据键值对保持插入顺序可以选择 `LinkedHashMap`,需要有序集合可以选择 `TreeSet`。

集合的选择取决于对数据结构特性、性能要求、并发需求和功能需求等方面的考量。根据具体的场景和需求,选择最合适的集合类型来存储和处理数据。

总的来说,这些集合类型的选择取决于数据的特性以及操作需求,理解它们的用法和特性有助于编写出更高效的代码。

参考文章

https://blog.csdn.net/Maddoxzhi/article/details/127338974

https://blog.csdn.net/onebeautifulman/article/details/120848801

常用的几种java集合类总结_java集合分为哪几大类-CSDN博客

你可能感兴趣的:(Java,java,开发语言)