【Java】java 集合框架(详解)零基础入门到精通,收藏这篇就够了


1. 概述 🚀

🔥 Java集合框架 提供了一系列用于存储和操作对象组的接口和类。这些工具是为了解决不同数据结构通用操作的需求而设计的。集合框架主要包括两种类型的容器:

  1. 一种是 集合(Collection),用于存储一个元素集合;
  2. 另一种是 图(Map),用于存储键 / 值对映射
1.1 Collection(单列集合)的分类和特点

🌈 集合分为几个接口,主要有ListSetQueue

常用接口的实现类如下:

  • List:一个有序集合,可以包含重复的元素,允许精确控制每个元素的插入位置。常用实现类有ArrayList、LinkedList和Vector。

    • **ArrayList:**是基于动态数组的实现,适合随机访问元素

    • **LinkedList :**基于链表的实现,适合插入和删除操作

    • Vector:ArrayList 类似,但它是同步的,适合多线程环境

  • Set:一个不允许有重复元素的集合。常用实现类有HashSet、LinkedHashSet和TreeSet。

    • **HashSet:**基于哈希表实现,元素无序且唯一

    • **LinkedHashSet:**维护元素插入的顺序

    • **TreeSet:**保证元素处于排序状态

  • Queue:一个队列接口,代表先进先出(FIFO)的集合。实现类如PriorityQueueDeque,其中PriorityQueue允许按元素的自然顺序或者根据构造器提供的Comparator进行元素排序

**注意:**Collection 是集合中的最基本接口,一般不直接使用该接口

1.2 Map(双列集合)的分类和特点

Map接口提供键到值的映射,不能包含重复的键,每个键最多只能映射到一个值。

  • 常用实现类HashMap、Hashtable、LinkedHashMapTreeMap

  1. HashMap: 允许使用null值和null键,非同步
  2. Hashtable: 不允许null值和null键,是同步的
  3. LinkedHashMap: 维护着插入顺序或访问顺序
  4. TreeMap: 保证键处于排序状态
1.3 集合和图的使用场景
  • ArrayList:适用于频繁查找操作的场景

  • LinkedList:适用于频繁插入和删除操作的场景

  • HashSet:适用于需要唯一元素且顺序不重要的场景

  • TreeSet:适用于需要排序的集合

  • HashMap:适用于需要快速访问键值对的场景

  • TreeMap:适用于需要按自然顺序或自定义顺序遍历键的场景

1.4 线程安全与非线程安全(了解)

💦集合类中,如ArrayList、LinkedList和HashSet是非线程安全的,而Vector、Hashtable是线程安全的。在多线程环境下,需要考虑集合的线程安全问题,可以使用Collections工具类提供的同步包装器来包装非线程安全的集合

2. Collection 接口

2.1 定义

Collection 接口Java单列集合 中的 根接口,它定义了各种具体单列集合的共性其他单列集合大多直接 或 间接继承该接口。由于是接口不能直接使用,就需要通过类来实现

Collection 接口 的定义如下:

public interface Collection<E> extends Iterable<E> (
//Query Operations
}

Collection 接口的定义可以看到,CollectionIterable 的子接口,CollectionIterable 后面的 表示 它两 都使用了泛型

2.2 常用方法

Collection 接口的常用方法如下:

方法名功能描述
boolean add(E e)

| 添加元素到集合的末尾(追加) |
|

boolean remove(Object o)

| 删除指定的元素,成功则返回true(底层调用equles) |
|

void clear()

| 清空集合中所有元素 |
|

boolean contains(Object o)

| 判断元素在集合中是否存在,存在则返回true(底层调用equles) |
|

boolean isEmpty()

| 判断集合是否为空,空则返回true |
|

int size()

| 返回集合中元素个数 |
|

Iteratoe iterator()

| 获取集合的迭代器(迭代器用于遍历整个集合) |

3. List 接口

3.1 List 接口简历
  1. List 接口继承自Collection 接口,List接口实例中允许存储重复的元素,所有的元素以线性方式存储。
  2. 在程序中可以通过索引访问List接口实例中存储的元素。
  3. List 接口实例中存储的元素是有序的,即 元素的存入顺序和取出顺序一致

List 作为Collection 集合的子接口,不但继承了Collection 接口中的全部方法,而且增加了一些根据元素索引操作集合的特有方法.

List 接口的常用方法如下:

