一篇文章来你学完Java中的集合(超详细)

目录

一:集合的体系

Collection接口体系 

Map接口体系

二:Collection集合

Collection的常见方法

迭代器

迭代器的基本方法

代码解释

增强for循环

语法

注意事项

使用 Consumer 和 Lambda 表达式遍历集合

案例演示

集合中存储元素本质

对象和引用

集合存储引用

示例:添加对象到ArrayList

总结

List 集合

List 集合中的特有方法

ArrayList集合 

LinkedList集合

LinkedList集合的底层原理

LinkedList的底层实现:

双向链表的特点

LinkedList的特有方法

 LinkedList 集合业务场景 

Set集合

前言

HashSet

LinkedHashSet

TreeSet

HashSet集合详解

HashSet集合的底层原理

一、哈希值概述

二、对象哈希值的特点

HashSet集合的底层实现 

TreeSet集合详解

TreeSet自定义排序规则

集合的并发修改异常

并发修改异常示例

如何避免并发修改异常

Collections

三:Map集合


一:集合的体系

Java中的集合体系结构是一个为表示和操作集合而设计的统一标准的体系结构,它主要由两大接口体系构成:Collection接口体系和Map接口体系。

Collection接口体系 

Collection接口是集合框架的根接口,用于表示一组元素的集合。它定义了集合的基本操作,如添加、删除、遍历等。Collection接口体系主要包括以下几个子接口:

  1. List
    • 特性:有序的集合,允许元素重复,有索引。
    • 常用实现类
      • ArrayList:基于动态数组实现,适合随机访问。
      • LinkedList:基于链表实现,适合频繁的插入和删除操作。
  2. Set
    • 特性:无序的集合,不允许元素重复,无索引。
    • 常用实现类
      • HashSet:基于哈希表实现,不保证元素的顺序,效率较高。
      • LinkedHashSet:继承自HashSet,通过链表维护元素的插入顺序。
      • TreeSet:基于红黑树实现,可以对元素进行排序。

Map接口体系

Map接口用于存储键值对(key-value pairs),一个键可以映射到最多一个值。Map接口体系主要包括以下几个实现类:

  1. HashMap
    • 特性:基于哈希表的实现,提供快速的键查找能力。
    • 注意:不保证映射的顺序,且允许一个键为null和多个值为null
  2. LinkedHashMap
    • 特性:继承自HashMap,同时维护了一个双向链表来记录插入顺序或访问顺序。
    • 注意:根据创建时指定的参数,可以维护插入顺序或访问顺序。
  3. TreeMap
    • 特性:基于红黑树实现,可以对键进行排序。
    • 注意:自然排序或根据创建时提供的Comparator进行排序。

二:Collection集合

Collection的常见方法

Collection是单列集合的祖宗,它规定的方法是全部单列集合都会继承的。所以我们优先学习Collection的常见方法。

下面我将详细讲解Java中Collection接口的一些常见方法,并通过代码示例来说明它们的使用。

                  方法名                          说明
 boolean add(E e)把改定的对象添加到当前集合中
 boolean addAll(Collection<? extends E> c)向集合中添加另一个集合的所有元素
 void clear()移除集合中的所有元素
boolean contains(Object o)检查集合是否包含指定的元素
boolean remove(Object o)从集合中移除指定的元素
int size()返回集合中的元素数量
 boolean isEmpty()检查集合是否为空
Object[] toArray()把集合中的元素存储到数组中

1. boolean add(E e)

向集合中添加一个元素。如果集合改变(即元素被添加),则返回true

import java.util.ArrayList;  
import java.util.Collection;  
  
public class CollectionExample {  
    public static void main(String[] args) {  
        Collection<String> collection = new ArrayList<>();  
        boolean result = collection.add("Hello");  
        System.out.println(result);

        collection.add("小白杨");
        System.out.println(collection);
        
        输出:
        true
        [Hello, 小白杨]
    }  
}

2. boolean addAll(Collection<? extends E> c)

向集合中添加另一个集合的所有元素。

import java.util.ArrayList;  
import java.util.Arrays;  
import java.util.Collection;  
  
public class CollectionExample {  
    public static void main(String[] args) {  
        Collection<String> collection1 = new ArrayList<>(Arrays.asList("Hello", "World"));  
        Collection<String> collection2 = new ArrayList<>(Arrays.asList("Java", "Programming"));  
  
        collection1.addAll(collection2);  
  
        System.out.println(collection1); 
        // 输出: [Hello, World, Java, Programming]  
    }  
}

3. void clear()

移除集合中的所有元素。

import java.util.ArrayList;  
import java.util.Collection;  
  
public class CollectionExample {  
    public static void main(String[] args) {  
        Collection<String> collection = new ArrayList<>(Arrays.asList("Hello", "World"));  
  
        collection.clear();  
  
        System.out.println(collection.isEmpty()); // 输出: true  
    }  
}

4. boolean contains(Object o)

检查集合是否包含指定的元素。

import java.util.ArrayList;  
import java.util.Collection;  
  
public class CollectionExample {  
    public static void main(String[] args) {  
        Collection<String> collection = new ArrayList<>(Arrays.asList("Hello", "World"));  
  
        boolean containsHello = collection.contains("Hello");  
        System.out.println("Contains 'Hello': " + containsHello); 
// 输出: Contains 'Hello': true  
  
        boolean containsJava = collection.contains("Java");  
        System.out.println("Contains 'Java': " + containsJava); 
// 输出: Contains 'Java': false  
    }  
}

5. boolean remove(Object o)

从集合中移除指定的元素(如果存在)。

import java.util.ArrayList;  
import java.util.Collection;  
  
public class CollectionExample {  
    public static void main(String[] args) {  
        Collection<String> collection = new ArrayList<>(Arrays.asList("Hello", "World"));  
  
        boolean removed = collection.remove("Hello");  
        System.out.println("Removed 'Hello': " + removed);
// 输出: Removed 'Hello': true  
  
        System.out.println(collection); 
// 输出: [World]  
    }  
}

6. int size()

返回集合中的元素数量。

import java.util.ArrayList;  
import java.util.Collection;  
  
public class CollectionExample {  
    public static void main(String[] args) {  
        Collection<String> collection = new ArrayList<>(Arrays.asList("Hello", "World", "Java"));  
  
        int size = collection.size();  
        System.out.println("Size of the collection: " + size); 
// 输出: Size of the collection: 3  
    }  
}

7. boolean isEmpty()

检查集合是否为空。

import java.util.ArrayList;  
import java.util.Collection;  
  
public class CollectionExample {  
    public static void main(String[] args) {  
        Collection<String> collection = new ArrayList<>();  
  
        boolean isEmpty = collection.isEmpty();  
        System.out.println("Is the collection empty? " + isEmpty); 
// 输出: Is the collection empty? true  
  
        collection.add("Hello");  
        isEmpty = collection.isEmpty();  
        System.out.println("Is the collection empty now? " + isEmpty); 
// 输出: Is the collection empty now? false  
    }  
}

