java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > LinkedHashSet的源码

Java中LinkedHashSet的源码分析

作者:_雨_

这篇文章主要介绍了Java中LinkedHashSet的源码分析,LinkedHashSet 是 Java 中的一个集合类,它是 HashSet 的子类,同时也实现了 Set 接口,与 HashSet 不同的是,LinkedHashSet 保留了元素插入的顺序,并且具有 HashSet 的快速查找特性,需要的朋友可以参考下

LinkedHashSet的基本介绍

LinkedHashSet 是 Java 中的一个集合类,它是 HashSet 的子类,同时也实现了 Set 接口。与 HashSet 不同的是,LinkedHashSet 保留了元素插入的顺序,并且具有 HashSet 的快速查找特性。

LinkedHashSet 继承了 HashSet,所以它是在 HashSet 的基础上维护了元素添加顺序的功能。

它的构造方法是 LinkedHashSet()。 LinkedHashSet 是一个基于 LinkedHashMap 实现的有序去重集合列表。

它中的元素没有重复,有顺序,并且可以存储 null 值。

需要注意的是,LinkedHashSet 是一个线程不安全的容器。

  1. LinkedHashSet是HashSet的子类
  2. LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双向链表
  3. LinkedHashSet 根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序(图),这使得元素看起来是以插入顺序保存的。
  4. LinkedHashSet不允许添重复元素

LinkedHashSet源码分析

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

2.每一个节点有pre和next属性,这样可以形成双向链表

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

tail.next=newElement//简单指定
newElement.pre=tail
tail = newEelment;

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

源码分析

因为LinkedHashSet是HashSet的子类,因此在底层使用的方法,还是HashSet的方法,因为HashSet的底层是HashMap,所以最终走的还是HashMap的putVal方法

可以参考putVal方法,我们在将HashSet的时候已经详细的解释过了

/*
对HashSet 的源码解读
1. 执行 HashSet()
    public HashSet() {
        map = new HashMap<>();
    }
2. 执行 add()
   public boolean add(E e) {//e = "java"
        //这里的PRESENT 是一个空对象数组,起到占位符作用
        return map.put(e, PRESENT)==null;//(static) PRESENT = new Object();
   }
 3.执行 put() , 该方法会执行 hash(key) 得到key对应的hash值 算法h = key.hashCode()) ^ (h >>> 16)
   根据我们传入进来的值,去计算hash值,在Table中存放的位置
     public V put(K key, V value) {//key = "java" value = PRESENT 共享
        return putVal(hash(key), key, value, false, true);
    }
 4.执行 putVal
 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
           boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i; //定义了辅助变量
        //table 就是 HashMap 的一个数组,类型是 Node[]
        //if 语句表示如果当前table 是null, 或者 大小=0
        //就会进行第一次扩容,到16个空间.
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        //(1)根据key,得到hash 去计算该key应该存放到table表的哪个索引位置
        //并把这个位置的对象,赋给 p
        //(2)判断p 是否为null
        //(2.1) 如果p 为null, 表示还没有存放元素, 就创建一个Node (key="java",value=PRESENT)
        //(2.2) 就放在该位置 tab[i] = newNode(hash, key, value, null) 就是直接存放进去
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            //一个开发技巧提示: 在需要局部变量(辅助变量)时候,在创建
            Node<K,V> e; K k; //
            //当进入else的时候,就说明我们当前计算出来的Hash值在数组中的位置已经存在了 ,那么就先进行判断
            //如果当前索引位置对应的链表的第一个元素的hash值和准备添加的key的hash值一样
            //并且满足 下面两个条件之一:
            //(1) 准备加入的keyhash值 和 p 指向的Node 结点的hash值相同,那就说明是是同一个对象
            //(2)  当前的key对象 或者和我们传入对象的地址相同,因为==在判断引用类型的时候,判断的是地址是否相同,如果地址相同,或者他们的内容相同
            //就不能加入     如果不能加入就把p赋给e
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            //再判断 p 是不是一颗红黑树,
            //如果是一颗红黑树,就调用 putTreeVal , 来进行添加
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
                //如果上面的情况都不是的话,那么就说明,此时这个索引对应的位置是一个链表了
            else {//如果table对应索引位置,已经是一个链表, 就使用for循环比较
                  //(1) 依次和该链表的每一个元素比较后,都不相同, 则加入到该链表的最后
                  //    注意在把元素添加到链表后,立即判断 该链表是否已经达到8个结点
                  //    , 就调用 treeifyBin() 对当前这个链表进行树化(转成红黑树)
                  //    注意,在转成红黑树时,要进行判断, 判断条件
                  //    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY(64))
                  //            resize();
                  //    如果上面条件成立,先table扩容.
                  //    只有上面条件不成立时,才进行转成红黑树
                  //(2) 依次和该链表的每一个元素比较过程中,如果有相同情况,就直接break
                //这是一个死循环,会一直进行 比较,只有两种情况,才会退出循环
                //第一种:当数组中的其中一条列表的长度到达了7,准备进行树化的时候
                //第二种:就是发现我们加入的元素,在这个列表中发现了重复的,也会直接跳出循环
                for (int binCount = 0; ; ++binCount) {
                        这里e=p.next 因为我们在最开始上面的时候,已经对第一个元素进行了判断,所以这里直接从下一个元素开始判断
                        如果下一个元素为空,那么就直接加入
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        加入完一个元素之后,马上的进行判断,当前列表的个数有几个,是否进行树化
                        if (binCount >= TREEIFY_THRESHOLD(8) - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    //这里就是,在循环比较的过程中,如果发现有相同的内容,那么会直接break
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                       //这里就是让p 指向下一个
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        //size 就是我们每加入一个结点Node(k,v,h,next), size++
        if (++size > threshold)
            resize();//扩容
        afterNodeInsertion(evict);
        return null;
    }
 */
