java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > java数组和集合

JAVA基础之数组和集合区别对比分析

作者:王依华

文章主要介绍了Java中数组和集合的基本概念、使用方法以及它们之间的区别,文章还探讨了不可变集合的创建方式及其线程安全和不可篡改的优势,感兴趣的朋友跟随小编一起看看吧

一、数组:固定长度的连续存储容器

数组是 Java 中最基础的数据结构之一,适用于存储固定长度、相同数据类型的元素,在内存中占据连续空间,支持通过索引快速访问。

1.1 数组的核心特性

1.2 数组的声明与初始化

数组的初始化分为 “动态初始化”(先指定长度,后赋值)和 “静态初始化”(直接指定元素,长度由元素个数决定),两种方式不可同时使用。

// 1. 动态初始化:指定长度,元素为默认值
int[] arr1 = new int[3]; // 长度3,元素默认值为0、0、0
arr1[0] = 10; // 手动赋值第一个元素
// 2. 静态初始化:指定元素,长度自动为3
int[] arr2 = new int[]{10, 20, 30}; 
// 简化写法(仅声明时可用)
int[] arr3 = {10, 20, 30}; 
// 3. 引用数据类型数组初始化
String[] strArr = new String[2]; // 默认值为[null, null]
strArr[0] = "Java"; // 赋值第一个元素为字符串对象

1.3 多维数组

多维数组本质是 “数组的数组”,最常用的是二维数组,适用于存储表格类数据(如矩阵)。

// 1. 规则二维数组(每行长度相同)
int[][] matrix1 = new int[2][3]; // 2行3列,元素默认值为0
matrix1[0][1] = 5; // 给第1行第2列元素赋值
// 2. 不规则二维数组(每行长度可不同)
int[][] matrix2 = new int[2][]; // 先指定行数,不指定列数
matrix2[0] = new int[3]; // 第1行长度为3
matrix2[1] = new int[2]; // 第2行长度为2
for (int i = 0; i < matrix2.length; i++) { // 遍历行
    for (int j = 0; j < matrix2[i].length; j++) { // 遍历每行的元素
        System.out.print(matrix2[i][j] + " ");
    }
    System.out.println();
}

1.4 Arrays 工具类

Java 提供java.util.Arrays类,封装了数组的常用操作(排序、查找、填充等),无需手动实现复杂逻辑。

方法名功能描述示例
sort(数组)对数组进行升序排序(基本类型用快速排序,引用类型用 TimSort)Arrays.sort(arr1);(排序 int 数组)
binarySearch(数组, 目标值)二分查找目标值在有序数组中的索引,未找到返回负数int index = Arrays.binarySearch(arr2, 20);
fill(数组, 填充值)将数组所有元素替换为指定填充值Arrays.fill(arr1, 5);(将 arr1 所有元素设为 5)
toString(数组)将数组转为字符串(如[10, 20, 30]),方便打印System.out.println(Arrays.toString(arr2));
copyOf(原数组, 新长度)复制原数组,新数组长度为指定值,超出部分用默认值填充int[] newArr = Arrays.copyOf(arr2, 5);(新数组长度 5,后 2 个元素为 0)

1.5 数组的使用场景与局限性

二、集合:动态长度的灵活存储框架

集合是 Java 为解决数组局限性设计的动态数据结构,支持自动扩容、内置增删改查方法,且仅存储引用数据类型(基本类型需通过包装类存储,如int对应Integer)。

集合框架体系
├─ Collection(单列集合:存储单个元素)
│  ├─ List(有序、可重复、有索引)
│  │  ├─ ArrayList(底层数组,查询快、增删慢)
│  │  └─ LinkedList(底层双向链表,增删快、查询慢)
│  └─ Set(无序、不可重复、无索引)
│     ├─ HashSet(底层哈希表,增删查快,无序)
│     └─ TreeSet(底层红黑树,自动排序,有序)
└─ Map(双列集合:存储键值对Key-Value)
   ├─ HashMap(底层哈希表,无序、键唯一,允许null键/值)
   └─ TreeMap(底层红黑树,按键排序,有序,不允许null键)

2.1 Collection 接口:单列集合的顶层规范

Collection是所有单列集合的父接口,定义了单列集合的通用方法,所有实现类(如ArrayListHashSet)都需遵守这些规范。

