当前位置: 代码网 > it编程>编程语言>Java > JAVA基础之数组和集合区别对比分析

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

2025年11月02日 Java 我要评论
一、数组:固定长度的连续存储容器数组是 java 中最基础的数据结构之一,适用于存储固定长度、相同数据类型的元素,在内存中占据连续空间,支持通过索引快速访问。1.1 数组的核心特性长度不可变:数组在创

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

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

1.1 数组的核心特性

  • 长度不可变:数组在创建时必须显式指定长度(如int[] arr = new int[5]),一旦创建,长度无法动态修改。若需增减元素,需手动创建新数组并复制原元素。
  • 元素类型统一:数组中所有元素必须是同一数据类型,可分为两类:
    • 基本数据类型数组:存储具体数值(如int[]double[]),默认值为对应基本类型的零值(int为 0,double为 0.0,char'\u0000')。
    • 引用数据类型数组:存储对象的引用(如string[]student[]),默认值为null
  • 连续内存存储:数组元素在内存中按顺序连续排列,索引(从 0 开始)直接对应元素在内存中的偏移量,因此访问任意元素的时间复杂度为o(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 数组的使用场景与局限性

  • 适用场景
    • 存储长度固定、需频繁查询的数据。
    • 底层实现其他数据结构。
  • 局限性
    • 长度固定,无法动态增减元素,增删操作需手动处理数组复制,效率低(时间复杂度 o (n))。
    • 仅支持索引访问,无内置的增删改查方法,需手动实现(如判断元素是否存在、删除指定元素)。
    • 无法直接存储不同数据类型的元素(如需存储多种类型,需用object[],但会丢失类型安全性)。 

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

集合是 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 的核心特性
  • 索引支持:可通过索引(0 开始)访问、修改、删除元素(如get(0)获取第一个元素,set(1, "new")修改第二个元素)。
  • 元素可重复:允许添加多个值相同的元素(如list.add("java"); list.add("java"),集合中会存在两个 "java")。
  • 有序性:元素的存储顺序与取出顺序完全一致(如按a、b、c的顺序添加,遍历也会按a、b、c返回)。
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 的性能分析

  • 查询效率:通过索引直接访问元素,时间复杂度o(1),效率极高。
  • 增删效率
    • 末尾增删:直接操作数组末尾,时间复杂度o(1)
    • 中间增删:需移动后续元素(如在索引 1 插入元素,需移动索引 1 及之后的所有元素),时间复杂度o(n),效率低。
  • 扩容机制:扩容时需创建新数组并复制原元素,频繁扩容会消耗性能,因此建议提前估算元素个数,通过arraylist(int initialcapacity)指定初始容量。
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 的性能分析

  • 增删效率
    • 首尾增删:直接修改首尾节点的引用,时间复杂度o(1),效率极高。
    • 中间增删:需先通过遍历找到目标节点(时间复杂度 o (n)),再修改节点引用(o (1)),整体效率低于首尾操作。
  • 查询效率:无索引,查询指定元素需从链表头 / 尾开始遍历,时间复杂度o(n),效率低。
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)
  • 元素不可重复:添加重复元素时,add()方法返回false,集合不会存储重复值(去重逻辑由具体实现类决定)。
  • 无索引:不支持通过索引访问元素,因此无法使用普通for循环遍历,只能用迭代器、增强forforeach
  • 无序性
    • hashset:完全无序,元素存储顺序与添加顺序无关。
    • treeset:有序,会按元素的 “自然顺序” 或 “自定义比较器顺序” 排序。
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)关键注意事项

  • 若自定义类(如studentbook)的对象要存入hashset必须重写hashcode()equals()方法,否则会默认使用object类的方法(hashcode()返回对象地址,equals()比较地址),导致无法正确去重。
  • 重写规则:
    • 若两个对象equals()返回true,则它们的hashcode()必须相等。
    • 若两个对象hashcode()不相等,则equals()必须返回false(减少哈希冲突)。

(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 的关键注意事项

  • 排序逻辑决定去重:treeset判断元素是否重复的依据是 “compareto()compare()方法返回 0”,若返回 0,认为元素重复,不存入。
  • 不支持 null 元素:由于排序时无法比较null与其他元素,存入null会抛出nullpointerexception
  • 排序效率稳定:红黑树的增删查时间复杂度均为o(log n),适合需要排序且数据量较大的场景。
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 的核心特性

  • 键值对结构:每个元素是一个 “键值对”(java 中称为map.entry对象),键与值一一对应,通过键可唯一确定值。
  • 键唯一:同一个map中,键不能重复(重复添加会覆盖原键对应的值),值可以重复。
  • 无索引:不支持通过索引访问元素,需通过键或键值对遍历。
  • 引用类型存储:键和值都必须是引用数据类型(基本类型需用包装类,如int对应integer)。

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 的核心特性

  • 无序性:键值对的存储顺序与添加顺序无关,遍历顺序不固定。
  • null 支持:允许 1 个 null 键,允许多个 null 值(如map.put(null, 10); map.put(null, 20);会覆盖为 null 键对应的值 20)。
  • 线程不安全:多线程环境下,若同时修改hashmap(如添加 / 删除元素),可能导致数据不一致或抛出concurrentmodificationexception,若需线程安全,可使用concurrenthashmap(推荐)或collections.synchronizedmap(new hashmap<>())

(2)hashmap 的扩容机制

  • 初始容量:默认初始容量为 16(数组长度),可通过构造方法指定(如new hashmap<>(32))。
  • 负载因子:默认值为 0.75,表示当键值对数量超过 “容量 × 负载因子”(如 16×0.75=12)时,触发扩容。
  • 扩容规则:扩容时将数组长度扩大为原容量的 2 倍(如 16→32→64...),并重新计算所有键的哈希值,将键值对迁移到新数组中(“重哈希”),频繁扩容会消耗性能,建议提前估算数据量,指定合适的初始容量。

(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()静态方法,可直接传入元素创建不可变集合,元素不可修改,且不依赖原集合(深拷贝)。

  • 特点
    • 元素不可重复(set.of()map.of()),重复会抛illegalargumentexception
    • map.of()最多支持 10 个键值对,超过需用map.ofentries()
  • 示例
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 原生方式。

  • 依赖(maven)
<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 不可变集合的核心优势与适用场景

  • 核心优势
    • 线程安全:无需同步锁,多线程可安全共享,避免并发修改问题。
    • 不可篡改:数据创建后无法修改,保证数据一致性(如配置参数不被意外修改)。
    • 性能优化:不可变集合无需预留扩容空间,内存占用更小,部分操作(如哈希值)可提前计算,提升效率。
  • 适用场景
    • 存储固定不变的数据(如系统配置、枚举列表、常量字典)。
    • 作为方法返回值(避免外部调用者修改集合内容,保证 api 安全性)。
    • 多线程环境下共享数据(无需额外同步,简化代码)。

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

对比维度数组集合
长度特性固定长度,创建后不可修改动态长度,支持自动扩容
元素类型支持基本类型和引用类型仅支持引用类型(基本类型需用包装类)
内存存储连续内存空间非连续(如 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 自动装箱与拆箱(集合存储基本类型的原理)

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

  • 自动装箱:如list.add(10),编译器自动转为list.add(integer.valueof(10)),将int转为integer
  • 自动拆箱:如int num = list.get(0),编译器自动转为int num = list.get(0).intvalue(),将integer转为int
  • 注意事项:避免在循环中频繁装箱(如for (int i=0; i<1000; i++) list.add(i)),会创建大量临时integer对象,建议提前手动装箱或使用基本类型集合(如 eclipse collections、fastutil)。

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数组和集合内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com