8. Object[] toArray()

返回一个包含集合中所有元素的数组。返回的数组是对象类型,因此你可能需要类型转换来访问具体的元素。

import java.util.ArrayList;  
import java.util.Collection;  
  
public class CollectionExample {  
    public static void main(String[] args) {  
        Collection<String> collection = new ArrayList<>(Arrays.asList("Hello", "World", "Java"));  
  
        Object[] array = collection.toArray();  
        for (Object obj : array) {  
            System.out.println(obj); // 输出集合中的每个元素  
        }  
  
        // 如果你知道数组的类型,可以这样做:  
        String[] stringArray = collection.toArray(new String[0]);  
        for (String str : stringArray) {  
            System.out.println(str); // 同样输出集合中的每个元素,但避免了类型转换  
        }  
    }  
}

迭代器

由于Collection接口不直接支持通过索引访问元素(这是List接口的特性),因此它提供了一种名为迭代器(Iterator)的机制来遍历集合中的元素。

迭代器是一个对象,它提供了一种统一的方法来遍历集合中的元素,而无需了解集合的内部结构。迭代器允许在遍历集合时移除元素,但不允许直接添加元素(尽管某些集合,如List,提供了自己的方法来在迭代过程中添加元素,但这与迭代器本身无关)。

迭代器的基本方法
  • boolean hasNext():检查集合中是否还有元素未被遍历。
  • E next():返回集合中下一个元素,并将迭代器的游标向前移动一位。如果集合中没有更多元素,则抛出NoSuchElementException
  • void remove():从集合中移除通过next()方法返回的最后一个元素(迭代器必须先调用next()方法,才能调用remove()方法,否则会抛出IllegalStateException)。
代码解释

以下是一个使用迭代器遍历Collection中元素的示例代码:

import java.util.ArrayList;  
import java.util.Collection;  
import java.util.Iterator;  
  
public class IteratorExample {  
    public static void main(String[] args) {  
        // 创建一个Collection集合  
        Collection<String> collection = new ArrayList<>();  
        collection.add("Apple");  
        collection.add("Banana");  
        collection.add("Cherry");  
  
        // 获取迭代器  
        Iterator<String> iterator = collection.iterator();  
  
        // 使用迭代器遍历集合  
        while (iterator.hasNext()) {  
            // 迭代器移动到下一个元素,并返回当前(已移动前)的元素  
            String fruit = iterator.next();   
            System.out.println(fruit); // 打印当前元素  
输出:
Apple
Banana
Cherry
//当读取到最后一个数据,迭代器移到下一个位置,此时已经没有元素了。hasNext返回的就是false。
  

        }  
  
        // 注意:此时迭代器已经遍历完集合,再调用next()会抛出NoSuchElementException  
    }  
}

增强for循环

①增强for可以用来遍历集合或者数组。

②增长for遍历集合,本质就是迭代器遍历集合的简化写法。

语法

对于数组:

for(类型 变量名 : 数组名) {  
    // 循环体  
}

示例:

int[] numbers = {1, 2, 3, 4, 5};  
for(int number : numbers) {  
    System.out.println(number);  
}

对于集合(Collection及其子接口如ListSet等):

for(类型 变量名 : 集合名) {  
    // 循环体  
}

示例:

List<String> fruits = new ArrayList<>();  
fruits.add("Apple");  
fruits.add("Banana");  
fruits.add("Cherry");  
  
for(String fruit : fruits) {  
    System.out.println(fruit);  
}
输出:
Apple  
Banana  
Cherry
注意事项
  1. 修改集合:虽然增强型for循环简化了遍历集合的过程,但它不直接支持在遍历过程中修改集合(如添加或删除元素)。如果需要在遍历过程中修改集合,应该考虑使用传统的for循环或迭代器,并通过迭代器的remove()方法来安全地删除元素。

  2. 类型推断:在增强型for循环中,变量变量名的类型是由数组或集合中元素的类型自动推断的,这得益于Java的类型推断机制(也称为“目标类型推断”)。

  3. 只读访问:由于增强型for循环不提供直接访问元素索引的方式,因此它主要用于对集合进行只读遍历。如果需要基于索引的访问,则应考虑使用传统的for循环或其他方法。

  4. 适用范围:增强型for循环特别适用于遍历数组和实现了Iterable接口的集合(如ListSet等)。对于没有实现Iterable接口的集合(如Map),则不能直接使用增强型for循环进行遍历,但可以通过entrySet()keySet()values()方法将其转换为可迭代对象后再进行遍历。

使用 Consumer 和 Lambda 表达式遍历集合

由于Consumer<T>是一个函数式接口,接口不能创建对象。所以你可以直接去new一个匿名内部类的对象出来。

import java.util.Arrays;
import java.util.List;

public class LambdaExample {
    public static void main(String[] args) {
        List<String> fruits = Arrays.asList("Apple", "Banana", "Cherry");

        // 使用Lambda表达式遍历List
        fruits.forEach(fruit -> System.out.println(fruit));
    }
}

 在没有Lambda表达式和默认方法之前,你可能需要这样遍历集合:

for (String fruit : fruits) {  
    System.out.println(fruit);  
}

或者,使用迭代器:

Iterator<String> iterator = fruits.iterator();  
while (iterator.hasNext()) {  
    String fruit = iterator.next();  
    System.out.println(fruit);  
}

但是,从Java 8开始,你可以使用 forEach 方法和Lambda表达式来更简洁地实现相同的功能:

fruits.forEach(new Consumer<String>() {  
    @Override  
    public void accept(String fruit) {  
        System.out.println(fruit);  
    }  
});

然而,上面的写法仍然使用了匿名内部类。通过Lambda表达式,我们可以进一步化简这个代码:

fruits.forEach(fruit -> System.out.println(fruit));

案例演示

以下是一个简单的示例,展示了如何使用ArrayList来存储和展示电影信息。

首先,我们需要定义一个电影(Movie)类,包含电影的基本属性,如名称、导演、上映年份等。

public class Movie {  
    private String name;  
    private String director;  
    private int releaseYear;  
  
    // 构造方法  
    public Movie(String name, String director, int releaseYear) {  
        this.name = name;  
        this.director = director;  
        this.releaseYear = releaseYear;  
    }  
  
    // Getter 和 Setter 方法  
    public String getName() {  
        return name;  
    }  
  
    public void setName(String name) {  
        this.name = name;  
    }  
  
    public String getDirector() {  
        return director;  
    }  
  
    public void setDirector(String director) {  
        this.director = director;  
    }  
  
    public int getReleaseYear() {  
        return releaseYear;  
    }  
  
    public void setReleaseYear(int releaseYear) {  
        this.releaseYear = releaseYear;  
    }  
  
