Java 集合基础知识

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  在扩容时不在使用头插法,使用 尾插法 避免循环问题出现

     (有其它理解的小伙伴,多多留言~)

Java集合Java编程中非常重要的一部分,它提供了一种方便的方式来处理一组对象。Java集合框架包括List、Set、Map等接口和实现它们的类。下面是Java集合基础知识的介绍: 1. List接口:List是一个有序的集合,它可以包含重复的元素。List接口的常用实现类有ArrayList和LinkedList。其中,ArrayList是一个动态数组,它可以自动扩容以容纳更多的元素;而LinkedList是一个双向链表,它可以快速地在列表中插入或删除元素。 2. Set接口:Set是一个不允许重复元素的集合。Set接口的常用实现类有HashSet和TreeSet。其中,HashSet是一个基于哈希表的实现,它可以快速地查找元素;而TreeSet是一个基于红黑树的实现,它可以对元素进行排序。 3. Map接口:Map是一个键值对的集合,它允许使用键来查找值。Map接口的常用实现类有HashMap和TreeMap。其中,HashMap是一个基于哈希表的实现,它可以快速地查找键值对;而TreeMap是一个基于红黑树的实现,它可以对键进行排序。 下面是一个示例代码,演示了如何使用ArrayList集合存储学生的成绩,并遍历这个集合: ```java // 创建一个ArrayList集合,向这个集合中存入学生的成绩 ArrayList<Integer> al = new ArrayList<Integer>(); al.add(78); al.add(67); // 对集合遍历 // 方式1 for (Object obj : al) { System.out.println(obj);} // 方式2 for (Integer i : al) { System.out.println(i); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值