2.1.1 Collection 的通用方法

方法名功能描述示例代码
boolean add(E e)向集合添加元素,成功返回true,失败(如 Set 重复)返回falseList<String> list = new ArrayList<>(); list.add("Java");
boolean remove(Object o)删除集合中指定元素,成功返回true,无此元素返回falselist.remove("Java");
boolean removeIf(Predicate filter)按条件删除元素(Java 8+),过滤逻辑由Predicate接口实现list.removeIf(s -> s.length() > 5);(删除长度 > 5 的元素)
void clear()清空集合中所有元素,集合变为空(长度 0)list.clear();
boolean contains(Object o)判断集合是否包含指定元素,包含返回trueboolean hasJava = list.contains("Java");
boolean isEmpty()判断集合是否为空(长度 0),空返回trueboolean isEmpty = list.isEmpty();
int size()返回集合中元素的个数(长度)int count = list.size();
Object[] toArray()将集合转为数组,方便兼容数组操作Object[] arr = list.toArray();

2.1.2 Collection 的遍历方式

遍历是集合的核心操作,Collection提供 3 种常用遍历方式,适用于不同场景:

方式 1:迭代器(Iterator)—— 支持遍历中删除元素

迭代器是Collection的内置遍历工具,通过iterator()方法获取,支持在遍历过程中安全删除元素(避免ConcurrentModificationException异常)。

List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("cherry");
// 1. 获取迭代器对象
Iterator<String> iterator = list.iterator();
// 2. 遍历:hasNext()判断是否有下一个元素,next()获取元素并移动指针
while (iterator.hasNext()) {
    String element = iterator.next();
    if ("banana".equals(element)) {
        // 迭代器的remove()方法:删除当前遍历到的元素
        iterator.remove(); 
    }
    System.out.println(element); // 输出apple、banana、cherry(删除后集合中无banana)
}
方式 2:增强 for 循环(for-each)—— 简洁的遍历

增强 for 循环是 Java 5 引入的简化语法,底层基于迭代器实现,适用于 “仅遍历,不修改集合结构” 的场景,代码简洁易读。

// 语法:for (元素类型 变量名 : 集合/数组)
for (String element : list) {
    System.out.println(element); // 输出apple、cherry(已删除banana)
}
方式 3:Lambda 表达式 + forEach(Java 8+)—— 函数式遍历

Java 8 为Collection新增forEach()方法,支持通过 Lambda 表达式传递遍历逻辑,代码更简洁,适合函数式编程风格。

// 语法:collection.forEach(元素 -> 遍历逻辑)
list.forEach(element -> {
    if (element.startsWith("a")) { // 筛选以"a"开头的元素
        System.out.println(element); // 输出apple
    }
});

2.1.3 List 接口:有序可重复的单列集合

ListCollection的子接口,特点是有序(存储与取出顺序一致)、可重复(允许元素值相同)、有索引(支持通过索引访问元素),适用于需要 “按顺序存储、可通过位置操作” 的场景(如购物车、任务列表)。

2.1.3.1 List 的核心特性
2.1.3.2 ArrayList实现类:数组实现的高效查询集合

ArrayList底层基于动态数组实现,默认初始容量为 10,当元素个数超过阈值(容量 × 负载因子 0.75)时,会自动扩容为原容量的 1.5 倍(如 10→15→22...),适合频繁查询、少量增删的场景。

(1)ArrayList 的构造方法

构造方法功能描述示例
ArrayList()创建默认初始容量为 10 的空集合List<String> list = new ArrayList<>();
ArrayList(int initialCapacity)创建指定初始容量的空集合(避免频繁扩容)List<String> list = new ArrayList<>(20);(初始容量 20)
ArrayList(Collection<? extends E> c)将其他 Collection 集合转为 ArrayListSet<String> set = new HashSet<>(); List<String> list = new ArrayList<>(set);

(2)ArrayList 的核心方法

索引相关操作

List<String> list = new ArrayList<>();
list.add("apple"); // 末尾添加元素
list.add(1, "banana"); // 索引1处插入元素(原元素后移)
String first = list.get(0); // 获取索引0的元素(apple)
list.set(0, "orange"); // 修改索引0的元素为orange
String removed = list.remove(1); // 删除索引1的元素(banana),返回被删除元素

