1、体系
Collection
- List
- ArrayList
- LinkedList
- Vector
- Set
- HashSet
- Linked HashSet
- Tree Set
- HashSet
Map
- HashMap
- Linked HashMap
- TreeMap
- Hashtable
- Properties
2、ArrayList
1)ArrayList中维护了一个Object类型的数组
2)当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第1
次添加,则扩容elementData为10,如需要再次扩容,则扩容elementData为1.5倍。
- 0 10 15 22 33
3)如果使用的是指定大小的构造器容量为指定大小,如果需要扩容,
则直接扩容elementData为1.5倍。
扩容逻辑
首次扩容 10
//首次: DEFAULT_CAPACITY为10,minCapacity=1,肯定取 10
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//新长度 = 旧长度 + 旧长度/2。所以为:1.5倍
//旧长度 第一次为 0,1.5 倍依然为 0
int newCapacity = oldCapacity + (oldCapacity >> 1);
//如果 新长度 - 10(首次) < 0。新长度首次为 0
if (newCapacity - minCapacity < 0)
//新长度 赋值为 默认长度
newCapacity = minCapacity;
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
第11次,扩容为15
modCount++;
//minCapacity为11 - 10 为1,
//如果最小的容量(当前容量+1的) - 当前数组实际的大小 > 0,说明不够了,需要扩容了
if (minCapacity - elementData.length > 0)
grow(minCapacity);
//为10
int oldCapacity = elementData.length;
//为15
int newCapacity = oldCapacity + (oldCapacity >> 1);
//15 - 11 >0
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;//不执行这个逻辑
//直接 使用15这个长度,复制数组。
拷贝数组底层
//原来为空数组,0,新数组,0。 两个0代表,拷贝的位置
//从源数组拷贝多少个数据到目标数组,
//为:原数组实际长度 和 新数组长度的 最小值。主要是防止 新数组 长度太短。
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
3、LinkedList
- LinkedList底层维护了一个双向链表.
- LinkedList中维护了两个属性first和last分别指向首节点和尾节点
3)每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表.
- 删除只需要改变:Node对象里, A B C 。需:A next 改为 C,C的 prev 改为 A。B就被 删除了。
4)所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高。
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
}
极简模拟链表
Node n1 = new Node("张三");
Node n2 = new Node("李四");
Node n3 = new Node("王五");
//n1 n2 n3
n1.next = n2;
n2.next = n3;
//n3 n2 n1
n3.pre = n2;
n2.pre = n1;
//测试节点 插入到 n2 和 n3之间
Node t = new Node("test");
//指向下一个节点
t.next = n3;
//指向前一个 节点
t.pre = n2;
//n3的前一个节点,指向t
n3.pre = t;
//n2的下一个节点,指向t
n2.next = t;
Node first = n1;
Node last = n3;
while (true) {
if (first == null) {
break;
}
System.out.println(first.name);
first = first.next;
}
}
static class Node {
Node pre;
Node next;
private String name;
public Node(String name) {
this.name = name;
}
}
删除首元素源码
LinkedList linkedList = new LinkedList();
linkedList.add(1);
linkedList.add(2);
linkedList.remove(); // 这里默认删除的是第一个结点
public E remove() {
return removeFirst();
}
public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
private E unlinkFirst(Node<E> f) {
//获取元素里的 内容,用于返回
final E element = f.item;
//获取 首元素的 第二个节点
final Node<E> next = f.next;
//首元素的 元素内容和 下一个指针都清空。
f.item = null;
f.next = null; // help GC
//首指针,现在指向:第二个节点。
first = next;
//如果 第二个元素 为null了,说明没了,
if (next == null)
last = null;//这 指针 下一个 指为 null
else
//否则:就把这个,刚刚升为 首指针的 Node的 上一个指针,指为null
next.prev = null;
//长度 -1
size--;
//修改数++
modCount++;
return element;
}
1. next = f.next ,取出 原来的 第二个节点(现在作为 第一个节点)。
2. 原来的 首节点 的 next 指向 断开。 f.next = null; **重要**
3. first 首节点指针,指向第二个节点 first = next; **重要**
4. 原来的第二个 节点的 prev = null (现在变为 首节点了) **重要**
增加为尾元素源码
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
//获取 尾结点
final Node<E> l = last;
//创建 一个新节点 为 尾结点。上一个节点为 原尾结点,下一个节点为null
final Node<E> newNode = new Node<>(l, e, null);
//尾指针 = 尾结点
last = newNode;
if (l == null) //如果原来尾结点 为null,首次添加
first = newNode;//新节点 也指向 首节点
else//否则 尾结点为 null
l.next = newNode;//尾结点的下一个 为新节点
size++;//长度++
modCount++;//修改次数++
}
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
4、HashSet 和 HashMap 转成树
- HashSet 实际是 hashMap。是数组 + 链表+红黑树
public HashSet() {
map = new HashMap<>();
}
- 链表,变成红黑树
-
链表的数据 添加了第9个(超过8个),数组(也叫table)的大小,>= 64
-
如果 table表 没到,table 表 会扩容(2倍)
-
p.next = newNode(hash, key, value, null); //即:添加第9个后。下面会说明。 //binCount >= 7。binCount从0开始,上面新添加了一个。 if (binCount >= TREEIFY_THRESHOLD - 1) treeifyBin(tab, hash); ```
-
//如果 < 64,依然扩容。不转成树 if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY) resize();
-
2。添加一个元素时,先得到hash值-会转成->索引值
3.找到存储数据表table,看这个索引位置是否已经存放的有元素
4。如果没有,直接加入
5.如果有,调用equals比较,如果相同,就放弃添加,如果不相同,则添加到最后
整理
1.先获取元素的哈希值( hashCode方法)
2.对哈希值进行运算(hashCode & 最大索引),得出一个索引值即为要存放在哈希表中的位置号
3.如果该位置上没有其他元素,则直接存放
如果该位置上已经有其他元素,则需要进行equals判断
- 如果相等,则不再添加。
- 如果不相等,则以链表的方式添加。
HashSet的扩容和转成红黑树机制
1.HashSet底层是HashMap,第一次添加
时,table数组扩容到16,临界值(threshold)是16加载因子
(loadFactor)是0.75 = 12
2.如果table数组使用到了临界值12 (超过12,第13次添加后),就
会扩容到16*2= 32,新的临界值就是32*0.75 = 24,依次类推
3.在Java8中,如果一条链表的元素个数到
达 TREEIFY_THRESHOLD(默认是8,第9次添加后),并且table的大小>=
MIN TREEIFY CAPACITY(默认64),就会进行树化(红黑树),否则仍然采用数组扩容机制
HashSet的添加返回值 布尔
- 因为返回 旧值,也没有意义,就是 一个空对象而已。
public boolean add(E e) {
//hashMap返回的是 null,hashSet 判断了,返回 true
return map.put(e, PRESENT)==null;
}
HashMap添加的返回值
- 添加成功,返回 null
- 添加添加失败,返回被替换掉的旧值。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
if (e != null) {
//添加失败,返回 旧值,就是 被替换的值。
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
//新值 会进行替换。
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
//添加成功返回null
return null;
}
HashMap h=new HashMap();
Object put1 = h.put(1, 100);
//添加成功,返回 null
System.out.println(put1);
//添加添加失败,返回被替换掉的旧值。为100
Object put2 = h.put(1, 200);
System.out.println(put2);
//{1=200}
System.out.println(h);
HashMap的 hash方法解析
static final int hash(Object key) {
int h;
//key如果为 null,就是 0,所以:所有的null值,都放在第一个。
//key 取 hashCode() ^ h无符号 右移动16位。
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
//hash("java") = 3254803, 注意:这个值,不是 hashCode
//因为无符号右移 16位,得到了一个很小的,又和这个 很小的值 异或了。
//比如下面:774889 ^ 11 = 774882 ,小了 7
//总之 就是为了 避免碰撞。
-
注意:这个值,不是 hashCode
-
2进制 100,为4,右移1为 就是2
左 右 移位
- 算术右移 >>:低位溢出,符号位不变,并用符号位补溢出的高位
- 第一位为1,表示 负数
-
算术左移 <<: 符号位不变,低位补 0
-
逻辑右移也叫无符号右移,运算规则是: 低位溢出,高位补 0
-
特别说明:没有 <<< 符号
按位异或^
1代表 真, 0代表假
- 两位一个为0,一个为1,结果为1,否则为0 (不同为1,相同为0)
- 111 ^ 101 结果为:010
- 当 a 和 b 不同时,则结果为 true
String的 hashCode方法
- 单字符,为字符的 unicode 编码
//new一个 char数组。
char val[] = new char[]{'A'};
//char数组直接 可以复制为 in
int unicode = val[0];
// 大A的 unicode 为 65,也就是 “A".hashCode()
System.out.println(unicode);
//小a是97,"1" 为 49
- “Aa”.hashCode() 为:65*31 + 97 = 2112
- 第一次循环为:65
- 第二次循环为: 65*31 + 97
- Aa1.hashCode() 为:(65*31+97 )* 31 + 49 = 65521
- 第一次循环为: 65
- 第二次循环为:65*31 + 97 = 2112
- 第三次循环为:2112 * 31 + 49 = 65472+49=65521
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
扰动函数 hashCode 异或 右移
- (h = key.hashCode()) ^ (h >>> 16)
h.put(new Person("张三"), "1");
//key.hashCode() 如上 就是调用 Person对象向的 hashCode方法
@Override
public int hashCode() {
return 1;
}
//二进制 0001 0000 0000 0000 0000,10进制为 65536
//右移动 16位 为 1
//0001 0000 0000 0000 0000 异或 0000 0000 0000 0000 0001
//相同为0,不同为1,结果为:65537
System.out.println(65536 >>> 16);//1
System.out.println(65536 ^ 1);//65537
负载因子0.75
- 在 有参和无参构造时 初始化
static final float DEFAULT_LOAD_FACTOR = 0.75f;
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
HashMap的Node
- Node 是内部类,内部类 模拟的 单向链表
- Node 构成的数组,称作:table
transient Node<K,V>[] table;
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
//hash,key,value, 下一个 指针
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
}
(1)HashMap的put首次执行
主方法 put:最大索引 & hash
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//hash key value,仅仅是缺席的,为false。驱逐为true
//onlyIfAbsent – if true, don't change existing value
//如果为真,不改变现有值。现在传递为 false
//evict – if false, the table is in creation mode.
//如果为false,表处于创建模式。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
//首次 执行初始化。tab进行了,赋值。n赋值为了16
n = (tab = resize()).length;
//n-1为15,i赋值为了15。
//15 & 一个非常大的数,结果最大为 15,即:最大索引。所以:不会越界。
//从 tab[i] 中 取出值,赋值给 p,首次 肯定为 null
if ((p = tab[i = (n - 1) & hash]) == null)
//首次存入,hash,key,value,下个指针为null
tab[i] = newNode(hash, key, value, null);
else {
xx暂时不看
}
//修改次数++
++modCount;
//添加后,长度+1 判断是否 大于 阈值12
if (++size > threshold)
resize();
//空的钩子方法。添加成功返回null
afterNodeInsertion(evict);
return null;
}
void afterNodeInsertion(boolean evict) { }
resize 扩容16和2倍
- 默认16,每次达到0.75,扩容2倍。下次为32
- ArrayList 默认是10,满了就扩容1.5倍。下次为15
final Node<K,V>[] resize() {
//首次 table 为null,所以 oldTable 也为null
Node<K,V>[] oldTab = table;
//所以:oldCap 为 0
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//threshold 默认为0,所以 oldThr也为0
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
//本次不执行,这便是扩容2倍的逻辑,详见下面的代码。
(newCap = oldCap << 1) //总容量扩容2倍
newThr = oldThr << 1; // double threshold。阈值 原来是12,也扩容2倍。
}
else if (oldThr > 0) //不执行
newCap = oldThr;
else {
//0 初始化 零 初始 阈值 表示 使用 默认值
// zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;//16
//0.75 * 16 = 12
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
//不执行。
}
//12 赋值给 threshold,即:扩容阈值。
threshold = newThr;
//创建了一个 Node 数组
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
//赋值给了 table
table = newTab;
//返回 创建的数组。此数组 长度为 16
return newTab;
}
(2)put 第多次执行(非扩容 未冲突)
HashMap h = new HashMap();
h.put("A", "B");
//本次断点,这一行代码
h.put("AA", "BB");
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//tab 已经不为Null,第一个条件为假。
//执行第二个条件,n赋值为 tab的长度,为 16
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//依然是: 15&非常大的值,赋值为i(即为 这个数组的索引)
//先取一下 赋值为 p,此时p 为 null
if ((p = tab[i = (n - 1) & hash]) == null)
//把最新的值,放入数组
tab[i] = newNode(hash, key, value, null);
else {}
//下面的逻辑一样
return null;
}
(3)put 第多次执行(非扩容 键冲突)
HashMap h = new HashMap();
h.put("A", "B");
h.put("A", "AA");
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//n 赋值为 16
if ((tab = table) == null || (n = tab.length) == 0)
//不执行这里
//p 为 取出的值, 冲突时 tab[i] !=null
if ((p = tab[i = (n - 1) & hash]) == null)
//不执行这里
else {
//执行这里
Node<K,V> e; K k;
//p为 数组中[i] 存入的值,的 hash (hash都是key) 这里 参数传递的 hash 是相同的。
// p.ke == key,也是为 true
if (p.hash == hash &&
((k = p.key) == key ||
(key != null && key.equals(k))))
//表中i 位置的 Node 赋值给了 e
e = p;
else if (p instanceof TreeNode){
}
//e不为null
if (e != null) {
// existing mapping for key
//取出旧的值
V oldValue = e.value;
//onlyIfAbsent – if true, don't change existing value
//如果为真,不改变现有值。现在传递为 false
//不为真 或者 旧值为 null
if (!onlyIfAbsent || oldValue == null)
// 数组里 [i] 的位置,使用新值 替换。
e.value = value;
//钩子方法
afterNodeAccess(e);
//返回旧值
return oldValue;
}
}
void afterNodeAccess(Node<K,V> p) { }
(4)hashCode()相同,equals 不同。转成树
HashMap h = new HashMap();
h.put(new Person("张三"), "1");
h.put(new Person("李四"), "2");
h.put(new Person("李四"), "3");
@Data
@AllArgsConstructor
static
class Person {
String name;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return 1;
}
}
static final int hash(Object key) {
int h;
//重写hashCode后,HashMap所有的 hash方法,都返回1
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
- 添加 张三 时
//15 & 1 依然为 1,会放到 数组为 1的位置,i为1
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
}
- 添加 李四 时
//p 不为null,p指向了 张三
if ((p = tab[i = (n - 1) & hash]) == null)
//这里不会执行
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
// hash相同
// p的 key为 person张三 == person李四,条件为假
//继续执行:key.equals(k),重写过 equals,条件依然为 假
//真 && 假 || 假 ,次判断条件为 假
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 {
//进入到这个逻辑里
//死循环,从0开始遍历
for (int binCount = 0; ; ++binCount) {
//使用next 指针的逻辑。 如果 p.next 就是 Node[i]取出的person张三,下一个指针为 null
//此判断成立
if ((e = p.next) == null) {
//添加到 person张三后面,
p.next = newNode(hash, key, value, null);//添加了第9个。执行: treeifyBin(tab, hash);
//判断一下,这个链表的长度是否 >= 7。binCount从0开始。就是是否 >=8。
//在加上 上面添加的一个。即:添加第9个后。
//binCount=0,上面添加的为 第2个
//binCount=7,上面添加的为 第9个后。
if (binCount >= TREEIFY_THRESHOLD - 1)
treeifyBin(tab, hash);
break;
}
//h.put(new Person("李四"), "3");再次添加 李四,即可进入这个逻辑。
//上面的 p = tab[i = (n - 1) & hash],p是元素的首节点。链表的首节点(table里[i]位置第一个元素)
//e = p.next。e是 下一个节点。 让 e 和 要插入的新的key(结点) 比较
//有相同了,就 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;
}
}
(5)第二次多次扩容
HashMap h = new HashMap();
//断点 第13 次循环
for (int i = 1; i <= 13; i++) {
String k = "k" + i;
String v = "v" + i;
h.put(k, v);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
...添加完毕后
++modCount;
//第12次,size为11,++size为12
//第13次,size为12,++size为13,> threshold
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
final Node<K,V>[] resize() {
//table,现在放置了 13个元素
Node<K,V>[] oldTab = table;
//oldCap 为 16
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//老的 阈值,12 赋值给 oldThr
int oldThr = threshold;
//
int newCap, newThr = 0;
//成立
if (oldCap > 0) {
//oldCap 肯定 不大于 1 << 30
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//扩容逻辑来了。newCap 扩容原来的 2倍。扩容后32
//oldCap >= 1 << 4为16
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
//阈值也扩容2倍
newThr = oldThr << 1; // double threshold
}
else if
else 都不执行
//新的 阈值 24,赋值给 threshold
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
//创建一个 32 长度的 Node 数组
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
// 新数组,赋值给 table
table = newTab;
//如果原来表 存在,执行复制(原表 复制到 新表)
if (oldTab != null) {
//从 0 复制 到 15
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
//如果 旧的数组的位置上 有值,赋值给 e
if ((e = oldTab[j]) != null) {
//旧数组的 位置置空
oldTab[j] = null;
//如果 e的 下一个指针为空(说明 这个[j]位置,就存了一个元素,)
if (e.next == null)
//e.hash & 31,就是存到 0 - 31 索引处。hash & 最大索引,就是 O1 查找的精髓。
newTab[e.hash & (newCap - 1)] = e;
//这里 如果为 红黑树,e.next 肯定不为null
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else {// preserve order,i位置是 链表的处理
//声明2个头 和 2个 尾结点
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
//获取这个 位置的下一个节点
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
}
//最终返回 newTab
return newTab;
HashMap 的内部类 EntrySet
transient Set<Map.Entry<K,V>> entrySet;
final class EntrySet extends AbstractSet<Map.Entry<K,V>> {
public final int size() { return size; }
public final void clear() { HashMap.this.clear(); }
}
//1. k-v 最后是 HashMap$Node node = newNode(hash, key, value, null)
//2. k-v 为了方便程序员的遍历,还会 创建 EntrySet 集合 ,该集合存放的元素的类型 Entry
// 而一个Entry对象就有k,v EntrySet<Entry<K,V>> 即:
transient Set<Map.Entry<K,V>> entrySet;
//3. entrySet 中, 定义的类型是 Map.Entry ,但是实际上存放的还是 HashMap$Node
//values的方法返回 Collection 集合。 指向HashMap$Node 的Value
//keySet的方法返回 Set集合。 指向HashMap$Node 的Key
//这s是因为
static class Node<K,V> implements Map.Entry<K,V>
//4. 当把 HashMap$Node 对象 存放到 entrySet 就方便我们的遍历,
//因为 Map.Entry 提供了重要方法 : K getKey(); V getValue();
Set set = map.entrySet();
System.out.println(set.getClass());
// HashMap$EntrySet,里面放的 Map.Entry,真实为实现类 HashMap$Node
for (Object obj : set) {
//System.out.println(obj.getClass()); //HashMap$Node
//为了从 HashMap$Node 取出k-v
//1. 先做一个向下转型
Map.Entry entry = (Map.Entry) obj;
System.out.println(entry.getKey() + "-" + entry.getValue() );
}
//第二个遍历:
Set set1 = map.keySet();
System.out.println(set1.getClass()); //class java.util.HashMap$KeySet
//使用 map.getValue(key)
Collection values = map.values();
System.out.println(values.getClass());//class java.util.HashMap$Values
5、LinkedHashSet 和 LinkedHashMap
基本说明
1)LinkedHashSet是 HashSet的子类
- LinkedHashSet底层是一个LinkedHashMap,底层维护了一个数组+双向链表
3)LinkedHashSet根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序(图),这使得元素看起来是以插入顺序保存的。
4)LinkedHashSet不允许添重复元素
- 加入顺序和取出元素/数据的顺序一致
- 底层维护的是一个LinkedHashMap(是HashMap的子类)
- 底层结构 (数组table+双向链表)
tail.next = newElement //结尾的下一个 指向新节点
newElement.pre = tail //新节点的前一个,指向结尾
tail = newEelment; //新节点 指向 结尾
Set set = new LinkedHashSet();
源码解读
- LinkedHashSet 调用父类 HashSet,使用的是:LinkedHashMap 继承了:LinkedHashMap
public LinkedHashSet() {
super(16, .75f, true);
}
HashSet(int initialCapacity, float loadFactor, boolean dummy) {
map = new LinkedHashMap<>(initialCapacity, loadFactor);
}
public LinkedHashMap(int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
accessOrder = false;
}
public class LinkedHashMap<K,V>
extends HashMap<K,V>
implements Map<K,V>{
transient LinkedHashMap.Entry<K,V> head;
transient LinkedHashMap.Entry<K,V> tail;
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);
}
}
}
//Hash的是 单项链表
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}