List、Map、Set 三个接口存取元素时,各有什么特点

发布于:2024-10-15 ⋅ 阅读:(52) ⋅ 点赞:(0)

List、Map、Set是Java集合框架中的三个核心接口,它们在存取元素时各自具有独特的特点。以下是对这三个接口存取元素特点的详细分析:

List接口

  • 有序性

List中的元素是有序的,它们按照插入的顺序进行排列。

  • 可重复性

List允许存储重复的元素,即List中可以有多个相同的元素。

  • 索引访问

List提供了基于索引的访问方式,可以通过索引(位置)来访问和修改List中的元素。这类似于数组的访问方式。

  • 实现类

List接口的主要实现类包括ArrayList和LinkedList。ArrayList基于动态数组实现,适合随机访问,但插入和删除效率较低;LinkedList基于双向链表实现,插入和删除操作效率高,但查询效率较低。

Set接口

  • 元素唯一性

Set中的元素是唯一的,不允许重复。如果尝试向Set中添加一个已经存在的元素,则添加操作会失败。其实通过查看源码就知道其实是Set的add方法中通过map不能存储重复的key来保证唯一的,Set 集合的 add 方法有一个 boolean 的返回值,当集合中没有某个元素,此时 add 方法可成功加入该元素时, 则返回 true,当集合含有与某个元素 equals 相等的元素时,此时 add 方法无法加入该元素, 返回结果为 false

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}
  • 无序性

Set中的元素没有特定的顺序,即不保证元素存储的顺序与插入顺序一致。但是,某些Set的实现类(如LinkedHashSet)可以维护元素的插入顺序。

  • 无索引访问

Set不支持通过索引来访问和修改元素。它主要通过迭代器(Iterator)或增强型for循环来遍历元素。

  • 实现类

Set接口的主要实现类包括HashSet、LinkedHashSet和TreeSet。HashSet基于哈希表实现,查找效率高,但不保证顺序;LinkedHashSet继承自HashSet,使用双向链表维护插入顺序;TreeSet基于红黑树实现,可以按元素的自然顺序或自定义顺序进行排序。

HashSet延伸:
HashSet的存储方式

注意HashSet的存储方式是按照 hashcode 值的某种运算方式进行存储,而不是直接按 hashCode 值的大小进行存储。通过查看源码可以看到运算方式如下:

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
HashSet如何比较相等

在 Java 中,HashSet 是一个基于哈希表的数据结构,用于存储不重复的元素。当你将一个对象添加到 HashSet 中时,它会根据对象的哈希码(通过调用对象的 hashCode() 方法)来确定该对象应该存放在哪个桶(bucket)中。为了确定两个对象是否相等,HashSet 依赖两个条件:

  1. 哈希码相等:两个对象的哈希码必须相等。这是通过调用对象的 hashCode() 方法来确定的。
  2. equals 方法:如果两个对象的哈希码相等,那么 HashSet 会进一步调用对象的 equals() 方法来确认它们是否确实相等。

因此,要使 HashSet 正确比较两个对象是否相等,必须满足以下要求:

  • 覆盖 hashCode() 方法:确保两个相等的对象具有相同的哈希码。
  • 覆盖 equals() 方法:确保 equals() 方法定义了两个对象相等的逻辑。

 代码实例:

class Student {
    private String name;
    private int age;

    public Student(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;
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

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

public class HashSetEqualsTest {
    public static void main(String[] args) {
        HashSet<Student> set = new HashSet<>();
        Student p1 = new Student("Alice", 30);
        Student p2 = new Student("Alice", 30);

        set.add(p1);
        set.add(p2);

        System.out.println("Set size: " + set.size()); // 输出应为 1,因为 p1 和 p2 相等
        System.out.println(set); // 输出 Student{name='Alice', age=30}
    }
}

输出结果:

Set size: 1
[Student{name='Alice', age=30}]

 如果Student不实现hashcodeequals方法,则输入结果如下:

Set size: 2
[Student{name='Alice', age=30}, Student{name='Alice', age=30}]

Map接口

  • 键值对存储

Map以键值对(key-value)的形式存储数据。每个键都是唯一的,但值可以重复。

  • 无序性

Map中的键值对没有特定的顺序,即不保证键值对的存储顺序与插入顺序一致。但是,某些Map的实现类(如LinkedHashMap和TreeMap)可以维护键值对的顺序。

  • 键的唯一性

Map中的键是唯一的,每个键最多映射到一个值。Map使用键的equals()和hashCode()方法来比较键是否相等。

  • 值的多重性

Map中的值可以重复,即多个键可以映射到相同的值。

  1. 实现类

Map接口的主要实现类包括HashMap、TreeMap、Hashtable、LinkedHashMap和ConcurrentHashMap。HashMap基于哈希表实现,查找效率高,但不保证顺序;TreeMap基于红黑树实现,按键的自然顺序或自定义顺序进行排序;LinkedHashMap继承自HashMap,使用双向链表维护插入顺序或访问顺序;Hashtable是线程安全的HashMap,但性能较低;ConcurrentHashMap是线程安全的HashMap,适用于高并发场景。

总结

综上所述,List、Map、Set三个接口在存取元素时各有其独特的特点。List适合存储有序的元素列表,并允许重复元素;Set适合存储不重复的元素集合,并提供了高效的查找性能;Map则适合存储键值对映射,其中键是唯一的,而值可以重复。在选择使用哪个接口时,应根据具体的应用场景和需求来决定。