    // toString 方法,用于展示电影信息  
    @Override  
    public String toString() {  
        return "Movie{" +  
                "name='" + name + '\'' +  
                ", director='" + director + '\'' +  
                ", releaseYear=" + releaseYear +  
                '}';  
    }  
}

接下来,我们可以使用ArrayList来存储多个电影对象,并遍历这个列表来展示所有电影的信息。

import java.util.ArrayList;  
import java.util.List;  
  
public class MovieDisplay {  
    public static void main(String[] args) {  
        // 创建一个ArrayList来存储电影  
        List<Movie> movies = new ArrayList<>();  
  
        // 向ArrayList中添加电影  
        movies.add(new Movie("肖申克的救赎", "弗兰克·德拉邦特", 1994));  
        movies.add(new Movie("控方证人", "比利·怀尔德", 1957));  
        movies.add(new Movie("星际穿越", "克里斯托弗·诺兰", 2014));  
  
        // 遍历ArrayList并展示电影信息  
        for (Movie movie : movies) {  
            System.out.println(movie);  
        }  
    }  
}

在这个示例中,我们首先创建了一个Movie类的实例,用于表示电影。然后,我们创建了一个ArrayList<Movie>的实例来存储多个电影对象。通过调用add方法,我们将几个电影对象添加到列表中。最后,我们使用增强的for循环(也称为"for-each"循环)来遍历这个列表,并调用每个电影对象的toString方法来展示其信息。

这样,我们就完成了使用Collection集合(具体为ArrayList)来展示电影信息的任务。

集合中存储元素本质

在Java中,集合(Collection)框架用于存储和操作对象组。当我们在集合中存储元素时,实际上存储的是对象的引用(即内存地址),而不是对象本身。以下是详细解释这一概念的过程:

对象和引用

在Java中,一切皆为对象。当我们创建一个对象时,比如一个字符串或一个自定义类的实例,JVM会在堆内存中为这个对象分配空间。每个对象都有一个唯一的内存地址。

String fruit = new String("Apple");

在这个例子中,fruit是一个引用变量,它存储了"Apple"字符串对象的内存地址。

集合存储引用

当我们将对象添加到集合中时,实际上是将对象的引用添加到集合的数据结构中。

示例:添加对象到ArrayList
List<String> fruits = new ArrayList<>();
fruits.add(fruit); // 添加的是fruit引用,而不是"Apple"字符串本身

以下是添加对象到ArrayList时发生的事情:

  1. 分配空间ArrayList在内部使用数组来存储元素。当添加一个新元素时,ArrayList可能会检查是否有足够的空间来存储新的引用。

  2. 存储引用:如果空间足够,ArrayList将在数组的下一个空闲位置存储fruit引用(内存地址)。如果数组已满,ArrayList会扩容并复制现有元素到新的数组中,然后再添加新的引用。

总结
  • 存储的是引用:Java集合存储的是对象的引用,而不是对象本身。
  • 操作的是引用:对集合的操作(如添加、获取、更新、删除)实际上是在操作这些引用。
  • 影响垃圾回收:集合中存储的引用会影响对象的垃圾回收过程,因为只要引用存在,对象就不会被回收。

List 集合

List 集合是 Java 集合框架中的一个重要接口,它继承自 Collection 接口,并扩展了一些特有的方法,这些方法主要与列表的索引操作、迭代器的修改以及列表的批量操作相关。下面,我将详细讲解 List 集合中的一些特有方法,并通过代码示例来说明它们的使用。 

List 集合中的特有方法

1. 添加元素

  • add(int index, E element):在列表的指定位置插入指定的元素(可选操作)。如果列表的大小因此超过了其容量,则容量将被增加。
    import java.util.ArrayList;
    import java.util.List;
    
    public class demo1 {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();//一行经典代码
            list.add("Apple");
            list.add("grape");
            list.add(1, "Banana");
            System.out.println(list);
        }
    }
    
    //[Apple, Banana, grape]

2. 访问元素

  • get(int index):返回列表中指定位置的元素。
    List<String> list = new ArrayList<>();  
    list.add("Apple");  
    list.add("Banana");  
    System.out.println(list.get(0)); // 输出:Apple

3. 替换元素

  • set(int index, E element):用指定的元素替换列表中指定位置的元素(可选操作)。
    List<String> list = new ArrayList<>();  
    list.add("Apple");  
    list.add("Banana");  
    list.set(0, "Orange"); // 将索引0的元素替换为"Orange"  
    System.out.println(list); // 输出:[Orange, Banana]

4. 查找元素索引

  • indexOf(Object o):返回列表中首次出现的指定元素的索引,如果列表不包含该元素,则返回 -1。
  • lastIndexOf(Object o):返回列表中最后一次出现的指定元素的索引,如果列表不包含该元素,则返回 -1。
    List<String> list = new ArrayList<>();  
    list.add("Apple");  
    list.add("Banana");  
    list.add("Apple");  
    System.out.println(list.indexOf("Apple")); // 输出:0  
    System.out.println(list.lastIndexOf("Apple")); // 输出:2

5. 移除元素

  • remove(int index):移除列表中指定位置的元素(可选操作)。
  • remove(Object o):从列表中移除首次出现的指定元素(如果存在)(可选操作)。
    List<String> list = new ArrayList<>();  
    list.add("Apple");  
    list.add("Banana");  
    list.remove(0); // 移除索引0的元素  
    System.out.println(list); // 输出:[Banana]  
      
    list.add("Apple");  
    list.remove("Apple"); // 移除首次出现的"Apple"  
    System.out.println(list); // 输出:[]

ArrayList集合 

 

LinkedList集合

LinkedList集合的底层原理

LinkedList是Java集合框架中的另一个重要类,它实现了List接口和Deque接口,用于存储和操作元素的有序集合。与ArrayList不同,LinkedList的底层实现不是基于数组,而是基于双向链表(Doubly Linked List)。双向链表是一种链式数据结构,其中每个元素都包含数据部分和两个指针(或引用),分别指向前一个元素和后一个元素。

LinkedList的底层实现:

LinkedList的底层实现就是基于上述的双向链表结构。每个节点(Node)都包含三个主要部分:元素值(item)、指向前一个节点的指针(prev)和指向后一个节点的指针(next)。LinkedList类内部维护了一个头节点(header)和一个尾节点(tail),分别指向链表中的第一个元素和最后一个元素。

双向链表的特点
  • 双向性:每个节点都包含两个指针,分别指向前一个节点和后一个节点,这使得在链表中的向前和向后遍历都变得非常方便。
  • 动态性:链表的大小可以动态地增加和减少,不需要像数组那样在添加或删除元素时进行大量的数据移动。
  • 内存效率:链表在内存中不是连续存储的,这意味着它可以在内存中非连续的区域存储元素,从而提高了内存使用的灵活性。
LinkedList的特有方法