package idea.chapter14.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);
        System.out.println("set=" + set);
        //源码分析
        //1. LinkedHashSet 加入顺序和取出元素/数据的顺序一致,因为LinkedHashSet在底层维护了一个双向链表+数组,因此可以保证数据取出的顺序保持一致
        //2. LinkedHashSet 底层维护的是一个LinkedHashMap(是HashMap的子类)
        //3. LinkedHashSet 底层结构 (数组table+双向链表)
        //4. 添加第一次时,直接将 数组table 扩容到 16 ,存放的结点类型是 LinkedHashMap$Entry
        //5. 数组是 HashMap$Node[] 存放的元素/数据是 LinkedHashMap$Entry类型
        /*
                //继承关系是在内部类完成.
                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);
                    }
                }
         */
    }
}
class Customer {
    private String name;
    private int no;
    public Customer(String name, int no) {
        this.name = name;
        this.no = no;
    }
}

LinkedHashset练习

代码演示:

在没有重写equals方法和hashcode方法的时候都是可以加入进去的,因为都是创建出来新的对象

package idea.chapter14.set_;
import java.util.LinkedHashSet;
import java.util.Objects;
/*
Car类(属性:name,price), 如果name和price一样,
。则认为是相同元素,就不能添加。5min. I
 */
@SuppressWarnings({"all"})
public class LinkedHashSetExercise {
    public static void main(String[] args) {
        LinkedHashSet linkedHashSet = new LinkedHashSet();
        linkedHashSet.add(new Car("奥拓", 1000));//OK
        linkedHashSet.add(new Car("奥迪", 300000));//OK
        linkedHashSet.add(new Car("法拉利", 10000000));//OK
        linkedHashSet.add(new Car("奥迪", 300000));//加入不了
        linkedHashSet.add(new Car("保时捷", 70000000));//OK
        linkedHashSet.add(new Car("奥迪", 300000));//加入不了
        System.out.println(linkedHashSet);
    }
}
class Car {
    private String name;
    private double price;
    public Car(String name, double price) {
        this.name = name;
        this.price = price;
    }
    @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 +
                '}';
    }
}

到此这篇关于Java中LinkedHashSet的源码分析的文章就介绍到这了,更多相关LinkedHashSet的源码内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文