1. List集合常用实现类
1.1ArrayList
-
ArrayList : 底层使用数组实现
- 初始容量为10 当数组满后 会扩容为原来数组的1.5倍大小
- 数组访问时 根据元素下标可以快速定位到具体的元素 所以查询效率很高
- 添加/删除
- 直接添加到数组末尾/删除 : 不会进行数据拷贝工作
- 指定索引位置添加/删除 : 需要把指定位置到数组最后一个元素的所有元素集体向后移动一位 即拷贝数组(且 其索引位置越靠前 则其效率越低)
-
使用无参构造创建对象后 不会马上实例化 而是在添加第一个元素的时候实例化集合
-
使用指定集合长度创建对象 会对其进行实例化 但是不会修改size的长度
- 所以通过这种方法创建的对象 在不添加元素进行其元素修改时 会报索引越界异常(看源码可以做到在进行删除和修改操作时会校验索引是否在0 - size的范围内 否则抛出索引越界异常)
1.2 LinkedList
- LinkedList : 底层使用双向链表实现
- 相对于数组而言 双向链表由于每个节点都存在一个指向前一个节点和指向后一个节点的指针 所以其所占用内存比较大
- 在进行查询时
- 在LinkedList里 使用了一个二分查找的实现 首先判断该节点的索引值是否小于集合长度一半 如果大于 则倒叙遍历后半个链表 否则正序遍历前半个链表
- 由于链表的查询需要根据每个节点的指针来查询下一个节点位置 所以其查询速度相对于数组来说要慢
- 添加/删除 :
- 链表在添加/删除元素节点时 直接改变相邻节点的指针指向即可 而不需要继续大量的复制操作 所以 速度要快于数组
1.3 vector
- vector底层实现和ArrayList类似 只是在方法上添加了synchronized修饰 已确保线程安全
1.4 疑问
- 什么时候使用ArrayList 什么时候使用LinkedList ?
- 当要进行大量的元素修改操作时 如果确定修改的元素在集合的尾部 则使用ArrayList <由于ArrayList的修改效率是随着其位置靠前就越慢 > 如果不确定或者其修改元素位置大多集中在集合头部位置则使用LinkedList
- 进行大量查询操作时使用ArrayList
- 在数组进行添加和删除操作时是否有更好一些的办法来解决其效率低的问题 ?
- 添加 : 如果对元素顺序没有需求 直接把指定添加位置的元素和数组最后一个元素进行替换 这样就不会进行大量的拷贝工作
- 删除 : 先把要删除的节点标记下来 暂时不要删除 在进行查询之前进行统一删除 这样有效防止了对同一个元素的多次拷贝<类似于jvm的 标记–清理算法>
2. Map集合常用实现类
2.1HashMap
- HashMap : 底层是散列表 + 红黑树
- 初始容量为16 最大容量为 1<<30(1073741824)
- 初始扩容大小为 0.75(加载因子) * 16(初始容量) 当达到这个值时 会扩容为原来的2倍
- 链表转树阈值为8 树转链表阈值为6
- 在树化方法中 先判断当前散列表数组长度是否小于MIN_TREEIFY_CAPACITY(最小容量阈值) 如果小于 会直接扩容 而不是树化 只有大于这个值才会树化链表(避免集合在树化和扩容之间纠结)
- 为什么HashMap的加载因子是0.75 ?
- 尽可能的利用空间 如果设置为0.5 每次扩容为原来的2倍 其剩余空间会越来越大 而设置为1 那么哈希冲突概率会大大加强
- 为什么树化的阈值是8 ?
- 在节点小于8时 其链表结构的查询效率要高于树的效率(而且树还会涉及一些旋转操作)
- 为什么要设置最小数化容量阈值为64 ?
- 散列表最理想的存储状态就是一个下标对应一个节点(类似于一个数组) 设置其最小数化阈值为了尽可能减少其哈希冲突 这样每个桶下的节点少了 其查询效率相对就会高一些
- HashMap是如何快速定位具体元素值的 ?
- 在查询时首先根据key计算对应的哈希值对应到具体的桶 然后在根据key值去使用eqals方法查询对应的值
1.2 LinkedHashMap
- LinkedHashMap : 底层是在HashMap基础之上添加了一条双向链表 以此保证其插入元素有序性(可以看做是HashMap + LinkedList)
- LinkedHashMap 具备HashMap 所有特性和缺点
- 由于其多维护了一条双向链表 所以其内存开销要大一些 效率也要低一些
1.3 TreeMap
- TreeMap : 底层是红黑树
- 红黑树是一种类似于平衡二叉树的结构
- TreeMap根据key值(实现了comparable接口)进行自然排序 或者根据构造方法 自定义传入的构造器(实现Comparator接口)进行自定义排序
- 既然有了平衡二叉树 为什么还要使用红黑树
- 平衡二叉树是一种严格的结构(追求绝对平衡) 必须保证每个节点都是一个平衡二叉树
- 红黑树只是保证了大致平衡 就实现其功能
- 平衡二叉树在添加/删除操作时 可能其旋转的次数不确定 而旋转次数越多 其效率就会越低
- 红黑树 只是进行了3次旋转来保证其局部平衡
- 整体来讲 红黑树在实现了功能的基础上 效率要高于平衡二叉树
1.4 HashTable
- HashTable : 底层使用链表 而没有结合红黑树进行存储
- 方法多被synchronized修饰 所以是线程安全的
- 在修改数据时会把整个散列表锁住 所以效率很低
1.5 ConcurrentHashMap
- ConcurrentHashMap : 使用散列表 + 红黑树 和HashMap实现类似
- 也是线程安全的 但是其相对于HashTable 锁住一整个散列表做了优化 他只是锁住了 具体的某一个桶 所以其效率要高于HashTable