LinkedList除了继承自List接口的方法外,还实现了Deque接口,因此它拥有了一些特有的方法,这些方法主要用于在链表的头部和尾部进行操作,如添加、删除和访问元素。以下是一些特有的方法:

  • void addFirst(E e):在链表开头添加元素。
  • void addLast(E e):在链表末尾添加元素(等同于add(E e))。
  • E getFirst():返回链表的第一个元素(不移除)。
  • E getLast():返回链表的最后一个元素(不移除)。
  • E removeFirst():移除并返回链表的第一个元素。
  • E removeLast():移除并返回链表的最后一个元素。
  • boolean offerFirst(E e):在链表开头插入指定的元素(如果可能)。
  • boolean offerLast(E e):在链表末尾插入指定的元素(如果可能)。
  • E pollFirst():移除并返回链表的第一个元素;如果列表为空,则返回null。
  • E pollLast():移除并返回链表的最后一个元素;如果列表为空,则返回null。
  • E peekFirst():返回链表的第一个元素(不移除);如果列表为空,则返回null。
  • E peekLast():返回链表的最后一个元素(不移除);如果列表为空,则返回null。

代码示例:

import java.util.LinkedList;  
  
public class LinkedListExample {  
    public static void main(String[] args) {  
        LinkedList<String> list = new LinkedList<>();  
  
        // 添加元素  
        list.addFirst("First");  
        list.addLast("Last");  
        list.add("Middle");  
  
        // 访问元素  
        System.out.println("First element: " + list.getFirst());  
        System.out.println("Last element: " + list.getLast());  
  
        // 移除元素  
        System.out.println("Removed first element: " + list.removeFirst());  
        System.out.println("Now, first element: " + list.getFirst());  
  
        // 遍历链表  
        for (String s : list) {  
            System.out.println(s);  
        }  
  
        // 使用Deque特有的方法  
        list.offerFirst("New First");  
        System.out.println("After offering new first: " + list.getFirst());  
  
        String lastElement = list.pollLast();  
        System.out.println("Removed last element: " + lastElement);  
        System.out.println("Now, last element: " + list.peekLast());  
    }  
}
 LinkedList 集合业务场景 

LinkedList 集合在 Java 中因其基于双向链表的实现而具有一系列独特的特性,这些特性使得它在某些业务场景中特别适用。以下是一些 LinkedList 集合适合的业务场景及其代码示例:

1. 需要频繁插入和删除元素的场景

由于 LinkedList 在插入和删除元素时不需要移动其他元素(只需要修改指针),因此它在需要频繁进行这些操作的情况下性能优于基于数组的集合(如 ArrayList)。

代码示例

import java.util.LinkedList;  
  
public class LinkedListExample {  
    public static void main(String[] args) {  
        LinkedList<String> list = new LinkedList<>();  
  
        // 插入元素  
        list.add("Element 1");  
        list.add("Element 2");  
        list.addFirst("First Element"); // 在链表开头插入  
  
        // 删除元素  
        list.remove("Element 2"); // 删除指定元素  
        list.removeFirst(); // 删除并返回链表开头的元素  
  
        // 遍历链表  
        for (String element : list) {  
            System.out.println(element);  
        }  
    }  
}

2. 作为栈(Stack)或队列(Queue)的实现

LinkedList 实现了 Deque 接口,因此它可以作为栈或队列使用。栈是后进先出(LIFO)的数据结构,而队列是先进先出(FIFO)的数据结构。

作为栈使用的代码示例

import java.util.LinkedList;  
  
public class StackExample {  
    public static void main(String[] args) {  
        LinkedList<Integer> stack = new LinkedList<>();  
  
        // 入栈  
        stack.push(1);  
        stack.push(2);  
  
        // 出栈  
        System.out.println(stack.pop()); // 输出 2  
        System.out.println(stack.peek()); // 查看栈顶元素,不移除,输出 1  
    }  
}

注意:虽然 Java 提供了 Stack 类作为栈的实现,但 LinkedList 通常是更好的选择,因为它更灵活且效率更高。

作为队列使用的代码示例(尽管 Java 提供了 LinkedList 实现的 LinkedListQueue,但这里为了说明 LinkedList 的用途,我们手动模拟队列操作):

import java.util.LinkedList;  
  
public class QueueExample {  
    public static void main(String[] args) {  
        LinkedList<String> queue = new LinkedList<>();  
  
        // 入队  
        queue.addLast("Element 1");  
        queue.offer("Element 2"); // 等同于 addLast  
  
        // 出队  
        System.out.println(queue.poll()); // 移除并返回队列头部的元素,输出 Element 1  
        System.out.println(queue.peek()); // 查看队列头部的元素,不移除,输出 Element 2  
  
        // 遍历队列  
        for (String element : queue) {  
            System.out.println(element); // 此时队列中只剩 Element 2  
        }  
    }  
}

Set集合

前言

Java中的Set集合主要用于存储不重复的元素。Set接口继承自Collection接口,但它不保证维护任何特定的顺序(除非该集合是某个特定类型的,比如LinkedHashSet,它按照元素被插入的顺序来遍历元素)。Set接口的实现类主要有HashSetLinkedHashSetTreeSet等。

注意:

Set要用到的常用方法基本上就是Collection提供的。自己几乎没有额外新增一些常用功能。 

下面我将通过Java代码来详细对比HashSetLinkedHashSetTreeSet这三种Set集合的特点。

HashSet

HashSet基于哈希表实现,它不保证集合的迭代顺序,且允许使用null元素。

import java.util.HashSet;  
import java.util.Set;  
  
public class HashSetExample {  
    public static void main(String[] args) {  
        Set<String> hashSet = new HashSet<>();  
        hashSet.add("Apple");  
        hashSet.add("Banana");  
        hashSet.add("Cherry");  
        hashSet.add(null); // 允许null元素  
  
        // 尝试添加重复元素  
        hashSet.add("Apple"); // 添加失败,集合不变  
  
        // 遍历HashSet,注意输出顺序可能与插入顺序不同   
        for (String fruit : hashSet) {  
            System.out.println(fruit);  
        }  
输出
null
Apple
Cherry
Banana
    }  
}

LinkedHashSet

LinkedHashSetHashSet的一个子类,它维护着一个运行于所有条目的双重链接列表。这意味着它遍历元素的顺序是按照元素被插入的顺序来进行的。

import java.util.LinkedHashSet;  
import java.util.Set;  
  
public class LinkedHashSetExample {  
    public static void main(String[] args) {  
        Set<String> linkedHashSet = new LinkedHashSet<>();  
        linkedHashSet.add("Apple");  
        linkedHashSet.add("Banana");  
        linkedHashSet.add("Cherry");  
  
        // 遍历LinkedHashSet,输出顺序与插入顺序相同  
        for (String fruit : linkedHashSet) {  
            System.out.println(fruit);  
//输出
Apple
Banana
Cherry

        }  
    }  
}
TreeSet

