Java集合(七)LinkedHashSet

LinkedHashSet是HashSet的子类,它在添加元素时维护了一个双向链表,保证了元素的插入顺序。底层实现基于LinkedHashMap,结合数组和双向链表,提供高效的遍历性能。元素的添加会根据hashCode计算位置,并通过before和after属性形成链表,不允许添加重复元素。通过示例代码和源码分析,展示了LinkedHashSet如何确保插入和遍历顺序的一致性。

LinkedHashSet的全面说明:

(1)LinkedHashSet是HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。对于频繁的遍历操作,LinkedHashSet效率要高于HashSet.

(2)LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双向链表

HashSet为数组+单向链表

(3)LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的。

(4)LinkedHashSet不允许添重复元素。

我们进入源码进行查看:

public class LinkedHashSet<E>
    extends HashSet<E>
    implements Set<E>, Cloneable, java.io.Serializable {
public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{

LinkedHashSet底层机制说明:

(1)在LinkedHashSet中维护了一个hash表和双向链表(LinkedHashSet有head(头)和tail(尾))

(2)每一个结点有before和after属性,这样可以形成双向链表

(3)在添加一个元素时,先求hash值,在求索引,确定该元素在table的位置,然后将添加的元素加入到双向链表(如果已经存在,不添加【原则和hashset一样】)

tail.next=newElement  //示意代码

newElement=tail

tail=newElement;

(4)这样的话,我们遍历LinkedHashSet也能确保插入顺序和遍历顺序一致。

我们设计的代码如下所示:

package com.rgf.set;

import java.util.LinkedHashSet;
import java.util.Set;

@SuppressWarnings({"all"})
public class LinkedHashSetSource {
    public static void main(String[] args) {
        //分析一下LinkedHashSet的底层机制
        Set set = new LinkedHashSet();
        set.add(new String("AA"));
        set.add(456);
        set.add(456);
        set.add(new Customer("刘",1001));
        set.add(123);
        set.add("HSP");
        System.out.println("set="+set);
    }
}
class Customer{
    private String name;
    private int no;

    public Customer(String name, int no) {
        this.name = name;
        this.no = no;
    }
}

我们运行之后如下所示:

 我们根据运行结果,我们发现:LinkedHashSet加入顺序和取出元素/数据的顺序一致

之后我们根据Debug来进行查看:

我们发现创建了table表初始化仍然为16个,同时底层发生了变化,table表的元素不再是原先的Node类型了,而是Entry这样子一个对象。添加第一次时,直接将数组table扩容到16,存放的结点类型是LinkedHashMap$Entry,table是HashMap$Node内部类数组的这样子一个类型,但是他里面存放的真实的元素是LinkedHashMap$Entry这种类型,他们之间有继承或者实现关系,Entry继承或者实现了Node。数组是HashMap$Node[],存放的元素/数据是LinkedHashMap$Entry

多态就是父类引用指向子类对象,多态数组就是存放的数据类型是数组类型的子类

我们来进行查看LinkedHashMap的源码来进行了解:

我们进入源码界面后,找到Structure,发现了entey方法:

//Entry是LinkedHashMap里面的静态内部类,继承了HashMap.Node
static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }

我们查看完源码后,继续进行查看:

 我们进入HashMap源码:

 我们找到Node类:

static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

为静态内部类,底层源码才可以用HashMap.Node

我们添加元素的过程如下所示:

 

 这样子即形成了一个双向链表。

我们还可以进行查看head和tail。 

我们的代码如下所示:

package com.rgf.set;

import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

@SuppressWarnings({"all"})
public class LinkedHashSetSource {
    public static void main(String[] args) {
        //分析一下LinkedHashSet的底层机制
        Set set = new LinkedHashSet();
        set.add(new String("AA"));
        set.add(456);
        set.add(456);
        set.add(new Customer("刘",1001));
        set.add(new Customer("任",1002));
        set.add(123);
        set.add(new Customer("杨",1003));
        set.add("HSP");
        System.out.println("set="+set);
        //1.LinkedHashSet加入顺序和取出元素/数据的顺序一致
        //2.LinkedHashSet底层维护的是一个LinkedHashMap
        //3.LinkedHashSet 底层结构(数组+双向链表)
        //4.添加第一次时,直接将数组table扩容到16,存放的结点类型是LinkedHashMap$Entry
        //table是HashMap$Node内部类数组的这样子一个类型,但是他里面存放的真实的元素是LinkedHashMap$Entry这种类型
        //他们之间有继承或者实现关系,Entry继承或者实现了Node.
        //5.数组是HashMap$Node[],存放的元素/数据是LinkedHashMap$Entry,
        //继承关系是在内部类完成的

    }
}
class Customer{
    private String name;
    private int no;

    public Customer(String name, int no) {
        this.name = name;
        this.no = no;
    }
}

实例练习:

Car类(属性:name,price),如果name和price一样,则认为是相同元素,就不能添加。

我们设计的代码如下所示:

package com.rgf.set;

import java.util.LinkedHashSet;
import java.util.Objects;
import java.util.Set;

@SuppressWarnings({"all"})
public class LinkedHashSetExercise {
    public static void main(String[] args) {
        Set set = new LinkedHashSet();
        set.add(new Car("奥拓",1000));
        set.add(new Car("奥迪",30000));
        set.add(new Car("法拉利",10000000));
        set.add(new Car("奥迪",30000));
        set.add(new Car("保时捷",70000000));
        set.add(new Car("奥迪",30000));
        System.out.println("set="+set);
    }
}
class Car{
    private String name;
    private double price;

    public Car(String name, double price) {
        this.name = name;
        this.price = price;
    }
//重写equals方法和hashCode
    //当name和price相同时,就返回相同的hashCode值,equals返回t
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Car car = (Car) o;
        return Double.compare(car.price, price) == 0 && Objects.equals(name, car.name);
    }

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

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }
}