方法声明功能描述
void add(int index, Object element)将元素 element 插入 list 的索引 index 处
boolean addAll(int index, Collection c)将集合 c 包含的所有元素插入 List 集合的索引 index 处
Object get(int index)返回集合索引 index 处的元素
Object set(int index, Object element)将索引 index 处的元素替换成 element 对象,并将替换后的元素返回
Object remove(int index)删除索引 index 处的元素
int indexOf(Object o)返回对象 o 在 List 中第一次出现的索引
int lastIndex(Object o)返回对象 o 在 List 中最后一次出现的索引
List subList(int fromIndex, int toIndex)返回从索引 【fromIndex, toIndex)的所有元素组成的子集合
  • List 接口的所有实现类都可以调用表中的方法来操作集合元素
3.2 ArrayList

🔥 ArrayListList 接口 的一个实现类,它是程序中最常见的一种集合。

💞ArrayList 内部封装了一个长度可变的数组对象,当存入的元素超过数组长度时,ArrayList 会在内存中分配一个更大的数组来存储这些元素,因此可以将 ArrayList 看作一个长度可变的数组。ArrayList的元素插入过程如下:

使用案例:

public class Test {
    public static void main(String[] args) {
        ArrayList <String> slist = new ArrayList<>(); // 指定类型创建,泛型定义为 String
        ArrayList list = new ArrayList(); // 不指定类型创建
        // 1. 向集合中增加元素
        list.add("12");
        list.add(1); // 不限制类型插入
        // slist.add(1322); // 指定为 String的,也只能插入 String
        list.add("13");
        list.add("14");

        // 2. 查看集合元素
        System.out.println("集合为:" + list);
        System.out.println("第 2 个元素为:" + list.get(1));
        System.out.println("集合元素个数为:" + list.size());

        // 3. 删除
        list.remove(2);
        System.out.println("删除后的集合为:" + list);

        // 4. 替换
        list.set(2, "15");
        System.out.println("替换后的集合为:" + list);

        // 5. 截取
        List l = list.subList(0,2);
        System.out.println("[1, 2) 的子集为:" + l);
    }
}

输出结果如下:

分析:

  • 索引为 1 的元素是集合中的第 2 个元素,这就说明集合和数组一样,索引的取值范围为【0,n - 1】。在访问元素时一定要注意索引不可超出此范围,否则程序会抛出 索引越界异常(IndexOutOfBoundsException)
  • 由于 ArrayList的底层是使用一个数组存储元素,在增加或删除指定位置的元素时,会创建新的数组,效率比较低,因此 Arraylist集合不适合做大量的增删操作,而适合元素的查找

ArrayList 的 优缺点及优化

数组优点:

  1. 检索效率比较高。(每个元素占用空间大小相同,内存地址是连续的,知道首元素内存地址,并且知道下标,通过数学表达式计算出元素的内存地址,所以检索效率最高)
  2. 向数组末尾添加元素,效率很高,不受影响

数组缺点:

  1. 随机增删元素效率比较低。
  2. 另外数组无法存储大数据量。(很难找到一块非常巨大的连续的内存空间)

ArrayList 集合底层是数组,怎么优化 ❓
尽可能的少扩容

  • 原因:数组扩容效率比较低,建议在使用 ArrayList 集合的时候应该先预估计元素的个数,给定一个初始化容量大小
3.3 LinkedList

🍒上面讲解的 ArrayList 在查询元素时速度很快,但在增删元素时效率较低

因此为了克服这种局限性,可以使用 List 接口的另一个实现类 ——LinkedList

  • LinkedList 内部维护了一个双向循环链表,链表中的每一个元素都使用引用的方式记录它的前一个元素和后一个元素,从而可以将所有的元素彼此连接起来。当
  • 插入一个新元素时,只需要修改元素之间的引用关系即可,删除一个节点也是如此。正因为LinkedList 具有这样的存储结构,所以其增删效率非常高
  • LinkedList 添加元素和删除元素的过程 如下所示

上图中的 黑色线箭头表示建立新的引用关系绿色线箭头表示 删除引用关系

  1. a) 为添加元素,元素1和元素2在集合中为前后关系,在它们之间新增一个元素时,只需要让元素1记录它后面的元素为新元素,让元素2记录它前面的元素为新元素。
  2. b)为删除元素,要想删除元素1与元素2之间的元素3,只需要让元素1与元素2变成前后引用关系。

针对元素的添加、删除和获取操作,LinkedList 定义了一些特有的方法(代码演示),如下代码:

public class Test {
    public static void main(String[] args) {
        LinkedList link = new LinkedList(); // 创建集合
        // 1. 增加元素
        link.add("张三"); // 默认以追加形式插入
        link.add("李四");
        link.add("王五");
        link.addFirst(12); //将指定元素加到开头
        link.add(4,"赵六"); //指定插入位置,指定插入的索引下标不能超过元素个数
        link.offer("田七"); // 添加到结尾,返回值为 boolean
        link.push(23); // 将指定元素添加到集合开头

        // 2. 获取和打印元素
        System.out.println(link);
        System.out.println(link.toString()); // 和上面输出效果相同
        System.out.println(link.getFirst()); // 获取第一个元素

        Object x = link.peekFirst(); //获取第一个元素
        System.out.println(x);

        // 3. 删除
        link.remove(1); // 删除指定索引下标为 1 的值
        Object a = link.pollFirst(); // 删除并返回第一个元素
        System.out.println(link);
    }
}

// 运行结果:
[23, 12, 张三, 李四, 王五, 赵六, 田七]
[23, 12, 张三, 李四, 王五, 赵六, 田七]
23
23
[张三, 李四, 王五, 赵六, 田七]
3.4 List 的优缺点

💖 LinkedListArrayList 方法一样,只是底层实现不一样。ArrayList 底层为数组存储,LinkedList 是以双向链表存储。

  • LinkedList 集合**没有初始化容量,**最初这个链表中没有任何元素。firstlast 引用都是 NULL

链表的优点:

  1. 由于链表上的元素在空间存储上内存地址不连续。 所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高
  2. 在以后的开发中,如果遇到随机增删集合中元素的业务比较多时,建议使用 LinkedList

链表的缺点:

  1. 不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头节点开始遍历,直到找到为止。 所以 LinkedList 集合检索/查找的效率较低
  • ArrayList:把检索发挥到极致(末尾添加元素效率还是很高的)
  • LinkedList :把随机增删发挥到极致
  • 由于加元素都是往末尾添加,所以 ArrayList 用的比 LinkedList 多。

4. 集****合遍历

在实际开发中,经常需要按照某种次序对集合中的每个元素进行访问,并且仅访问一次,这种对集合的访问也称为集合的遍历。针对这种需求,JDK提供了Iterator 接口和foreach 循环。本节将对 Iterator 接口和foreach循环遍历集合进行详细讲解。

4.1 Iterator 接口

lterator 接口是 Java 集合框架中的一员,但它与Collection 接口和Map 接口有所不同。

  • Collection 接口和 Map 接口主要用于存储元素,
  • Iterator 接口主要用于迭代访问(遍历集合中的元素,通常情况下Iterator对象也被称为迭代器)

下面通过一个案例介绍如何使用Iterator 接口遍历集合中的元素:

public class Test {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add("张三");
        list.add("李四");
        list.add("王五");
        list.add("赵六");
        Iterator it = list.iterator(); // 获取 Iterator 对象
        while(it.hasNext()){ // 判断集合是否有下一个元素
            Object obj = it.next(); // 获取集合元素
            System.out.println(obj);
        }
    }
}

运行结果如下:

Iterator 对象在遍历集合时,内部采用指针的方式来跟踪集合中的元素。Iterator遍历集合中的元素的过程如下图所示:


在上图中,在调用 Iterator 的 **next()**方法之前,Iterator 的指针位于第一个元素之前,不指向任何元素;

  1. 第一次调用Iterator的 **next()**方法时,Iterator 的指针会向后移动一位,指向第一个元素并将该元素返回;
  2. 当第二次调用 **next()**方法时,Iterator的指针会指向第二个元素并将该元素返回;
  3. 以此类推,直到 hasNext() 方法返回 false,表示已经遍历完集合中所有的元素,终止对元素遍历

🍒需要注意的是:通过 Iterator 获取集合中的元素时,这些元素的类型都是Object类型。如果想获取特定类型的元素,则需要对数据类型进行强制转换

💢 **小知识:**并发修改异常

在使用Iterator对集合中的元素进行遍历时,如果调用了集合对象的 remove()方法删除元素,然后继续使用Iterator遍历元素,会出现异常。

下面通过一个案例演示这种异常。假设在一个集合中存储了一所学校所有学生的姓名,由于一个名为张三的学生中途转学,这时就需要在迭代集合时找出该元素并将其删除,如下:

public class Test {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add("张三");
        list.add("李四");
        list.add("王五");
        Iterator it = list.iterator(); // 获取 Iterator 对象
        while(it.hasNext()){
            Object obj = it.next();
            if("张三".equals(obj)){ // 判断是否有等于张三的元素
                list.remove(obj); // 删除集合中查找到的元素
            }
        }
        System.out.println(list);
    }
}