(3)ArrayList 的性能分析

2.1.3.3 LinkedList实现类:链表实现的高效增删集合

LinkedList底层基于双向链表实现,每个元素(节点)包含 “前驱节点引用、自身值、后继节点引用”,无需连续内存空间,适合频繁增删、少量查询的场景(如队列、栈)。

(1)LinkedList 的特有方法(操作首尾元素)

由于链表结构的特性,LinkedList提供了直接操作首尾元素的方法,时间复杂度均为O(1)

LinkedList<String> list = new LinkedList<>();
list.addFirst("a"); // 链表开头添加元素
list.addLast("b"); // 链表末尾添加元素
String first = list.getFirst(); // 获取开头元素(a)
String last = list.getLast(); // 获取末尾元素(b)
String removedFirst = list.removeFirst(); // 删除并返回开头元素(a)
String removedLast = list.removeLast(); // 删除并返回末尾元素(b)

(2)LinkedList 的性能分析

2.1.3.4 ArrayList 与 LinkedList 的对比选择
对比维度ArrayListLinkedList
底层结构动态数组双向链表
查询效率高(O (1))低(O (n))
首尾增删低(O (n),需扩容 / 移动元素)高(O (1))
中间增删低(O (n),需移动元素)中(O (n),需遍历找节点)
内存占用连续内存,可能有空闲空间(扩容预留)非连续内存,每个节点需存储前后引用,内存开销略大
适用场景频繁查询、少量增删(如商品列表、数据展示)频繁首尾增删(如队列、栈)、少量查询

2.1.4 Set 接口:无序不可重复的单列集合

SetCollection的子接口,特点是无序(存储与取出顺序可能不一致)、不可重复(元素值唯一)、无索引,适用于需要 “去重” 的场景(如用户 ID 列表、不重复的标签)。

2.1.4.1 Set 的核心特性(补充 Collection)
2.1.4.2 HashSet:哈希表实现的高效去重集合

 HashSet 判断元素是否重复的过程如下:

HashSet底层基于哈希表(数组 + 链表 / 红黑树) 实现,是Set最常用的实现类,特点是增删查效率高、完全无序、支持 null 元素

(1)HashSet 的去重原理:hashCode () + equals ()

HashSet判断元素是否重复的核心是 “先比哈希值,再比内容”,需依赖元素的hashCode()equals()方法,具体流程如下:

  1. 调用新增元素的hashCode()方法,计算其哈希值,根据哈希值确定在哈希表中的 “桶位置”(数组索引)。
  2. 若该桶位置为空,直接将元素存入(无重复)。
  3. 若该桶位置不为空(哈希冲突),则调用元素的equals()方法,与桶中已有的元素逐一比较:
    • equals()返回true:元素重复,不存入。
    • equals()返回false:元素不重复,将元素存入桶中(JDK 8 + 中,若桶中元素超过 8 个,链表会转为红黑树,提升查询效率)。

因此,为了确保 HashSet 能够正确判断元素的唯一性,需要重写元素类的 hashCode() 和 equals() 方法。

(2)关键注意事项

(3)HashSet 的构造方法与常用方法

// 1. 构造方法
HashSet<String> set1 = new HashSet<>(); // 默认初始容量16,负载因子0.75
HashSet<String> set2 = new HashSet<>(20); // 指定初始容量
Set<String> temp = new ArrayList<>();
HashSet<String> set3 = new HashSet<>(temp); // 从其他Collection转换
// 2. 常用方法(与Collection一致,无特有方法)
set1.add("Java");
set1.add("Python");
set1.add("Java"); // 重复元素,add()返回false,集合中仅存1个"Java"
boolean hasPython = set1.contains("Python"); // true
set1.remove("Python"); // 删除元素,返回true
int size = set1.size(); // 1
2.1.4.3 TreeSet:红黑树实现的有序去重集合

TreeSet底层基于红黑树(一种自平衡二叉搜索树) 实现,特点是自动排序、元素不可重复、不支持 null 元素,适用于需要 “去重且排序” 的场景(如按价格排序的商品列表、按学号排序的学生列表)。

(1)TreeSet 的排序方式

TreeSet的排序依赖 “比较逻辑”,分为两种方式:

自然排序(默认):元素类需实现Comparable接口,并重写compareTo()方法,定义元素的排序规则。

// 自定义Student类,实现Comparable接口,按学号升序排序
class Student implements Comparable<Student> {
    private int id;
    private String name;
    // 构造方法、getter/setter省略
    @Override
    public int compareTo(Student other) {
        // 按id升序:当前id - 其他id,返回正数则当前元素在后,负数在前
        return this.id - other.id; 
    }
}
// 使用TreeSet存储Student,自动按id升序排序
TreeSet<Student> studentSet = new TreeSet<>();
studentSet.add(new Student(3, "Alice"));
studentSet.add(new Student(1, "Bob"));
studentSet.add(new Student(2, "Charlie"));
// 遍历输出:Bob(id=1)、Charlie(id=2)、Alice(id=3)
for (Student s : studentSet) {
    System.out.println(s.getName());
}

自定义排序:若元素类无法修改(如StringInteger),或需临时改变排序规则,可在创建TreeSet时传入Comparator接口实现类(或 Lambda 表达式)。

// 存储String,按字符串长度降序排序(自定义比较器)
TreeSet<String> strSet = new TreeSet<>((s1, s2) -> {
    // 按长度降序:s2长度 - s1长度
    return s2.length() - s1.length(); 
});
strSet.add("apple"); // 5个字符
strSet.add("banana"); // 6个字符
strSet.add("pear"); // 4个字符
// 遍历输出:banana(6)、apple(5)、pear(4)
for (String s : strSet) {
    System.out.println(s);
}

(2)TreeSet 的关键注意事项

2.1.4.4 HashSet 与 TreeSet 的对比选择
对比维度HashSetTreeSet
底层结构哈希表(数组 + 链表 / 红黑树)红黑树
排序特性完全无序有序(自然排序 / 自定义排序)
增删查效率高(O (1),无哈希冲突时)中(O (log n))
去重依据hashCode() + equals()compareTo() / compare()返回 0
null 支持支持 1 个 null 元素不支持 null 元素
适用场景仅需去重,无需排序(如用户 ID、标签)去重且需排序(如排序的商品价格、学号)

2.2 Map 接口:键值对存储的双列集合

Map是 Java 中专门用于存储键值对(Key-Value) 的双列集合,每个键(Key)对应唯一的值(Value),键不可重复,值可重复,适用于 “通过键快速查找值” 的场景(如用户信息表:Key 为用户 ID,Value 为用户对象)。

2.2.1 Map 的核心特性

2.2.2 Map 的通用方法

所有Map实现类(如HashMapTreeMap)都支持以下通用方法:

// 创建Map对象(以HashMap为例)
Map<String, Integer> scoreMap = new HashMap<>();
// 1. 添加/修改键值对:键存在则覆盖值,返回旧值;键不存在则添加,返回null
Integer oldScore = scoreMap.put("Alice", 95); // null(首次添加)
oldScore = scoreMap.put("Alice", 98); // 95(覆盖旧值,返回旧值)
scoreMap.put("Bob", 88);
// 2. 删除键值对:根据键删除,返回被删除的值;键不存在返回null
Integer removedScore = scoreMap.remove("Bob"); // 88
// 3. 判断存在性
boolean hasAlice = scoreMap.containsKey("Alice"); // true(判断键是否存在)
boolean has98 = scoreMap.containsValue(98); // true(判断值是否存在)
// 4. 获取值:根据键获取值,键不存在返回null
Integer aliceScore = scoreMap.get("Alice"); // 98
// 5. 清空与长度
scoreMap.clear(); // 清空所有键值对
int size = scoreMap.size(); // 0(清空后长度为0)
boolean isEmpty = scoreMap.isEmpty(); // true

2.2.3 Map 的遍历方式

Map的遍历需围绕 “键”“值”“键值对” 三种维度,共 3 种常用方式:

方式 1:遍历键集(keySet ())—— 通过键找值

先获取所有键的集合(keySet()),再遍历键,通过get(Key)获取对应的值,适合仅需键和值的场景。