TreeSet基于红黑树实现,它能够确保集合元素处于排序状态。TreeSet不允许null元素,且元素的排序方式可以是自然排序(元素的类实现了Comparable接口),或者根据创建TreeSet时提供的Comparator进行排序。

import java.util.Set;  
import java.util.TreeSet;  
  
public class TreeSetExample {  
    public static void main(String[] args) {  
        // 使用自然排序(假设String类已经实现了Comparable接口)  
        Set<String> treeSet = new TreeSet<>();  
        treeSet.add("Banana");  
        treeSet.add("Apple");  
        treeSet.add("Cherry");  
  
        // 尝试添加null元素(会抛出NullPointerException)  
        // treeSet.add(null); // 取消注释将抛出异常  
  
        // 遍历TreeSet,输出顺序是排序后的  
        System.out.println("TreeSet (Natural Ordering):");  
        for (String fruit : treeSet) {  
            System.out.println(fruit);  
        }  
  
        // 使用自定义Comparator(这里只是示例,实际String已经可排序)  
        Set<String> customTreeSet = new TreeSet<>((s1, s2) -> s2.compareTo(s1)); // 逆序  
        customTreeSet.add("Banana");  
        customTreeSet.add("Apple");  
        customTreeSet.add("Cherry");  
  
        // 遍历自定义排序的TreeSet  
        System.out.println("TreeSet (Custom Ordering):");  
        for (String fruit : customTreeSet) {  
            System.out.println(fruit);  
        }  
    }  
}

//输出:
TreeSet (Natural Ordering):
Apple
Banana
Cherry
TreeSet (Custom Ordering):
Cherry
Banana
Apple

HashSet集合详解

HashSet集合的底层原理

在我们正式了解HashSet集合的底层原理之前,我们需要先搞清楚一个前置知识:哈希值!

一、哈希值概述

哈希值,也称为哈希码(Hash Code),是JDK根据对象、字符串或数字等通过哈希算法计算出来的一个int类型的数值。这个数值代表了输入数据的某种“指纹”或“摘要”,用于在数据处理中快速定位或比较数据。

二、对象哈希值的特点
  1. 唯一性(逻辑上)
    • 在Java中,同一个对象多次调用hashCode()方法返回的哈希值是相同的。这是由哈希算法的性质决定的。
    • 需要注意的是,这里的唯一性是逻辑上的唯一性,因为哈希算法的输出范围是有限的(对于hashCode()方法来说,其输出是一个int类型的数值,范围从-231到231-1),而输入的数据集合是无限的,因此必然存在不同的输入产生相同哈希值的情况,这称为哈希冲突。
  2. 不同对象的哈希值不同(默认情况下)
    • 在默认情况下,不同对象的哈希码值是不同的。这是因为每个对象在JVM中的内存地址是唯一的(尽管这里的地址是虚拟的,不是真实的物理地址),而哈希码通常是基于对象的内存地址或其他特定信息计算得出的。
    • 但是,通过重写hashCode()方法,可以实现让不同对象的哈希值相同。这在某些特定场景下是有用的,比如在使用哈希表时,如果希望将多个对象视为相等(尽管它们在物理上不是同一个对象),则可以重写它们的hashCode()方法使它们返回相同的哈希值。
  3. 与equals方法的关系
    • 在Java中,hashCode()方法和equals()方法是紧密相关的。根据Java的规范,如果两个对象通过equals()方法比较相等,那么它们的哈希码也必须相同。这是为了确保在使用哈希表等数据结构时,相等的对象能够正确地被映射到同一个位置。
    • 反之,如果两个对象的哈希码相同,这并不意味着它们一定相等(即equals()方法返回true)。这只能说明它们在哈希表中可能会映射到同一个位置,但在实际比较时还需要通过equals()方法来确定它们是否真正相等。
HashSet集合的底层实现 

TreeSet集合详解

 

TreeSet自定义排序规则

在Java中,TreeSet 是一个基于红黑树实现的集合,它可以确保元素处于排序状态。自定义排序规则主要有两种方式:

第一种方式:实现Comparable接口

当集合中存储的元素是自定义类时,可以让该类实现Comparable接口,并重写compareTo方法来指定排序规则。

以下是一个示例代码:

import java.util.TreeSet;

// 自定义类实现Comparable接口
class Person implements Comparable<Person> {
    private String name;
    private int age;

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

    @Override
    public int compareTo(Person other) {
        // 按年龄升序排序
        return Integer.compare(this.age, other.age);
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + '}';
    }
}

public class Main {
    public static void main(String[] args) {
        TreeSet<Person> people = new TreeSet<>();
        people.add(new Person("Alice", 30));
        people.add(new Person("Bob", 25));
        people.add(new Person("Charlie", 35));

        for (Person person : people) {
            System.out.println(person);
        }
    }
}

在这个例子中,Person类实现了Comparable接口,并重写了compareTo方法。在compareTo方法中,我们根据age属性来比较两个Person对象。

第二种方式:使用Comparator对象

如果不希望修改类的定义,或者需要多种排序方式,可以通过TreeSet的有参构造器传递一个Comparator对象。

以下是一个示例代码:

import java.util.Comparator;
import java.util.TreeSet;

class Person {
    private String name;
    private int age;

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

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + '}';
    }
}

// 自定义Comparator
class AgeComparator implements Comparator<Person> {
    @Override
    public int compare(Person p1, Person p2) {
        // 按年龄降序排序
        return Integer.compare(p2.age, p1.age);
    }
}

public class Main {
    public static void main(String[] args) {
        TreeSet<Person> people = new TreeSet<>(new AgeComparator());
        people.add(new Person("Alice", 30));
        people.add(new Person("Bob", 25));
        people.add(new Person("Charlie", 35));

        for (Person person : people) {
            System.out.println(person);
        }
    }
}

在这个例子中,我们定义了一个AgeComparator类,它实现了Comparator接口并重写了compare方法。在compare方法中,我们根据age属性来比较两个Person对象,并且这次是按年龄降序排序。

在创建TreeSet时,我们将AgeComparator的实例传递给了TreeSet的构造器,这样集合就会使用我们自定义的比较规则。

集合的并发修改异常

在Java中,并发修改异常(ConcurrentModificationException)是在迭代器遍历集合时,如果集合的内容被修改(通常是添加、删除操作),迭代器就会抛出这个异常。这是因为在迭代过程中修改集合的结构会导致迭代器不知道如何继续遍历,所以抛出异常以避免潜在的错误。

并发修改异常示例

下面是一个简单的例子,展示了在遍历ArrayList时删除元素导致的并发修改异常:

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");

        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String item = iterator.next();
            if (item.equals("B")) {
                list.remove(item); // 这里会抛出ConcurrentModificationException
            }
        }
    }
}

在上面的代码中,我们试图在迭代过程中通过list.remove(item)删除元素,这会触发并发修改异常。

