java中的LinkedHashSet类添加对象机制

1.LinkedHashSet类基本介绍

LinstendHash是hashset的子类,底层维护的是LinstendHashmap->hashmap 的子类

LinstendHash是有序的,是一个双向链表形式 底层是数组+双向链表

2.LinstendHashSet底层添加元素原理机制

第一次添加元素会将数组扩容到16,存放的节点类型是存放节点类型是 LinkedHashMap$Entry

LinstendHash是hashset的子类,hashset底层是hashmap,底层添加元素还是调用的hashmap的add方法

添加一个元素时,先得到hash值,会转成索引值

找到数据存储表table,看这个索引位置是否存放的有元素,

如果没有,直接加入

如果有,调用equals比较,如果相同,放弃添加,如果不同,则添加到最后

3.LinstendHashSet添加对象源码解读

public static void main(String[] args) {
        HashSet<Object> objects = new HashSet<>();
        System.out.println(objects.add(new String("123"));
        System.out.println(objects.add(new String("123"));
    }

        想必大家都知道这是一道很经典的面试题,这两个对象只能成功添加一个,至于为什么只能添加一个,我们通过这道题目对LinstendHashSet添加元素的机制进行源码解读,

第一步 执行hashmap的add方法

 public boolean add(E e) { e = "java"
                return map.put(e, PRESENT)==null;
           }

第二步 执行put方法,该方法会执行hash(key)方法 ,会得到一个hash值

 public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

第三步 执行hash方法

从这里不难看出,null的hash值为0,对象的hash值就是调用对象的hashcode方法然后再进行处理得到hash值

所以说通过这个方法,我们不难看出上述两个字符串都为123的两个str对象,他们的hash值是一样的,因为String类重写了hashcode方法,感兴趣的小伙伴可以看下String的hashcode方法的原码,这里我不做过多解释

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

我们接着往下走

第四步 执行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为空或者大小为0,第一次添加元素table肯定为空,所以调用resize()方法第一次扩容到
//长度为16的Node数组,resize()方法我不再追加
        if ((tab = table) == null || (n = tab.length) == 0) 
            n = (tab = resize()).length;
// 这里就是根据刚才的到的hash值来计算元素在tab存放的索引,
// 由于上述两个str对象的hash值是一样的,所以上述两个对象的下标i是一样的,所以我们在第二次添加new //String("123")对象的时候,就不会进入到这个if里,因为tab[i]不为空,所以直接走下面的else代码,
        if ((p = tab[i = (n - 1) & hash]) == null) //i=hash%n 判断tab[i] 是否为空 如果为证明此索引没有被添加过对象
            tab[i] = newNode(hash, key, value, null);

        else {
            Node<K,V> e; K k;
            // 当前索引的hash值和准备添加的hash是否相等 && 当前索引的key值和准备添加的key值是        
            //否相等 || (准备加入的key不等于null && 准备加入的key.equals(当前索引的key值))
        // 由于两个str的hash值一样,并且两个对象的值一样,  直接将e指向tab[i],这个tab[i]就是之
//前第一次添加的str对象,最后将e返回,读到这里小伙伴们应该也明白了第二次没有添加对象了吧
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    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;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

3.根据实际距离解读LinstendHashSet添加对象的过程

1.我们这里有一个car类,维护了name和price属性,有参构造器也为name和price,我们这里认为 如果name和price一样就是同一个元素则不能添加成功,添加三个对象

Car("宝马",110) ;Car("宝马1",110); Car("奥迪",110)

分析  如果name和price一样就是同一个元素那我们就要重新写car的hashcode方法和equals方法,

根据name和price来控制hashcode和两个对象值是否一样

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); // 这里hashcode我只用name属性来控制,事项给大家演示下hash值一样,对象的值不一样的这种场景
     }
 }
   public static void main(String[] args) {
        LinkedHashSet linkedHashSet = new LinkedHashSet();

        linkedHashSet.add(new Car("宝马",100));
        System.out.println(linkedHashSet.add(new Car("宝马",110)));
        System.out.println(linkedHashSet.add(new Car("宝马1",110)));
        System.out.println(linkedHashSet.add(new Car("奥迪",110)));

    }

}

然后我们通过图解分析LinstendHashSet添加对象的过程

以上就是通过LinkedHashSet底层原理和一道比较经典的面试题向大家简单介绍了LinkedHashSet添加元素的底层原理,

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值