java容器

List

在 Java 中,List 是一个非常重要的容器接口,它继承自 Collection 接口,用于存储有序、可重复的元素集合。以下将从基本概念、常用实现类、基本操作、遍历方式以及性能特点等方面详细介绍 Java 的 List 容器。

基本概念

List 接口代表一个有序的元素序列,用户可以精确控制每个元素的插入位置,并且可以通过索引访问元素。List 允许存储重复的元素,这与 Set 接口不同,Set 不允许存储重复元素。

常用实现类

Java 提供了多个实现 List 接口的类,常见的有 ArrayListLinkedList 和 Vector

1. ArrayList
  • 特点:基于动态数组实现,支持随机访问,通过索引访问元素的效率很高,时间复杂度为 \(O(1)\)。但在插入和删除元素时,尤其是在列表中间或开头进行操作,需要移动大量元素,效率较低,时间复杂度为 \(O(n)\)。
  • 适用场景:适用于需要频繁随机访问元素,而插入和删除操作较少的场景。
2. LinkedList
  • 特点:基于双向链表实现,插入和删除元素的效率较高,尤其是在列表的开头或结尾进行操作,时间复杂度为 \(O(1)\)。但随机访问元素的效率较低,需要从头或尾开始遍历链表,时间复杂度为 \(O(n)\)。
  • 适用场景:适用于需要频繁进行插入和删除操作,而随机访问操作较少的场景。
3. Vector
  • 特点:与 ArrayList 类似,也是基于动态数组实现,但它是线程安全的。由于线程安全机制带来了额外的开销,所以在单线程环境下性能不如 ArrayList
  • 适用场景:适用于多线程环境下需要保证线程安全的场景。

Vector

在 Java 中,Vector 是一个动态数组类,位于 java.util 包下。它实现了 List 接口,是线程安全的,这意味着多个线程可以同时访问和修改 Vector 对象,而不会出现数据不一致的问题。不过,由于其线程安全机制带来了额外的开销,在单线程环境下,通常更推荐使用 ArrayList。以下是关于 Vector 的详细使用方法:

1. 导入 Vector 类

要使用 Vector 类,首先需要在代码中导入它:

import java.util.Vector;

2. 创建 Vector 对象

可以使用以下几种方式创建 Vector 对象:

// 创建一个默认初始容量为 10 的空 Vector
Vector vector1 = new Vector<>();

// 创建一个指定初始容量的空 Vector
Vector vector2 = new Vector<>(20);

// 创建一个包含指定集合元素的 Vector
import java.util.ArrayList;
import java.util.List;
List list = new ArrayList<>();
list.add(1.1);
list.add(2.2);
Vector vector3 = new Vector<>(list);
  • 复制而非引用:这里是将 list 中的元素复制到 vector3 中,而不是将 list 的引用赋给 vector3。也就是说,list 和 vector3 是两个独立的对象,对其中一个对象的修改不会影响另一个对象。例如,后续对 list 进行添加、删除元素等操作,不会改变 vector3 中的元素;反之亦然。
  • 泛型类型匹配list 和 vector3 的泛型类型必须兼容。在这个例子中,它们都是 Double 类型,所以可以正常复制元素。如果类型不匹配,会导致编译错误。

3. 添加元素

可以使用 add 方法向 Vector 中添加元素:

Vector vector = new Vector<>();
// 在末尾添加元素
vector.add("apple");
vector.add("banana");
// 在指定位置插入元素
vector.add(1, "cherry"); 
  1. 首先执行 vector.add("apple");,此时 Vector 中有一个元素 ["apple"]
  2. 接着执行 vector.add("banana");Vector 变为 ["apple", "banana"]
  3. 最后执行 vector.add(1, "cherry");,这会将 "cherry" 插入到索引为 1 的位置,原来索引 1 及之后的元素会依次后移。所以最终 Vector 中的元素顺序是 ["apple", "cherry", "banana"]
  4. 元素在 Vector 中的存储顺序是由添加元素的顺序决定的
  5. 调用 Collections.sort() 方法Collections.sort(vector); 会对 vector 中的元素进行排序。当比较两个字符串时,会从两个字符串的第一个字符开始,依次比较对应位置字符的 Unicode 编码值。                                                                                                如果对应位置字符的编码值不同:编码值小的字符所在的字符串排在前面。例如,比较 "apple" 和 "banana",先比较第一个字符 'a'(编码值 97)和 'b'(编码值 98),因为 97 < 98,所以 "apple" 排在 "banana" 前面。                                                      如果对应位置字符的编码值相同:则继续比较下一个位置的字符,直到找到不同的字符或者其中一个字符串结束。例如,比较 "apple" 和 "app",前三个字符都相同,当比较到第四个字符时,"apple" 有字符 'l',而 "app" 已经结束,此时较短的字符串 "app" 排在前面。

4. 访问元素

可以使用 get 方法根据索引访问 Vector 中的元素:

Vector vector = new Vector<>();
vector.add("apple");
vector.add("banana");
// 获取索引为 0 的元素
String element = vector.get(0); 
System.out.println(element); 

5. 修改元素

可以使用 set 方法修改 Vector 中指定索引位置的元素:

Vector vector = new Vector<>();
vector.add("apple");
vector.add("banana");
// 将索引为 1 的元素修改为 "cherry"
vector.set(1, "cherry"); 
System.out.println(vector.get(1)); 

6. 删除元素

可以使用 remove 方法删除 Vector 中的元素:

Vector vector = new Vector<>();
vector.add("apple");
vector.add("banana");
// 根据索引删除元素
vector.remove(0); 
// 根据元素值删除元素
vector.remove("banana"); 

当 Vector 内部有多个 "banana" 元素时,vector.remove("banana"); 只会移除该 Vector 中首次出现的 "banana" 元素。

  while (vector.remove("banana")) {
            // 循环继续,直到没有 "banana" 元素可移除
        }

7. 获取 Vector 的大小

可以使用 size 方法获取 Vector 中元素的数量:

Vector vector = new Vector<>();
vector.add("apple");
vector.add("banana");
int size = vector.size();
System.out.println("Vector 的大小为: " + size);

8. 遍历 Vector

可以使用多种方式遍历 Vector 中的元素:

使用 for 循环
Vector vector = new Vector<>();
vector.add("apple");
vector.add("banana");
for (int i = 0; i < vector.size(); i++) {
    System.out.println(vector.get(i));
}
使用增强 for 循环
Vector vector = new Vector<>();
vector.add("apple");
vector.add("banana");
for (String element : vector) {
    System.out.println(element);
}
使用迭代器
import java.util.Iterator;
Vector vector = new Vector<>();
vector.add("apple");
vector.add("banana");
Iterator iterator = vector.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

迭代器刚创建时并不指向 Vector 中的任何元素,而是处于 Vector 第一个元素之前的位置

Java迭代器(Iterator)是 Java 集合框架中的一种机制,是一种用于遍历集合(如列表、集合和映射等)的接口。

它提供了一种统一的方式来访问集合中的元素,而不需要了解底层集合的具体实现细节。

Java Iterator(迭代器)不是一个集合,它是一种用于访问集合的方法,可用于迭代 ArrayList 和 HashSet 等集合。

  • next() - 返回迭代器的下一个元素,并将迭代器的指针移到下一个位置。

  • hasNext() - 用于判断集合中是否还有下一个元素可以访问。

  • remove() - 从集合中删除迭代器最后访问的元素(可选操作)。

Iterator 的 remove() 方法必须在调用 next() 方法之后才能调用,它会移除 next() 方法返回的元素。如果在调用 remove() 之前没有调用 next(),或者在一次 next() 调用后多次调用 remove(),都会抛出 IllegalStateException 异常。

示例代码
import java.util.Iterator;
import java.util.Vector;

public class IteratorRemoveExample {
    public static void main(String[] args) {
        // 创建一个 Vector 并添加元素
        Vector vector = new Vector<>();
        vector.add("apple");
        vector.add("banana");
        vector.add("cherry");

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

        // 遍历 Vector 并移除元素
        while (iterator.hasNext()) {
            String element = iterator.next();
            if ("banana".equals(element)) {
                // 移除当前元素
                iterator.remove();
            }
        }

        // 输出移除元素后的 Vector
        System.out.println("移除元素后的 Vector: " + vector);
    }
}

9. 其他常用方法

  • isEmpty():判断 Vector 是否为空。
Vector vector = new Vector<>();
boolean isEmpty = vector.isEmpty();
System.out.println("Vector 是否为空: " + isEmpty);
  • contains():判断 Vector 是否包含指定元素
Vector vector = new Vector<>();
vector.add("apple");
boolean contains = vector.contains("apple");
System.out.println("Vector 是否包含 'apple': " + contains);
  • clear():清空 Vector 中的所有元素。
Vector vector = new Vector<>();
vector.add("apple");
vector.clear();
System.out.println("Vector 的大小为: " + vector.size());

示例代码总结

import java.util.Vector;

public class VectorExample {
    public static void main(String[] args) {
        // 创建一个 Vector
        Vector vector = new Vector<>();

        // 添加元素
        vector.add("apple");
        vector.add("banana");
        vector.add(1, "cherry");

        // 访问元素
        System.out.println("第一个元素: " + vector.get(0));

        // 修改元素
        vector.set(1, "date");
        System.out.println("修改后的第二个元素: " + vector.get(1));

        // 删除元素
        vector.remove(0);
        System.out.println("删除第一个元素后,Vector 的大小: " + vector.size());

        // 遍历元素
        System.out.println("遍历 Vector:");
        for (String element : vector) {
            System.out.println(element);
        }

        // 其他常用方法
        System.out.println("Vector 是否为空: " + vector.isEmpty());
        System.out.println("Vector 是否包含 'banana': " + vector.contains("banana"));
        vector.clear();
        System.out.println("清空后,Vector 的大小: " + vector.size());
    }
}

ArrayList

在 Java 中,ArrayList 是一个非常常用的容器类,它位于 java.util 包下,实现了 List 接口,是一种动态数组。以下将从多个方面详细介绍 ArrayList 容器。

1. 特点

  • 动态大小:与传统的数组不同,ArrayList 的大小是可以动态变化的。当添加元素时,如果内部数组容量不足,它会自动进行扩容。
  • 有序性ArrayList 会维护元素的插入顺序,即元素在 ArrayList 中的存储顺序与插入顺序一致。
  • 允许重复元素:可以在 ArrayList 中存储多个相同的元素。
  • 支持随机访问:可以通过索引快速访问 ArrayList 中的元素,时间复杂度为 \(O(1)\)。

2. 基本使用

2.1 导入类

要使用 ArrayList,首先需要在代码中导入该类:

import java.util.ArrayList;
2.2 创建 ArrayList 对象

可以使用以下几种方式创建 ArrayList 对象:

// 创建一个默认初始容量的空 ArrayList
ArrayList list1 = new ArrayList<>();

// 创建一个指定初始容量的空 ArrayList
ArrayList list2 = new ArrayList<>(20);

// 创建一个包含另一个集合元素的 ArrayList
import java.util.Arrays;
import java.util.List;
List collection = Arrays.asList(1.1, 2.2, 3.3);
ArrayList list3 = new ArrayList<>(collection);

Arrays.asList() 方法可以接受一个数组作为参数,并返回一个包含数组元素的 List

2.3 添加元素

可以使用 add 方法向 ArrayList 中添加元素:

ArrayList list = new ArrayList<>();
// 在末尾添加元素
list.add("apple");
list.add("banana");
// 在指定位置插入元素
list.add(1, "cherry"); 

"apple""cherry""banana"

2.4 访问元素

可以使用 get 方法根据索引访问 ArrayList 中的元素:

ArrayList list = new ArrayList<>();
list.add("apple");
list.add("banana");
// 获取索引为 0 的元素
String element = list.get(0); 
System.out.println(element); 
2.5 修改元素