如何避免并发修改异常

为了避免在遍历集合时修改集合内容导致的并发修改异常,可以采用以下几种方法:

使用迭代器的remove方法

迭代器提供了一个remove方法,可以在遍历时安全地删除元素:

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String item = iterator.next();
    if (item.equals("B")) {
        iterator.remove(); // 安全地删除元素
    }
}

使用for-each循环结合ListremoveIf方法

Java 8 引入了removeIf方法,允许你传递一个Predicate来删除满足条件的所有元素:

list.removeIf(item -> item.equals("B"));

或者结合for-each循环:

for (String item : list) {
    if (item.equals("B")) {
        list.remove(item);
    }
}

但是请注意,上述for-each循环中直接使用list.remove(item)仍然不安全,因为它会修改列表结构。如果必须使用for-each循环,请使用以下方法。

使用索引遍历并手动处理索引

可以通过索引来遍历集合,并手动处理索引以避免并发修改异常:

for (int i = 0; i < list.size(); i++) {
    if (list.get(i).equals("B")) {
        list.remove(i);
        i--; // 重要:删除元素后需要递减索引
    }
}

在这个方法中,删除元素后需要递减索引i,因为删除元素后,后续元素会前移,所以下一个索引i需要重新检查。

Collections

java.util.Collections 类提供了许多用于处理集合(Collection)和映射(Map)的静态方法。以下是一些常用的静态方法,并通过代码示例来解释它们的使用。

1. sort(List<T> list)

对列表中的元素进行排序。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CollectionsSortExample {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(3);
        numbers.add(1);
        numbers.add(4);
        numbers.add(2);

        Collections.sort(numbers);
        System.out.println(numbers); // 输出: [1, 2, 3, 4]
    }
}

2. sort(List<T> list, Comparator<? super T> c)

根据指定的比较器对列表进行排序。

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class CollectionsSortWithComparatorExample {
    public static void main(String[] args) {
        List<String> words = new ArrayList<>();
        words.add("banana");
        words.add("apple");
        words.add("cherry");

        // 逆序排序
        Collections.sort(words, Comparator.reverseOrder());
        System.out.println(words); // 输出: [cherry, banana, apple]
    }
}

3. reverse(List<?> list)

反转列表中元素的顺序。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CollectionsReverseExample {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
        numbers.add(4);

        Collections.reverse(numbers);
        System.out.println(numbers); // 输出: [4, 3, 2, 1]
    }
}

4. shuffle(List<?> list)

使用默认的随机源随机排列列表中的元素。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CollectionsShuffleExample {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
        numbers.add(4);

        Collections.shuffle(numbers);
        System.out.println(numbers); // 输出: [元素随机排列]
    }
}

5. binarySearch(List<? extends Comparable<? super T>> list, T key)

使用二分搜索法搜索列表,以查找指定元素。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CollectionsBinarySearchExample {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
        numbers.add(4);

        int index = Collections.binarySearch(numbers, 3);
        System.out.println("Index of 3: " + index); // 输出: Index of 3: 2
    }
}

6. fill(List<? super T> list, T obj)

用指定的元素替换列表中的所有元素。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CollectionsFillExample {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
        numbers.add(4);

        Collections.fill(numbers, 0);
        System.out.println(numbers); // 输出: [0, 0, 0, 0]
    }
}

7. replaceAll(List<T> list, T oldVal, T newVal)

将列表中所有出现的老值替换为新值。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CollectionsReplaceAllExample {
    public static void main(String[] args) {
        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(1);
        numbers.add(4);

        Collections.replaceAll(numbers, 1, 5);
        System.out.println(numbers); // 输出: [5, 2, 5, 4]
    }
}

8.Collections.addAll(Collection<? super T> c, T... elements)

此方法将所有指定的元素添加到指定的集合中。这是一个可变参数方法,因此你可以传递任意数量的元素。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CollectionsAddAllExample {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        
        // 使用Collections.addAll()方法添加多个元素
        Collections.addAll(list, "Apple", "Banana", "Cherry", "Date");
        
        System.out.println(list); // 输出: [Apple, Banana, Cherry, Date]
    }
}

三:Map集合

认识Map集合

Map集合的业务场景

Map集合在Java中主要用于存储键值对(key-value pairs),其中每个键都是唯一的,而值可以重复。以下是一些Map集合的主要业务场景:

  1. 购物车应用:Map集合非常适合用于实现购物车功能。在这种场景下,可以将商品作为键(key),购买数量作为值(value)。这样,每个商品都与一个购买数量相对应,方便管理和更新 3 。

  2. 缓存实现:Map集合可用于实现缓存,存储键值对,其中键是数据的标识符,值是数据本身。这种应用在需要快速查找和更新数据的情况下非常有效。

  3. 关联数组:Map可以作为一种关联数组使用,其中键是索引,值是存储的数据。这在处理需要动态索引的数据结构时非常有用。

  4. 配置参数存储:在应用程序中,Map集合可用于存储和管理配置参数,其中键是参数名,值是参数值。

  5. 映射关系维护:在需要维护两个不同域之间映射关系的情况下,例如用户ID与用户详情之间的映射,Map集合是理想的选择。

Map集合体系

Map集合特点 

Map集合是Java中一种非常重要的数据结构,它用于存储键值对(key-value pairs)。Map集合的特点主要包括以下几个方面:

  1. 键值对映射:Map集合中的每个元素都是一个键值对,键(Key)是唯一的,而值(Value)则可以是重复的。通过键可以快速地找到对应的值。

  2. 无序性:Map集合不保证映射的顺序;特别是它不保证该顺序恒久不变。不过,有些Map实现(如LinkedHashMap)可以保持插入顺序,而TreeMap则可以按照键的自然顺序或创建时提供的Comparator进行排序。

  3. 键的唯一性:Map集合中的键必须是唯一的,不允许有重复的键。如果尝试使用已存在的键向Map中添加元素,则新的值会替换旧的值。

  4. 支持null键和null值:大多数Map实现(如HashMap)允许使用null作为键和值,但具体行为可能因实现而异。例如,TreeMap不允许使用null作为键,但允许使用null作为值。

  5. 丰富的操作方法:Map集合提供了丰富的操作方法,如put(K key, V value)用于添加或更新键值对,get(Object key)用于根据键获取值,remove(Object key)用于根据键移除键值对,containsKey(Object key)containsValue(Object value)用于检查Map中是否包含特定的键或值等。

下面通过HashMap的代码示例来详细解释Map集合的特点:

import java.util.HashMap;  
import java.util.Map;  
  