Map<String, Integer> scoreMap = new HashMap<>();
scoreMap.put("Alice", 95);
scoreMap.put("Bob", 88);
// 1. 获取所有键的集合
Set<String> keys = scoreMap.keySet();
// 2. 遍历键,获取对应值
for (String key : keys) {
    Integer value = scoreMap.get(key);
    System.out.println(key + " : " + value); // Alice:95, Bob:88
}
方式 2:遍历键值对集(entrySet ())—— 直接遍历键值对

获取所有键值对的集合(entrySet()),每个元素是Map.Entry对象,可直接通过getKey()getValue()获取键和值,效率比方式 1 高(无需多次调用get(Key))。

// 1. 获取所有键值对的集合
Set<Map.Entry<String, Integer>> entrySet = scoreMap.entrySet();
// 2. 遍历键值对
for (Map.Entry<String, Integer> entry : entrySet) {
    String key = entry.getKey();
    Integer value = entry.getValue();
    System.out.println(key + " : " + value); // Alice:95, Bob:88
}
// Lambda简化遍历(Java 8+)
scoreMap.entrySet().forEach(entry -> {
    System.out.println(entry.getKey() + " : " + entry.getValue());
});
方式 3:遍历值集(values ())—— 仅遍历值

若仅需遍历值,无需键,可通过values()获取所有值的集合(Collection类型),直接遍历。

// 1. 获取所有值的集合
Collection<Integer> values = scoreMap.values();
// 2. 遍历值
for (Integer value : values) {
    System.out.println(value); // 95, 88
}

2.2.4 HashMap:哈希表实现的高效键值对集合

HashMapMap最常用的实现类,底层基于哈希表(数组 + 链表 / 红黑树) 实现,特点是无序、键唯一、支持 null 键和 null 值、增删查效率高,适用于大多数键值对存储场景。

(1)HashMap 的核心特性

(2)HashMap 的扩容机制

(3)HashMap 的构造方法

// 1. 默认构造:初始容量16,负载因子0.75
Map<String, Integer> map1 = new HashMap<>();
// 2. 指定初始容量:负载因子默认0.75
Map<String, Integer> map2 = new HashMap<>(32);
// 3. 指定初始容量和负载因子
Map<String, Integer> map3 = new HashMap<>(32, 0.8f);
// 4. 从其他Map转换
Map<String, Integer> tempMap = new HashMap<>();
tempMap.put("a", 1);
Map<String, Integer> map4 = new HashMap<>(tempMap);

2.2.5 TreeMap:红黑树实现的有序键值对集合

TreeMap底层基于红黑树实现,特点是按键排序、键唯一、不支持 null 键、有序,适用于需要 “按键排序” 的键值对场景(如按日期排序的日志记录:Key 为日期,Value 为日志内容)。

(1)TreeMap 的排序方式

TreeSet类似,TreeMap的排序依赖键的比较逻辑,分为两种方式:

自然排序:键的类需实现Comparable接口,并重写compareTo()方法,按键的自然顺序排序。

// 键为Integer(已实现Comparable),按键升序排序
Map<Integer, String> treeMap1 = new TreeMap<>();
treeMap1.put(3, "C");
treeMap1.put(1, "A");
treeMap1.put(2, "B");
// 遍历输出:1:A, 2:B, 3:C(按键升序)
for (Map.Entry<Integer, String> entry : treeMap1.entrySet()) {
    System.out.println(entry.getKey() + ":" + entry.getValue());
}

自定义排序:创建TreeMap时传入Comparator接口实现类,自定义键的排序规则。

// 键为String,按字符串长度降序排序
Map<String, Integer> treeMap2 = new TreeMap<>((k1, k2) -> {
    return k2.length() - k1.length(); // 按键长度降序
});
treeMap2.put("apple", 5);
treeMap2.put("banana", 6);
treeMap2.put("pear", 4);
// 遍历输出:banana:6, apple:5, pear:4(按键长度降序)
for (Map.Entry<String, Integer> entry : treeMap2.entrySet()) {
    System.out.println(entry.getKey() + ":" + entry.getValue());
}

(2)TreeMap 的特有方法(排序相关)

由于TreeMap按键有序,提供了一些基于键排序的特有方法:

Map<Integer, String> treeMap = new TreeMap<>();
treeMap.put(1, "A");
treeMap.put(2, "B");
treeMap.put(3, "C");
treeMap.put(4, "D");
Integer firstKey = treeMap.firstKey(); // 获取最小键(1)
Integer lastKey = treeMap.lastKey(); // 获取最大键(4)
Integer lowerKey = treeMap.lowerKey(3); // 获取小于3的最大键(2)
Integer higherKey = treeMap.higherKey(3); // 获取大于3的最小键(4)
// 获取键≥2且<4的子Map(包含2,不包含4)
Map<Integer, String> subMap = treeMap.subMap(2, true, 4, false);
// 子Map内容:2:B, 3:C

2.2.6 HashMap 与 TreeMap 的对比选择

对比维度HashMapTreeMap
底层结构哈希表(数组 + 链表 / 红黑树)红黑树
排序特性无序(按哈希值存储)有序(按键的自然 / 自定义顺序)
增删查效率高(O (1),无哈希冲突时)中(O (log n))
键的 null 支持允许 1 个 null 键不允许 null 键(会抛空指针)
线程安全不安全不安全
适用场景无需排序,需高效增删查(如用户信息、配置映射)需按键排序(如按日期的日志、按价格的商品映射)

三、不可变集合:线程安全的常量容器

不可变集合是创建后无法修改的集合(添加、删除、修改元素会抛出UnsupportedOperationException),具有线程安全、不可篡改的特性,适用于存储常量数据(如配置参数、固定枚举值)或作为公共 API 的返回值(避免外部修改)。

3.1 不可变集合的创建方式

Java 提供 4 种创建不可变集合的方式,各有适用场景:

方式 1:Collections.unmodifiableXXX () —— 不可变视图(浅拷贝)

通过Collections工具类的unmodifiableList()unmodifiableSet()unmodifiableMap()方法,从现有可变集合创建 “不可变视图”。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class UnmodifiableExample {
    public static void main(String[] args) {
        // 1. 创建可变集合
        List<String> mutableList = new ArrayList<>();
        mutableList.add("apple");
        // 2. 创建不可变视图
        List<String> immutableList = Collections.unmodifiableList(mutableList);
        // 3. 视图修改会抛异常
        // immutableList.add("banana"); // UnsupportedOperationException
        // 4. 原集合修改,视图会同步变化(浅拷贝特性)
        mutableList.add("banana");
        System.out.println(immutableList); // 输出:[apple, banana]
    }
}

方式 2:List/Set/Map.of () —— 直接创建不可变集合(深拷贝)

Java 9 + 中,ListSetMap接口新增of()静态方法,可直接传入元素创建不可变集合,元素不可修改,且不依赖原集合(深拷贝)。

import java.util.List;
import java.util.Map;
import java.util.Set;
public class ImmutableOfExample {
    public static void main(String[] args) {
        // 1. 创建不可变List
        List<String> immutableList = List.of("apple", "banana", "cherry");
        // 2. 创建不可变Set(元素不可重复)
        Set<Integer> immutableSet = Set.of(1, 2, 3);
        // 3. 创建不可变Map(键不可重复,最多10个键值对)
        Map<String, Integer> immutableMap = Map.of(
            "apple", 1,
            "banana", 2,
            "cherry", 3
        );
        // 修改操作均抛异常
        // immutableList.add("date"); // UnsupportedOperationException
        // immutableSet.remove(1); // UnsupportedOperationException
        // immutableMap.put("pear", 4); // UnsupportedOperationException
    }
}

方式 3:Collectors.toUnmodifiableXXX () —— Stream 流收集(Java 10+)

Java 10 + 中,Collectors工具类新增toUnmodifiableList()toUnmodifiableSet()toUnmodifiableMap()方法,可将Stream流的结果收集为不可变集合。

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class ImmutableCollectorExample {
    public static void main(String[] args) {
        // 将Stream流中的字符串转为大写,收集为不可变List
        List<String> upperList = Stream.of("apple", "banana", "cherry")
            .map(String::toUpperCase)
            .collect(Collectors.toUnmodifiableList());
        // 修改抛异常
        // upperList.add("DATE"); // UnsupportedOperationException
        System.out.println(upperList); // 输出:[APPLE, BANANA, CHERRY]
    }
}

方式 4:第三方库(如 Guava)—— 更灵活的不可变集合

