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集合里面;