public class MapExample {  
    public static void main(String[] args) {  
        // 创建一个HashMap实例  
        Map<String, Integer> map = new HashMap<>();  
  
        // 向Map中添加键值对  
        map.put("Apple", 100);  
        map.put("Banana", 200);  
        map.put("Cherry", 150);  
  
        // 尝试添加重复的键,会覆盖旧的值  
        map.put("Apple", 120); // 现在,"Apple"对应的值变为120  
  
        // 通过键获取值  
        System.out.println("The value for Apple is: " + map.get("Apple")); // 输出: The value for Apple is: 120  
  
        // 检查Map中是否包含特定的键或值  
        System.out.println("Does the map contain the key 'Banana'? " + map.containsKey("Banana")); // 输出: true  
        System.out.println("Does the map contain the value 200? " + map.containsValue(200)); // 输出: true  
  
        // 移除键值对  
        map.remove("Banana");  
  
        // 遍历Map(注意:这里只是演示遍历的一种方式,实际中有多种遍历Map的方法)  
        for (Map.Entry<String, Integer> entry : map.entrySet()) {  
            System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());  
        }  
  
        // 输出可能类似于:  
        // Key = Apple, Value = 120  
        // Key = Cherry, Value = 150  
    }  
}

Map集合常见方法 

Map集合在Java中提供了丰富的操作方法,用于添加、删除、获取、检查以及遍历键值对。以下是Map集合的一些常用方法,并通过代码示例进行详细说明:

1. 添加元素

  • put(K key, V value): 向Map中添加一个键值对。如果Map之前包含该键的映射,则替换旧的值(返回旧的值)。如果不包含,则添加新的键值对。
Map<String, Integer> map = new HashMap<>();  
map.put("Apple", 100);  
map.put("Banana", 200);

2. 获取Map的大小

  • size(): 返回Map中键值对的数量。
int size = map.size(); // 返回Map中键值对的数量

3. 清除Map

  • clear(): 移除Map中的所有键值对。
int size = map.size(); // 返回Map中键值对的数量

4. 判断Map是否为空

  • isEmpty(): 如果Map不包含任何键值对,则返回true
​
boolean isEmpty = map.isEmpty(); // 如果Map为空,则返回true

5. 获取元素

  • get(Object key): 根据键获取对应的值。如果Map中包含该键的映射,则返回对应的值;如果不包含,则返回null
Integer value = map.get("Apple"); // 返回100

6. 删除元素

  • remove(Object key): 根据键移除键值对。如果Map中包含该键的映射,则移除它,并返回被移除的值;如果不包含,则返回null
map.remove("Banana"); // 移除键为"Banana"的键值对

7. 检查元素

  • containsKey(Object key): 检查Map中是否包含指定的键。
boolean containsKey = map.containsKey("Apple"); // 返回true
  • containsValue(Object value): 检查Map中是否包含指定的值。注意,这可能需要遍历整个Map,因为值可能不唯一。
boolean containsValue = map.containsValue(100); // 返回true

 还有一些常用的方法,下面通过完整的代码,演示一下这些常见的方法。

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

public class MapMethodsExample {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();

        // 添加键值对
        map.put("key1", "value1");
        map.put("key2", "value2");
        map.put("key3", "value3");

        // 获取值
        System.out.println("Value for key1: " + map.get("key1"));

        // 判断是否包含键或值
        System.out.println("Contains key 'key2': " + map.containsKey("key2"));
        System.out.println("Contains value 'value3': " + map.containsValue("value3"));

        // 移除键值对
        map.remove("key2");

        // 获取所有键
        Set<String> keys = map.keySet();
        System.out.println("Keys: " + keys);

        // 获取所有值
        System.out.println("Values: " + map.values());

        // 获取所有键值对
        Set<Map.Entry<String, String>> entries = map.entrySet();
        for (Map.Entry<String, String> entry : entries) {
            System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
        }

        // Map的大小
        System.out.println("Size of map: " + map.size());

        // 清空Map
        map.clear();
        System.out.println("Map after clear: " + map);
    }
}

Map集合的遍历方式 

Map集合是Java中一种非常重要的集合类型,用于存储键值对(key-value pairs)。Map集合的遍历主要有以下两种方式:

1. 通过键遍历值

这种方式是通过Map的keySet()方法获取所有的键,然后通过get(key)方法来获取对应的值。

以下是具体的代码示例:

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

public class MapTraversalExample {
    public static void main(String[] args) {
        // 创建一个Map实例
        Map<String, String> map = new HashMap<>();
        // 添加一些键值对
        map.put("key1", "value1");
        map.put("key2", "value2");
        map.put("key3", "value3");

        // 通过keySet()方法获取所有的键
        Set<String> keys = map.keySet();
        // 遍历键
        for (String key : keys) {
            // 通过get方法获取每个键对应的值
            String value = map.get(key);
            System.out.println("Key: " + key + ", Value: " + value);
        }
    }
}
2. 通过Map.Entry遍历键值对

这种方式是通过Map的entrySet()方法获取Map.Entry的集合,然后通过Map.Entry的getKey()和getValue()方法来获取键和值。

以下是具体的代码示例:

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

public class MapTraversalEntryExample {
    public static void main(String[] args) {
        // 创建一个Map实例
        Map<String, String> map = new HashMap<>();
        // 添加一些键值对
        map.put("key1", "value1");
        map.put("key2", "value2");
        map.put("key3", "value3");

        // 通过entrySet()方法获取Map.Entry的集合
        Set<Map.Entry<String, String>> entries = map.entrySet();
        // 遍历Map.Entry
        for (Map.Entry<String, String> entry : entries) {
            // 通过Map.Entry的getKey和getValue方法获取键和值
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println("Key: " + key + ", Value: " + value);
        }
    }
}

两种方法各有优缺点:

  • 第一种方法在键集合不是很大时效率较高,但如果只需要键或值,这种方法比较合适。
  • 第二种方法在遍历的时候效率较高,尤其是在键和值都需要时,因为它不需要再次调用get方法来获取值,减少了查找的hMap 

HashMap 

哈希Map的特点

哈希Map(HashMap)是Java中常用的集合类,用于存储键值对(key-value pairs)。其特点主要包括:

  1. 快速访问:通过哈希码(hash code)快速定位键值对的位置,从而实现快速的插入、删除和查找操作。
  2. 无序性:HashMap不保证映射的顺序,即迭代器的分裂器提供的是弱一致性的视图,这反映了某一时间点或者迭代开始时集合的状态,不反映任何后续修改。
  3. 允许null键和null值:HashMap允许使用null作为键和值,但最多只能有一个null键。
  4. 动态扩容:当HashMap中的元素数量超过其容量和加载因子(load factor)的乘积时,HashMap会自动扩容,并重新哈希所有元素以减少冲突。
底层原理