Google 的 Guava 库提供了更强大的不可变集合实现(如ImmutableListImmutableMap),支持 Builder 模式,可创建任意长度的不可变集合,且性能优于 JDK 原生方式。

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>32.1.3-jre</version>
</dependency>
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
public class GuavaImmutableExample {
    public static void main(String[] args) {
        // 1. 用of()创建不可变List
        ImmutableList<String> list = ImmutableList.of("a", "b", "c");
        // 2. 用Builder创建不可变Map(支持任意长度)
        ImmutableMap<String, Integer> map = ImmutableMap.<String, Integer>builder()
            .put("a", 1)
            .put("b", 2)
            .put("c", 3)
            .build();
        // 修改抛异常
        // list.add("d"); // UnsupportedOperationException
    }
}

3.2 不可变集合的核心优势与适用场景

四、数组与集合的对比总结

对比维度数组集合
长度特性固定长度,创建后不可修改动态长度,支持自动扩容
元素类型支持基本类型和引用类型仅支持引用类型(基本类型需用包装类)
内存存储连续内存空间非连续(如 LinkedList、HashSet)或部分连续(如 ArrayList)
核心方法无内置方法,需手动实现(或用 Arrays 工具类)内置增删改查方法(add、remove、contains 等)
遍历方式普通 for 循环、增强 for 循环增强 for 循环、迭代器、forEach(Lambda)
线程安全本身无线程安全特性,需手动同步大部分集合(ArrayList、HashMap)不安全,需用 Concurrent 系列或不可变集合
适用场景固定长度、频繁查询(如数组下标访问)动态长度、需频繁增删(如购物车、用户列表)

五、知识扩展

5.1 哈希表的工作原理(HashMap/HashSet 底层)

哈希表(Hash Table)是 “数组 + 链表 / 红黑树” 的组合结构,核心是通过哈希函数将键映射到数组的指定位置(桶),实现高效的增删查:

  1. 哈希函数:通过键的hashCode()计算哈希值,再通过 “哈希值 & (数组长度 - 1)”(等价于取模,效率更高)确定桶位置(数组索引)。
  2. 哈希冲突:不同键计算出相同桶位置的情况,解决方案:
    • 链表法:将同一桶中的元素连成链表,查询时遍历链表。
    • 红黑树法:JDK 8 + 中,当链表长度超过 8 且数组长度≥64 时,链表转为红黑树,将查询时间复杂度从 O (n) 降至 O (log n)。
  3. 负载因子:控制哈希表的 “满度”,默认 0.75,平衡空间与时间效率:负载因子过高会增加哈希冲突,降低查询效率;过低会浪费内存空间。

5.2 红黑树的特性(TreeMap/TreeSet 底层)

红黑树是一种自平衡的二叉搜索树,通过以下规则保证平衡,确保增删查时间复杂度为 O (log n):

  1. 每个节点要么是红色,要么是黑色。
  2. 根节点是黑色。
  3. 所有叶子节点(NIL 节点)是黑色。
  4. 若一个节点是红色,其两个子节点必须是黑色(无连续红色节点)。
  5. 从任意节点到其所有叶子节点的路径中,黑色节点的数量相同(黑高一致)。

5.3 自动装箱与拆箱(集合存储基本类型的原理)

集合仅支持引用类型,存储基本类型时需通过 “自动装箱”(基本类型→包装类)和 “自动拆箱”(包装类→基本类型)实现,本质是编译器的语法糖:

5.4 线程安全的集合类

默认的集合类(如 ArrayList、HashMap、HashSet)均为线程不安全,多线程环境下需使用线程安全的集合,常用选择:

  1. Concurrent 系列:JDK 提供的高效线程安全集合,如ConcurrentHashMap(HashMap 的线程安全版)、CopyOnWriteArrayList(ArrayList 的线程安全版),通过分段锁、写时复制等机制实现安全,性能优于同步集合。
  2. 同步集合:通过Collections.synchronizedXXX()创建,如Collections.synchronizedList(new ArrayList<>()),底层用synchronized关键字加锁,性能较低(全表锁),适合并发量小的场景。
  3. 不可变集合:如List.of()ImmutableList,本身不可修改,天然线程安全,适合存储常量数据。

到此这篇关于JAVA基础之数组和集合区别对比分析的文章就介绍到这了,更多相关java数组和集合内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文