运行界面如下所示:

### LinkedHashSet 的使用 `LinkedHashSet` 是 Java 集合框架中的一种特殊集合实现,它继承自 `HashSet`,并实现了 `Set` 接口。与 `HashSet` 不同的是,`LinkedHashSet` 通过维护一个双向链表来记录元素的插入顺序,从而保证迭代顺序与插入顺序一致[^1]。这种特性使得 `LinkedHashSet` 在需要有序且唯一元素集合的场景中非常有用。 使用 `LinkedHashSet` 时,可以通过以下方式创建和操作集合: ```java import java.util.LinkedHashSet; public class LinkedHashSetExample { public static void main(String[] args) { // 创建一个 LinkedHashSet LinkedHashSet<String> set = new LinkedHashSet<>(); // 添加元素 set.add("Apple"); set.add("Banana"); set.add("Orange"); // 遍历集合 for (String fruit : set) { System.out.println(fruit); } } } ``` 在上述代码中,元素按照插入顺序输出,即 "Apple"、"Banana"、"Orange"。此外,`LinkedHashSet` 不允许重复元素,重复的元素将被忽略。 ### LinkedHashSet 的原理 `LinkedHashSet` 内部实际上是通过 `LinkedHashMap` 来实现的。当向 `LinkedHashSet` 中添加元素时,实际上是将该元素作为 `LinkedHashMap` 的键,而值则是一个固定的虚拟对象(通常是一个 `Object` 实例)[^1]。这种设计使得 `LinkedHashSet` 能够利用 `LinkedHashMap` 的特性来维护元素的插入顺序。 `LinkedHashSet` 的双向链表结构确保了插入顺序的保持。每个元素在被插入时都会被链接到链表的末尾,而重复的元素则不会被重新插入。由于底层是哈希表实现,因此 `LinkedHashSet` 的插入、删除和查找操作的时间复杂度均为 O(1),在保持顺序的同时提供了高效的性能[^1]。 ### LinkedHashSet 的常见问题 1. **为什么 `LinkedHashSet` 能保持插入顺序?** `LinkedHashSet` 能够保持插入顺序的原因在于其内部使用的 `LinkedHashMap`。`LinkedHashMap` 通过维护一个双向链表来记录元素的插入顺序,从而保证了 `LinkedHashSet` 的有序性[^1]。 2. **`LinkedHashSet` 和 `HashSet` 的区别是什么?** `LinkedHashSet` 与 `HashSet` 的主要区别在于顺序性。`HashSet` 不保证元素的顺序,而 `LinkedHashSet` 保证迭代顺序与插入顺序一致。这种区别源于 `LinkedHashSet` 使用 `LinkedHashMap` 实现,而 `HashSet` 使用普通的 `HashMap` 实现[^1]。 3. **`LinkedHashSet` 是否允许重复元素?** 不允许。与所有 `Set` 接口的实现类一样,`LinkedHashSet` 不允许包含重复元素。重复的元素会被忽略,不会被再次添加到集合中[^1]。 4. **`LinkedHashSet` 和 `ArrayList` 的区别是什么?** `LinkedHashSet` 和 `ArrayList` 的主要区别在于唯一性和顺序性。`LinkedHashSet` 保证元素的唯一性和插入顺序,而 `ArrayList` 允许重复元素且不保证顺序。此外,`LinkedHashSet` 提供了更快的查找和删除操作,时间复杂度为 O(1),而 `ArrayList` 的查找和删除操作时间复杂度为 O(n)[^1]。 5. **如何在 `LinkedHashSet` 中自定义对象?** 在 `LinkedHashSet` 中使用自定义对象时,必须确保该对象的类正确重写了 `equals()` 和 `hashCode()` 方法,以确保 `LinkedHashSet` 能够正确识别重复对象并维护集合的唯一性[^1]。 ### 示例代码 以下是一个使用 `LinkedHashSet` 存储自定义对象的示例: ```java import java.util.LinkedHashSet; 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 obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Person person = (Person) obj; if (age != person.age) return false; return name.equals(person.name); } @Override public int hashCode() { int result = name.hashCode(); result = 31 * result + age; return result; } @Override public String toString() { return "Person{name='" + name + "', age=" + age + "}"; } } public class Main { public static void main(String[] args) { LinkedHashSet<Person> people = new LinkedHashSet<>(); people.add(new Person("Alice", 30)); people.add(new Person("Bob", 25)); people.add(new Person("Alice", 30)); // 重复对象,将被忽略 for (Person person : people) { System.out.println(person); } } } ``` 在上述代码中,`Person` 类重写了 `equals()` 和 `hashCode()` 方法,以确保 `LinkedHashSet` 能够正确识别重复对象并维护集合的唯一性。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一直再追梦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值