运行结果如下:

在运行时抛出了并发修改异常(ConcurrentModificationException)。这个异常是Iterator对象抛出的,出现异常的原因是集合在Iterator运行期间删除了元素,会导致Iterator 预期的迭代次数发生改变,Iterator的迭代结果不准确。

要解决上述问题,可以采用以下两种方法:

(1)从业务逻辑上讲,只想将姓名为张三的学生删除,至于后面还有多少学生并不需要关心,因此只需找到该学生后跳出循环,不再迭代即可,也就是在第12行代码下面增加break语句,代码如下。

在使用break语句跳出循环以后,由于不再使用Iterator 对集合中的元素进行遍历,所以在集合中删除元素对程序没有任何影响,就不会再出现异常。

(2)如果需要在集合的迭代期间对集合中的元素进行删除,可以使用Iterator本身的删除方法,将上面的 list.remove() 替换成 it.remove() 即可解决这个问题,代码如下:

根据上面运行结果可以看出,学生张三确实被删除了,并且没有出现异常。

**由此可以得出结论:**调用 Iterator 对象的remove()方法删除元素所导致的迭代次教变化对于Iterator对象本身来讲是可预知的。

4.2 foreach 接口

虽然 Iterator 可以用来遍历集合中的元素,但在写法上比较烦琐。为了简化书写,从JDK 5开始,JDK提供了 foreach循环,它是一种更加简洁的for循环,主要用于遍历数组或集合中的元素,语法格式如下:

for (容器中元素类型 临时变量:容器变量) {
执行语句
}

由上述 foreach 循环语法格式可知, 与 for 循环相比, foreach 循环不需要获得集合的长度,也不需要获得集合的长度,也不需要根据索引访问集合中的元素,就能够自动遍历集合中的元素。

下面通过一个例演示foreach循环的用法,

public class Test {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add("张三");
        list.add("李四");
        list.add("王五");
        for(Object obj: list) {
            System.out.print(obj + " ");
        }
    }
}
// 输出
张三 李四 王五 

foreach 循环在遍历集合时语法非常简洁,没有循环条件,也没有迭代语句,所有这些工作都交给Java虚拟机执行了。 foreach 循环的次数是由集合中元素的个数决定的,每次循环时, foreach 都通过临时变量将当前循环的元素记住,从而将集合中的元素分别打印出来。

💢 **小知识:**foreach循环缺陷
foreach 循环虽然书写起来很简洁,但在使用时也存在一定的局限性。当使用 foreach 循环遍历集合和数组时,只能访问其中的元素,不能对其中的元素进行修改。

**原因:**只是将获取的临时变量赋值为 新的字符串,这和数组中的元素没有任何关系

5. Set 接口

🔥 Set 接口也继承自Collection接口,它的方法与Collection接口的方法基本一致,并没有对Collection接口进行功能上的扩充。与List接口不同的是,Set接口中的元素是无序的,并且都会以某种规则保证存入的元素不出现重复。
💖Set 接口常见的实现类有 3个,分别是 HashSet、LinkedHashSet 和TreeSet

  1. HashSet 根据对象的哈希值确定元素在集合中的存储位置,具有良好的存取和查找性能
  2. LinkedHashSet 是链表和哈希表组合的一个数据存储结构;
  3. TreeSet 则以二叉树的方式存储元素,它可以对集合中的元素进行排序

接下来将对Set接口的这3个实现类进行详细讲解

5.1 HashSet

💧 HashSetSet接口的一个实现类,它存储的元素是不可重复的。当向 HashSet中添加一个元素时,首先会调用 hashCode() 方法确定元素的存储位置,然后再调用 equals() 方法确保该位置没有重复元素。Set 接口与 List接口 存取元素的方式是一样的,但是Set集合中的元素是无序的。

代码演示如下:

public class Test {
    public static void main(String[] args) {
       HashSet hset = new HashSet();
        hset.add("张三");
        hset.add("李四");
        hset.add("李四");
        hset.add("王五");
        for(Object obj: hset) {
            System.out.print(obj + " ");
        }
    }
}

// 输出
李四 张三 王五

从上面输出可以看出,取出元素的顺序与添加元素的顺序并不一致,并且重复被去除了,只添加了一次。
HashSet 之所以能确保不出现重复的元素,是因为它在存入无序时做了很多工作

  1. 当调用 HashSet 的add()方法存入元素时,首先调用hashCode()方法获得该元素的哈希值,然后根据哈希值计算存储位置。
  2. 如果该位置上没有元素,则直接将元素存入;
  3. 如果该位置上有元素,则调用 equals()方法 将要存入的元素和该位置上的元素进行比较,根据返回结果确定是否存人元素。
  4. 如果返回的结果为 false,就将该元素存入集合;
  5. 如果返回的结果为 true,则说明有重复元素,就将要存人的重复元素舍弃。