可以使用 set 方法修改 ArrayList 中指定索引位置的元素:

ArrayList list = new ArrayList<>();
list.add("apple");
list.add("banana");
// 将索引为 1 的元素修改为 "cherry"
list.set(1, "cherry"); 
System.out.println(list.get(1)); 
2.6 删除元素

可以使用 remove 方法删除 ArrayList 中的元素:

ArrayList list = new ArrayList<>();
list.add("apple");
list.add("banana");
// 根据索引删除元素
list.remove(0); 
// 根据元素值删除元素
list.remove("banana"); 
2.7 获取 ArrayList 的大小

可以使用 size 方法获取 ArrayList 中元素的数量:

ArrayList list = new ArrayList<>();
list.add("apple");
list.add("banana");
int size = list.size();
System.out.println("ArrayList 的大小为: " + size);

3. 遍历 ArrayList

可以使用多种方式遍历 ArrayList 中的元素:

3.1 使用 for 循环
ArrayList list = new ArrayList<>();
list.add("apple");
list.add("banana");
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}
3.2 使用增强 for 循环
ArrayList list = new ArrayList<>();
list.add("apple");
list.add("banana");
for (String element : list) {
    System.out.println(element);
}
3.3 使用迭代器
import java.util.Iterator;
ArrayList list = new ArrayList<>();
list.add("apple");
list.add("banana");
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
    System.out.println(iterator.next());
}

4. 常用方法

  • isEmpty():判断 ArrayList 是否为空。
ArrayList list = new ArrayList<>();
boolean isEmpty = list.isEmpty();
System.out.println("ArrayList 是否为空: " + isEmpty);
  • contains():判断 ArrayList 是否包含指定元素。
ArrayList list = new ArrayList<>();
list.add("apple");
boolean contains = list.contains("apple");
System.out.println("ArrayList 是否包含 'apple': " + contains);
  • clear():清空 ArrayList 中的所有元素。
ArrayList list = new ArrayList<>();
list.add("apple");
list.clear();
System.out.println("ArrayList 的大小为: " + list.size());

5. 扩容机制

  ArrayList 内部使用数组来存储元素,当添加元素时,如果数组容量不足,会触发扩容操作。默认情况下,ArrayList 的初始容量为 10,当元素数量达到数组容量时,会创建一个新的数组,新数组的容量为原数组容量的 1.5 倍(oldCapacity + (oldCapacity >> 1)),然后将原数组中的元素复制到新数组中。

6. 性能分析

  • 插入和删除操作:在 ArrayList 的末尾插入或删除元素的时间复杂度为 \(O(1)\),但在中间或开头插入或删除元素时,需要移动后续元素,时间复杂度为 \(O(n)\)。
  • 随机访问操作:通过索引访问元素的时间复杂度为 \(O(1)\),这是 ArrayList 的一个优点。

7. 与 Vector 的比较

  • 线程安全性Vector 是线程安全的,而 ArrayList 是非线程安全的。在多线程环境下,如果需要保证线程安全,可以使用 Vector;在单线程环境下,建议使用 ArrayList,因为它的性能更高。
  • 扩容机制Vector 扩容时默认将容量翻倍,而 ArrayList 扩容时将容量增加为原来的 1.5 倍。

综上所述,ArrayList 是一个功能强大、使用方便的动态数组容器,适用于大多数需要存储和操作一组元素的场景。

LinkedList

LinkedList 是 Java 集合框架中 List 接口的一个实现类,同时也实现了 Deque 接口,它基于双向链表的数据结构来存储元素。以下从基本概念、特点、常用操作、性能分析、适用场景等方面详细介绍 LinkedList

基本概念

LinkedList 使用链表来存储元素,每个元素(节点)包含三部分:数据、指向前一个节点的引用和指向后一个节点的引用,这种结构被称为双向链表。这使得 LinkedList 在插入和删除元素时具有较高的效率。

特点

  1. 有序性LinkedList 会维护元素的插入顺序,即元素在 LinkedList 中的存储顺序与插入顺序一致。
  2. 允许重复元素:可以在 LinkedList 中存储多个相同的元素。
  3. 支持 null 元素LinkedList 允许存储 null 元素。
  4. 双向链表结构:每个节点都包含指向前一个节点和后一个节点的引用,这使得在链表的任意位置进行插入和删除操作都比较高效。
  5. 非线程安全:在多线程环境下,如果多个线程同时访问和修改 LinkedList,可能会导致数据不一致的问题。如果需要在多线程环境下使用,可以考虑使用 Collections.synchronizedList() 方法将其转换为线程安全的列表。

常用操作

1. 创建 LinkedList 对象
import java.util.LinkedList;

public class LinkedListExample {
    public static void main(String[] args) {
        // 创建一个空的 LinkedList
        LinkedList linkedList = new LinkedList<>();
    }
}
2. 添加元素
import java.util.LinkedList;

public class LinkedListAddExample {
    public static void main(String[] args) {
        LinkedList linkedList = new LinkedList<>();
        // 在末尾添加元素
        linkedList.add("apple");
        linkedList.add("banana");
        // 在指定位置插入元素
        linkedList.add(1, "cherry");
        // 在头部添加元素
        linkedList.addFirst("kiwi");
        // 在尾部添加元素
        linkedList.addLast("grape");
    }
}

"kiwi""apple""cherry""banana""grape"

3. 访问元素
import java.util.LinkedList;

public class LinkedListAccessExample {
    public static void main(String[] args) {
        LinkedList linkedList = new LinkedList<>();
        linkedList.add("apple");
        linkedList.add("banana");
        // 通过索引访问元素
        String element = linkedList.get(1);
        // 获取第一个元素
        String firstElement = linkedList.getFirst();
        // 获取最后一个元素
        String lastElement = linkedList.getLast();
    }
}
4. 修改元素
import java.util.LinkedList;

