Set

set集合的特点:无序的,不能有重复的只能有一个null,没有索引!

HashSet

hashset底层其实是hashMap,他实现了Set接口,他的顺序是不一致的取出来的时候是根据hash值去计算的!

底层是一个哈希表的[数组,链表,红黑树]模拟一个数组和链表的代码

public class HashSet_ {
    public static void main(String[] args) {
        Node table [] = new Node[16];
        Node node = new Node("one",null);
        table[0] = node;
        Node node1 =  new Node("one1",null);
        node.next = node1;
        Node node2 =  new Node("one2",null);
        node1.next = node2;
        System.out.println(table[0]);
    }
}
class Node{
    Object item;
    Node next;
    public Node(Object item, Node next) {
        this.item = item;
        this.next = next;
    }

    @Override
    public String toString() {
        return "Node{" +
                "item=" + item +
                ", next=" + next +
                '}';
    }
}

HashSet在add方法的时候步骤[hashCode()+equals()] 先的到对象的hashCode的值,然后在根据算法计算出对象的存放的位置。 如果这个位置没有值,就加入。 如果有值用equals()方法去判断值是否相同,一样就不能加入不一样就添加在后面。

hashSet的扩容:是链表里的next有8个了,Nodetables 达到了64个 ===》变化成为红黑树。 这个table非常特殊,如果你到达了8个但是table没有到64他会先扩容自己看够不够。 table初始化是16,但是有一个加载因子 0.75 如果带打了0.75这个点就扩容自己2倍看够不够,到了2倍的0.75之后再看不够在扩容。如果大于等于64的时候就转成为红黑树了

hashSet的源码解读

//一:new一个hashmap
public HashSet() {
        map = new HashMap<>();
    }
//二:调用他的add方法 
//   e 就是传入的对象
public boolean add(E e) {
  //PRESENT == private static final Object PRESENT = new Object();
  //这里的put方法里面的PRESENT就是一个value的站位符
  //
        return map.put(e, PRESENT)==null;
    }
//这里调用的就是map的put方法,add方法是判断是否添加成功
//map.put(e, PRESENT) 返回一个null添加成功,返回一个对象添加失败

public V put(K key, V value) {
  //hash(key)这里是把你的对象,计算一下的到位置在哪里
        return putVal(hash(key), key, value, false, true);
    }
//putVal(对象的位置, 对象, 占位符 false, true);这里面就是核心对象
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是map里面的一个Node<K,V>[]的一个数组
  //如果是对象是空,或者对象的长度是0给他一个size初始化大小
        if ((tab = table) == null || (n = tab.length) == 0)
  //resize扩容之后返回了一个new 的table [新的Node<K,V>[]的一个数组]
  //初始化是16不过到了 0.75 * 12就会扩容
            n = (tab = resize()).length;
  //用table数组的初始化大小按位计算&他的之前计算对象的位置值 得到一个table[索引],
 //判断这个位置是否有对象,没有就添加进去
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
         // 最重要就是hash值和equals相同
         //  如果当前的哈希值和,你传入对象的哈希值一样
            if (p.hash == hash &&
             //   如果当前的key和你传入的key一样 或者(key不等于null并且传入的key的equals的当前key一样)
                ((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;
  //判断是否大于12
        if (++size > threshold)
            resize();
  //空方法给别人去弄的
        afterNodeInsertion(evict);
        return null;
    }

add方法: 一没有重复的元素: 初始化扩容16 判断hash是否一样,在判断equals比较是否一样 主要有一个不一样就返回null 可以添加了 二有重复元素: 先判断hash是否一样,在判断equals比较是否一样, 判断当前元素是红黑树,如果是树调用树话方法 单反hash一样,循环判断里面的equals是否一样 扩容:初始化16,加载因子0.75 到16 * 0.75 = 12 时候就扩容 到达64 链表8个 就树化

linkedHashSet

1,hashSet的子类 2,底层其实是hashMap,是一个数组+双向链表的 3,也是通过元素的hashCode值来存储元素的位置的,他可以以双向链表来保证元素的顺序 4,一样不能重复的 5,他是存入正Node里面的Entry对象

Map

map接口的特点: 1,存储的方式是key-value 2,key-value是obj类型的是存储在Node对象里面的 3,key不可以重复,value可以重复如果有重复的则替换掉前面的值 4,可以提供key找到value 5,他在催促Node对象的时候,会把数据被变为一个Entry 然后在把Entry 放到entrySet 集合里面,这样就可以调用entrySet里面的方法,方便了我们遍历 6,重要的方法KeySet获取到所有的key values获取到所有的value entrySet获取到所有的k - v

map 1,key - value方式存储的 2,key和value可以是任何的数据类型,是封装在HashMap$Node这个对象中的 3,map里面的key不可以重复只能有一个null,但是value是可以的。 4,key重复则覆盖上面的元素 5,一个key对应一个value

key——value的存储是放在Node里面的,但是把 为了我们方便遍历map集合 创建一个EntrySet集合,里面是entry<k,v>对象 EntrySet<entry<k,y>> 实际上里面还是一个Node,只不过是编译类型是entry运行类型是Node 相当于这样 EntrySet<entry<k,y>> = {entry<k,y>,entry<k,y>,...} entry<k,y> = node<k,y> entry对象里面有两个重要方法:get key(); get value();

key里面的数据,放在了set集合里面 value里面的数据,放在collection集合里面 放在一个entry里面,然后把这个entry放在了EntrySet集合里面;

alt