注意:当向集合中存入元素时,为了保证集合正常工作,要求在存入元素时重写Object 类中的hashCode()equals() 方法。

在上述代码中,将数据存入集合时,String类已经重写了 hashCode()equals() 方法

下面通过一个案例演示如何向集合中存储自定义类对象,如下所示:

class P {
    private String id;
    private String name;
    public P(String id, String name) {
        this.id = id;
        this.name = name;
    }

    // 重写
    @Override
    public String toString() {
        return id + ":" + name;
    }

    @Override
    public int hashCode() {
        return id.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) { // 判断是否是同一个对象
            return true;
        }
        if (!(obj instanceof P)) { // 判断是不是 P 类型
            return false;
        }
        P p = (P) obj; // 强转
        return this.id.equals(p.id); // 判断id 是否相等
    }
}

public class Test {
    public static void main(String[] args) {
       HashSet hs = new HashSet();
       P p1 =new P("1", "张三");
       P p2 =new P("2", "李四");
       P p3 =new P("3", "王五");
        hs.add(p1);
        hs.add(p2);
        hs.add(p2);
        hs.add(p3);
        System.out.println(hs);
    }
}

// 输出:
[1:张三, 2:李四, 3:王五]


5.2 LinkedHashSet

🍉HashSet 存储的元素是无序的,如果想让元素的存取顺序一致,可以使用Java提供的LinkedHashSet 类,LinkedHashSet类是HashSet的子类,与LinkedList一样,它也使用双向链表来维护内部元素的关系。

下面通过一个案例学习LinkedHashSet 类的用法:

public class Test {
    public static void main(String[] args) {
        LinkedHashSet set = new LinkedHashSet();
        set.add("张三");
        set.add("李四");
        set.add("王五");
        System.out.println(set);
    }
}
// 输出:
[张三, 李四, 王五]

从上面可知:元素遍历顺序 和 存入顺序 是一致的

5.3 TreeSet

🌈 TreeSet是Set接口的另一个实现类,它内部采用二叉树存储元素,这样的结构可以保证集合中没有重复的元素,并且可以对元素进行排序

二叉树概念及特点如下:

  1. 二叉树就是每个节点最多有两个子节点的有序树
  2. 子树:一个节点及其子节点组成的树称为子树,通常左侧的子树称为左子树,右侧的子树称为右子树,
  3. 左子树上的元素都小于它的根节点,右子树上的元素都大于它的根节点。
  4. 对于同一层的元素,左边的元素总是小于右边的元素

TreeSet 特有的方法

方法声明功能描述
Object first()返回集合第一个元素
Object last()返回集合最后应该元素
Object lower(Object o)返回集合中小于给定元素的最大元素,不存在则返回 NULL
Object floor(Object o)返回集合中小于或者等于给定元素的最大元素,不存在则返回 NULL
Object higher(Object o)返回集合中大于给定元素的最小元素,不存在则返回 NULL
Object ceiling(Object o)返回集合中大于或者等于给定元素的最小元素,不存在则返回 NULL
Object pollFirst()移除并返回集合的第一个元素
Object pollLast()移除并返回集合的最后一个元素

案例代码如下:

public class Test {
    public static void main(String[] args) {
        //创建集合
        TreeSet ts= new TreeSet();
        //向集合中添加元素
        ts.add(3);ts.add(29);ts.add(101);ts.add(21);
        System.out.println("创建的TreeSet集合为:"+ts);
        //获取首尾元素
        System.out.println("TreeSet 集合首元素为:"+ts.first());
        System.out.println("TreeSet集合尾部元素为:"+ts.last());//比较并获取元素
        System.out.println("集合中小于或等于9的最大的元素为:" +ts.floor(9));
        System.out.println("集合中大于10的最小的元素为:"+ts.higher(10));
        System.out.println("集合中大于100的最小的元素为:" +ts.higher(100));
        //删除元素
        Object first = ts.pollFirst();
        System.out.println("删除的第一个元素为:"+first);
        System.out.println("删除第一个元素后TreeSet 集合变为:"+ts);
    }
}