public class LinkedListModifyExample {
    public static void main(String[] args) {
        LinkedList linkedList = new LinkedList<>();
        linkedList.add("apple");
        linkedList.add("banana");
        // 修改指定索引位置的元素
        linkedList.set(1, "cherry");
    }
}

"apple""cherry"

5. 删除元素
import java.util.LinkedList;

public class LinkedListRemoveExample {
    public static void main(String[] args) {
        LinkedList linkedList = new LinkedList<>();
        linkedList.add("apple");
        linkedList.add("banana");
        // 根据索引删除元素
        linkedList.remove(0);
        // 删除第一个元素
        linkedList.removeFirst();
        // 删除最后一个元素
        linkedList.removeLast();
    }
}
6. 遍历元素
import java.util.LinkedList;
import java.util.Iterator;

public class LinkedListTraversalExample {
    public static void main(String[] args) {
        LinkedList linkedList = new LinkedList<>();
        linkedList.add("apple");
        linkedList.add("banana");

        // 使用增强 for 循环遍历
        for (String element : linkedList) {
            System.out.println(element);
        }

        // 使用迭代器遍历
        Iterator iterator = linkedList.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

性能分析

  • 插入和删除操作:在链表的头部或尾部插入和删除元素的时间复杂度为 \(O(1)\),因为只需要修改相邻节点的引用。在链表中间插入和删除元素的时间复杂度也为 \(O(1)\),但需要先定位到插入或删除的位置,定位操作的时间复杂度为 \(O(n)\)。
  • 随机访问操作:通过索引访问元素的时间复杂度为 \(O(n)\),因为需要从头或尾开始遍历链表,直到找到指定索引的元素。

适用场景

  • 频繁的插入和删除操作:由于 LinkedList 在插入和删除元素时具有较高的效率,因此适用于需要频繁进行插入和删除操作的场景,如实现栈、队列等数据结构。
  • 不频繁的随机访问操作:如果不需要频繁地通过索引访问元素,而是更注重插入和删除操作的性能,那么 LinkedList 是一个不错的选择。
特性 ArrayList LinkedList Vector
数据结构 动态数组 双向链表 动态数组
线程安全性 非线程安全 非线程安全 线程安全
随机访问效率 高 (\(O(1)\)) 低 (\(O(n)\)) 高 (\(O(1)\))
插入删除效率 尾部快 (\(O(1)\)),其他位置慢 (\(O(n)\)) 首尾快 (\(O(1)\)),中间定位慢 (\(O(n)\)) 尾部快 (\(O(1)\)),其他位置慢 (\(O(n)\))
内存占用 连续内存,可能有空闲 每个节点有额外引用,开销大 连续内存,可能有空闲

Set

在 Java 中,Set 是一个用于存储唯一元素的容器接口,它继承自 Collection 接口。Set 不允许包含重复的元素,也就是说,如果你尝试向 Set 中添加已经存在的元素,添加操作会失败。以下将从基本概念、常用实现类、基本操作、遍历方式等方面详细介绍 Java 的 Set 容器。

基本概念

Set 接口代表一个不包含重复元素的集合,它提供了添加、删除、检查元素是否存在等基本操作。由于 Set 是一个接口,不能直接实例化,需要使用它的具体实现类。

常用实现类

1. HashSet
  • 特点:基于哈希表实现,不保证元素的顺序,元素的存储顺序可能与插入顺序不同。HashSet 通过元素的 hashCode() 和 equals() 方法来判断元素是否重复,因此存储在 HashSet 中的元素需要正确重写这两个方法。
  • 性能:添加、删除和查找元素的时间复杂度为 \(O(1)\),性能较高。
  • 适用场景:适用于需要快速查找元素,且不关心元素顺序的场景。
2. TreeSet
  • 特点:基于红黑树实现,会根据元素的自然顺序或指定的比较器对元素进行排序。存储在 TreeSet 中的元素必须实现 Comparable 接口,或者在创建 TreeSet 时提供一个 Comparator 对象。
  • 性能:添加、删除和查找元素的时间复杂度为 \(O(log n)\)。
  • 适用场景:适用于需要对元素进行排序的场景。
3. LinkedHashSet
  • 特点:继承自 HashSet,基于哈希表和链表实现,它维护了一个插入顺序的链表,因此可以保证元素的插入顺序。
  • 性能:添加、删除和查找元素的时间复杂度为 \(O(1)\)。
  • 适用场景:适用于需要保证元素插入顺序,同时又需要快速查找元素的场景。

HashSet

HashSet 是 Java 集合框架中 Set 接口的一个重要实现类,位于 java.util 包下。它主要用于存储不重复的元素,并且不保证元素的存储顺序。以下从多个方面详细介绍 HashSet

1. 基本概念

HashSet 基于哈希表(实际上是 HashMap)实现,它使用哈希算法来存储和查找元素。当向 HashSet 中添加元素时,会根据元素的哈希码(通过 hashCode() 方法计算)来确定元素在哈希表中的存储位置。如果两个元素的哈希码相同,还会使用 equals() 方法来进一步判断它们是否相等,如果相等则认为是重复元素,不会重复存储。

2. 特点

  • 元素唯一性HashSet 不允许存储重复的元素。如果尝试添加已经存在于 HashSet 中的元素,添加操作将不会生效。
  • 无序性HashSet 不保证元素的存储顺序,元素的存储顺序可能与插入顺序不同,并且每次运行程序时元素的顺序也可能不同。
  • 允许存储 null 元素HashSet 允许存储一个 null 元素。
  • 非线程安全HashSet 不是线程安全的,如果在多线程环境下使用,可能会出现数据不一致的问题。若需要在多线程环境下使用,可以使用 Collections.synchronizedSet() 方法将其转换为线程安全的集合。

3. 常用构造方法

  • HashSet():创建一个空的 HashSet,初始容量为 16,负载因子为 0.75。
import java.util.HashSet;
import java.util.Set;

public class HashSetCreation {
    public static void main(String[] args) {
        Set set = new HashSet<>();
    }
}
 
  • HashSet(int initialCapacity):创建一个具有指定初始容量的空 HashSet,负载因子为 0.75。
import java.util.HashSet;
import java.util.Set;

public class HashSetWithInitialCapacity {
    public static void main(String[] args) {
        Set set = new HashSet<>(20);
    }
}
 
  • HashSet(int initialCapacity, float loadFactor):创建一个具有指定初始容量和负载因子的空 HashSet
  • 负载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度,它是一个浮点数。在 HashSet 中,内部使用 HashMap 来存储元素,负载因子决定了在哈希表的元素数量达到一定比例时,哈希表会进行扩容操作。
 
import java.util.HashSet;
import java.util.Set;

public class HashSetWithCapacityAndLoadFactor {
    public static void main(String[] args) {
        Set set = new HashSet<>(20, 0.8f);
    }
}
 
  • HashSet(Collection c):创建一个包含指定集合中所有元素的 HashSet
 
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class HashSetFromCollection {
    public static void main(String[] args) {
        List list = new ArrayList<>();
        list.add("apple");
        list.add("banana");
        Set set = new HashSet<>(list);
    }
}

4. 常用方法

  • add(E e):向 HashSet 中添加元素,如果元素已经存在,则添加失败并返回 false;否则添加成功并返回 true
 
import java.util.HashSet;
import java.util.Set;

public class HashSetAddExample {
    public static void main(String[] args) {
        Set set = new HashSet<>();
        boolean result1 = set.add("apple"); 
        boolean result2 = set.add("apple"); 
        System.out.println(result1); 
        System.out.println(result2); 
    }
}
 
  • remove(Object o):从 HashSet 中移除指定元素,如果元素存在则移除并返回 true;否则返回 false
 
import java.util.HashSet;
import java.util.Set;

public class HashSetRemoveExample {
    public static void main(String[] args) {
        Set set = new HashSet<>();
        set.add("apple");
        boolean result = set.remove("apple"); 
        System.out.println(result); 
    }
}
 
  • contains(Object o):检查 HashSet 中是否包含指定元素,如果包含则返回 true;否则返回 false
import java.util.HashSet;
import java.util.Set;

public class HashSetContainsExample {
    public static void main(String[] args) {
        Set set = new HashSet<>();
        set.add("apple");
        boolean result = set.contains("apple"); 
        System.out.println(result); 
    }
}
 
  • size():返回 HashSet 中元素的数量。
 
import java.util.HashSet;
import java.util.Set;

public class HashSetSizeExample {
    public static void main(String[] args) {
        Set set = new HashSet<>();
        set.add("apple");
        set.add("banana");
        int size = set.size(); 
        System.out.println(size); 
    }
}
 
  • isEmpty():检查 HashSet 是否为空,如果为空则返回 true;否则返回 false
 
import java.util.HashSet;
import java.util.Set;

public class HashSetIsEmptyExample {
    public static void main(String[] args) {
        Set set = new HashSet<>();
        boolean result = set.isEmpty(); 
        System.out.println(result); 
    }
}
 
  • clear():清空 HashSet 中的所有元素。
 
import java.util.HashSet;
import java.util.Set;

public class HashSetClearExample {
    public static void main(String[] args) {
        Set set = new HashSet<>();
        set.add("apple");
        set.clear(); 
        System.out.println(set.size()); 
    }
}

5. 遍历方式

 
  • 增强 for 循环
 
import java.util.HashSet;
import java.util.Set;

public class HashSetTraversalForEach {
    public static void main(String[] args) {
        Set set = new HashSet<>();
        set.add("apple");
        set.add("banana");
        for (String element : set) {
            System.out.println(element);
        }
    }
}
 
  • 迭代器
 
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

public class HashSetTraversalIterator {
    public static void main(String[] args) {
        Set set = new HashSet<>();
        set.add("apple");
        set.add("banana");
        Iterator iterator = set.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

6. 性能分析

  • 添加、删除和查找操作:平均时间复杂度为 \(O(1)\),因为哈希表的特性使得可以通过哈希码快速定位元素的存储位置。但在哈希冲突较多的情况下,性能可能会下降。
  • 遍历操作:时间复杂度为 \(O(n)\),其中 n 是 HashSet 中元素的数量。

7. 注意事项

  • 重写 hashCode() 和 equals() 方法:如果要将自定义对象存储在 HashSet 中,需要确保该对象正确重写了 hashCode() 和 equals() 方法,以保证元素的唯一性判断正确。
 
import java.util.HashSet;
import java.util.Set;

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && name.equals(person.name);
    }

    @Override
    public int hashCode() {
        int result = name.hashCode();
        result = 31 * result + age;
        return result;
    }
}

public class HashSetCustomObject {
    public static void main(String[] args) {
        Set set = new HashSet<>();
        Person p1 = new Person("Alice", 20);
        Person p2 = new Person("Alice", 20);
        set.add(p1);
        set.add(p2);
        System.out.println(set.size()); 
    }
}
  • hashCode() 方法:该方法是 Object 类中的一个实例方法,所有 Java 类都继承了这个方法。hashCode() 方法返回一个对象的哈希码,这是一个整数,用于在哈希表中快速定位对象。在 HashSet 中,hashCode() 方法用于确定元素应该存储在哈希表的哪个 “桶(bucket)” 中。
  • equals() 方法:同样是 Object 类中的实例方法,用于比较两个对象是否相等。默认情况下,equals() 方法比较的是两个对象的引用是否相等,即是否指向同一个内存地址。但在很多情况下,我们需要根据对象的内容来判断它们是否相等,因此需要重写 equals() 方法。

综上所述,HashSet 是一个高效的集合类,适用于需要存储不重复元素且对元素顺序没有要求的场景。

HashMap 

HashMap 是 Java 中非常常用的一个数据结构,它位于 java.util 包下,实现了 Map 接口,用于存储键值对(key - value)。以下从多个方面详细介绍 HashMap

1. 基本概念

HashMap 基于哈希表实现,它通过键的哈希码(hashCode)来确定键值对的存储位置,从而实现快速的查找、插入和删除操作。HashMap 不保证键值对的顺序,并且允许存储 null 键和 null 值,但 null 键只能有一个。

2. 数据结构

在 Java 8 之前,HashMap 采用数组 + 链表的结构。数组中的每个元素称为一个桶(bucket),当发生哈希冲突(即不同的键计算出相同的哈希码)时,会将冲突的键值对以链表的形式存储在同一个桶中。

从 Java 8 开始,为了提高在哈希冲突严重时的性能,HashMap 采用数组 + 链表 + 红黑树的结构。当链表长度达到一定阈值(默认为 8)且数组长度达到 64 时,链表会转换为红黑树;当红黑树的节点数减少到一定阈值(默认为 6)时,红黑树会转换回链表。

3. 常用构造方法

  • HashMap():创建一个默认初始容量为 16,负载因子为 0.75 的空 HashMap
import java.util.HashMap;
import java.util.Map;

public class HashMapCreation {
    public static void main(String[] args) {
        Map map = new HashMap<>();
    }
}
  • HashMap(int initialCapacity):创建一个具有指定初始容量,负载因子为 0.75 的空 HashMap
import java.util.HashMap;
import java.util.Map;

public class HashMapWithInitialCapacity {
    public static void main(String[] args) {
        Map map = new HashMap<>(32);
    }
}
  • HashMap(int initialCapacity, float loadFactor):创建一个具有指定初始容量和负载因子的空 HashMap
import java.util.HashMap;
import java.util.Map;

public class HashMapWithCapacityAndLoadFactor {
    public static void main(String[] args) {
        Map map = new HashMap<>(32, 0.8f);
    }
}
  • HashMap(Map m):创建一个包含指定 Map 中所有键值对的 HashMap
import java.util.HashMap;
import java.util.Map;

public class HashMapFromAnotherMap {
    public static void main(String[] args) {
        Map originalMap = new HashMap<>();
        originalMap.put("apple", 1);
        originalMap.put("banana", 2);
        Map newMap = new HashMap<>(originalMap);
    }
}

4. 常用方法

  • put(K key, V value):将指定的键值对插入到 HashMap 中。如果键已经存在,则会更新该键对应的值。
import java.util.HashMap;
import java.util.Map;

public class HashMapPutExample {
    public static void main(String[] args) {
        Map map = new HashMap<>();
        map.put("apple", 1);
        map.put("banana", 2);
        map.put("apple", 3); 
    }
}
  • get(Object key):根据指定的键获取对应的值。如果键不存在,则返回 null
import java.util.HashMap;
import java.util.Map;

public class HashMapGetExample {
    public static void main(String[] args) {
        Map map = new HashMap<>();
        map.put("apple", 1);
        Integer value = map.get("apple"); 
    }
}
  • remove(Object key):根据指定的键移除对应的键值对,并返回该键对应的值。如果键不存在,则返回 null
import java.util.HashMap;
import java.util.Map;

public class HashMapRemoveExample {
    public static void main(String[] args) {
        Map map = new HashMap<>();
        map.put("apple", 1);
        Integer removedValue = map.remove("apple"); 
    }
}
  • containsKey(Object key):检查 HashMap 中是否包含指定的键。如果包含则返回 true,否则返回 false
import java.util.HashMap;
import java.util.Map;

public class HashMapContainsKeyExample {
    public static void main(String[] args) {
        Map map = new HashMap<>();
        map.put("apple", 1);
        boolean containsKey = map.containsKey("apple"); 
    }
}
  • containsValue(Object value):检查 HashMap 中是否包含指定的值。如果包含则返回 true,否则返回 false
import java.util.HashMap;
import java.util.Map;

public class HashMapContainsValueExample {
    public static void main(String[] args) {
        Map map = new HashMap<>();
        map.put("apple", 1);
        boolean containsValue = map.containsValue(1); 
    }
}
  • size():返回 HashMap 中键值对的数量。
import java.util.HashMap;
import java.util.Map;

public class HashMapSizeExample {
    public static void main(String[] args) {
        Map map = new HashMap<>();
        map.put("apple", 1);
        map.put("banana", 2);
        int size = map.size(); 
    }
}
  • clear():清空 HashMap 中的所有键值对。
import java.util.HashMap;
import java.util.Map;

public class HashMapClearExample {
    public static void main(String[] args) {
        Map map = new HashMap<>();
        map.put("apple", 1);
        map.clear(); 
    }
}

5. 遍历方式

  • 遍历键集

    map.keySet()该方法返回一个包含 Map 中所有键的 Set 集合。

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

public class HashMapKeySetTraversal {
    public static void main(String[] args) {
        Map map = new HashMap<>();
        map.put("apple", 1);
        map.put("banana", 2);
        for (String key : map.keySet()) {
            System.out.println("Key: " + key + ", Value: " + map.get(key));
        }
    }
}
  • 遍历值集

 map.values()获取值的集合

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

public class HashMapValuesTraversal {
    public static void main(String[] args) {
        Map map = new HashMap<>();
        map.put("apple", 1);
        map.put("banana", 2);
        for (Integer value : map.values()) {
            System.out.println("Value: " + value);
        }
    }
}
  • 遍历键值对

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

public class HashMapEntrySetTraversal {
    public static void main(String[] args) {
        Map map = new HashMap<>();
        map.put("apple", 1);
        map.put("banana", 2);
        for (Map.Entry entry : map.entrySet()) {
            System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
        }
    }
}

1. map.entrySet() 方法

  • map 是一个 Map 类型的对象,例如 HashMapTreeMap 等实现了 Map 接口的类的实例。

  • entrySet() 是 Map 接口中定义的一个方法,它的作用是返回一个包含 Map 中所有键值对的 Set 集合。集合中的每个元素都是一个 Map.Entry 对象,Map.Entry 是 Map 接口的一个内部接口,它表示 Map 中的一个键值对。

2. Map.Entry

  • Map.Entry 是一个泛型接口,这里使用了泛型参数 ,表示 Map 中的键是 String 类型,值是 Integer 类型。

  • 通过指定泛型参数,可以确保在后续操作中使用正确的数据类型,避免类型转换错误。

3. 增强 for 循环

  • 增强 for 循环(也称为 foreach 循环)是 Java 5 引入的一种简化循环结构,用于遍历数组或集合。

  • 语法结构为:for (元素类型 元素变量 : 集合或数组) { 循环体 }

  • 在这段代码中,Map.Entry entry 定义了一个 Map.Entry 类型的变量 entry,用于存储 map.entrySet() 返回的集合中的每个元素(即每个键值对)。

4. entry.getKey() 和 entry.getValue() 方法

  • entry.getKey() 是 Map.Entry 接口中定义的方法,用于获取当前键值对的键。

  • entry.getValue() 也是 Map.Entry 接口中定义的方法,用于获取当前键值对的值。

6. 负载因子和扩容机制

  • 负载因子(Load Factor):是哈希表在其容量自动增加之前可以达到多满的一种尺度,默认值为 0.75。当 HashMap 中键值对的数量达到 “容量 * 负载因子” 时,会触发扩容操作。
  • 扩容机制:当需要扩容时,HashMap 会创建一个新的数组,其容量为原数组的两倍,然后将原数组中的所有键值对重新计算哈希码并插入到新数组中。

7. 线程安全性

HashMap 是非线程安全的,如果在多线程环境下使用,可能会出现数据不一致的问题。如果需要在多线程环境下使用,可以使用 ConcurrentHashMap 或者通过 Collections.synchronizedMap() 方法将 HashMap 转换为线程安全的 Map

8. 注意事项

  • 键的 hashCode() 和 equals() 方法:存储在 HashMap 中的键需要正确重写 hashCode() 和 equals() 方法,以确保键的唯一性和哈希表的正常工作。
  • 性能考虑:在使用 HashMap 时,需要根据实际情况选择合适的初始容量和负载因子,以平衡内存使用和性能。

综上所述,HashMap 是一个功能强大、使用广泛的数据结构,适用于需要快速查找、插入和删除键值对的场景。

Queue

队列(Queue)

基本概念

队列是一种遵循先进先出(FIFO,First-In-First-Out)原则的数据结构,即最先进入队列的元素最先被移除。

LinkedList 类LinkedList 是 Java 集合框架中的一个具体类,同样位于 java.util 包下。它实现了多个接口,其中就包括 Queue 接口,同时还实现了 ListDeque 等接口。这意味着 LinkedList 不仅可以作为链表使用,还能当作队列、双端队列来使用,因为它提供了这些接口所定义方法的具体实现

插入元素
  • add(E e)
    • 功能:将指定元素插入队列。如果队列已满或由于其他原因无法插入元素,会抛出 IllegalStateException 异常。
    • 返回值:如果插入成功,返回 true
    • 示例代码
 
import java.util.LinkedList;
import java.util.Queue;

public class QueueAddExample {
    public static void main(String[] args) {
        Queue queue = new LinkedList<>();
        boolean result = queue.add("apple");
        System.out.println("插入是否成功: " + result);
    }
}
 
  • offer(E e)
    • 功能:将指定元素插入队列。如果队列已满或由于其他原因无法插入元素,返回 false
    • 返回值:插入成功返回 true,否则返回 false
    • 示例代码
 
import java.util.LinkedList;
import java.util.Queue;

public class QueueOfferExample {
    public static void main(String[] args) {
        Queue queue = new LinkedList<>();
        boolean result = queue.offer("apple");
        System.out.println("插入是否成功: " + result);
    }
}
移除元素
  • remove()
    • 功能:移除并返回队列的头部元素。如果队列为空,会抛出 NoSuchElementException 异常。
    • 返回值:队列的头部元素。
    • 示例代码
 
import java.util.LinkedList;
import java.util.Queue;

public class QueueRemoveExample {
    public static void main(String[] args) {
        Queue queue = new LinkedList<>();
        queue.add("apple");
        String removedElement = queue.remove();
        System.out.println("移除的元素: " + removedElement);
    }
}
 
  • poll()
    • 功能:移除并返回队列的头部元素。如果队列为空,返回 null
    • 返回值:队列的头部元素,如果队列为空则返回 null
    • 示例代码
 
import java.util.LinkedList;
import java.util.Queue;

public class QueuePollExample {
    public static void main(String[] args) {
        Queue queue = new LinkedList<>();
        String removedElement = queue.poll();
        System.out.println("移除的元素: " + removedElement);
    }
}
查看头部元素
  • element()
    • 功能:返回队列的头部元素,但不移除它。如果队列为空,会抛出 NoSuchElementException 异常。
    • 返回值:队列的头部元素。
    • 示例代码
 
import java.util.LinkedList;
import java.util.Queue;

public class QueueElementExample {
    public static void main(String[] args) {
        Queue queue = new LinkedList<>();
        queue.add("apple");
        String headElement = queue.element();
        System.out.println("队列头部元素: " + headElement);
    }
}
 
  • peek()
    • 功能:返回队列的头部元素,但不移除它。如果队列为空,返回 null
    • 返回值:队列的头部元素,如果队列为空则返回 null
    • 示例代码
 
import java.util.LinkedList;
import java.util.Queue;

public class QueuePeekExample {
    public static void main(String[] args) {
        Queue queue = new LinkedList<>();
        String headElement = queue.peek();
        System.out.println("队列头部元素: " + headElement);
    }
}
Java 中的队列接口和实现类
1. Queue 接口

Queue 是 Java 集合框架中定义队列的接口,它继承自 Collection 接口,定义了队列的基本操作方法,如入队(add()offer())、出队(remove()poll())、查看队首元素(element()peek())等。

import java.util.LinkedList;
import java.util.Queue;

public class QueueExample {
    public static void main(String[] args) {
        // 使用 LinkedList 实现 Queue 接口
        Queue queue = new LinkedList<>();

        // 入队操作
        queue.add("apple");
        queue.offer("banana");

        // 出队操作
        String firstElement = queue.poll();
        System.out.println("出队元素: " + firstElement);

        // 查看队首元素
        String peekElement = queue.peek();
        System.out.println("队首元素: " + peekElement);
    }
}
2. Deque 接口

Deque 是双端队列(Double-Ended Queue)的接口,它继承自 Queue 接口,允许在队列的两端进行插入和删除操作。常见的实现类有 LinkedList 和 ArrayDeque

 
  • LinkedList:基于双向链表实现,适合频繁的插入和删除操作
 
import java.util.Deque;
import java.util.LinkedList;

public class LinkedListDequeExample {
    public static void main(String[] args) {
        Deque deque = new LinkedList<>();
        // 在队首插入元素
        deque.addFirst("apple");
        // 在队尾插入元素
        deque.addLast("banana");
        // 从队首移除元素
        String first = deque.removeFirst();
        // 从队尾移除元素
        String last = deque.removeLast();
    }
}
 
  • ArrayDeque:基于数组实现,具有较高的性能,适合作为栈或队列使用。
 
import java.util.ArrayDeque;
import java.util.Deque;

public class ArrayDequeExample {
    public static void main(String[] args) {
        Deque deque = new ArrayDeque<>();
        deque.add(1);
        deque.add(2);
        int element = deque.poll();
    }
}
3. PriorityQueue 类
 

PriorityQueue 是一个优先队列,它不遵循 FIFO 原则,而是根据元素的优先级进行排序,优先级高的元素先出队。元素必须实现 Comparable 接口或在创建 PriorityQueue 时提供一个 Comparator 对象

 
import java.util.PriorityQueue;
import java.util.Queue;

public class PriorityQueueExample {
    public static void main(String[] args) {
        Queue priorityQueue = new PriorityQueue<>();
        priorityQueue.add(3);
        priorityQueue.add(1);
        priorityQueue.add(2);
        while (!priorityQueue.isEmpty()) {
            System.out.println(priorityQueue.poll());
        }
    }
}

    Stack 

    栈(Stack)

    基本概念

    栈是一种遵循后进先出(LIFO,Last-In-First-Out)原则的数据结构,即最后进入栈的元素最先被移除。

    Java 中的栈实现类
    1. Stack 类

    Stack 是 Java 早期提供的用于实现栈的类,它继承自 Vector 类,是线程安全的。但由于 Vector 类的性能较低,现在更推荐使用 Deque 接口的实现类来替代 Stack 类。

    import java.util.Stack;
    
    public class StackExample {
        public static void main(String[] args) {
            Stack stack = new Stack<>();
            // 入栈操作
            stack.push("apple");
            stack.push("banana");
            // 出栈操作
            String topElement = stack.pop();
            System.out.println("出栈元素: " + topElement);
        }
    }
    
    2. 使用 Deque 接口实现栈

    可以使用 Deque 接口的实现类(如 ArrayDeque)来实现栈的功能,这种方式性能更好。

    import java.util.ArrayDeque;
    import java.util.Deque;
    
    public class DequeAsStackExample {
        public static void main(String[] args) {
            Deque stack = new ArrayDeque<>();
            // 入栈操作
            stack.push("apple");
            stack.push("banana");
            // 出栈操作
            String topElement = stack.pop();
            System.out.println("出栈元素: " + topElement);
        }
    }
    
    1. push(E item)
    • 功能:将一个元素压入栈顶。
    • 返回值:返回被压入栈的元素。
    • 示例代码
     
    import java.util.Stack;
    
    public class StackPushExample {
        public static void main(String[] args) {
            Stack stack = new Stack<>();
            String pushedElement = stack.push("apple");
            System.out.println("被压入栈的元素: " + pushedElement);
        }
    }
    
    2. pop()
    • 功能:移除并返回栈顶元素。如果栈为空,会抛出 EmptyStackException 异常。
    • 返回值:栈顶元素。
    • 示例代码
     
    import java.util.Stack;
    
    public class StackPopExample {
        public static void main(String[] args) {
            Stack stack = new Stack<>();
            stack.push("apple");
            String poppedElement = stack.pop();
            System.out.println("弹出的元素: " + poppedElement);
        }
    }
    
    3. peek()
    • 功能:返回栈顶元素,但不移除它。如果栈为空,会抛出 EmptyStackException 异常。
    • 返回值:栈顶元素。
    • 示例代码
     
    import java.util.Stack;
    
    public class StackPeekExample {
        public static void main(String[] args) {
            Stack stack = new Stack<>();
            stack.push("apple");
            String topElement = stack.peek();
            System.out.println("栈顶元素: " + topElement);
        }
    }
    
    4. empty()
    • 功能:检查栈是否为空。
    • 返回值:如果栈为空,返回 true;否则返回 false
    • 示例代码
    import java.util.Stack;
    
    public class StackEmptyExample {
        public static void main(String[] args) {
            Stack stack = new Stack<>();
            boolean isEmpty = stack.empty();
            System.out.println("栈是否为空: " + isEmpty);
        }
    }
    
    5. search(Object o)
    • 功能:返回对象在栈中的位置,以 1 为基数。如果对象不在栈中,返回 -1。
    • 返回值:对象在栈中的位置。
    • 示例代码
     
    import java.util.Stack;
    
    public class StackSearchExample {
        public static void main(String[] args) {
            Stack stack = new Stack<>();
            stack.push("apple");
            stack.push("banana");
            int position = stack.search("banana");
            System.out.println("元素 'banana' 在栈中的位置: " + position);
        }
    }

    总结

    • 队列Queue 接口是队列的基本接口,Deque 接口扩展了队列的功能,支持双端操作。PriorityQueue 则提供了优先队列的实现。
    • :虽然有 Stack 类,但推荐使用 Deque 接口的实现类(如 ArrayDeque)来实现栈的功能,以获得更好的性能。

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