HashMap的底层原理主要包括以下几个方面:

  1. 哈希算法:HashMap通过哈希算法将键的哈希码映射到数组的索引上,以减少冲突并提高访问速度。在Java 8中,HashMap优化了哈希算法,通过hashCode()的高16位异或低16位来提高哈希碰撞分布性。
  2. 数组+链表/红黑树:HashMap的底层数据结构是一个数组,每个数组元素是一个链表(或红黑树)。当多个键映射到相同的索引位置时,它们会被存储在同一个链表或红黑树中。在Java 8中,当链表长度超过一定阈值(默认为8)时,链表会转换为红黑树,以减少查找时间。
  3. 扩容与rehash:当HashMap中的元素数量超过其容量和加载因子的乘积时,HashMap会进行扩容。扩容后,原数组中的所有元素需要重新计算哈希值,并放入新的数组中。
如何实现键的唯一性

HashMap通过hashCode()equals()方法来实现键的唯一性。具体过程如下:

  1. 计算哈希值:当向HashMap中添加一个键值对时,HashMap会调用键的hashCode()方法来获取该键的哈希值。
  2. 定位索引:根据哈希值和数组的长度,通过一定的算法(如取模运算)计算出该键在数组中的索引位置。
  3. 处理冲突:如果索引位置上已经存在其他键,则会比较这些键的hashCode()值。如果hashCode()值不同,则认为它们不相等;如果hashCode()值相同,则会进一步调用equals()方法来比较键的内容是否相等。
    • 如果equals()方法返回true,则认为这两个键相等,新添加的键值对会覆盖原有的键值对。
    • 如果equals()方法返回false,则说明发生了哈希冲突,此时会将新添加的键值对以链表或红黑树的形式存储在该索引位置上。

通过这种方式,HashMap可以确保每个键在HashMap中都是唯一的。需要注意的是,在自定义类作为HashMap的键时,需要重写hashCode()equals()方法,以确保它们的行为与HashMap的期望一致。

LinkedHashMap 

TreeMap 

TreeMap集合的特点

TreeMap是Java集合框架中的一部分,它实现了NavigableMap接口,并基于红黑树(Red-Black tree)的数据结构进行实现。TreeMap的主要特点包括:

  1. 键值对存储:TreeMap允许存储键值对,其中键(key)是唯一的,而值(value)可以重复。
  2. 有序性:TreeMap中的元素会根据键的自然顺序或者通过构造时提供的Comparator进行排序。这意味着TreeMap能够提供一个有序的键值对访问方式,例如按照键的升序或降序遍历集合。
  3. 高效的存储和检索:通过使用红黑树,TreeMap能够以对数时间(O(log n))的复杂度进行存储和检索操作,即使在大规模数据量下也能保持高效性能。
  4. 不允许键重复:TreeMap中的键必须是唯一的,而值则没有这样的限制。
TreeMap集合的原理

TreeMap基于红黑树实现,这是一种自平衡的二叉查找树。红黑树通过确保树从根到叶子的最长路径不会超过最短路径的两倍长来保持树的平衡。在TreeMap中,每个节点代表一个键值对,节点的键用于在树中定位节点,而值则与键相关联。当向TreeMap添加、删除或查找元素时,红黑树的性质会被维护,以确保操作的高效性。

TreeMap集合对定义类型的对象排序指定排序规则的方式

对于定义类型的对象,TreeMap可以通过两种方式指定排序规则:

  1. 自然排序(Natural Ordering)
    • 要求键对象所属的类实现Comparable接口,并覆盖compareTo方法。在compareTo方法中定义排序规则。当创建TreeMap实例时,如果没有提供Comparator,则会使用键对象的自然排序。
    • 示例:如果有一个Company类,并希望根据公司的编号(no)进行排序,那么可以让Company类实现Comparable接口,并在compareTo方法中根据no进行比较。
  2. 定制排序(Custom Ordering)
    • 在创建TreeMap实例时,通过构造函数提供一个Comparator对象。Comparator是一个接口,其中定义了compare方法,用于比较两个对象。通过这种方式,可以独立于键对象的自然排序来定义排序规则。
    • 示例:同样以Company类为例,如果希望根据公司的名称(name)而不是编号进行排序,可以创建一个实现了Comparator接口的类,并在其compare方法中根据name进行比较。然后,将这个Comparator对象传递给TreeMap的构造函数。

以下是通过代码示例来详细解释TreeMap中定义类型的对象如何采用自然排序(Natural Ordering)和定制排序(Custom Ordering)的两种方式。

自然排序(Natural Ordering)

自然排序要求键对象所属的类实现Comparable接口,并覆盖compareTo方法。这里以Company类为例,假设我们想要根据公司的ID(id)来进行排序。

import java.util.TreeMap;  
  
// Company类实现Comparable接口  
class Company implements Comparable<Company> {  
    private int id;  
    private String name;  
  
    // 构造函数、getter和setter省略  
  
    // 实现compareTo方法  
    @Override  
    public int compareTo(Company other) {  
        return Integer.compare(this.id, other.id); // 根据id进行升序排序  
    }  
  
    // toString方法省略,用于打印公司信息  
}  
  
public class TreeMapExampleNaturalOrdering {  
    public static void main(String[] args) {  
        TreeMap<Company, String> map = new TreeMap<>();  
  
        // 添加元素到TreeMap中  
        map.put(new Company(1, "Google"), "Tech Giant");  
        map.put(new Company(3, "Facebook"), "Social Media");  
        map.put(new Company(2, "Apple"), "Consumer Electronics");  
  
        // 遍历并打印TreeMap  
        for (Map.Entry<Company, String> entry : map.entrySet()) {  
            System.out.println(entry.getKey() + ": " + entry.getValue());  
        }  
    }  
}

定制排序(Custom Ordering)

定制排序在创建TreeMap实例时,通过构造函数提供一个Comparator对象来实现。这里同样以Company类为例,但这次我们不要求Company类实现Comparable接口,而是定义一个外部的Comparator

import java.util.Comparator;  
import java.util.TreeMap;  
  
// Company类不需要实现Comparable接口  
class Company {  
    private int id;  
    private String name;  
  
    // 构造函数、getter和setter省略  
  
    // toString方法省略,用于打印公司信息  
}  
  
// 定义一个Comparator来根据公司的name进行排序  
class CompanyNameComparator implements Comparator<Company> {  
    @Override  
    public int compare(Company c1, Company c2) {  
        return c1.getName().compareTo(c2.getName()); // 假设getName方法已定义  
    }  
}  
  
public class TreeMapExampleCustomOrdering {  
    public static void main(String[] args) {  
        // 使用自定义的Comparator来创建TreeMap  
        TreeMap<Company, String> map = new TreeMap<>(new CompanyNameComparator());  
  
        // 添加元素到TreeMap中  
        map.put(new Company(1, "Google"), "Tech Giant");  
        map.put(new Company(3, "Apple"), "Consumer Electronics");  
        map.put(new Company(2, "Facebook"), "Social Media");  
  
        // 遍历并打印TreeMap  
        for (Map.Entry<Company, String> entry : map.entrySet()) {  
            System.out.println(entry.getKey() + ": " + entry.getValue());  
        }  
    }  
}

 

 

  • 33
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值