这里写目录标题
LinkedHashSet源码
它是HashSet的子类,底层结构是LinkedHashMap,底层维护数组和双向链表
韩老师的讲解图
双向链表的构建:从AA指向456,456又指回来,然后456重复不添加,再又指向 刘 ,刘 又指回来。。。。。。这就是底层双向链表的指向
Hashmap底层放的节点是Node
LinkedHashMap底层存放的节点为Entry
Entry类型:
继承了HashMap.Node<K,V>
类型
添加操作
其实就是hashmap的添加,判断hash,再重复则不添加,不重复则跟到数组位置本身的链表后面 用next指向要添加的节点
看看具体节点的属性:
before:表示指向双向链表的前一个节点
after:表示指向双向链表的后一个节点
hash:当前节点的hash值
key:因为是linkedhashset 所以是添加的数据
value:hashset底层默认的一个Object数据
next:表示指向当前处于同一个数组位置上的后一个节点,
其实在数组的某个位置上的一串节点,还是单向链表,用next链接
所有的节点是双向链表,用before和after链接
不要搞错了next和before、After,next是用于维护HashMap指定table位置上连接的Entry的顺序的,before、After是用于维护Entry插入的先后顺序的。
TreeSet与TreeMap一起放到后面分析
HashMap源码
疑问:为什么通常用String类作为key
因为String已经重写了hashcode和equals方法,我们自己新建类的话,还得自己重写 。加上String类确实常用。
而且string类的tostring方法默认返回自己,能够显示出来
HashMap的遍历
为了方便程序员去便利hashmap,我们还创建了EntrySet这个类,来专门放每个Node节点的引用,方便便利,本质上存放的还是Node类型,只不过可以放进去是因为Node这个类实现了Map.Entry这个接口,父类引用指向子类对象嘛。
还有Keyset这个类,专门存放每个节点的key
values这个类,专门存放每个节点的value
可以通过map.getEntrySet
得到EntrySet 然后可以通过增强for或者使用迭代器进行遍历
可以通过map.getKeySet
得到KeySet 然后可以通过增强for或者使用迭代器进行遍历
可以通过map.getValues
得到Values 然后可以通过增强for或者使用迭代器进行遍历
这就6种遍历方式了,不想写代码了,自己在网上看看。
HashMap源码在我之前的博客将HashSet就讲过了,可以自己去看!!
HashTable源码
它是线程安全的,是因为底层方法被synchronized修饰了
HashTable底层记录数据的类是
private static class Entry<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Entry<K,V> next;
protected Entry(int hash, K key, V value, Entry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
@SuppressWarnings("unchecked")
protected Object clone() {
return new Entry<>(hash, key, value,
(next==null ? null : (Entry<K,V>) next.clone()));
}
// Map.Entry Ops
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public V setValue(V value) {
if (value == null)
throw new NullPointerException();
V oldValue = this.value;
this.value = value;
return oldValue;
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&
(value==null ? e.getValue()==null : value.equals(e.getValue()));
}
public int hashCode() {
return hash ^ Objects.hashCode(value);
}
public String toString() {
return key.toString()+"="+value.toString();
}
}
HashTable初始化的数组,是一个HashTable$Entry类型的数组也就是下面显示的这个
注意:底层第一次添加数据后。数组大小为设置11(hashmap设置为16)
可以看见它的阈值是8(11*0.75),加载因子为0.75(hashmap也是0.75)
HashTable的数组大小默认扩容是按2倍+1扩容的
就是这句显示扩容大小为多少
测试,debug一下
Hashtable hashtable = new Hashtable();
hashtable.put('1',"数据1");
hashtable.put('2',"数据2");
hashtable.put('3',"数据3");
for (int i = 0; i < 10; i++) {
hashtable.put(i+"","数据"+i);
}
System.out.println(hashtable);
添加数据到第八个时候,数组大小为11
添加数据到第9个时候,数组大小为23(11*2+1)
行了,大差不差的,很容易理解,HashTable就到这儿!
TreeSet(底层是TreeMap)
TreeSet是有顺序的,但是和LinkedHashMap和LinkedHashSet那些不一样
LinkedHashMap和LinkedHashSet说的有序指的是输出的顺序和插入的顺序一致
TreeSet是有顺序的,指的是插入的数据从小到大进行排序,比如随即插入1~10,输出结果从1排到10
使用无参构造,创建TreeSet的时候,默认是自然顺序排序
使用有参构造,创建TreeSet的时候,可以传入一个比较器,专门用来指定排列顺序的
为什么无参构造的时候有一个自然排序呢?
当我们第一次put,往里面放数据的时候
执行这个方法
注意这里!!!
这里要走到compare
方法,如下:
无参构造的话,没有传入比较器,那么comparator==null就为空
就执行这个语句((Comparable<? super K>)k1).compareTo((K)k2)
也就是调用你传入的key的默认比较器,例如:我们传入一个String类型的数据,它已经实现了比较器接口
也对比较方法进行了重写
因此!!!!!!!!!!!!!
key为String类型的时候,就默认调用String类型的比较器,所以在我们看来没有传入比较器,但是他还是实现了自然排序
添加
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
有一个注意点:
添加数据,再进行比较的时候,如果发生比较失败,则数据不会添加进去,因为它直接return
了
TreeMap中的键,值能否为null?(要看哦)
看这个博主的
https://blog.csdn.net/u012156116/article/details/81073570
到现在,这里面源码再不考虑树的添加数据方面的源码的情况下,逻辑很简单,不再看了!!!
Collections工具类
怎么选择使用哪一种集合?
就到这儿结束吧!集合不难,很多源码我没有讲解,基本上读了一两个源码后,了解其他的就很容易了!!! 愿诸君加油!!!