// 输出:
创建的TreeSet集合为:[3, 21, 29, 101]
TreeSet 集合首元素为:3
TreeSet集合尾部元素为:101
集合中小于或等于9的最大的元素为:3
集合中大于10的最小的元素为:21
集合中大于100的最小的元素为:101
删除的第一个元素为:3
删除第一个元素后TreeSet 集合变为:[21, 29, 101]

结果也可以看出,向 TreeSet 集合添加元素时,不论元素的添加顺序如何,这些元素都能够按照一定的顺序排列。其原因是如下:

  1. 每次向 TreeSet 集合中存入一个元素时,就会将该元素与其他元素进行比较,最后将它插入有序的对象序列中。
  2. 集合中的元素在进行比较时都会调用 compareTo() 方法,该方法是在 Comparable接口中定义的,因此要想对集合中的元素进行排序,就必须实现 Comparable接口
  3. Java中大部分的类实现了Comparable接口,并默认实现了该接口中的CompareTo()方法,如Integer、Double和String等。

在实际开发中,除了会向 TreeSet集合中存储一些Java中默认类型的数据外,还会存储一些用户自定义的类型的数据,如Student 类型的数据、Teacher类型的数据等。

由于这些自定义类型的数据没有实现 Comparable 接口,因此也就无法直接在TreeSet集合中进行排序操作。为了解决这个问题,Java提供了两种 TreeSet集合的排序规则,分别为自然排序 和 自定义排序。

其实之前在这篇博客【Java 学习】:抽象类&接口里面就谈过了这两个内容,这里就做个温习了

在默认情况下,TreeSet集合都采用自然排序。接下来对这两种排序规则进行详细讲解。

5.3.1 自然排序

🍒自然排序要求向TreeSet集合中存储的元素所在类必须实现Comparable 接口、并重写compareTo()方法,然后TreeSet集合就会对该类型的元素使用 compareTo()方法进行比较。

  • compareTo()方法 将当前对象与指定的对象按顺序进行比较,返回值为一个整数,其中,返回负整数、零或正整数分别表示当前对象小于、等于或大于指定对象,默认根据比较结果顺序排列
class Student implements Comparable {
    private String name;
    private int age;
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    //重写toString()方法
    @Override
    public String toString() {
        return name + ":" + age;
    }

    //重写Comparable接口的compareTo()方法
    public int compareTo(Object obj) {
        Student stu = (Student) obj;
        //定义比较方式,先比较age,再比较name
        if (this.age - stu.age > 0) {
            return 1;
        }
        if (this.age - stu.age == 0) {
            return this.name.compareTo(stu.name);
        }
        return -1;
    }
}

public class Test {
    public static void main(String[] args) {
        TreeSet ts = new TreeSet();
        ts.add(new Student("张三", 18));
        ts.add(new Student("李四", 16));
        ts.add(new Student("王五", 20));
        ts.add(new Student("张三", 18));
        System.out.println(ts);
    }
}

// 输出
[李四:16, 张三:18, 王五:20]
5.3.2 自定义排序

🎈 如果不想按照实现了Comparable接口的类中compareTo()方法的规则进行排序,可以通过自定义比较器的方式对TreeSet 集合中的元素自定义排序规则。实现Comparator 接口的类都是一个自定义比较器,可以在自定义比较器的compare() 方法中自定义排序规则。

  • 案例如下
class Student{
    String name;
    String id;
    public Student(String id,String name) {
        this.id = id;
        this.name = name;
    }
    @Override
    public String toString() {
        return name + ":" + id;
    }
}

public class Test {
    public static void main(String[] args) {
//排序规则是:先根据Student 的 id升序排列;如果id相同,则根据 name进行升序排列
        TreeSet ts = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                Student s1 = (Student) o1;
                Student s2 = (Student) o2;
                if(!s1.id.equals(s2.id)){
                    return s1.id.compareTo(s2.id);
                }
                else{
                    return s1.name.compareTo(s2.name);
                }
            }
        });
        ts.add(new Student("2", "张三"));
        ts.add(new Student("1","李四"));
        ts.add(new Student("3","王五"));
        ts.add(new Student("2","张三"));
        System.out.println(ts);
    }
}

6. Map 接口

数学中的函数描述了自变量到因变量的映射。Map 接口借鉴了数学中函数的思想 —— Map 接口中的每个元素都是由键到值的映射,即 Map 接口中的每个元素都由一个键值对组成

6.1 Map 接口简介

🎈 Map 接口是一种双列集合,它的每个元素都包含一个键对象Key和一个值对象Value。键和值之间存在一种对应关系,称为映射。Map中的键不允许重复,访问Map集合中的元素时,只要指定了键,就能找到对应的值

  • Map 接口定义了一系列方法用于访问元素,常用方法如下:
