ArrayList 底层实现:
1. Array List 底层是用动态数组实现的
2. 没有给初始容量时,初始容量为 0,在第一次添加数据时进行扩容数据为 10
3. Array List 数组每次扩容量是原来的 1.5 倍 ,每次扩容后都会进行数组拷贝
4. 数据在添加数据时:
4.1 先进行 seize 加 1 并计算出添加新数据时数组的最小容量
4.2 用最小容量与当前数组最大容量比较,如果大于当前数据容量,依挡墙容量的1.5倍进行扩容并进行数组拷贝
4.3 将新的数据添加到数组中并 size 值加 1
练习: new ArrayList(10) list 集合会扩容几次? 0次
LinkedList 与 ArrayList 比较:
补充:LinkedList 底层用双向链表实现
1.ArrayList 使用数组实现支持索引查询数据,LinkedList 使用双向连边实现不支持索引查询
2.如果给数据索引ArrayList查询数据比LinkedList效率高 ,不给索引都需要循环集合,添加删除数据 LinkedList 比 ArrayList 效率高
3. 储存同样的数据 LinkedList 占用内存比 ArrayList 多,LinkedList 每个节点还需要存前后节点的地址值
4. ArrayList 与 LinkedList 都不是线程安全的
hashMap 底层实现:
hashMap 使用数组加链表或红黑树实现,在put数据时先更具key计算出hash值然后在与数组的长度进行与运算得到数组下标,如果数组已经存在数据进行两个key比较,如果两个key 相同用新的数据覆盖旧数据,key 不相同将新数据存放在链表或红黑树中
hashMap put 数据流程:
1. 根据key 的 hashCode 与 hashCode 右移16 位的值做 与 运算获得新的hash 值
2. 判断是不是第一次添加数据,如果是第一次添加数据,先进行数据初始化创建数组、设置扩容阈值
3. 使用 hashCode 和 数组长度做 与运算 获得数组下标
4. 先判断当前下标中是否存在数据 ,存在数据且两个数据 key 不相同,判断当前数组中对象类型是否属于红黑树节点类型,如果是红黑树节点类型,根据 hash 值查找红黑树中是否有存在 key 相同的节点并覆盖旧数据,没有key相同的接口创建一个新的节点添加到红黑树中,如果不是红黑树节点类型,遍历链表比较 key 数据,找到 key 相同节点覆盖旧数据,否则创建一个新的节点放到链表中
链表长度大于 8 且数组长度大于 64 链表会转换为红黑树,数组长度小于 64 会进行数组扩容,重新计算数组索引下标存储数据
1.7 HashMap 多线程死循环问题:
1.7 版本 HashMap 数据结构为 数组 + 链表,HashMap 扩容时链表数据使用的时头插入法,进行数据迁移时多线程下可能出现死循环问题
问题探讨:环境描述,有两个线程 A 、B , 链表数据 节点1 和 节点2 且节点1 Next 指针 节点2
void transfer(Entry[] newTable) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while (null != e) {
Entry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
假设线程A先进行数据迁移 进入for 循环,假如此代码中的 e 变量是 节点1,从节点1 获取到下一个节点数据 (e.next) 并存放到 next 变量中 ,然后计算出 节点1 在新数组的位置(int i = indexFor(e.hash, newCapacity)),然后获取新数组对应的数据(newTable[i] )并将 节点1 的下一个节点的指针指向新数组对应的数据(e.next = newTable[i]), 随后把 节点1 存放到新数组中(newTable[i] = e),将 节点2 数据复制给变量 e (e = next) ,现在数组中的值是 节点 1
现在我们迁移 节点2 ,先获取指针对应的数据(e.next),因为 节点2 在链表尾部所以数据为空(next = null),然后在计算节点在新数组的下标(int i = indexFor(e.hash, newCapacity)),假设 节点1 与 节点2 对应的下标一致,然后根据下标获取数组中数据(newTable[i] ),其实就是上一次迁移的数据 节点 1 ,随后将 节点2 的下一个节点的指针指向 节点1(e.next = newTable[i]) ,然后将 节点2 存放到数组中(newTable[i] = e)
现在我们看一下两个节点位置, 节点2 存放在数组中,下一个节点的指针指向 节点1
下面我们看一下线程B数据迁移
注意:线程B数据还没有迁移 for 循环中的变量 e 对应的节点还是 节点1 ,next 变量对应的值是 节点2 , 但是线程A 操作后两个节点的属性数据已经发生了变化!!!
现在开始线程B 数据迁移,计算出 节点1 在新数组的位置(int i = indexFor(e.hash, newCapacity)),然后获取新数组对应的数据(newTable[i] ), 此时新数组对应的数据就是 节点2 ,将 节点1 的下一个节点的指针指向新数组对应的数据也就是节点2(e.next = newTable[i]), 随后把 节点1 存放到新数组中(newTable[i] = e),将 节点2 数据复制给变量 e (e = next) ,现在就出现一个问题,节点2 的下一个节点的指针指向 节点1 , 节点1 的下一个节点的指针指向 节点2 ,while 循环条件会一致成立出现死循环了
JDK 8 在扩容时不在使用头插法,使用 尾插法 避免循环问题出现
(有其它理解的小伙伴,多多留言~)