全面解析:单列集合Collection和双列集合Map

server/2024/11/24 6:31:56/

        Java中的集合(Collection)是一个框架,用于存储、操作数据。集合框架包括了许多接口和类,用于表示数据的存储方式。集合主要分为两大类:CollectionMap。


  • 单列集合Collection的继承体系图:

Collection 接口提供了一些通用的方法,用于操作集合的元素。常用的方法包括:

  • boolean add(E e):向集合中添加一个元素。
  • boolean remove(Object o):从集合中删除指定的元素。
  • boolean contains(Object o):检查集合中是否包含指定的元素。
  • boolean isEmpty():检查集合是否为空。
  • int size():返回集合中元素的数量。
  • void clear():清空集合中的所有元素。
  • Iterator<E> iterator():返回一个迭代器,用于遍历集合中的元素。

  •  双列集合Map的继承体系图:

Map 接口提供了几个常用的方法来操作集合:

  • put(K key, V value):将指定的键值对添加到 Map 中。如果键已存在,则更新对应的值。
  • get(Object key):根据指定的键获取对应的值。如果键不存在,返回 null
  • remove(Object key):删除指定键的键值对。
  • containsKey(Object key):检查 Map 中是否包含指定的键。
  • containsValue(Object value):检查 Map 中是否包含指定的值。
  • keySet():返回 Map 中所有键的集合。
  • values():返回 Map 中所有值的集合。
  • entrySet():返回 Map 中所有键值对的集合。

 注意:定义集合类时使用了“<参数化类型>”的方式指定了该类中方法操作的数据类型,即泛型:(不懂的伙伴可以先看下面这期:)

说说你对泛型的理解-CSDN博客


一、实现List接口的单列集合

    List 接口是集合框架的一部分,它代表一种有序集合,允许存储重复的元素ArrayListLinkedListVector 都是实现 List 接口的常见类,具有不同的特性和使用场景。


1. ArrayList:基于动态数组实现

    ArrayList 是最常用的 List 实现类之一,它基于动态数组(即数组的大小会根据需要自动调整)来存储元素。它允许快速随机访问元素,插入和删除元素的效率较低,特别是在中间位置插入或删除元素时。

特点:
  • 查询效率高:基于数组实现,支持按索引访问元素,时间复杂度为 O(1)。
  • 插入和删除效率较低:尤其在中间插入或删除时,其他元素需要移动,时间复杂度为 O(n)。
  • 动态扩容:当数组容量不够时,ArrayList 会自动扩展,通常扩展为原来容量的 1.5 倍。
  • 线程不安全ArrayList 不是线程安全的,如果多个线程同时访问一个 ArrayList,并且至少有一个线程修改了该集合,就需要外部同步。

解释O(1)是什么意思

在算法和数据结构中,O(1) 是一种常见的时间复杂度表示,它代表常数时间复杂度(Constant Time Complexity)。当我们说某个操作的时间复杂度是 O(1) 时,意味着无论数据的规模如何增加,操作的执行时间都不会发生变化,它的执行时间是固定的。

O(1) 和 O(n) 等其他复杂度的对比

  • O(1) 操作的时间是常数,它不随数据量变化而变化。
  • O(n) 操作的时间随输入数据的规模线性增加,即数据量增加一倍,执行时间也增加一倍。例如,遍历一个包含 n 个元素的数组需要 O(n) 时间。
  • O(log n) 操作的时间增长速度较慢,常见于二分查找或平衡二叉搜索树等算法。
举个例子:
  • O(1):数组访问某个元素、栈的 push 操作、哈希表的插入操作。
  • O(n):遍历一个数组、在链表中查找元素。
  • O(log n):二分查找、平衡二叉树的查找或插入。
 示例代码:
import java.util.ArrayList;
import java.util.List;public class ArrayListExample {public static void main(String[] args) {List<String> list = new ArrayList<>();// 添加元素list.add("Java");list.add("Python");list.add("C++");// 获取元素System.out.println("List: " + list);  // 输出:[Java, Python, C++]// 获取元素的索引System.out.println("Element at index 1: " + list.get(1));  // 输出:Python// 删除元素list.remove("C++");System.out.println("List after removal: " + list);  // 输出:[Java, Python]// 修改元素list.set(0, "JavaScript");System.out.println("List after modification: " + list);  // 输出:[JavaScript, Python]}
}

 2. LinkedList:基于双向链表实现

    LinkedListList 接口的另一个实现类,它基于双向链表实现。每个元素(节点)包含一个指向前一个元素和后一个元素的引用。由于 LinkedList 使用链表存储元素,它在插入和删除操作上具有较好的性能,尤其是在中间位置。

特点:
  • 查询效率低:与 ArrayList 不同,LinkedList 需要遍历链表才能找到元素,时间复杂度为 O(n)。
  • 插入和删除效率高:在链表的两端插入或删除元素的时间复杂度为 O(1),在中间插入或删除时需要调整链表指针,时间复杂度为 O(n)。
  • 支持双端队列LinkedList 还实现了 Deque 接口,可以作为队列(FIFO)或栈(LIFO)使用。
  • 线程不安全LinkedList 也不是线程安全的。
示例代码:
java">import java.util.LinkedList;
import java.util.List;public class LinkedListExample {public static void main(String[] args) {List<String> list = new LinkedList<>();// 添加元素list.add("Java");list.add("Python");list.add("C++");// 获取元素System.out.println("List: " + list);  // 输出:[Java, Python, C++]// 获取元素的索引System.out.println("Element at index 1: " + list.get(1));  // 输出:Python// 删除元素list.remove("C++");System.out.println("List after removal: " + list);  // 输出:[Java, Python]// 在链表头部插入元素list.addFirst("Ruby");System.out.println("List after adding to the first: " + list);  // 输出:[Ruby, Java, Python]// 在链表尾部插入元素list.addLast("C#");System.out.println("List after adding to the last: " + list);  // 输出:[Ruby, Java, Python, C#]}
}

3. Vector:基于动态数组实现(线程安全)

    Vector 类是 List 接口的一个早期实现,和 ArrayList 类似,也基于动态数组实现。但是,Vector 在设计上支持线程安全。它在每次扩展容量时都会重新分配数组,并且其扩展的倍数为原容量的 2 倍(不同于 ArrayList 的 1.5 倍扩展)。

特点:
  • 线程安全Vector 使用 synchronized 来保证线程安全,这使得它在多线程环境下比 ArrayList 更安全。
  • 查询效率较高:与 ArrayList 相似,支持按索引访问,时间复杂度为 O(1)。
  • 性能较差:由于内置的同步机制,Vector 在单线程环境下的性能比 ArrayList 差。如果不需要线程安全,ArrayList 通常更高效。
  • 容量自动增长Vector 的容量在元素个数超出当前容量时,自动增长为原容量的两倍。
示例代码:
java">import java.util.List;
import java.util.Vector;public class VectorExample {public static void main(String[] args) {List<String> list = new Vector<>();// 添加元素list.add("Java");list.add("Python");list.add("C++");// 获取元素System.out.println("List: " + list);  // 输出:[Java, Python, C++]// 获取元素的索引System.out.println("Element at index 1: " + list.get(1));  // 输出:Python// 删除元素list.remove("C++");System.out.println("List after removal: " + list);  // 输出:[Java, Python]// 修改元素list.set(0, "JavaScript");System.out.println("List after modification: " + list);  // 输出:[JavaScript, Python]}
}

4.ArrayListLinkedListVector 的对比 


总结

  • ArrayList:适用于读操作频繁、插入删除操作较少的场景,查询效率高,插入删除效率低。它是大多数情况下的首选实现。
  • LinkedList:适用于插入和删除操作频繁,尤其是列表两端插入删除操作较多的场景。由于是链表结构,查询效率较低。
  • Vector:与 ArrayList 类似,但内置同步机制使其线程安全。由于同步的开销,Vector 性能较差,不推荐在单线程环境下使用。

二、实现Set接口的单列集合

        在 Java 中,Set 接口表示一个不允许重复元素的集合,它是集合框架中的一种类型。HashSetLinkedHashSetTreeSet 都是 Set 接口的实现类,它们在存储、排序和访问元素的方式上有所不同。


1. HashSet:基于哈希表实现

    HashSetSet 接口最常用的实现之一,它基于哈希表(HashMap)来存储元素。HashSet 不保证集合的顺序,也不允许重复元素。如果两个对象的哈希值相同,HashSet 会根据 equals() 方法来判断它们是否相等,因此,如果对象没有正确实现 hashCode()equals() 方法,可能会出现逻辑错误。

特点:
  • 无序性:元素的顺序是不可预测的,因为它基于哈希表实现,插入元素的顺序不一定与元素存储的顺序相同。
  • 不允许重复元素HashSet 会自动删除重复的元素。它通过 hashCode()equals() 方法来判断两个元素是否相等。
  • 性能高HashSet 提供常数时间复杂度 O(1) 来进行添加、删除和包含检查(假设哈希函数良好)。
示例代码:
java">import java.util.HashSet;
import java.util.Set;public class HashSetExample {public static void main(String[] args) {Set<String> set = new HashSet<>();// 添加元素set.add("Apple");set.add("Banana");set.add("Cherry");set.add("Banana");  // 重复元素不会被添加// 输出集合System.out.println("HashSet: " + set);  // 输出:[Apple, Banana, Cherry]// 检查元素是否存在System.out.println("Contains 'Apple': " + set.contains("Apple"));  // 输出:true}
}

2. LinkedHashSet:基于哈希表实现,维护元素的插入顺序

    LinkedHashSetHashSet 的子类,除了具有 HashSet 的所有特性外,它还通过链表维护元素的插入顺序。换句话说,LinkedHashSet 在插入元素时会记住元素的顺序,因此遍历时会按照元素的插入顺序返回。

特点:
  • 有序性:元素的顺序是插入顺序,这意味着每次迭代时都会按照元素被添加到集合的顺序返回。
  • 不允许重复元素:与 HashSet 一样,LinkedHashSet 也不允许重复的元素。
  • 性能略逊于 HashSet:因为它需要维护插入顺序,因此在插入和删除操作时会稍微消耗更多的时间,但通常差别不大。
示例代码:
java">import java.util.LinkedHashSet;
import java.util.Set;public class LinkedHashSetExample {public static void main(String[] args) {Set<String> set = new LinkedHashSet<>();// 添加元素set.add("Apple");set.add("Banana");set.add("Cherry");set.add("Banana");  // 重复元素不会被添加// 输出集合System.out.println("LinkedHashSet: " + set);  // 输出:[Apple, Banana, Cherry]// 遍历元素,保持插入顺序for (String fruit : set) {System.out.println(fruit);  // 按照插入顺序输出}}
}

输出:

java">LinkedHashSet: [Apple, Banana, Cherry]
Apple
Banana
Cherry

3. TreeSet:基于红黑树实现

    TreeSetNavigableSet 接口的实现,它基于红黑树(一个自平衡的二叉查找树)来存储元素。与 HashSetLinkedHashSet 不同,TreeSet 会根据元素的自然顺序或构造 Comparator 来对元素进行排序,因此它是有序的。

特点:
  • 排序TreeSet 自动对元素进行排序,可以按元素的自然顺序排序(对于 Comparable 类型的元素),也可以通过提供 Comparator 进行自定义排序。
  • 不允许重复元素:与 HashSetLinkedHashSet 一样,TreeSet 也不允许重复的元素。重复的元素会被自动排除。
  • 性能较低TreeSet 基于红黑树实现,因此它的插入、删除和查找的时间复杂度是 O(log n),比 HashSet 的 O(1) 性能要差一些。
  • 高效的范围操作TreeSet 提供了一些用于范围查询的方法,如 headSet(), tailSet()subSet(),非常适用于需要区间操作的场景。
示例代码:
java">import java.util.TreeSet;
import java.util.Set;public class TreeSetExample {public static void main(String[] args) {Set<Integer> set = new TreeSet<>();// 添加元素set.add(10);set.add(5);set.add(20);set.add(15);set.add(10);  // 重复元素不会被添加// 输出集合(自动排序)System.out.println("TreeSet: " + set);  // 输出:[5, 10, 15, 20]// 遍历元素,按自然顺序输出for (Integer num : set) {System.out.println(num);  // 输出:5 10 15 20}}
}

输出:

java">TreeSet: [5, 10, 15, 20]
5
10
15
20

4. HashSetLinkedHashSetTreeSet 的对比

总结

  • HashSet:适用于需要快速查找、插入和删除元素的场景,但不关心元素的顺序。
  • LinkedHashSet:适用于需要保持插入顺序的场景,同时保持 HashSet 的高效性。
  • TreeSet:适用于需要排序的场景,元素会自动排序,并且支持范围查询,但性能比 HashSetLinkedHashSet 略逊一筹。

三、实现Map接口的双列集合

        在 Java 中,Map 接口表示一个键值对集合,它不允许键重复,每个键最多只能映射到一个值。Map 接口的实现类有多种,每个实现类在存储、访问、顺序性和性能上都有不同的特点。常见的 Map 实现类包括 HashMapTreeMapHashtableLinkedHashMapProperties。下面将逐一详细解释这些类的特点、用法及示例。


1. HashMap:基于哈希表实现

    HashMapMap 接口的最常用实现之一,基于哈希表(HashMap 使用 HashMap$Entry 数组和链表)来存储键值对。HashMap 允许 null 值和 null 键,并且不保证键值对的顺序。

特点:
  • 无序性HashMap 不保证插入的顺序。遍历时的顺序是随机的。
  • 不允许重复键:每个键只能映射到一个值。如果你试图将相同的键与不同的值关联,原来的值将被新值覆盖。
  • 允许 null 键和 nullHashMap 允许一个 null 键和多个 null 值。
  • 线程不安全HashMap 是线程不安全的,多个线程同时访问时可能导致数据不一致。如果在多线程环境下使用,可以考虑使用 ConcurrentHashMap
示例代码:
java">import java.util.HashMap;
import java.util.Map;public class HashMapExample {public static void main(String[] args) {Map<String, Integer> map = new HashMap<>();// 添加键值对map.put("Apple", 3);map.put("Banana", 2);map.put("Orange", 5);// 获取值System.out.println("Apple count: " + map.get("Apple"));  // 输出:Apple count: 3// 检查是否包含键System.out.println("Contains Banana: " + map.containsKey("Banana"));  // 输出:Contains Banana: true// 删除键值对map.remove("Orange");System.out.println("After removing Orange: " + map);  // 输出:{Apple=3, Banana=2}}
}

2. TreeMap:基于红黑树实现

    TreeMapMap 接口的一个实现类,基于红黑树(自平衡的二叉查找树)来存储键值对。它会根据键的自然顺序(或者提供的 Comparator)对键进行排序。

特点:
  • 有序性TreeMap 按照键的自然顺序或自定义顺序存储元素。
  • 不允许 nullTreeMap 不允许 null 键,但允许 null 值。如果试图插入 null 键,将抛出 NullPointerException
  • 性能TreeMap 提供了 O(log n) 的时间复杂度来进行插入、删除、查找等操作。
示例代码:
java">import java.util.Map;
import java.util.TreeMap;public class TreeMapExample {public static void main(String[] args) {Map<String, Integer> map = new TreeMap<>();// 添加键值对map.put("Apple", 3);map.put("Banana", 2);map.put("Orange", 5);// 获取值System.out.println("Apple count: " + map.get("Apple"));  // 输出:Apple count: 3// 遍历并按自然顺序输出System.out.println("TreeMap contents:");for (Map.Entry<String, Integer> entry : map.entrySet()) {System.out.println(entry.getKey() + ": " + entry.getValue());}}
}

输出:

java">Apple count: 3
TreeMap contents:
Apple: 3
Banana: 2
Orange: 5

3. Hashtable:基于哈希表实现(线程安全)

    Hashtable 是较旧的 Map 实现类,它的底层实现也是哈希表,但不同于 HashMapHashtable线程安全的。每个方法都被同步,因此在多线程环境中,Hashtable 会自动处理并发问题。

特点:
  • 线程安全Hashtable 是线程安全的,适用于多线程环境。
  • 无序性Hashtable 不保证插入顺序,和 HashMap 类似。
  • 不允许 null 键和 nullHashtable 不允许使用 null 作为键或值。如果尝试插入 null,会抛出 NullPointerException
  • 性能较差:由于它使用了同步机制,Hashtable 在多线程环境下的性能较差。通常推荐使用 ConcurrentHashMap 来替代。
示例代码:
java">import java.util.Hashtable;
import java.util.Map;public class HashtableExample {public static void main(String[] args) {Map<String, Integer> map = new Hashtable<>();// 添加键值对map.put("Apple", 3);map.put("Banana", 2);map.put("Orange", 5);// 获取值System.out.println("Apple count: " + map.get("Apple"));  // 输出:Apple count: 3// 删除键值对map.remove("Orange");System.out.println("After removing Orange: " + map);  // 输出:{Apple=3, Banana=2}}
}

4. LinkedHashMap:基于哈希表实现,维护插入顺序

    LinkedHashMapHashMap 的子类,它除了提供哈希表的高效查找性能外,还能保持元素的插入顺序,或者可以按访问顺序(accessOrder)进行排序。

特点:
  • 有序性LinkedHashMap 保证了元素按插入顺序(或者访问顺序)进行迭代。
  • 性能LinkedHashMap 在大多数操作中与 HashMap 相似,性能略差一些,因为它还需要维护插入顺序或访问顺序。
  • 线程不安全LinkedHashMap 不是线程安全的。
示例代码:
java">import java.util.LinkedHashMap;
import java.util.Map;public class LinkedHashMapExample {public static void main(String[] args) {Map<String, Integer> map = new LinkedHashMap<>();// 添加键值对map.put("Apple", 3);map.put("Banana", 2);map.put("Orange", 5);// 获取值System.out.println("Apple count: " + map.get("Apple"));  // 输出:Apple count: 3// 遍历元素,保持插入顺序System.out.println("LinkedHashMap contents:");for (Map.Entry<String, Integer> entry : map.entrySet()) {System.out.println(entry.getKey() + ": " + entry.getValue());}}
}

输出:

java">Apple count: 3
LinkedHashMap contents:
Apple: 3
Banana: 2
Orange: 5

5. Properties:用于处理配置属性

    PropertiesHashtable 的子类,专门用于管理配置属性。它通常用于存储应用程序的配置文件,支持将键值对存储到文件中,并能通过 load()store() 方法进行读写。

特点:
  • 继承自 HashtableProperties 继承自 Hashtable,因此具有线程安全的特性。
  • 键和值都是字符串Properties 中的键和值都是 String 类型。
  • 常用于配置文件Properties 通常用于读取和写入配置文件(如 .properties 文件)。
示例代码:
java">import java.util.Properties;
import java.io.FileWriter;
import java.io.IOException;public class PropertiesExample {public static void main(String[] args) throws IOException {Properties properties = new Properties();// 添加键值对properties.setProperty("username", "admin");properties.setProperty("password", "12345");// 保存到文件properties.store(new FileWriter("config.properties"), "User credentials");// 输出配置内容System.out.println("Properties: " + properties);}
}

6.总结对比

        每个 Map 实现类都有不同的特点,选择哪个取决于你的需求:是否需要排序、是否需要线程安全、是否关心插入顺序等。


http://www.ppmy.cn/server/144470.html

相关文章

【什么是RabbitMQ】

RabbitMQ&#xff1a;可靠、灵活的消息中间件 在当今的分布式系统和微服务架构中&#xff0c;消息中间件扮演着至关重要的角色。RabbitMQ&#xff0c;作为一款开源的消息代理软件&#xff0c;以其可靠性、灵活性、可扩展性和多语言支持等特点&#xff0c;在众多消息队列系统中…

SQL99版外连接

外连接 看这样的场景&#xff0c;在ta和tb两表中查询没有对应年龄数据的学生姓名和年龄 SELECT tb.name,ta.age FROM tb INNER JOIN ta ON tb.ta_idta.id WHERE ta.id IS NULL; 结果没有,所以前面的查询是解决不了这种问题&#xff01;&#xff01;&#xff01; 所以外连接…

工商银行湖仓智一体创新应用实践

数智技术已经成为企业数字化转型的核心动力 国家《“十四五”数字经济发展规划》指出数字经济是未来的主要经济形态,数据因其倍增效应和乘数效应,可以带来全要素效率的提升,已经成为数字经济的核心要素资源,是企业数字化转型的新要素、新动能。为了高质量推进企业数字化转…

华为HCCDA云技术认证--分布式云架构

大家好呀&#xff01;我是reload。今天继续带大家学习华为HCCDA云技术认证&#xff0c;涵盖华为云最为核心的计算、存储、网络、数据库、安全、部署等服务。今天学习分布式云架构与资源弹性伸缩相关内容。 一、弹性实现原理 1、问题引入 假设在双十一或其他大促期间的流量波…

IText创建加盖公章的pdf文件并生成压缩文件

第一、前言 此前已在文章&#xff1a;Java使用IText根据pdf模板创建pdf文件介绍了Itex的基本使用技巧&#xff0c;本篇以一个案例为基础&#xff0c;主要介绍IText根据pdf模板填充生成pdf文件&#xff0c;并生成压缩文件。 第二、案例 以下面pdf模板为例&#xff0c;生成一个p…

C++ 编程指南05 - 编译时检查优于运行时检查

一&#xff1a;概述 编译时错误检查是C编程中一条非常重要的原则&#xff0c;它强调了在可能的情况下&#xff0c;应该优先依赖编译时检查&#xff08;静态检查&#xff09;而不是运行时检查。这样做的主要目的是提高程序的性能、安全性和可维护性。 编译时检查&#xff0c;即在…

深入理解 HTTP 请求头与请求体

在Web开发中&#xff0c;每一次HTTP请求都由请求行、请求头和请求体组成。虽然看似简单&#xff0c;但它们在数据传输和接口设计中扮演着至关重要的角色。本文将通过简要分析&#xff0c;帮助你深入理解请求头与请求体的关系和应用。 请求头&#xff1a;请求的元信息 请求头是…

基于物联网设计的人工淡水湖养殖系统(华为云IOT)_253

文章目录 一、前言1.1 项目介绍【1】项目开发背景【2】设计实现的功能【3】项目硬件模块组成【4】设计意义【5】国内外研究现状【6】摘要1.2 设计思路1.3 系统功能总结1.4 开发工具的选择【1】设备端开发【2】上位机开发1.5 参考文献1.6 系统框架图1.7 系统原理图1.8 实物图1.9…