方法声明功能描述
void put(Object key, Object value)将指定的值和键存入集合并进行映射关联
Object get(Object key)返回指定的键映射的值,如果不存在则返回NULL
void clear()移除所有 键值 关系
V remove(Object key)根据键删去对应值,并返回被删除的值
int size()返回集合中的键值对个数
boolean containsKey(Object key)判断此映射是否包含指定键的映射关系
boolean containsValue(Object value)判断此映射是否能将一个或多个键映射到指定值
Set keySet()返回此映射包含的 Set 集合
Collection<V> values()返回此映射中包含的值的 Collection 集合
Set<Map, Entry<K, V>> entrySet()返回此映射包含的映射关系的 Set 集合
6.2 HashMap

HashMap 是Map 接口的一个实现类,HashMap 中的大部分方法都是 Map接口方法的实现。在开发中,通常把 HashMap 集合对象的引用赋值给Map接口变量接口变量就可以调用 HashMap 类实现的接口方法

  • HashMap集合用于存储键值映射关系,但 HashMap集合没有重复的键并且元素无序
public static void main(String[] args) {
    Map map = new HashMap(); // 创建 HashMap 集合
    map.put("1","张三");
    map.put("2","李四");
    map.put("3","王五");
    // 根据键获取值
    System.out.println("1: " + map.get("1"));
    System.out.println("2: " + map.get("2"));
    System.out.println("3: " + map.get("3"));
}

上面说到了 HashMap 集合 中的键具有唯一性,那么 向 map 中存储一个相同的键, 判断一下会出现 什么 情况?如下:

由上可知:虽然 Map 集合中仍然只有 3 个 元素,但是第二次添加的值 “赵六” 覆盖了 原来的值, 这也证明了 我们上面的说法**(键的唯一性)**,结论:键相同,值覆盖

在程序开发中取出 HashMap 集合中所有的键和值 有两种方式,如下:

(1)先遍历 HashMap 集合中所有的键,再根据键获取对应的值

public static void main(String[] args) {
    Map map = new HashMap();
    map.put("1", "张三");
    map.put("2", "李四");
    Set keySey = map.keySet(); //获取键的集合
    Iterator it = keySey.iterator();
    while (it.hasNext()) {
        Object key = it.next();
        Object value = map.get(key);
        System.out.println(key + ":" + value);
    }
}

(2)先遍历集合中所有的映射关系,然后再从映射关系中取出 键和值

public static void main(String[] args) {
    Map map = new HashMap();
    map.put("1", "张三");
    map.put("2", "李四");
    Set entrySey = map.entrySet(); //获取键的集合
    Iterator it = entrySey.iterator();
    while (it.hasNext()) {
        // 获取集合中键值对映射关系
        Map.Entry entry = (Map.Entry) (it.next());
        Object key = entry.getKey(); // 获取键
        Object value = entry.getValue(); //获取值
        System.out.println(key + ":" + value);
    }
}
6.3 LinkedHashMap & TreeMap

HashMap 集合遍历元素的顺序和存入的顺序是不一致的。

  1. 如果想让集合中的元素遍历顺序与存入顺序一致,可以使用LinkedHashMap 集合。LinkedHashMap 是 HashMap的子类。与LinkedList一样,LinkedHashMap集合也使用双向链表维护内部元素的关系
  2. HashMap 集合存储的元素的键是无序的和不可重复的,为了对集合中的元素的键选行排序,Map接口还提供了一个可以对集合中元素的键进行排序的实现类 – TreeMap

这两个我们就不过多举例了,大家可以自己去试试,操作和上面差不多

6.4 Properties

🎒 Map接口还有一个实现类 – HashTable ,它和HashTable 十分相似,区别在于 HashTable 类是线程安全的。HashTable类存取元素时速度很慢,目前基本上被 HashMap类所取代。但HashTable类有一个很重要的子类 一 Properties,应用非常广泛。

  • Properties 主要用于存储字符串类型的键和值。

在实际开发中,经常使用 Properties 集合存储应用的配置项。假设有一个文本编辑工具,要求默认背景色是红色,字体大小为14px,语言为中文,其配置项如下面的代码所示:

Backgroup-color = red
Font-size= 14px
Language = chinese

在程序中可以使用 Properties集合对这些配置项进行存储。举个例子:

public static void main(String[] args) {
    Properties p = new Properties();
    p.setProperty("Backgroup-color", "red");
    p.setProperty("Font-size", "14px");
    p.setProperty("Language", "chinese");
    Enumeration names = p.propertyNames();
    while(names.hasMoreElements()){
        String key = (String) names.nextElement();
        String value = p.getProperty(key);
        System.out.println(key+"="+value);
    }
}

小结 📖

Java集合框架为开发者提供了强大的数据结构和算法,以便于存储和操作对象。理解每种集合的特点和适用场景,可以帮助开发者更高效地使用这些工具。在使用集合时,还需要考虑到线程安全问题,选择合适的实现类或者使用同步包装器来确保线程安全

【*★,°*:.☆( ̄▽ ̄)/$:*.°★* 】那么本篇到此就结束啦,如果我的这篇博客可以给你提供有益的参考和启示,可以三连支持一下 !!

因此捕获AI,掌握技术是关键,让AI成为我们最便利的工具.

一定要把现有的技术和大模型结合起来,而不是抛弃你们现有技术!掌握AI能力的Java工程师比纯Java岗要吃香的多。

即使现在裁员、降薪、团队解散的比比皆是……但后续的趋势一定是AI应用落地!大模型方向才是实现职业升级、提升薪资待遇的绝佳机遇!

如何学习AGI大模型?

作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

2025最新版优快云大礼包:《AGI大模型学习资源包》免费分享**

一、2025最新大模型学习路线

一个明确的学习路线可以帮助新人了解从哪里开始,按照什么顺序学习,以及需要掌握哪些知识点。大模型领域涉及的知识点非常广泛,没有明确的学习路线可能会导致新人感到迷茫,不知道应该专注于哪些内容。

我们把学习路线分成L1到L4四个阶段,一步步带你从入门到进阶,从理论到实战。

L1级别:AI大模型时代的华丽登场

L1阶段:我们会去了解大模型的基础知识,以及大模型在各个行业的应用和分析;学习理解大模型的核心原理,关键技术,以及大模型应用场景;通过理论原理结合多个项目实战,从提示工程基础到提示工程进阶,掌握Prompt提示工程。

L2级别:AI大模型RAG应用开发工程

L2阶段是我们的AI大模型RAG应用开发工程,我们会去学习RAG检索增强生成:包括Naive RAG、Advanced-RAG以及RAG性能评估,还有GraphRAG在内的多个RAG热门项目的分析。

L3级别:大模型Agent应用架构进阶实践

L3阶段:大模型Agent应用架构进阶实现,我们会去学习LangChain、 LIamaIndex框架,也会学习到AutoGPT、 MetaGPT等多Agent系统,打造我们自己的Agent智能体;同时还可以学习到包括Coze、Dify在内的可视化工具的使用。

L4级别:大模型微调与私有化部署

L4阶段:大模型的微调和私有化部署,我们会更加深入的探讨Transformer架构,学习大模型的微调技术,利用DeepSpeed、Lamam Factory等工具快速进行模型微调;并通过Ollama、vLLM等推理部署框架,实现模型的快速部署。

整个大模型学习路线L1主要是对大模型的理论基础、生态以及提示词他的一个学习掌握;而L3 L4更多的是通过项目实战来掌握大模型的应用开发,针对以上大模型的学习路线我们也整理了对应的学习视频教程,和配套的学习资料。

二、大模型经典PDF书籍

书籍和学习文档资料是学习大模型过程中必不可少的,我们精选了一系列深入探讨大模型技术的书籍和学习文档,它们由领域内的顶尖专家撰写,内容全面、深入、详尽,为你学习大模型提供坚实的理论基础(书籍含电子版PDF)

三、大模型视频教程

对于很多自学或者没有基础的同学来说,书籍这些纯文字类的学习教材会觉得比较晦涩难以理解,因此,我们提供了丰富的大模型视频教程,以动态、形象的方式展示技术概念,帮助你更快、更轻松地掌握核心知识

四、大模型项目实战

学以致用 ,当你的理论知识积累到一定程度,就需要通过项目实战,在实际操作中检验和巩固你所学到的知识,同时为你找工作和职业发展打下坚实的基础。

五、大模型面试题

面试不仅是技术的较量,更需要充分的准备。

在你已经掌握了大模型技术之后,就需要开始准备面试,我们将提供精心整理的大模型面试题库,涵盖当前面试中可能遇到的各种技术问题,让你在面试中游刃有余。


因篇幅有限,仅展示部分资料,需要点击下方链接即可前往获取

2025最新版优快云大礼包:《AGI大模型学习资源包》免费分享

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值