【java基础第一天】Set接口详解

一、基本介绍

1.定义

set接口是集合类接口collection的子接口,其拥有collection的字段和方法,也有自己的特性。set为单列集合,常用方法与collection一样,如add、remove等方法。其常用实现子类有HashSet、LinkedHashSet。

2.特点

1)无序

     添加和取出的顺序不一致,没有索引。

2)元素唯一

     不允许出现重复元素,所以最多包含一个null。

3)遍历方式

     可以使用迭代器和增强for循环,但不能使用普通for循环 。

代码说明:

public class HashSet_ {
    public static void main(String[] args) {
        //1.HashSet底层是实现HashMap()
        //public HashSet() {
        //        map = new HashMap<>();
        //    }
        HashSet set = new HashSet();
        //2.HashSet只能有一个null
        //3.HashSet不保证有序,取决于hash后,再确定索引的值
        set.add(null);set.add("rookie");set.add("shy");
        set.add("jk");set.add("ning");set.add(null);
        Iterator iterator = set.iterator();
        System.out.println("set=" + set);
        while (iterator.hasNext()){
            Object obj = iterator.next();
            System.out.println(obj);
        }
    }
}

运行结果:

set=[null, jk, shy, ning, rookie]
null
jk
shy
ning
rookie

二、实现类

1.HashSet(重点)

1)介绍

在这里插入图片描述
set拥有的特性HashSet都拥有

2)add(E)机制

    a.HashSet底层是HashMap(即数组+链表+红黑树)。
    b.添加一个元素时,先通过算法得到hash值,再转化为索引值。
    c.找到数据存储表table,看这个索引之前是否存放的有数据。
    d.如果没有,直接加入。
    e.如果有,调用equals比较,如果相同,就放弃添加;如果不相同就添加在最后。
    f.在java8中,如果一条链表的元素个数达到8个,而且table(即数据数组)的大小达到64就会进行树化(红黑树)
代码说明(包括源码解读):
    public class HashSetSource {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        hashSet.add("java");
        hashSet.add("c++");
        hashSet.add("java");
        System.out.println("hashSet=" + hashSet);
        <font color=red>
        /*源码分析(主要搞懂add方法的底层原理)
        1.创建对象(HashSet的底层就是HashMap)
        public HashSet() {
        map = new HashMap<>();
    }
        2.第一次add的过程
        即调用put方法,若返回true则添加成功,反之失败
        public boolean add(E e) {   e = "java"
        return map.put(e, PRESENT)==null;
    }
        public V put(K key, V value) {  (再次验证底层是HashMap,即操作的是键值对)
        return putVal(hash(key), key, value, false, true);
    }
        1)利用key的hashcode通过"(h = key.hashCode()) ^ (h >>> 16)"算法得到hash值
        static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
        2)执行putVal方法(重点)
         2)a   第一次扩容到 16空间
          Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
         resize()的部分代码:
              newCap为数据数组分配的元素大小,newThr为临界值,若超过newThr就扩容
            newCap = DEFAULT_INITIAL_CAPACITY;
            static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
            threshold = newThr;
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
            table = newTab;
            if (++size > threshold)
            resize();
         2)b  根据前面得到的hash值 去计算key应该存放到table的哪一个索引位置
              并将该位置所在的对象赋给p,再判断p是否为空,如果为空,则在该位置
              创建一个新的node(key,value)
            if ((p = tab[i = (n - 1) & hash]) == null)  i = 3
            tab[i] = newNode(hash, key, value, null);
        3.第二次add的过程
             if ((p = tab[i = (n - 1) & hash]) == null)  i = 2
            tab[i] = newNode(hash, key, value, null);
        4.第三次add的过程(加相同对象)
             因为相同对象的hash值相同,所以执行else里的代码
             else {
            Node<K,V> e; K k;
          a .如果p指向Node节点的key和准备添加的key的hash值一样
          并且满足下面两个条件之一:
          1)准备加入的key与p指向Node节点的key是同一个对象
          2)两者用equals比较后相同(equals方法可由程序员自行定义)
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            b .再判断是不是一颗红黑树
              如果是一颗红黑树,就调用putTreeVal进行添加
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            c.如果前两个都不是   则依次遍历链表,将待添加的key与各节点依次进行如a中的
              比较,直到链表结束,若其中有一个节点与之相同则break不添加,反之添加到链表的最后
              注意:如果将元素添加到链表中,就立刻判断binCount是否达到7即数组长度是否达到8,若
              达到则执行treeifyBin.但又由 if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
            resize(); 可知只有当数组长度达到64时才变为红黑树,否则只是扩容
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD(8) - 1) //
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
        */</font>
    }
}

运行结果:


3)扩容和树化详解

在这里插入图片描述
代码示例:

public class HashSetIncrease {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        for(int i = 0;i < 100;i++){
            hashSet.add(i+1);
        }
    }
}

调试验证:
数组元素小于12时:
由图可知集合此时扩容为16
数组元素超过12时:
在这里插入图片描述
代码示例:

public class HashSetIncrease {
    public static void main(String[] args) {
        HashSet hashSet = new HashSet();
        for(int i = 0;i < 100;i++){
            hashSet.add(new student());
        }
    }
}
class student{
    @Override
    public  int hashCode() {
        return 77;
    }
}

通过调试该代码可验证上诉结论。

2.LinkedHashSet

1)介绍

LinkedHashSet是HashSet的子类,与HashSet特征相似,相较于HashSet其不同在于集合中的元素是有序的。

2)详述

a. LinkedHashSet底层是实现LinkedHashMap(数组+双向链表)。
b. LinkedHashSet的map都有一个head和tail,分别指向头元素和尾元素。
c.table中的每个元素都有before和after字段,分别指向前一个和后一个元素。
d.数组是HashMap N o d e 类 型 , 元 素 是 L i n k e d H a s h M a p Node类型,元素是LinkedHashMap NodeLinkedHashMapEntry类型,后者是前者的子类。

      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);
       }
   }

e.LinkedHashSet add方法的底层与HashSet底层类似。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值